Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: dynamic properties #42

Merged
merged 1 commit into from
Dec 10, 2024
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
59 changes: 25 additions & 34 deletions src/main/java/io/kestra/plugin/docker/Build.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.annotations.PluginProperty;
import io.kestra.core.models.executions.metrics.Counter;
import io.kestra.core.models.property.Property;
import io.kestra.core.models.tasks.*;
import io.kestra.core.runners.FilesService;
import io.kestra.core.runners.RunContext;
Expand Down Expand Up @@ -50,7 +51,7 @@
dockerfile: |
FROM ubuntu
ARG APT_PACKAGES=""

RUN apt-get update && apt-get install -y --no-install-recommends ${APT_PACKAGES};
platforms:
- linux/amd64
Expand All @@ -73,8 +74,7 @@ public class Build extends Task implements RunnableTask<Build.Output>, Namespace
@Schema(
title = "The URI of your Docker host e.g. localhost"
)
@PluginProperty(dynamic = true)
private String host;
private Property<String> host;

@Schema(
title = "Credentials to push your image to a container registry."
Expand All @@ -85,55 +85,42 @@ public class Build extends Task implements RunnableTask<Build.Output>, Namespace
@Schema(
title = "The contents of your Dockerfile passed as a string, or a path to the Dockerfile"
)
@PluginProperty(dynamic = true)
private String dockerfile;
private Property<String> dockerfile;

@Schema(
title = "The target platform for the image e.g. linux/amd64."
)
@PluginProperty(dynamic = true)
private List<String> platforms;
private Property<List<String>> platforms;

@Schema(
title = "Whether to push the image to a remote container registry."
)
@PluginProperty(dynamic = true)
@Builder.Default
private Boolean push = false;
private Property<Boolean> push = Property.of(false);

@Schema(
title = "Always attempt to pull the latest version of the base image."
)
@PluginProperty(dynamic = true)
@Builder.Default
private Boolean pull = true;
private Property<Boolean> pull = Property.of(true);

@Schema(
title = "The list of tag of this image.",
description = "If pushing to a custom registry, the tag should include the registry URL. " +
"Note that if you want to push to an insecure registry (HTTP), you need to edit the `/etc/docker/daemon.json` file on your Kestra host to [this](https://gist.github.com/brian-mulier-p/0c5a0ae85e83a179d6e93b22cb471934) and restart docker service (`sudo systemctl daemon-reload && sudo systemctl restart docker`)."
)
@PluginProperty(dynamic = true)
@NotNull
private Set<String> tags;
private Property<List<String>> tags;

@Schema(
title = "Optional build arguments in a `key: value` format."
)
@PluginProperty(
additionalProperties = String.class,
dynamic = true
)
protected Map<String, String> buildArgs;
protected Property<Map<String, String>> buildArgs;

@Schema(
title = "Additional metadata for the image in a `key: value` format."
)
@PluginProperty(
additionalProperties = String.class,
dynamic = true
)
protected Map<String, String> labels;
protected Property<Map<String, String>> labels;

private NamespaceFiles namespaceFiles;

Expand All @@ -142,8 +129,9 @@ public class Build extends Task implements RunnableTask<Build.Output>, Namespace
@Override
public Output run(RunContext runContext) throws Exception {
DefaultDockerClientConfig.Builder builder = DefaultDockerClientConfig.createDefaultConfigBuilder()
.withDockerHost(DockerService.findHost(runContext, this.host));
Set<String> tags = runContext.render(this.tags).stream().map(this::removeScheme).collect(Collectors.toSet());
.withDockerHost(DockerService.findHost(runContext, runContext.render(this.host).as(String.class).orElse(null)));
List<String> renderedTags = runContext.render(this.tags).asList(String.class).isEmpty() ? new ArrayList<>() : runContext.render(this.tags).asList(String.class);
Set<String> tags = renderedTags.stream().map(this::removeScheme).collect(Collectors.toSet());

if (this.getCredentials() != null) {
Path config = DockerService.createConfig(
Expand Down Expand Up @@ -172,10 +160,10 @@ public Output run(RunContext runContext) throws Exception {

try (DockerClient dockerClient = DockerService.client(builder.build())) {
BuildImageCmd buildImageCmd = dockerClient.buildImageCmd()
.withPull(this.pull);
.withPull(runContext.render(this.pull).as(Boolean.class).orElseThrow());

Path path = runContext.workingDir().path();
String dockerfile = runContext.render(this.dockerfile);
String dockerfile = runContext.render(this.dockerfile).as(String.class).orElse(null);
Path dockerFile;

if (path.resolve(dockerfile).toFile().exists()) {
Expand All @@ -186,25 +174,28 @@ public Output run(RunContext runContext) throws Exception {

buildImageCmd.withDockerfile(dockerFile.toFile());

if (this.platforms != null) {
runContext.render(this.platforms).forEach(buildImageCmd::withPlatform);
List<String> renderedPlatforms = runContext.render(platforms).asList(String.class);
if (!renderedPlatforms.isEmpty()) {
renderedPlatforms.forEach(buildImageCmd::withPlatform);
}

buildImageCmd.withTags(tags);

if (this.buildArgs != null) {
runContext.renderMap(this.buildArgs).forEach(buildImageCmd::withBuildArg);
var renderedArgs = runContext.render(this.buildArgs).asMap(String.class, String.class);
if (!renderedArgs.isEmpty()) {
renderedArgs.forEach(buildImageCmd::withBuildArg);
}

if (this.labels != null) {
buildImageCmd.withLabels(runContext.renderMap(this.labels));
var renderedLabel = runContext.render(this.labels).asMap(String.class, String.class);
if (!renderedLabel.isEmpty()) {
buildImageCmd.withLabels(renderedLabel);
}

String imageId = buildImageCmd
.exec(new BuildImageResultCallback(runContext))
.awaitImageId();

if (this.push) {
if (runContext.render(this.push).as(Boolean.class).orElseThrow()) {
for (String tag : tags) {
PushResponseItemCallback resultPush = dockerClient.pushImageCmd(tag)
.exec(new PushResponseItemCallback(runContext));
Expand Down
61 changes: 24 additions & 37 deletions src/main/java/io/kestra/plugin/docker/Run.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.kestra.core.models.annotations.Example;
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.annotations.PluginProperty;
import io.kestra.core.models.property.Property;
import io.kestra.core.models.tasks.*;
import io.kestra.core.models.tasks.runners.TaskRunner;
import io.kestra.core.runners.RunContext;
Expand All @@ -14,9 +15,7 @@
import lombok.*;
import lombok.experimental.SuperBuilder;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;

@SuperBuilder
@ToString
Expand Down Expand Up @@ -63,8 +62,7 @@ public class Run extends Task implements RunnableTask<ScriptOutput>, NamespaceFi
@Schema(
title = "Docker API URI."
)
@PluginProperty(dynamic = true)
private String host;
private Property<String> host;

@Schema(
title = "Docker configuration file.",
Expand All @@ -90,42 +88,37 @@ public class Run extends Task implements RunnableTask<ScriptOutput>, NamespaceFi
@Schema(
title = "User in the Docker container."
)
@PluginProperty(dynamic = true)
protected String user;
protected Property<String> user;

@Schema(
title = "Docker entrypoint to use."
)
@PluginProperty(dynamic = true)
protected List<String> entryPoint;
protected Property<List<String>> entryPoint;

@Schema(
title = "Extra hostname mappings to the container network interface configuration."
)
@PluginProperty(dynamic = true)
protected List<String> extraHosts;
protected Property<List<String>> extraHosts;

@Schema(
title = "Docker network mode to use e.g. `host`, `none`, etc."
)
@PluginProperty(dynamic = true)
protected String networkMode;
protected Property<String> networkMode;

@Schema(
title = "List of volumes to mount.",
description = "Must be a valid mount expression as string, example : `/home/user:/app`.\n\n" +
"Volumes mount are disabled by default for security reasons; you must enable them on server configuration by setting `kestra.tasks.scripts.docker.volume-enabled` to `true`."
)
@PluginProperty(dynamic = true)
protected List<String> volumes;
protected Property<List<String>> volumes;

@Schema(
title = "The pull policy for an image.",
description = "Pull policy can be used to prevent pulling of an already existing image `IF_NOT_PRESENT`, or can be set to `ALWAYS` to pull the latest version of the image even if an image with the same tag already exists."
)
@PluginProperty
@Builder.Default
protected PullPolicy pullPolicy = PullPolicy.ALWAYS;
protected Property<PullPolicy> pullPolicy = Property.of(PullPolicy.ALWAYS);

@Schema(
title = "A list of device requests to be sent to device drivers."
Expand Down Expand Up @@ -156,24 +149,18 @@ public class Run extends Task implements RunnableTask<ScriptOutput>, NamespaceFi
title = "Size of `/dev/shm` in bytes.",
description = "The size must be greater than 0. If omitted, the system uses 64MB."
)
@PluginProperty(dynamic = true)
private String shmSize;
private Property<String> shmSize;

@Schema(
title = "Additional environment variables for the Docker container."
)
@PluginProperty(
additionalProperties = String.class,
dynamic = true
)
private Map<String, String> env;
private Property<Map<String, String>> env;

@Builder.Default
@Schema(
title = "Whether to set the task state to `WARNING` if any `stdErr` is emitted."
)
@PluginProperty
private Boolean warningOnStdErr = true;
private Property<Boolean> warningOnStdErr = Property.of(true);

private NamespaceFiles namespaceFiles;

Expand All @@ -186,37 +173,37 @@ public class Run extends Task implements RunnableTask<ScriptOutput>, NamespaceFi
)
@PluginProperty(dynamic = true)
@Builder.Default
private List<String> commands = Collections.emptyList();
private Property<List<String>> commands = Property.of(new ArrayList<>());

@Override
public ScriptOutput run(RunContext runContext) throws Exception {
TaskRunner taskRunner = Docker
.builder()
.type(Docker.class.getName())
.host(this.host)
.host(runContext.render(this.host).as(String.class).orElse(null))
.config(this.config)
.credentials(this.credentials)
.user(this.user)
.entryPoint(this.entryPoint)
.extraHosts(this.extraHosts)
.networkMode(this.networkMode)
.volumes(this.volumes)
.pullPolicy(this.pullPolicy)
.user(runContext.render(this.user).as(String.class).orElse(null))
.entryPoint(runContext.render(this.entryPoint).asList(String.class).isEmpty() ? null : runContext.render(this.entryPoint).asList(String.class))
.extraHosts(runContext.render(this.extraHosts).asList(String.class).isEmpty() ? null : runContext.render(this.extraHosts).asList(String.class))
.networkMode(runContext.render(this.networkMode).as(String.class).orElse(null))
.volumes(runContext.render(this.volumes).asList(String.class).isEmpty() ? null : runContext.render(this.volumes).asList(String.class))
.pullPolicy(runContext.render(this.pullPolicy).as(PullPolicy.class).orElseThrow())
.deviceRequests(this.deviceRequests)
.cpu(this.cpu)
.memory(this.memory)
.shmSize(this.shmSize)
.shmSize(runContext.render(this.shmSize).as(String.class).orElse(null))
.build();

var commandWrapper = new CommandsWrapper(runContext)
.withEnv(this.getEnv())
.withEnv(runContext.render(this.getEnv()).asMap(String.class, String.class).isEmpty() ? new HashMap<>() : runContext.render(this.getEnv()).asMap(String.class, String.class))
.withContainerImage(this.containerImage)
.withTaskRunner(taskRunner)
.withWarningOnStdErr(this.getWarningOnStdErr())
.withWarningOnStdErr(runContext.render(this.getWarningOnStdErr()).as(Boolean.class).orElseThrow())
.withNamespaceFiles(this.namespaceFiles)
.withInputFiles(this.inputFiles)
.withOutputFiles(this.outputFiles)
.withCommands(this.commands);
.withCommands(runContext.render(this.commands).asList(String.class).isEmpty() ? null : runContext.render(this.commands).asList(String.class));

return commandWrapper.run();
}
Expand Down
27 changes: 14 additions & 13 deletions src/test/java/io/kestra/plugin/docker/BuildTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.kestra.plugin.docker;

import com.google.common.collect.ImmutableMap;
import io.kestra.core.models.property.Property;
import io.kestra.core.runners.RunContext;
import io.kestra.core.runners.RunContextFactory;
import io.kestra.core.utils.TestsUtils;
Expand All @@ -27,16 +28,16 @@ void inline() throws Exception {
Build task = Build.builder()
.id("unit-test")
.type(Build.class.getName())
.platforms(List.of("linux/amd64"))
.buildArgs(Map.of("APT_PACKAGES", "curl"))
.labels(Map.of("unit-test", "true"))
.tags(Set.of("unit-test"))
.dockerfile("""
.platforms(Property.of(List.of("linux/amd64")))
.buildArgs(Property.of(Map.of("APT_PACKAGES", "curl")))
.labels(Property.of(Map.of("unit-test", "true")))
.tags(Property.of(List.of("unit-test")))
.dockerfile(Property.of("""
FROM ubuntu
ARG APT_PACKAGES=""

RUN apt-get update && apt-get install -y --no-install-recommends ${APT_PACKAGES};
""")
"""))
.build();

RunContext runContext = TestsUtils.mockRunContext(runContextFactory, task, ImmutableMap.of());
Expand All @@ -53,18 +54,18 @@ void local() throws Exception {
Files.writeString(path, """
FROM ubuntu
ARG APT_PACKAGES=""

RUN apt-get update && apt-get install -y --no-install-recommends ${APT_PACKAGES};
""");

Build task = Build.builder()
.id("unit-test")
.type(Build.class.getName())
.platforms(List.of("linux/amd64"))
.buildArgs(Map.of("APT_PACKAGES", "curl"))
.labels(Map.of("unit-test", "true"))
.tags(Set.of("unit-test"))
.dockerfile(path.getFileName().toString())
.platforms(Property.of(List.of("linux/amd64")))
.buildArgs(Property.of(Map.of("APT_PACKAGES", "curl")))
.labels(Property.of(Map.of("unit-test", "true")))
.tags(Property.of(List.of("unit-test")))
.dockerfile(Property.of(path.getFileName().toString()))
.build();

Build.Output run = task.run(runContext);
Expand Down
3 changes: 2 additions & 1 deletion src/test/java/io/kestra/plugin/docker/RunTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.kestra.plugin.docker;

import com.google.common.collect.ImmutableMap;
import io.kestra.core.models.property.Property;
import io.kestra.core.runners.RunContext;
import io.kestra.core.runners.RunContextFactory;
import io.kestra.core.utils.TestsUtils;
Expand All @@ -26,7 +27,7 @@ void run() throws Exception {
.id("run")
.type(Run.class.getName())
.containerImage("ubuntu")
.commands(List.of("echo", "here"))
.commands(Property.of(List.of("echo", "here")))
.build();
RunContext runContext = TestsUtils.mockRunContext(runContextFactory, run, ImmutableMap.of());

Expand Down
Loading