Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion dev/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ COPY --from=docker.io/library/maven:3-eclipse-temurin-21 /usr/share/maven/ref/se

RUN ln -s ${MAVEN_HOME}/bin/mvn /usr/bin/mvn

ENV MAVEN_CONFIG "/root/.m2"
# The entrypoint copied from the maven image runs under `set -u` and dereferences
# $MAVEN_CONFIG with no default, so it must be set or the container fails to start.
# It is unset before mvn runs, so the value only governs where the entrypoint stages
# its reference files — point it at a path writable by any uid (ilo runs rootless).
ENV MAVEN_CONFIG "/tmp/.m2"

ENTRYPOINT ["/usr/local/bin/mvn-entrypoint.sh"]
CMD ["mvn"]
16 changes: 16 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,22 @@
</execution>
</executions>
</plugin>
<!-- Declaring the plugin activates the parent's managed JaCoCo agent + check executions, which
gate verify at 100% coverage (jacoco.minCoverage). The only configuration needed here is the
exclude set: the record-builder *Builder classes are generated (not ours to test, mirroring
the PMD/SpotBugs stance) and the native smoke stand-in runs as a native executable rather
than under the agent, so both are kept out of the coverage analysis. -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/*Builder.class</exclude>
<exclude>**/*Builder$*.class</exclude>
<exclude>**/NativeImageSmokeTest.class</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
6 changes: 5 additions & 1 deletion src/main/java/wtf/metio/devcontainer/Build.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
* image. Cached image identifiers are passed to the docker build command with --cache-from. Note that
* the array syntax will execute the command without a shell. You can learn more about formatting
* string vs array properties.
* @param options An array of additional arguments that should be passed to the docker build command when building a
* Dockerfile. Defaults to not set. For example: "build": { "options": [ "--add-host=host.docker
* .internal:host-gateway" ] }
*/
@RecordBuilder
@RecordBuilder.Options(buildMethodName = "create")
Expand All @@ -31,7 +34,8 @@ public record Build(
String context,
Map<String, String> args,
String target,
List<String> cacheFrom) implements BuildBuilder.With {
List<String> cacheFrom,
List<String> options) implements BuildBuilder.With {

public static BuildBuilder builder() {
return BuildBuilder.builder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import tools.jackson.databind.JavaType;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.exc.MismatchedInputException;
import tools.jackson.databind.node.StringNode;

public final class CommandDeserializer extends StdDeserializer<Command> {
Expand All @@ -38,7 +39,7 @@ public Command deserialize(final JsonParser parser, final DeserializationContext
return new Command(null, null, context.readTreeAsValue(node, type));
}

return context.reportInputMismatch(Command.class, "Cannot deserialize given input to Command");
throw MismatchedInputException.from(parser, Command.class, "Cannot deserialize given input to Command");
}

}
14 changes: 9 additions & 5 deletions src/main/java/wtf/metio/devcontainer/Devcontainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@
* @param securityOpt Defaults to []. Cross-orchestrator way to set container security options. For
* example: "securityOpt": [ "seccomp=unconfined" ]
* @param mounts Defaults to unset. Cross-orchestrator way to add additional mounts to a container.
* Each value is a string that accepts the same values as the Docker CLI --mount
* flag. Environment and pre-defined variables may be referenced in the value. For
* example: "mounts": [{ "source": "dind-var-lib-docker", "target":
* "/var/lib/docker", "type": "volume" }]
* Each value is either a string that accepts the same values as the Docker CLI
* --mount flag or an object with the equivalent named fields. Environment and
* pre-defined variables may be referenced in the value. For example: "mounts": [{
* "source": "dind-var-lib-docker", "target": "/var/lib/docker", "type": "volume" }]
* @param features An object of Dev Container Feature IDs and related options to be added into your
* primary container. The specific options that are available varies by feature, so
* see its documentation for additional details. For example: "features": {
Expand All @@ -90,6 +90,9 @@
* allows you to override the Feature install order when needed. For example:
* "overrideFeatureInstallorder": [ "ghcr.io/devcontainers/features/common-utils",
* "ghcr.io/devcontainers/features/github-cli" ]
* @param secrets Recommended secrets for this dev container. Recommendations are provided as
* environment variable keys, each mapped to optional metadata such as a description
* and documentation URL.
* @param customizations Product specific properties, defined in supporting tools
* @param image Required when using an image. The name of an image in a container registry
* (DockerHub, GitHub Container Registry, Azure Container Registry) that
Expand Down Expand Up @@ -200,9 +203,10 @@ public record Devcontainer(
Boolean privileged,
List<String> capAdd,
List<String> securityOpt,
List<Map<String, String>> mounts,
List<Mount> mounts,
Map<String, Map<String, String>> features,
List<String> overrideFeatureInstallOrder,
Map<String, Secret> secrets,
Map<String, Map<String, Object>> customizations,
String image,
Build build,
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/wtf/metio/devcontainer/Gpu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: The devcontainer.java Authors
* SPDX-License-Identifier: 0BSD
*/
package wtf.metio.devcontainer;

import io.soabase.recordbuilder.core.RecordBuilder;
import tools.jackson.databind.annotation.JsonDeserialize;

/**
* Wrapper for the various ways to specify the {@code hostRequirements.gpu} property. At most one of the parameters is
* set.
*
* @param enabled The boolean form: {@code true} requires a GPU, {@code false} does not.
* @param optional The string form. The only accepted value is {@code optional}, indicating a GPU is used when
* available but not required.
* @param requirements The object form, expressing detailed GPU requirements.
* @see <a href="https://containers.dev/implementors/json_reference/#min-host-reqs">schema reference</a>
*/
@RecordBuilder
@RecordBuilder.Options(buildMethodName = "create")
@JsonDeserialize(using = GpuDeserializer.class)
public record Gpu(
Boolean enabled,
String optional,
GpuRequirements requirements) implements GpuBuilder.With {

public static GpuBuilder builder() {
return GpuBuilder.builder();
}

}
39 changes: 39 additions & 0 deletions src/main/java/wtf/metio/devcontainer/GpuDeserializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: The devcontainer.java Authors
* SPDX-License-Identifier: 0BSD
*/
package wtf.metio.devcontainer;

import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.exc.MismatchedInputException;

public final class GpuDeserializer extends StdDeserializer<Gpu> {

public GpuDeserializer() {
this(Gpu.class);
}

public GpuDeserializer(final Class<?> vc) {
super(vc);
}

@Override
public Gpu deserialize(final JsonParser parser, final DeserializationContext context)
throws JacksonException {
final JsonNode node = context.readTree(parser);
if (node.isBoolean()) {
return new Gpu(node.booleanValue(), null, null);
} else if (node.isString()) {
return new Gpu(null, node.stringValue(), null);
} else if (node.isObject()) {
return new Gpu(null, null, context.readTreeAsValue(node, GpuRequirements.class));
}

throw MismatchedInputException.from(parser, Gpu.class, "Cannot deserialize given input to Gpu");
}

}
27 changes: 27 additions & 0 deletions src/main/java/wtf/metio/devcontainer/GpuRequirements.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: The devcontainer.java Authors
* SPDX-License-Identifier: 0BSD
*/
package wtf.metio.devcontainer;

import io.soabase.recordbuilder.core.RecordBuilder;

/**
* The object form of the {@code hostRequirements.gpu} property, used to express detailed GPU requirements.
*
* @param cores Indicates the minimum required number of cores. For example: "gpu": {"cores": 2}
* @param memory A string indicating minimum memory requirements with a tb, gb, mb, or kb suffix. For example, "gpu":
* {"memory": "8gb"}
* @see <a href="https://containers.dev/implementors/json_reference/#min-host-reqs">schema reference</a>
*/
@RecordBuilder
@RecordBuilder.Options(buildMethodName = "create")
public record GpuRequirements(
Integer cores,
String memory) implements GpuRequirementsBuilder.With {

public static GpuRequirementsBuilder builder() {
return GpuRequirementsBuilder.builder();
}

}
6 changes: 5 additions & 1 deletion src/main/java/wtf/metio/devcontainer/HostRequirements.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
* "hostRequirements": {"memory": "4gb"}
* @param storage A string indicating minimum storage requirements with a tb, gb, mb, or kb suffix. For example,
* "hostRequirements": {"storage": "32gb"}
* @param gpu Indicates whether a GPU is required. The string "optional" indicates that a GPU is used if available.
* An object value allows detailed GPU requirements to be expressed. For example: "hostRequirements":
* {"gpu": "optional"}
*/
@RecordBuilder
@RecordBuilder.Options(buildMethodName = "create")
public record HostRequirements(
Integer cpus,
String memory,
String storage) implements HostRequirementsBuilder.With {
String storage,
Gpu gpu) implements HostRequirementsBuilder.With {

public static HostRequirementsBuilder builder() {
return HostRequirementsBuilder.builder();
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/wtf/metio/devcontainer/Mount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: The devcontainer.java Authors
* SPDX-License-Identifier: 0BSD
*/
package wtf.metio.devcontainer;

import io.soabase.recordbuilder.core.RecordBuilder;
import tools.jackson.databind.annotation.JsonDeserialize;

/**
* Wrapper for the two ways to specify a {@code mounts} entry. At most one of the parameters is set.
*
* @param string The string form, accepting the same syntax as the Docker CLI {@code --mount} flag (e.g.
* {@code source=dind-var-lib-docker,target=/var/lib/docker,type=volume}).
* @param object The object form, expressing the mount with named fields.
* @see <a href="https://containers.dev/implementors/json_reference/#general-properties">schema reference</a>
*/
@RecordBuilder
@RecordBuilder.Options(buildMethodName = "create")
@JsonDeserialize(using = MountDeserializer.class)
public record Mount(
String string,
MountObject object) implements MountBuilder.With {

public static MountBuilder builder() {
return MountBuilder.builder();
}

}
37 changes: 37 additions & 0 deletions src/main/java/wtf/metio/devcontainer/MountDeserializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: The devcontainer.java Authors
* SPDX-License-Identifier: 0BSD
*/
package wtf.metio.devcontainer;

import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.exc.MismatchedInputException;

public final class MountDeserializer extends StdDeserializer<Mount> {

public MountDeserializer() {
this(Mount.class);
}

public MountDeserializer(final Class<?> vc) {
super(vc);
}

@Override
public Mount deserialize(final JsonParser parser, final DeserializationContext context)
throws JacksonException {
final JsonNode node = context.readTree(parser);
if (node.isString()) {
return new Mount(node.stringValue(), null);
} else if (node.isObject()) {
return new Mount(null, context.readTreeAsValue(node, MountObject.class));
}

throw MismatchedInputException.from(parser, Mount.class, "Cannot deserialize given input to Mount");
}

}
29 changes: 29 additions & 0 deletions src/main/java/wtf/metio/devcontainer/MountObject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: The devcontainer.java Authors
* SPDX-License-Identifier: 0BSD
*/
package wtf.metio.devcontainer;

import io.soabase.recordbuilder.core.RecordBuilder;

/**
* The object form of a {@code mounts} entry, mirroring the fields of the Docker CLI {@code --mount} flag.
*
* @param type The kind of mount. Required.
* @param source The source of the mount. Optional for a {@code volume} type, where Docker may create an anonymous
* volume.
* @param target The path the mount is created at inside the container. Required.
* @see <a href="https://containers.dev/implementors/json_reference/#general-properties">schema reference</a>
*/
@RecordBuilder
@RecordBuilder.Options(buildMethodName = "create")
public record MountObject(
MountType type,
String source,
String target) implements MountObjectBuilder.With {

public static MountObjectBuilder builder() {
return MountObjectBuilder.builder();
}

}
12 changes: 12 additions & 0 deletions src/main/java/wtf/metio/devcontainer/MountType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: The devcontainer.java Authors
* SPDX-License-Identifier: 0BSD
*/
package wtf.metio.devcontainer;

public enum MountType {

bind,
volume,

}
26 changes: 26 additions & 0 deletions src/main/java/wtf/metio/devcontainer/Secret.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: The devcontainer.java Authors
* SPDX-License-Identifier: 0BSD
*/
package wtf.metio.devcontainer;

import io.soabase.recordbuilder.core.RecordBuilder;

/**
* Metadata for a recommended secret, keyed by the environment variable name under {@code secrets}.
*
* @param description A description of the secret.
* @param documentationUrl A URL to documentation about the secret.
* @see <a href="https://containers.dev/implementors/json_reference/#general-properties">schema reference</a>
*/
@RecordBuilder
@RecordBuilder.Options(buildMethodName = "create")
public record Secret(
String description,
String documentationUrl) implements SecretBuilder.With {

public static SecretBuilder builder() {
return SecretBuilder.builder();
}

}
Loading