##// END OF EJS Templates
Implemented RunContainer, ExecContainer, StopContainer, RmContainer, ComposeExec tasks
cin -
r19:5e6d0e24a5d2 v1.3.0 default
parent child
Show More
@@ -0,0 +1,38
1 package org.implab.gradle.containers.cli;
2
3 import java.util.List;
4
5 public interface ComposeTraits {
6 public final String UP_COMMAND = "up";
7 public final String EXEC_COMMAND = "exec";
8 public final String STOP_COMMAND = "stop";
9 public final String RM_COMMAND = "rm";
10
11 ProcessSpec compose(String command);
12
13 default ProcessSpec stop() {
14 return compose(STOP_COMMAND);
15 }
16
17 default ProcessSpec up(boolean detached) {
18 var spec = compose(UP_COMMAND);
19 if (detached)
20 spec.args("--detach");
21 return spec;
22 }
23
24 default ProcessSpec exec(String service, List<String> options,
25 List<String> command) {
26 return compose(EXEC_COMMAND).args(options)
27 .args(service)
28 .args(command);
29 }
30
31 default ProcessSpec rm(boolean removeVolumes) {
32 var spec = compose(RM_COMMAND).args("--force", "--stop");
33 if (removeVolumes)
34 spec.args("--volumes");
35
36 return spec;
37 }
38 }
@@ -0,0 +1,111
1 package org.implab.gradle.containers.tasks;
2
3 import java.util.Optional;
4
5 import org.gradle.api.provider.ListProperty;
6 import org.gradle.api.tasks.Internal;
7 import org.implab.gradle.containers.cli.RedirectFrom;
8 import org.implab.gradle.containers.cli.RedirectTo;
9 import org.implab.gradle.containers.dsl.OptionsMixin;
10 import org.implab.gradle.containers.dsl.RedirectFromSpec;
11 import org.implab.gradle.containers.dsl.RedirectToSpec;
12
13 public abstract class BaseExecTask extends DockerCliTask implements OptionsMixin {
14 private final RedirectToSpec redirectStderr = new RedirectToSpec();
15
16 private final RedirectToSpec redirectStdout = new RedirectToSpec();
17
18 private final RedirectFromSpec redirectStdin = new RedirectFromSpec();
19
20 private boolean detached = false;
21
22 private boolean interactive = false;
23
24 private boolean allocateTty = false;
25
26 @Internal
27 public abstract ListProperty<String> getCommandLine();
28
29
30 /**
31 * STDIN redirection, if not specified, no input will be passed to the command
32 */
33 @Internal
34 public RedirectFromSpec getStdin() {
35 return redirectStdin;
36 }
37
38 /**
39 * STDOUT redirection, if not specified, redirected to logger::info
40 */
41 @Internal
42 public RedirectToSpec getStdout() {
43 return redirectStdout;
44 }
45
46 /**
47 * STDERR redirection, if not specified, redirected to logger::error
48 */
49 @Internal
50 public RedirectToSpec getStderr() {
51 return redirectStderr;
52 }
53
54 /**
55 * Specified whether the container should run in background. If the container
56 * has explicit IO redirects it will run in the foreground.
57 */
58 @Internal
59 public boolean isDetached() {
60 // if IO was redirected the container should run in foreground
61 if (redirectStdin.isRedirected() || redirectStdout.isRedirected() || redirectStderr.isRedirected())
62 return false;
63
64 return detached;
65 }
66
67 public void setDetached(boolean value) {
68 detached = value;
69 }
70
71 @Internal
72 public boolean getAllocateTty() {
73 return allocateTty;
74 }
75
76 public void setAllocateTty(boolean value) {
77 allocateTty = value;
78 }
79
80 /**
81 * Specified the interactive flag for the container, if the task specified
82 * STDIN redirection this flag is enabled automatically.
83 */
84 @Internal
85 public boolean isInteractive() {
86 // enable interactive mode when processing standard input
87 return redirectStdin.isRedirected() || interactive;
88 }
89
90 /** Appends specified parameters to the command line */
91 void commandLine(String... args) {
92 getCommandLine().addAll(args);
93 }
94
95
96 @Override
97 protected Optional<RedirectTo> stdoutRedirection() {
98 return redirectStdout.getRedirection().or(super::stdoutRedirection);
99 }
100
101 @Override
102 protected Optional<RedirectTo> stderrRedirection() {
103 return redirectStderr.getRedirection().or(super::stderrRedirection);
104 }
105
106 @Override
107 protected Optional<RedirectFrom> stdinRedirection() {
108 return redirectStdin.getRedirection();
109 }
110
111 }
@@ -0,0 +1,22
1 package org.implab.gradle.containers.tasks;
2
3 import java.io.File;
4
5 import org.gradle.api.provider.Property;
6 import org.gradle.api.provider.Provider;
7 import org.gradle.api.tasks.Internal;
8 import org.implab.gradle.containers.PropertiesMixin;
9 import org.implab.gradle.containers.cli.Utils;
10
11 public interface ContainerTaskMixin extends TaskServices, PropertiesMixin {
12 @Internal
13 Property<String> getContainerId();
14
15 default void fromIdFile(File file) {
16 getContainerId().set(provider(() -> Utils.readAll(file)));
17 }
18
19 default void fromIdFile(Provider<Object> file) {
20 getContainerId().set(file.map(Utils::normalizeFile).map(Utils::readAll));
21 }
22 }
@@ -0,0 +1,32
1 package org.implab.gradle.containers.tasks;
2
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.concurrent.ExecutionException;
6
7 import org.gradle.api.tasks.TaskAction;
8
9 public abstract class ExecContainer extends BaseExecTask implements ContainerTaskMixin {
10
11 public ExecContainer() {
12 onlyIfReason("No container specified", self -> getContainerId().isPresent());
13 }
14
15 @TaskAction
16 public void run() throws InterruptedException, IOException, ExecutionException {
17 var params = new ArrayList<>(getOptions().get());
18
19 if (isInteractive())
20 params.add("--interactive");
21 if (isDetached())
22 params.add("--detach");
23 if (getAllocateTty())
24 params.add("--tty");
25
26 exec(docker().containerExec(
27 getContainerId().get(),
28 params,
29 getCommandLine().get()));
30 }
31
32 }
@@ -0,0 +1,50
1 package org.implab.gradle.containers.tasks;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.concurrent.ExecutionException;
6 import java.util.stream.Collectors;
7
8 import org.gradle.api.provider.Property;
9 import org.gradle.api.provider.Provider;
10 import org.gradle.api.provider.SetProperty;
11 import org.gradle.api.tasks.Internal;
12 import org.gradle.api.tasks.TaskAction;
13 import org.implab.gradle.containers.cli.Utils;
14
15 public abstract class RmContainer extends DockerCliTask implements TaskServices {
16
17 @Internal
18 public abstract Property<Boolean> getRemoveVolumes();
19
20 @Internal
21 public abstract Property<Boolean> getForce();
22
23 @Internal
24 public abstract SetProperty<String> getContainerIds();
25
26 public RmContainer() {
27 getRemoveVolumes().convention(true);
28 getForce().convention(false);
29 onlyIfReason("No containers specified", self -> getContainerIds().get().size() > 0);
30 }
31
32 public void fromIdFile(File file) {
33 getContainerIds().add(provider(() -> Utils.readAll(file)));
34 }
35
36 public void fromIdFile(Provider<Object> file) {
37 getContainerIds().add(file.map(Utils::normalizeFile).map(Utils::readAll));
38 }
39
40 @TaskAction
41 public void run() throws InterruptedException, ExecutionException, IOException {
42 var alive = getContainerIds().get().stream().filter(this::containerExists).collect(Collectors.toSet());
43
44 if (alive.size() > 0)
45 exec(docker().containerRm(alive, getRemoveVolumes().get(), getForce().get()));
46 else
47 getLogger().info("No containers left, nothing to do");
48 }
49
50 }
@@ -0,0 +1,29
1 package org.implab.gradle.containers.tasks;
2
3 import org.gradle.api.Project;
4 import org.gradle.api.Task;
5 import org.gradle.api.logging.Logger;
6 import org.gradle.api.specs.Spec;
7 import org.gradle.api.tasks.Internal;
8
9 /** Task methods available by default, this interface is used by mixins to
10 * interact with their task.
11 */
12 public interface TaskServices {
13 @Internal
14 Project getProject();
15
16 void onlyIf(Spec<? super Task> spec);
17
18 @Internal
19 Logger getLogger();
20
21 default void onlyIfReason(String skipReason, Spec<? super Task> spec) {
22 onlyIf(self -> {
23 var satisfied = spec.isSatisfiedBy(self);
24 if (!satisfied)
25 getLogger().info("SKIP: {}", skipReason);
26 return satisfied;
27 });
28 }
29 }
@@ -1,17 +1,31
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import javax.inject.Inject;
4
5 import org.gradle.api.file.DirectoryProperty;
6 import org.gradle.api.file.ProjectLayout;
7 import org.gradle.api.file.RegularFile;
3 import org.gradle.api.provider.Property;
8 import org.gradle.api.provider.Property;
9 import org.gradle.api.provider.Provider;
4 import org.gradle.api.provider.SetProperty;
10 import org.gradle.api.provider.SetProperty;
5
11
6 public abstract class ComposeExtension {
12 public abstract class ComposeExtension {
7 public final String COMPOSE_FILE = "compose.yaml";
13 public final String COMPOSE_FILE = "compose.yaml";
8
14
9 public abstract SetProperty<String> getProfiles();
15 public abstract SetProperty<String> getProfiles();
10
16
17 public abstract DirectoryProperty getContextDirectory();
18
11 public abstract Property<String> getComposeFileName();
19 public abstract Property<String> getComposeFileName();
12
20
13 public ComposeExtension() {
21 public Provider<RegularFile> getComposeFile() {
22 return getContextDirectory().file(getComposeFileName());
23 }
24
25 @Inject
26 public ComposeExtension(ProjectLayout layout) {
27 getContextDirectory().convention(layout.getBuildDirectory().dir("context"));
14 getComposeFileName().convention(COMPOSE_FILE);
28 getComposeFileName().convention(COMPOSE_FILE);
15 }
29 }
16
30
17 }
31 }
@@ -1,169 +1,158
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import java.util.HashMap;
4 import java.util.HashMap;
5 import java.util.Map;
5 import java.util.Map;
6
6
7 import org.gradle.api.DefaultTask;
7 import org.gradle.api.DefaultTask;
8 import org.gradle.api.Plugin;
8 import org.gradle.api.Plugin;
9 import org.gradle.api.Project;
9 import org.gradle.api.Project;
10 import org.gradle.api.artifacts.Configuration;
10 import org.gradle.api.artifacts.Configuration;
11 import org.gradle.api.artifacts.Configuration.State;
11 import org.gradle.api.artifacts.Configuration.State;
12 import org.gradle.api.logging.Logger;
12 import org.gradle.api.logging.Logger;
13 import org.gradle.api.tasks.Copy;
13 import org.gradle.api.tasks.Copy;
14 import org.gradle.api.tasks.Delete;
14 import org.gradle.api.tasks.Delete;
15 import org.implab.gradle.containers.cli.Utils;
15 import org.implab.gradle.containers.cli.Utils;
16 import org.implab.gradle.containers.tasks.ComposeRm;
16 import org.implab.gradle.containers.tasks.ComposeRm;
17 import org.implab.gradle.containers.tasks.ComposeStop;
17 import org.implab.gradle.containers.tasks.ComposeStop;
18 import org.implab.gradle.containers.tasks.ComposeUp;
18 import org.implab.gradle.containers.tasks.ComposeUp;
19 import org.implab.gradle.containers.tasks.WriteEnv;
19 import org.implab.gradle.containers.tasks.WriteEnv;
20
20
21 public abstract class ComposePlugin implements Plugin<Project>, ProjectMixin {
21 public abstract class ComposePlugin implements Plugin<Project>, ProjectMixin {
22 public static final String BUILD_GROUP = "build";
22 public static final String BUILD_GROUP = "build";
23
23
24 public final String COMPOSE_IMAGES_CONFIGURATION = "composeImages";
24 public final String COMPOSE_IMAGES_CONFIGURATION = "composeImages";
25
25
26 public final String COMPOSE_PROJECT_NAME = "COMPOSE_PROJECT_NAME";
26 public final String COMPOSE_PROJECT_NAME = "COMPOSE_PROJECT_NAME";
27
27
28 public final String COMPOSE_EXTENSION = "compose";
28 public final String COMPOSE_EXTENSION = "compose";
29
29
30 public final String COMPOSE_UP_TASK = "up";
30 public final String COMPOSE_UP_TASK = "up";
31
31
32 public final String COMPOSE_STOP_TASK = "stop";
32 public final String COMPOSE_STOP_TASK = "stop";
33
33
34 public final String COMPOSE_RM_TASK = "rm";
34 public final String COMPOSE_RM_TASK = "rm";
35
35
36 public final String CLEAN_TASK = "clean";
36 public final String CLEAN_TASK = "clean";
37
37
38 public final String BUILD_TASK = "build";
38 public final String BUILD_TASK = "build";
39
39
40 public final String PROCESS_RESOURCES_TASK = "processResources";
40 public final String PROCESS_RESOURCES_TASK = "processResources";
41
41
42 public final String WRITE_ENV_TASK = "writeEnv";
42 public final String WRITE_ENV_TASK = "writeEnv";
43
43
44 public final String COMPOSE_VAR = "composeVar";
44 public final String COMPOSE_VAR = "composeVar";
45
45
46 public final String ENV_FILE_NAME = ".env";
46 public final String ENV_FILE_NAME = ".env";
47
47
48 public Logger getLogger() {
48 public Logger getLogger() {
49 return getProject().getLogger();
49 return getProject().getLogger();
50 }
50 }
51
51
52 @Override
52 @Override
53 public void apply(Project project) {
53 public void apply(Project project) {
54 var containerImages = configuration(COMPOSE_IMAGES_CONFIGURATION, Configurations.RESOLVABLE);
54 var containerImages = configuration(COMPOSE_IMAGES_CONFIGURATION, Configurations.RESOLVABLE);
55
55
56 // basic configuration, register extension
56 // basic configuration, register extension
57 var basePlugin = plugin(ContainerBasePlugin.class);
57 plugin(ContainerBasePlugin.class);
58 var containerExtension = basePlugin.getContainerExtension();
59
58
60 var composeExtension = extension(COMPOSE_EXTENSION, ComposeExtension.class);
59 var composeExtension = extension(COMPOSE_EXTENSION, ComposeExtension.class);
61
60
62 var composeFile = containerExtension.getContextDirectory()
63 .file(composeExtension.getComposeFileName());
64 var composeProfiles = composeExtension.getProfiles();
65
66 var cleanTask = task(CLEAN_TASK, Delete.class, t -> {
61 var cleanTask = task(CLEAN_TASK, Delete.class, t -> {
67 t.delete(containerExtension.getContextDirectory());
62 t.delete(composeExtension.getContextDirectory());
68 });
63 });
69
64
70 // copy task from src/main
65 // copy task from src/main
71 var processResources = task(PROCESS_RESOURCES_TASK, Copy.class, t -> {
66 var processResources = task(PROCESS_RESOURCES_TASK, Copy.class, t -> {
72 t.mustRunAfter(cleanTask);
67 t.mustRunAfter(cleanTask);
73 t.from(projectDirectory().dir("src/main"));
68 t.from(projectDirectory().dir("src/main"));
74 t.into(containerExtension.getContextDirectory());
69 t.into(composeExtension.getContextDirectory());
75 });
70 });
76
71
77 // write .env
72 // write .env
78 var writeEnvTask = task(WRITE_ENV_TASK, WriteEnv.class, t -> {
73 var writeEnvTask = task(WRITE_ENV_TASK, WriteEnv.class, t -> {
79 t.dependsOn(processResources, containerImages);
74 t.dependsOn(processResources, containerImages);
80 t.getEnvFile().set(containerExtension.getContextDirectory().file(ENV_FILE_NAME));
75 t.getEnvFile().set(composeExtension.getContextDirectory().file(ENV_FILE_NAME));
81
76
82 var group = project.getGroup();
77 var group = project.getGroup();
83 if (group != null && group.toString().length() > 0) {
78 if (group != null && group.toString().length() > 0) {
84 t.getEnvironment().put(COMPOSE_PROJECT_NAME, group.toString());
79 t.getEnvironment().put(COMPOSE_PROJECT_NAME, group.toString());
85 }
80 }
86
81
87 t.getEnvironment().putAll(containerImages.map(this::extractComposeEnv));
82 t.getEnvironment().putAll(containerImages.map(this::extractComposeEnv));
88
83
89 });
84 });
90
85
91 var buildTask = task(BUILD_TASK, DefaultTask.class, t -> {
86 var buildTask = task(BUILD_TASK, DefaultTask.class, t -> {
92 t.setGroup(BUILD_GROUP);
87 t.setGroup(BUILD_GROUP);
93 t.dependsOn(writeEnvTask);
88 t.dependsOn(writeEnvTask);
94 });
89 });
95
90
96 var stopTask = task(COMPOSE_STOP_TASK, ComposeStop.class, t -> {
91 var stopTask = task(COMPOSE_STOP_TASK, ComposeStop.class, t -> {
97 // stop must run after build
92 // stop must run after build
98 t.mustRunAfter(buildTask);
93 t.mustRunAfter(buildTask);
99
94
100 t.getProfiles().addAll(composeProfiles);
101 t.getComposeFile().set(composeFile);
102 });
95 });
103
96
104 var rmTask = task(COMPOSE_RM_TASK, ComposeRm.class, t -> {
97 var rmTask = task(COMPOSE_RM_TASK, ComposeRm.class, t -> {
105 // rm must run after build and stop
98 // rm must run after build and stop
106 t.mustRunAfter(buildTask, stopTask);
99 t.mustRunAfter(buildTask, stopTask);
107
100
108 t.getProfiles().addAll(composeProfiles);
109 t.getComposeFile().set(composeFile);
110 });
101 });
111
102
112 task(COMPOSE_UP_TASK, ComposeUp.class, t -> {
103 task(COMPOSE_UP_TASK, ComposeUp.class, t -> {
113 t.dependsOn(buildTask);
104 t.dependsOn(buildTask);
114 // up must run after stop and rm
105 // up must run after stop and rm
115 t.mustRunAfter(stopTask, rmTask);
106 t.mustRunAfter(stopTask, rmTask);
116
107
117 t.getProfiles().addAll(composeProfiles);
118 t.getComposeFile().set(composeFile);
119 });
108 });
120 }
109 }
121
110
122 /**
111 /**
123 * Processed the configurations, extracts composeVar extra property from
112 * Processed the configurations, extracts composeVar extra property from
124 * each dependency in this configuration and adds a value to the resulting
113 * each dependency in this configuration and adds a value to the resulting
125 * map. The values in the nap will contain image tags.
114 * map. The values in the nap will contain image tags.
126 */
115 */
127 private Map<String, String> extractComposeEnv(Configuration config) {
116 private Map<String, String> extractComposeEnv(Configuration config) {
128 if (config.getState() != State.UNRESOLVED) {
117 if (config.getState() != State.UNRESOLVED) {
129 getLogger().error("extractComposeEnv: The configuration {} isn't resolved.", config.getName());
118 getLogger().error("extractComposeEnv: The configuration {} isn't resolved.", config.getName());
130 throw new IllegalStateException("The specified configuration isn't resolved");
119 throw new IllegalStateException("The specified configuration isn't resolved");
131 }
120 }
132
121
133 getLogger().info("extractComposeEnv {}", config.getName());
122 getLogger().info("extractComposeEnv {}", config.getName());
134
123
135 var map = new HashMap<String, String>();
124 var map = new HashMap<String, String>();
136
125
137 for (var dependency : config.getDependencies()) {
126 for (var dependency : config.getDependencies()) {
138 // get extra composeVar if present
127 // get extra composeVar if present
139 extra(dependency, COMPOSE_VAR, String.class).optional().ifPresent(varName -> {
128 extra(dependency, COMPOSE_VAR, String.class).optional().ifPresent(varName -> {
140 // if we have a composeVar extra attribute on this dependency
129 // if we have a composeVar extra attribute on this dependency
141
130
142 // get files for the dependency
131 // get files for the dependency
143 var files = config.files(dependency);
132 var files = config.files(dependency);
144 if (files.size() == 1) {
133 if (files.size() == 1) {
145 // should bw exactly 1 file
134 // should bw exactly 1 file
146 var file = files.stream().findAny().get();
135 var file = files.stream().findAny().get();
147 getLogger().info("Processing {}: {}", dependency, file);
136 getLogger().info("Processing {}: {}", dependency, file);
148
137
149 try {
138 try {
150 // try read imageRef
139 // try read imageRef
151 Utils.readImageRef(file).getTag()
140 Utils.readImageRef(file).getTag()
152 .ifPresentOrElse(
141 .ifPresentOrElse(
153 tag -> map.put(varName, tag),
142 tag -> map.put(varName, tag),
154 () -> getLogger().error("ImageRef doesn't have a tag: {}", file));
143 () -> getLogger().error("ImageRef doesn't have a tag: {}", file));
155
144
156 } catch (IOException e) {
145 } catch (IOException e) {
157 getLogger().error("Failed to read ImageRef {}: {}", file, e);
146 getLogger().error("Failed to read ImageRef {}: {}", file, e);
158 }
147 }
159
148
160 } else {
149 } else {
161 getLogger().warn("Dependency {} must have exactly 1 file", dependency);
150 getLogger().warn("Dependency {} must have exactly 1 file", dependency);
162 }
151 }
163 });
152 });
164 }
153 }
165
154
166 return map;
155 return map;
167 }
156 }
168
157
169 }
158 }
@@ -1,56 +1,49
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import org.gradle.api.Plugin;
3 import org.gradle.api.Plugin;
4 import org.gradle.api.Project;
4 import org.gradle.api.Project;
5 import org.gradle.api.plugins.ExtraPropertiesExtension;
5 import org.gradle.api.plugins.ExtraPropertiesExtension;
6 import org.implab.gradle.containers.tasks.BuildImage;
6 import org.implab.gradle.containers.tasks.BuildImage;
7 import org.implab.gradle.containers.tasks.ComposeExec;
8 import org.implab.gradle.containers.tasks.ComposeRm;
9 import org.implab.gradle.containers.tasks.ComposeStop;
10 import org.implab.gradle.containers.tasks.ComposeUp;
11 import org.implab.gradle.containers.tasks.ExecContainer;
7 import org.implab.gradle.containers.tasks.PushImage;
12 import org.implab.gradle.containers.tasks.PushImage;
13 import org.implab.gradle.containers.tasks.RmContainer;
8 import org.implab.gradle.containers.tasks.RunContainer;
14 import org.implab.gradle.containers.tasks.RunContainer;
9 import org.implab.gradle.containers.tasks.SaveImage;
15 import org.implab.gradle.containers.tasks.SaveImage;
10 import org.implab.gradle.containers.tasks.StopContainer;
16 import org.implab.gradle.containers.tasks.StopContainer;
11 import org.implab.gradle.containers.tasks.TagImage;
17 import org.implab.gradle.containers.tasks.TagImage;
12
18
13 public class ContainerBasePlugin implements Plugin<Project> {
19 public abstract class ContainerBasePlugin implements Plugin<Project>, ProjectMixin {
14 public static final String CONTAINER_EXTENSION_NAME = "container";
20 public static final String CONTAINER_EXTENSION_NAME = "container";
15
21
16 private ContainerExtension containerExtension;
22 private ContainerExtension containerExtension;
17
23
18 ContainerExtension getContainerExtension() {
24 ContainerExtension getContainerExtension() {
19 if (containerExtension == null)
25 if (containerExtension == null)
20 throw new IllegalStateException();
26 throw new IllegalStateException();
21 return containerExtension;
27 return containerExtension;
22 }
28 }
23
29
24 void exportClasses(Project project, Class<?>... classes) {
30 void exportClasses(Project project, Class<?>... classes) {
25 ExtraPropertiesExtension extras = project.getExtensions().getExtraProperties();
31 ExtraPropertiesExtension extras = project.getExtensions().getExtraProperties();
26 for (var clazz : classes)
32 for (var clazz : classes)
27 extras.set(clazz.getSimpleName(), clazz);
33 extras.set(clazz.getSimpleName(), clazz);
28 }
34 }
29
35
30 @Override
36 @Override
31 public void apply(Project project) {
37 public void apply(Project project) {
32
38
33 containerExtension = project.getObjects().newInstance(ContainerExtension.class);
39 containerExtension = extension(CONTAINER_EXTENSION_NAME, ContainerExtension.class);
34
35 // TODO: move properties initialization into the constructor
36 containerExtension.getImageAuthority()
37 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
38
39 containerExtension.getImageGroup()
40 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
41
42 containerExtension.getCliCmd()
43 .convention(project
44 .provider(() -> (String) project.getProperties().get("containerCli"))
45 .orElse("docker"));
46
47 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
48
40
49 exportClasses(
41 exportClasses(
50 project,
42 project,
51 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunContainer.class,
43 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunContainer.class,
52 StopContainer.class);
44 StopContainer.class, ExecContainer.class, RmContainer.class, ComposeUp.class, ComposeExec.class,
45 ComposeRm.class, ComposeStop.class);
53
46
54 }
47 }
55
48
56 }
49 }
@@ -1,78 +1,88
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.FileNotFoundException;
4 import java.io.FileNotFoundException;
5 import java.io.IOException;
5 import java.io.IOException;
6 import java.util.Optional;
6 import java.util.Optional;
7
7
8 import javax.inject.Inject;
8 import javax.inject.Inject;
9
9
10 import org.gradle.api.Project;
10 import org.gradle.api.Project;
11 import org.gradle.api.file.DirectoryProperty;
11 import org.gradle.api.file.DirectoryProperty;
12 import org.gradle.api.file.ProjectLayout;
12 import org.gradle.api.file.ProjectLayout;
13 import org.gradle.api.provider.Property;
13 import org.gradle.api.provider.Property;
14 import org.gradle.api.provider.Provider;
14 import org.gradle.api.provider.Provider;
15 import org.implab.gradle.containers.cli.ImageName;
15 import org.implab.gradle.containers.cli.ImageName;
16 import org.implab.gradle.containers.cli.ImageRef;
16 import org.implab.gradle.containers.cli.ImageRef;
17 import org.implab.gradle.containers.cli.Utils;
17 import org.implab.gradle.containers.cli.Utils;
18
18
19 public abstract class ContainerExtension implements PropertiesMixin {
19 public abstract class ContainerExtension implements PropertiesMixin {
20
20
21 public abstract Property<String> getCliCmd();
21 public abstract Property<String> getCliCmd();
22
22
23 public abstract DirectoryProperty getContextDirectory();
23 public abstract DirectoryProperty getContextDirectory();
24
24
25 /**
25 /**
26 * Specifies the name of the registry where the image is located
26 * Specifies the name of the registry where the image is located
27 * {@code registry.my-company.org}
27 * {@code registry.my-company.org}
28 */
28 */
29 public abstract Property<String> getImageAuthority();
29 public abstract Property<String> getImageAuthority();
30
30
31 /**
31 /**
32 * Specified the path of the image like {@code my-company}
32 * Specified the path of the image like {@code my-company}
33 */
33 */
34 public abstract Property<String> getImageGroup();
34 public abstract Property<String> getImageGroup();
35
35
36 public Provider<ImageName> getImageName() {
36 public Provider<ImageName> getImageName() {
37 return provider(this::createImageName);
37 return provider(this::createImageName);
38 }
38 }
39
39
40 /**
40 /**
41 * Specifies local image part like {@code httpd}
41 * Specifies local image part like {@code httpd}
42 */
42 */
43 public abstract Property<String> getImageLocalName();
43 public abstract Property<String> getImageLocalName();
44
44
45 /**
45 /**
46 * This property is deprecated use imageLocalName
46 * This property is deprecated use imageLocalName
47 */
47 */
48 @Deprecated
48 @Deprecated
49 public Property<String> getImageShortName() {
49 public Property<String> getImageShortName() {
50 return getImageLocalName();
50 return getImageLocalName();
51 }
51 }
52
52
53 public abstract Property<String> getImageTag();
53 public abstract Property<String> getImageTag();
54
54
55 @Inject
55 @Inject
56 public ContainerExtension(ProjectLayout layout, Project project) {
56 public ContainerExtension(ProjectLayout layout, Project project) {
57 getContextDirectory().convention(layout.getBuildDirectory().dir("context"));
57 getContextDirectory().convention(layout.getBuildDirectory().dir("context"));
58
58
59 getImageLocalName().convention(project.getName());
59 getImageLocalName().convention(project.getName());
60
60
61 getImageTag().set(provider(() -> Optional.ofNullable(project.getVersion())
61 getImageTag().set(provider(() -> Optional.ofNullable(project.getVersion())
62 .map(Object::toString)
62 .map(Object::toString)
63 .orElse("latest")));
63 .orElse("latest")));
64
65 getImageAuthority()
66 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
67
68 getImageGroup()
69 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
70
71 getCliCmd().convention(project
72 .provider(() -> (String) project.getProperties().get("containerCli"))
73 .orElse("docker"));
64 }
74 }
65
75
66 ImageName createImageName() {
76 ImageName createImageName() {
67 return new ImageName(
77 return new ImageName(
68 getImageAuthority().getOrNull(),
78 getImageAuthority().getOrNull(),
69 getImageGroup().getOrNull(),
79 getImageGroup().getOrNull(),
70 getImageLocalName().get(),
80 getImageLocalName().get(),
71 getImageTag().getOrNull());
81 getImageTag().getOrNull());
72 }
82 }
73
83
74 public ImageRef readImageRef(File file) throws FileNotFoundException, IOException {
84 public ImageRef readImageRef(File file) throws FileNotFoundException, IOException {
75 return Utils.readImageRef(file);
85 return Utils.readImageRef(file);
76 }
86 }
77
87
78 } No newline at end of file
88 }
@@ -1,80 +1,67
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import org.gradle.api.GradleException;
3 import org.gradle.api.DefaultTask;
4 import org.gradle.api.Plugin;
4 import org.gradle.api.Plugin;
5 import org.gradle.api.Project;
5 import org.gradle.api.Project;
6 import org.gradle.api.artifacts.Dependency;
6 import org.gradle.api.artifacts.Dependency;
7 import org.gradle.api.file.ProjectLayout;
7 import org.gradle.api.file.ProjectLayout;
8 import org.gradle.api.tasks.Copy;
8 import org.gradle.api.tasks.Copy;
9 import org.gradle.api.tasks.Delete;
9 import org.gradle.api.tasks.Delete;
10 import org.gradle.api.tasks.TaskProvider;
11 import org.implab.gradle.containers.cli.ImageName;
10 import org.implab.gradle.containers.cli.ImageName;
12 import org.implab.gradle.containers.tasks.BuildImage;
11 import org.implab.gradle.containers.tasks.BuildImage;
13 import org.implab.gradle.containers.tasks.PushImage;
12 import org.implab.gradle.containers.tasks.PushImage;
14 import org.implab.gradle.containers.tasks.SaveImage;
13 import org.implab.gradle.containers.tasks.SaveImage;
15
14
16 public class ContainerPlugin implements Plugin<Project> {
15 public abstract class ContainerPlugin implements Plugin<Project>, ProjectMixin {
17
16
18 public static final String BUILD_GROUP = "build";
17 public static final String BUILD_GROUP = "build";
19
18
20 public static final String ARCHIVE_CONFIGURATION = "archive";
19 public static final String ARCHIVE_CONFIGURATION = "archive";
21
20
22 public void apply(Project project) {
21 public void apply(Project project) {
23 ProjectLayout layout = project.getLayout();
22 ProjectLayout layout = project.getLayout();
24
23
25 project.getPluginManager().apply(ContainerBasePlugin.class);
24 var basePlugin = plugin(ContainerBasePlugin.class);
26
25 var containerExtension = basePlugin.getContainerExtension();
27 var basePlugin = project.getPlugins().findPlugin(ContainerBasePlugin.class);
28 if (basePlugin == null)
29 throw new GradleException("The container-base plugin fails to be applied");
30
31 var containerExtension = basePlugin.getContainerExtension();
32
26
33 project.getConfigurations().create(Dependency.DEFAULT_CONFIGURATION, c -> {
27 configuration(Dependency.DEFAULT_CONFIGURATION, Configurations.CONSUMABLE);
34 c.setCanBeConsumed(true);
28 configuration(ARCHIVE_CONFIGURATION, Configurations.RESOLVABLE);
35 c.setCanBeResolved(false);
36 });
37
29
38 project.getConfigurations().create(ARCHIVE_CONFIGURATION, c -> {
30 var processResourcesTask = task("processResources", Copy.class, t -> {
39 c.setCanBeConsumed(true);
40 c.setCanBeResolved(false);
41 });
42
43 TaskProvider<Copy> processResourcesTask = project.getTasks().register("processResources", Copy.class, t -> {
44 t.setGroup(BUILD_GROUP);
31 t.setGroup(BUILD_GROUP);
45 t.from(layout.getProjectDirectory().dir("src/main"));
32 t.from(layout.getProjectDirectory().dir("src/main"));
46 t.into(containerExtension.getContextDirectory());
33 t.into(containerExtension.getContextDirectory());
47 });
34 });
48
35
49 TaskProvider<BuildImage> buildImageTask = project.getTasks().register("buildImage", BuildImage.class, t -> {
36 var buildImageTask = task("buildImage", BuildImage.class, t -> {
50 t.setGroup(BUILD_GROUP);
37 t.setGroup(BUILD_GROUP);
51 t.dependsOn(processResourcesTask);
38 t.dependsOn(processResourcesTask);
52
39
53 t.getContextDirectory().set(containerExtension.getContextDirectory());
40 t.getContextDirectory().set(containerExtension.getContextDirectory());
54 t.getImageIdFile().set(project.getLayout().getBuildDirectory().file("iid.json"));
41 t.getImageIdFile().set(project.getLayout().getBuildDirectory().file("iid.json"));
55
42
56 t.getImageName().set(containerExtension.getImageName().map(ImageName::toString));
43 t.getImageName().set(containerExtension.getImageName().map(ImageName::toString));
57 });
44 });
58
45
59 project.getTasks().register("clean", Delete.class, t -> {
46 task("clean", Delete.class, t -> {
60 t.delete(layout.getBuildDirectory());
47 t.delete(layout.getBuildDirectory());
61 });
48 });
62
49
63 project.getTasks().register("build", t -> {
50 task("build", DefaultTask.class, t -> {
64 t.setGroup(BUILD_GROUP);
51 t.setGroup(BUILD_GROUP);
65 t.dependsOn(buildImageTask);
52 t.dependsOn(buildImageTask);
66 });
53 });
67
54
68 project.getTasks().register("pushImage", PushImage.class, t -> {
55 task("pushImage", PushImage.class, t -> {
69 t.dependsOn(buildImageTask);
56 t.dependsOn(buildImageTask);
70 t.getImageName().set(buildImageTask.flatMap(BuildImage::getImageName));
57 t.getImageName().set(buildImageTask.flatMap(BuildImage::getImageName));
71 });
58 });
72
59
73 project.getTasks().register("saveImage", SaveImage.class, t -> {
60 task("saveImage", SaveImage.class, t -> {
74 t.dependsOn(buildImageTask);
61 t.dependsOn(buildImageTask);
75 t.getExportImages().add(buildImageTask.flatMap(BuildImage::getImageName));
62 t.getExportImages().add(buildImageTask.flatMap(BuildImage::getImageName));
76 });
63 });
77
64
78 project.getArtifacts().add(Dependency.DEFAULT_CONFIGURATION, buildImageTask);
65 project.getArtifacts().add(Dependency.DEFAULT_CONFIGURATION, buildImageTask);
79 }
66 }
80 }
67 }
@@ -1,69 +1,69
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import java.util.Collections;
3 import java.util.Collections;
4 import java.util.Optional;
4 import java.util.Optional;
5
5
6 import javax.inject.Inject;
6 import javax.inject.Inject;
7
7
8 import org.gradle.api.Action;
8 import org.gradle.api.Action;
9 import org.gradle.api.NamedDomainObjectProvider;
9 import org.gradle.api.NamedDomainObjectProvider;
10 import org.gradle.api.Plugin;
10 import org.gradle.api.Plugin;
11 import org.gradle.api.Project;
11 import org.gradle.api.Project;
12 import org.gradle.api.Task;
12 import org.gradle.api.Task;
13 import org.gradle.api.artifacts.Configuration;
13 import org.gradle.api.artifacts.Configuration;
14 import org.gradle.api.file.Directory;
14 import org.gradle.api.file.Directory;
15 import org.gradle.api.tasks.TaskProvider;
15 import org.gradle.api.tasks.TaskProvider;
16 import org.implab.gradle.containers.dsl.ExtraProps;
16 import org.implab.gradle.containers.dsl.ExtraProps;
17 import org.implab.gradle.containers.dsl.MapEntry;
17 import org.implab.gradle.containers.dsl.MapEntry;
18
18
19 /** Project configuration traits */
19 /** Project configuration traits */
20 public interface ProjectMixin {
20 public interface ProjectMixin {
21 @Inject
21 @Inject
22 Project getProject();
22 Project getProject();
23
23
24 /** registers the new task */
24 /** registers the new task */
25 default <T extends Task> TaskProvider<T> task(String name, Class<T> clazz, Action<? super T> configure) {
25 default <T extends Task> TaskProvider<T> task(String name, Class<T> clazz, Action<? super T> configure) {
26 return getProject().getTasks().register(name, clazz, configure);
26 return getProject().getTasks().register(name, clazz, configure);
27 }
27 }
28
28
29 /** Registers the new configuration */
29 /** Registers the new configuration */
30 default NamedDomainObjectProvider<Configuration> configuration(String name, Action<? super Configuration> configure) {
30 default NamedDomainObjectProvider<Configuration> configuration(String name, Action<? super Configuration> configure) {
31 return getProject().getConfigurations().register(name, configure);
31 return getProject().getConfigurations().register(name, configure);
32 }
32 }
33
33
34 /** Returns the project directory */
34 /** Returns the project directory */
35 default Directory projectDirectory() {
35 default Directory projectDirectory() {
36 return getProject().getLayout().getProjectDirectory();
36 return getProject().getLayout().getProjectDirectory();
37 }
37 }
38
38
39 /** Applies and returns the specified plugin, plugin is applied only once. */
39 /** Applies and returns the specified plugin, the plugin is applied only once. */
40 default <T extends Plugin<Project>> T plugin(Class<T> clazz) {
40 default <T extends Plugin<Project>> T plugin(Class<T> clazz) {
41 getProject().getPluginManager().apply(clazz);
41 getProject().getPluginManager().apply(clazz);
42 return getProject().getPlugins().findPlugin(clazz);
42 return getProject().getPlugins().findPlugin(clazz);
43 }
43 }
44
44
45 /** Creates and register a new project extension.
45 /** Creates and register a new project extension.
46 *
46 *
47 * @param <T> The type of the extension
47 * @param <T> The type of the extension
48 * @param extensionName The name of the extension in the project
48 * @param extensionName The name of the extension in the project
49 * @param clazz The class of the extension
49 * @param clazz The class of the extension
50 * @return the newly created extension
50 * @return the newly created extension
51 */
51 */
52 default <T> T extension(String extensionName, Class<T> clazz) {
52 default <T> T extension(String extensionName, Class<T> clazz) {
53 T extension = getProject().getObjects().newInstance(clazz);
53 T extension = getProject().getObjects().newInstance(clazz);
54 getProject().getExtensions().add(extensionName, extension);
54 getProject().getExtensions().add(extensionName, extension);
55 return extension;
55 return extension;
56 }
56 }
57
57
58 /** Return extra properties container for the specified object */
58 /** Return extra properties container for the specified object */
59 default Optional<ExtraProps> extra(Object target) {
59 default Optional<ExtraProps> extra(Object target) {
60 return ExtraProps.extra(target);
60 return ExtraProps.extra(target);
61 }
61 }
62
62
63 /** Returns accessor for the specified extra property name */
63 /** Returns accessor for the specified extra property name */
64 default <T> MapEntry<T> extra(Object target, String prop, Class<T> clazz) {
64 default <T> MapEntry<T> extra(Object target, String prop, Class<T> clazz) {
65 return ExtraProps.extra(target)
65 return ExtraProps.extra(target)
66 .map(x -> x.prop(prop, clazz))
66 .map(x -> x.prop(prop, clazz))
67 .orElseGet(() -> new MapEntry<T>(Collections.emptyMap(), prop, clazz));
67 .orElseGet(() -> new MapEntry<T>(Collections.emptyMap(), prop, clazz));
68 }
68 }
69 }
69 }
@@ -1,162 +1,107
1 package org.implab.gradle.containers.cli;
1 package org.implab.gradle.containers.cli;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.IOException;
4 import java.io.IOException;
5 import java.util.List;
5 import java.util.List;
6 import java.util.Optional;
7 import java.util.Set;
6 import java.util.Set;
8 import java.util.concurrent.ExecutionException;
7 import java.util.concurrent.ExecutionException;
9
8
10 import org.gradle.api.logging.Logger;
11
12 public abstract class DockerTraits {
9 public abstract class DockerTraits {
13
10
14 public final String BUILD_COMMAND = "build";
11 private final String BUILD_COMMAND = "build";
15 public final String PUSH_COMMAND = "push";
12 private final String PUSH_COMMAND = "push";
16 public final String RUN_COMMAND = "run";
13 private final String RUN_COMMAND = "run";
17 public final String SAVE_COMMAND = "save";
14 private final String EXEC_COMMAND = "exec";
18 public final String INSPECT_COMMAND = "inspect";
15 private final String SAVE_COMMAND = "save";
19 public final String IMAGE_COMMAND = "image";
16 private final String INSPECT_COMMAND = "inspect";
20 public final String TAG_COMMAND = "tag";
17 private final String IMAGE_COMMAND = "image";
21 public final String COMPOSE_COMMAND = "compose";
18 private final String TAG_COMMAND = "tag";
22 public final String UP_COMMAND = "up";
19 private final String COMPOSE_COMMAND = "compose";
23 public final String STOP_COMMAND = "stop";
20 private final String STOP_COMMAND = "stop";
24 public final String RM_COMMAND = "rm";
21 private final String START_COMMAND = "start";
25
22 private final String CONTAINER_COMMAND = "container";
26 public abstract Logger getLogger();
23 private final String RM_COMMAND = "rm";
27
24
28 public abstract Optional<File> getWorkingDir();
25 protected abstract ProcessSpec builder(String... command);
29
30 public abstract String getCliCmd();
31
26
32 protected boolean checkRetCode(ProcessSpec proc, int code)
27 public ProcessSpec imageBuild(String imageName, File contextDirectory, List<String> options) {
33 throws InterruptedException, ExecutionException, IOException {
28 return builder(BUILD_COMMAND)
34 if (getLogger().isInfoEnabled()) {
29 .args(options)
35 proc.redirectStdout(RedirectTo.consumer(getLogger()::info))
30 .args("-t", imageName, contextDirectory.getAbsolutePath());
36 .redirectStderr(RedirectTo.consumer(getLogger()::info));
37 }
38
39 getLogger().info("Starting: {}", proc.command());
40
41 return proc.start().get() == code;
42 }
31 }
43
32
44 protected void exec(ProcessSpec proc) throws InterruptedException, IOException, ExecutionException {
33 public ProcessSpec imagePush(String image, List<String> options) {
45 if (getLogger().isInfoEnabled())
34 return builder(PUSH_COMMAND)
46 proc.redirectStdout(RedirectTo.consumer(getLogger()::info));
35 .args(options)
47
36 .args(image);
48 if (getLogger().isErrorEnabled())
49 proc.redirectStderr(RedirectTo.consumer(getLogger()::error));
50
51 getLogger().info("Starting: {}", proc.command());
52
53 var code = proc.start().get();
54 if (code != 0) {
55 getLogger().error("The process exited with code {}", code);
56 throw new IOException("The process exited with error code " + code);
57 }
58 }
37 }
59
38
60 protected ProcessSpec builder(String... args) {
39 public ProcessSpec containerRun(String image, List<String> options, List<String> command) {
61 var spec = new ProcessSpec().args(getCliCmd()).args(args);
62
63 getWorkingDir().ifPresent(spec::directory);
64
65 return spec;
66 }
67
68 public void buildImage(String imageName, File contextDirectory, List<String> options)
69 throws IOException, InterruptedException, ExecutionException {
70 var spec = builder(BUILD_COMMAND)
71 .args(options)
72 .args("-t", imageName, contextDirectory.getAbsolutePath());
73
74 exec(spec);
75 }
76
77 public void pushImage(String image, List<String> options)
78 throws InterruptedException, IOException, ExecutionException {
79 var spec = builder(PUSH_COMMAND)
80 .args(options)
81 .args(image);
82
83 exec(spec);
84 }
85
86 public ProcessSpec runImage(String image, List<String> options, List<String> command)
87 throws InterruptedException, IOException {
88 return builder(RUN_COMMAND)
40 return builder(RUN_COMMAND)
89 .args(options)
41 .args(options)
90 .args(image)
42 .args(image)
91 .args(command);
43 .args(command);
92 }
44 }
93
45
94 public void stopContainer(String containerId, List<String> options) throws InterruptedException, IOException, ExecutionException {
46 public ProcessSpec containerExec(String containerId, List<String> options, List<String> command) {
95 exec(builder(STOP_COMMAND, containerId).args(options));
47 return builder(EXEC_COMMAND)
48 .args(options)
49 .args(containerId)
50 .args(command);
96 }
51 }
97
52
98 public void saveImage(Set<String> images, File output)
53 public ProcessSpec containerStop(String containerId, List<String> options) {
99 throws InterruptedException, IOException, ExecutionException {
54 return builder(STOP_COMMAND, containerId).args(options);
100 if (output.exists())
101 output.delete();
102
103 var spec = builder(SAVE_COMMAND)
104 .args("-o", output.getAbsolutePath())
105 .args(images);
106
107 exec(spec);
108 }
109
110 public void tagImage(String source, String target) throws InterruptedException, IOException, ExecutionException {
111 exec(builder(TAG_COMMAND, source, target));
112 }
55 }
113
56
114 public boolean imageExists(String imageId) throws InterruptedException, IOException, ExecutionException {
57 public ProcessSpec containerStart(Set<String> containers, List<String> options) {
115 getLogger().info("Check image {} exists", imageId);
58 return builder(START_COMMAND).args(options).args(containers);
116
117 return checkRetCode(
118 builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId),
119 0);
120 }
59 }
121
60
122 public boolean imageExists(File imageIdFile) {
61 public ProcessSpec containerRm(Set<String> containers, boolean removeVolumes, boolean force) {
123 if (imageIdFile.exists()) {
62 var spec = builder(RM_COMMAND);
124 try {
125 var imageId = Utils.readImageRef(imageIdFile);
126 return imageExists(imageId.getId());
127 } catch (IOException | InterruptedException | ExecutionException e) {
128 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
129 return false;
130 }
131 }
132 return false;
133 }
134
63
135 ProcessSpec compose(File primaryCompose, Set<String> profiles, String... extra) {
64 if(removeVolumes)
136 var spec = builder(COMPOSE_COMMAND, "--file", primaryCompose.getAbsolutePath());
65 spec.args("--volumes");
66 if (force)
67 spec.args("--force");
137
68
138 for (var profile : profiles)
69 spec.args(containers);
139 spec.args("--profile", profile);
140
141 spec.args(extra);
142
70
143 return spec;
71 return spec;
144 }
72 }
145
73
146 public void composeUp(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException, ExecutionException {
74 public ProcessSpec imageSave(Set<String> images, File output) {
147 exec(compose(primaryCompose, profiles, UP_COMMAND, "--detach"));
75 if (output.exists())
76 output.delete();
77
78 return builder(SAVE_COMMAND)
79 .args("-o", output.getAbsolutePath())
80 .args(images);
148 }
81 }
149
82
150 public void composeStop(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException, ExecutionException {
83 public ProcessSpec imageTag(String source, String target) {
151 exec(compose(primaryCompose, profiles, STOP_COMMAND));
84 return builder(TAG_COMMAND, source, target);
85 }
86
87 public ProcessSpec imageExists(String imageId) throws InterruptedException, IOException, ExecutionException {
88 return builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId);
152 }
89 }
153
90
154 public void composeRm(File primaryCompose, Set<String> profiles, boolean removeVolumes)
91 public ProcessSpec containerExists(String imageId) throws InterruptedException, IOException, ExecutionException {
155 throws InterruptedException, IOException, ExecutionException {
92 return builder(CONTAINER_COMMAND, INSPECT_COMMAND, "--format", "container-exists", imageId);
156 var spec = compose(primaryCompose, profiles, RM_COMMAND, "--force", "--stop");
93 }
157 if (removeVolumes)
158 spec.args("--volumes");
159
94
160 exec(spec);
95 public ComposeTraits compose(File primaryCompose, Set<String> profiles) {
96 return new ComposeTraits() {
97 @Override
98 public ProcessSpec compose(String command) {
99 var spec = builder(COMPOSE_COMMAND, "--file", primaryCompose.getAbsolutePath());
100
101 for (var profile : profiles)
102 spec.args("--profile", profile);
103 return spec.args(command);
104 }
105 };
161 }
106 }
162 }
107 }
@@ -1,32 +1,31
1 package org.implab.gradle.containers.cli;
1 package org.implab.gradle.containers.cli;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.FileInputStream;
4 import java.io.FileInputStream;
5 import java.io.InputStream;
5 import java.io.InputStream;
6 import java.io.OutputStream;
6 import java.io.OutputStream;
7 import java.util.concurrent.Callable;
8 import java.util.concurrent.CompletableFuture;
7 import java.util.concurrent.CompletableFuture;
9
8
10 public interface RedirectFrom {
9 public interface RedirectFrom {
11 CompletableFuture<Void> redirect(OutputStream to);
10 CompletableFuture<Void> redirect(OutputStream to);
12
11
13 public static RedirectFrom file(final File file) {
12 public static RedirectFrom file(final File file) {
14 return to -> CompletableFuture.runAsync(() -> {
13 return to -> CompletableFuture.runAsync(() -> {
15 try (var from = new FileInputStream(file); to) {
14 try (var from = new FileInputStream(file); to) {
16 from.transferTo(to);
15 from.transferTo(to);
17 } catch (Exception e) {
16 } catch (Exception e) {
18 // silence!
17 // silence!
19 }
18 }
20 });
19 });
21 }
20 }
22
21
23 public static RedirectFrom stream(final InputStream from) {
22 public static RedirectFrom stream(final InputStream from) {
24 return to -> CompletableFuture.runAsync(() -> {
23 return to -> CompletableFuture.runAsync(() -> {
25 try (from; to) {
24 try (from; to) {
26 from.transferTo(to);
25 from.transferTo(to);
27 } catch (Exception e) {
26 } catch (Exception e) {
28 // silence!
27 // silence!
29 }
28 }
30 });
29 });
31 }
30 }
32 }
31 }
@@ -1,156 +1,170
1 package org.implab.gradle.containers.cli;
1 package org.implab.gradle.containers.cli;
2
2
3 import java.io.ByteArrayOutputStream;
3 import java.io.ByteArrayOutputStream;
4 import java.io.Closeable;
4 import java.io.Closeable;
5 import java.io.File;
5 import java.io.File;
6 import java.io.FileNotFoundException;
6 import java.io.FileNotFoundException;
7 import java.io.FileOutputStream;
7 import java.io.FileOutputStream;
8 import java.io.FileReader;
8 import java.io.FileReader;
9 import java.io.IOException;
9 import java.io.IOException;
10 import java.io.InputStream;
10 import java.io.InputStream;
11 import java.io.OutputStream;
11 import java.io.OutputStream;
12 import java.io.Reader;
12 import java.io.Reader;
13 import java.util.Scanner;
13 import java.util.Scanner;
14 import java.util.Set;
14 import java.util.Set;
15 import java.util.concurrent.CompletableFuture;
15 import java.util.concurrent.CompletableFuture;
16 import java.util.stream.Collectors;
16 import java.util.stream.Collectors;
17 import java.util.stream.StreamSupport;
17 import java.util.stream.StreamSupport;
18 import java.nio.file.Files;
18 import java.nio.file.Files;
19 import java.util.List;
19 import java.util.List;
20 import org.gradle.api.Action;
20 import org.gradle.api.Action;
21 import org.gradle.api.GradleException;
22 import org.gradle.api.file.Directory;
23 import org.gradle.api.file.FileSystemLocation;
21 import org.gradle.internal.impldep.org.bouncycastle.util.Iterable;
24 import org.gradle.internal.impldep.org.bouncycastle.util.Iterable;
22
25
23 import com.fasterxml.jackson.core.exc.StreamWriteException;
26 import com.fasterxml.jackson.core.exc.StreamWriteException;
24 import com.fasterxml.jackson.databind.DatabindException;
27 import com.fasterxml.jackson.databind.DatabindException;
25 import com.fasterxml.jackson.databind.ObjectMapper;
28 import com.fasterxml.jackson.databind.ObjectMapper;
26 import com.fasterxml.jackson.databind.SerializationFeature;
29 import com.fasterxml.jackson.databind.SerializationFeature;
27 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
30 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
28
31
29 import groovy.json.JsonGenerator;
32 import groovy.json.JsonGenerator;
30 import groovy.json.JsonOutput;
33 import groovy.json.JsonOutput;
31 import groovy.json.JsonGenerator.Converter;
34 import groovy.json.JsonGenerator.Converter;
32 import groovy.lang.Closure;
35 import groovy.lang.Closure;
33
36
34 public final class Utils {
37 public final class Utils {
35 public static CompletableFuture<Void> redirectIO(final InputStream src, final Action<String> consumer) {
38 public static CompletableFuture<Void> redirectIO(final InputStream src, final Action<String> consumer) {
36 return CompletableFuture.runAsync(() -> {
39 return CompletableFuture.runAsync(() -> {
37 try (Scanner sc = new Scanner(src)) {
40 try (Scanner sc = new Scanner(src)) {
38 while (sc.hasNextLine()) {
41 while (sc.hasNextLine()) {
39 consumer.execute(sc.nextLine());
42 consumer.execute(sc.nextLine());
40 }
43 }
41 }
44 }
42 });
45 });
43 }
46 }
44
47
45 public static CompletableFuture<Void> redirectIO(final InputStream src, final File file) {
48 public static CompletableFuture<Void> redirectIO(final InputStream src, final File file) {
46 return CompletableFuture.runAsync(() -> {
49 return CompletableFuture.runAsync(() -> {
47 try (OutputStream out = new FileOutputStream(file)) {
50 try (OutputStream out = new FileOutputStream(file)) {
48 src.transferTo(out);
51 src.transferTo(out);
49 } catch (Exception e) {
52 } catch (Exception e) {
50 // silence!
53 // silence!
51 }
54 }
52 });
55 });
53 }
56 }
54
57
55 public static CompletableFuture<Void> redirectIO(final InputStream src, final OutputStream dst) {
58 public static CompletableFuture<Void> redirectIO(final InputStream src, final OutputStream dst) {
56 return CompletableFuture.runAsync(() -> {
59 return CompletableFuture.runAsync(() -> {
57 try (dst) {
60 try (dst) {
58 src.transferTo(dst);
61 src.transferTo(dst);
59 } catch (Exception e) {
62 } catch (Exception e) {
60 // silence!
63 // silence!
61 }
64 }
62 });
65 });
63 }
66 }
64
67
65 public static void closeSilent(Closeable handle) {
68 public static void closeSilent(Closeable handle) {
66 try {
69 try {
67 handle.close();
70 handle.close();
68 } catch (Exception e) {
71 } catch (Exception e) {
69 // silence!
72 // silence!
70 }
73 }
71 }
74 }
72
75
73 public static String readAll(final InputStream src) throws IOException {
76 public static String readAll(final InputStream src) throws IOException {
74 ByteArrayOutputStream out = new ByteArrayOutputStream();
77 ByteArrayOutputStream out = new ByteArrayOutputStream();
75 src.transferTo(out);
78 src.transferTo(out);
76 return out.toString();
79 return out.toString();
77 }
80 }
78
81
79 public static <T> T readJson(final Reader reader, Class<T> type) throws IOException {
82 public static <T> T readJson(final Reader reader, Class<T> type) throws IOException {
80 ObjectMapper objectMapper = new ObjectMapper()
83 ObjectMapper objectMapper = new ObjectMapper()
81 .registerModule(new Jdk8Module());
84 .registerModule(new Jdk8Module());
82 return objectMapper.readValue(reader, type);
85 return objectMapper.readValue(reader, type);
83 }
86 }
84
87
85 public static void writeJson(final File file, Object value)
88 public static void writeJson(final File file, Object value)
86 throws StreamWriteException, DatabindException, IOException {
89 throws StreamWriteException, DatabindException, IOException {
87 ObjectMapper objectMapper = new ObjectMapper()
90 ObjectMapper objectMapper = new ObjectMapper()
88 .enable(SerializationFeature.INDENT_OUTPUT)
91 .enable(SerializationFeature.INDENT_OUTPUT)
89 .registerModule(new Jdk8Module());
92 .registerModule(new Jdk8Module());
90 objectMapper.writeValue(file, value);
93 objectMapper.writeValue(file, value);
91 }
94 }
92
95
93 public static ImageRef readImageRef(final File file) throws FileNotFoundException, IOException {
96 public static ImageRef readImageRef(final File file) throws FileNotFoundException, IOException {
94 try (var reader = new FileReader(file)) {
97 try (var reader = new FileReader(file)) {
95 return readJson(reader, ImageRef.class);
98 return readJson(reader, ImageRef.class);
96 }
99 }
97 }
100 }
98
101
99 public static String readAll(final File src) throws IOException {
102 public static String readAll(final File src) {
100 return Files.readString(src.toPath());
103 try {
104 return src.isFile() ? Files.readString(src.toPath()) : null;
105 } catch (IOException e) {
106 throw new GradleException("Failed to read file " + src.toString(), e);
107 }
101 }
108 }
102
109
103 public static List<String> readAll(final Iterable<? extends File> files) {
110 public static List<String> readAll(final Iterable<? extends File> files) {
104 return StreamSupport.stream(files.spliterator(), false)
111 return StreamSupport.stream(files.spliterator(), false)
105 .map(file -> {
112 .map(Utils::readAll)
106 try {
107 return Utils.readAll(file);
108 } catch (IOException e) {
109 throw new RuntimeException(e);
110 }
111 })
112 .toList();
113 .toList();
113 }
114 }
114
115
115 public static String readAll(final InputStream src, String charset) throws IOException {
116 public static String readAll(final InputStream src, String charset) throws IOException {
116 ByteArrayOutputStream out = new ByteArrayOutputStream();
117 ByteArrayOutputStream out = new ByteArrayOutputStream();
117 src.transferTo(out);
118 src.transferTo(out);
118 return out.toString(charset);
119 return out.toString(charset);
119 }
120 }
120
121
121 public static JsonGenerator createDefaultJsonGenerator() {
122 public static JsonGenerator createDefaultJsonGenerator() {
122 return new JsonGenerator.Options()
123 return new JsonGenerator.Options()
123 .excludeNulls()
124 .excludeNulls()
124 .addConverter(new Converter() {
125 .addConverter(new Converter() {
125 public boolean handles(Class<?> type) {
126 public boolean handles(Class<?> type) {
126 return (File.class == type);
127 return (File.class == type);
127 }
128 }
128
129
129 public Object convert(Object value, String key) {
130 public Object convert(Object value, String key) {
130 return ((File) value).getPath();
131 return ((File) value).getPath();
131 }
132 }
132 })
133 })
133 .build();
134 .build();
134 }
135 }
135
136
136 public static String toJsonPretty(Object value) {
137 public static String toJsonPretty(Object value) {
137 return JsonOutput.prettyPrint(createDefaultJsonGenerator().toJson(value));
138 return JsonOutput.prettyPrint(createDefaultJsonGenerator().toJson(value));
138 }
139 }
139
140
140 public static boolean isNullOrEmptyString(String value) {
141 public static boolean isNullOrEmptyString(String value) {
141 return (value == null || value.length() == 0);
142 return (value == null || value.length() == 0);
142 }
143 }
143
144
144 public static <T> Action<T> wrapClosure(Closure<?> closure) {
145 public static <T> Action<T> wrapClosure(Closure<?> closure) {
145 return x -> {
146 return x -> {
146 closure.setDelegate(x);
147 closure.setDelegate(x);
147 closure.setResolveStrategy(Closure.OWNER_FIRST);
148 closure.setResolveStrategy(Closure.OWNER_FIRST);
148 closure.call(x);
149 closure.call(x);
149 };
150 };
150 }
151 }
151
152
152 public static Set<String> mapToString(Set<Object> set) {
153 public static Set<String> mapToString(Set<Object> set) {
153 return set.stream().map(Object::toString).collect(Collectors.toSet());
154 return set.stream().map(Object::toString).collect(Collectors.toSet());
154 }
155 }
155
156
157 public static File normalizeFile(Object file) {
158 if (file == null)
159 throw new IllegalArgumentException("file");
160
161 if (file instanceof String)
162 return new File((String)file);
163 if (file instanceof FileSystemLocation)
164 return ((FileSystemLocation)file).getAsFile();
165 else if (file instanceof File)
166 return (File)file;
167 throw new ClassCastException();
168 }
169
156 } No newline at end of file
170 }
@@ -1,39 +1,41
1 package org.implab.gradle.containers.dsl;
1 package org.implab.gradle.containers.dsl;
2
2
3 import java.util.concurrent.Callable;
3 import java.util.concurrent.Callable;
4
4
5 import org.gradle.api.provider.ListProperty;
5 import org.gradle.api.provider.ListProperty;
6 import org.gradle.api.tasks.Input;
6 import org.gradle.api.tasks.Input;
7 import org.gradle.api.tasks.Optional;
7 import org.implab.gradle.containers.PropertiesMixin;
8 import org.implab.gradle.containers.PropertiesMixin;
8
9
9 import groovy.lang.Closure;
10 import groovy.lang.Closure;
10
11
11 public interface OptionsMixin extends PropertiesMixin {
12 public interface OptionsMixin extends PropertiesMixin {
12
13
13 @Input
14 @Input
15 @Optional
14 ListProperty<String> getOptions();
16 ListProperty<String> getOptions();
15
17
16 default void option(String opt) {
18 default void option(String opt) {
17 getOptions().add(opt);
19 getOptions().add(opt);
18 }
20 }
19
21
20 default void option(Callable<String> optionProvider) {
22 default void option(Callable<String> optionProvider) {
21 getOptions().add(provider(optionProvider));
23 getOptions().add(provider(optionProvider));
22 }
24 }
23
25
24 default void option(Closure<String> optionProvider) {
26 default void option(Closure<String> optionProvider) {
25 getOptions().add(provider(optionProvider));
27 getOptions().add(provider(optionProvider));
26 }
28 }
27
29
28 default void options(String ...opts) {
30 default void options(String ...opts) {
29 getOptions().addAll(opts);
31 getOptions().addAll(opts);
30 }
32 }
31
33
32 default void options(Callable<Iterable<String>> optionsProvider) {
34 default void options(Callable<Iterable<String>> optionsProvider) {
33 getOptions().addAll(provider(optionsProvider));
35 getOptions().addAll(provider(optionsProvider));
34 }
36 }
35
37
36 default void options(Closure<Iterable<String>> optionsProvider) {
38 default void options(Closure<Iterable<String>> optionsProvider) {
37 getOptions().addAll(provider(optionsProvider));
39 getOptions().addAll(provider(optionsProvider));
38 }
40 }
39 }
41 }
@@ -1,34 +1,34
1 package org.implab.gradle.containers.dsl;
1 package org.implab.gradle.containers.dsl;
2
2
3 import java.util.ArrayList;
3 import java.util.ArrayList;
4
4
5 import org.gradle.api.provider.ListProperty;
6 import org.gradle.api.provider.Property;
5 import org.gradle.api.provider.Property;
6 import org.gradle.api.provider.SetProperty;
7
7
8 public abstract class VolumeSpec {
8 public abstract class VolumeSpec {
9
9
10 public abstract Property<String> getSource();
10 public abstract Property<String> getSource();
11
11
12 public abstract Property<String> getTarget();
12 public abstract Property<String> getTarget();
13
13
14 public abstract ListProperty<String> getOptions();
14 public abstract SetProperty<String> getOptions();
15
15
16 public void ro() {
16 public void ro() {
17 getOptions().add("ro");
17 getOptions().add("ro");
18 }
18 }
19
19
20 public String resolveSpec() {
20 public String resolveSpec() {
21 var parts = new ArrayList<String>();
21 var parts = new ArrayList<String>();
22
22
23 if (getSource().isPresent())
23 if (getSource().isPresent())
24 parts.add(getSource().get());
24 parts.add(getSource().get());
25
25
26 parts.add(getTarget().get());
26 parts.add(getTarget().get());
27
27
28 if (getOptions().isPresent())
28 if (getOptions().isPresent())
29 parts.add(String.join(",", getOptions().get()));
29 parts.add(String.join(",", getOptions().get()));
30
30
31 return String.join(":", parts);
31 return String.join(":", parts);
32 }
32 }
33
33
34 }
34 }
@@ -1,116 +1,132
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.util.ArrayList;
3 import java.util.ArrayList;
4 import java.util.HashMap;
4 import java.util.HashMap;
5 import java.util.List;
5 import java.util.List;
6 import java.util.Map;
6 import java.util.Map;
7
7
8 import org.gradle.api.Action;
8 import org.gradle.api.Action;
9 import org.gradle.api.file.Directory;
9 import org.gradle.api.file.Directory;
10 import org.gradle.api.file.DirectoryProperty;
10 import org.gradle.api.file.DirectoryProperty;
11 import org.gradle.api.file.RegularFile;
11 import org.gradle.api.file.RegularFile;
12 import org.gradle.api.file.RegularFileProperty;
12 import org.gradle.api.file.RegularFileProperty;
13 import org.gradle.api.provider.MapProperty;
13 import org.gradle.api.provider.MapProperty;
14 import org.gradle.api.provider.Property;
14 import org.gradle.api.provider.Property;
15 import org.gradle.api.provider.Provider;
15 import org.gradle.api.provider.Provider;
16 import org.gradle.api.tasks.Input;
16 import org.gradle.api.tasks.Input;
17 import org.gradle.api.tasks.InputDirectory;
17 import org.gradle.api.tasks.InputDirectory;
18 import org.gradle.api.tasks.Internal;
18 import org.gradle.api.tasks.Internal;
19 import org.gradle.api.tasks.Optional;
19 import org.gradle.api.tasks.Optional;
20 import org.gradle.api.tasks.OutputFile;
20 import org.gradle.api.tasks.OutputFile;
21 import org.gradle.api.tasks.SkipWhenEmpty;
21 import org.gradle.api.tasks.SkipWhenEmpty;
22 import org.gradle.api.tasks.TaskAction;
22 import org.gradle.api.tasks.TaskAction;
23 import java.io.File;
23 import java.io.File;
24 import java.io.IOException;
24
25
25 import org.implab.gradle.containers.cli.ImageRef;
26 import org.implab.gradle.containers.cli.ImageRef;
26 import org.implab.gradle.containers.cli.Utils;
27 import org.implab.gradle.containers.cli.Utils;
27 import org.implab.gradle.containers.dsl.MapPropertyEntry;
28 import org.implab.gradle.containers.dsl.MapPropertyEntry;
28 import org.implab.gradle.containers.dsl.OptionsMixin;
29 import org.implab.gradle.containers.dsl.OptionsMixin;
29
30
30 import groovy.lang.Closure;
31 import groovy.lang.Closure;
31
32
32 public abstract class BuildImage extends DockerCliTask implements OptionsMixin {
33 public abstract class BuildImage extends DockerCliTask implements OptionsMixin {
33
34
34 @InputDirectory
35 @InputDirectory
35 @SkipWhenEmpty
36 @SkipWhenEmpty
36 public abstract DirectoryProperty getContextDirectory();
37 public abstract DirectoryProperty getContextDirectory();
37
38
38 @Input
39 @Input
39 public abstract MapProperty<String, String> getBuildArgs();
40 public abstract MapProperty<String, String> getBuildArgs();
40
41
41 @Input
42 @Input
42 @Optional
43 @Optional
43 public abstract Property<String> getBuildTarget();
44 public abstract Property<String> getBuildTarget();
44
45
45 @Input
46 @Input
46 public abstract Property<Object> getImageName();
47 public abstract Property<Object> getImageName();
47
48
48 @Internal
49 @Internal
49 public abstract RegularFileProperty getImageIdFile();
50 public abstract RegularFileProperty getImageIdFile();
50
51
51 @OutputFile
52 @OutputFile
52 public Provider<File> getImageIdFileOutput() {
53 public Provider<File> getImageIdFileOutput() {
53 return getImageIdFile().map(RegularFile::getAsFile);
54 return getImageIdFile().map(RegularFile::getAsFile);
54 }
55 }
55
56
56 public BuildImage() {
57 public BuildImage() {
57 getOutputs().upToDateWhen(task -> getImageIdFile()
58 getOutputs().upToDateWhen(task -> getImageIdFile()
58 .map(RegularFile::getAsFile)
59 .map(this::imageExists)
59 .map(docker()::imageExists)
60 .getOrElse(false));
60 .getOrElse(false));
61 }
61 }
62
62
63 public void buildArgs(Action<Map<String, String>> spec) {
63 public void buildArgs(Action<Map<String, String>> spec) {
64 getBuildArgs().putAll(provider(() -> {
64 getBuildArgs().putAll(provider(() -> {
65 Map<String, String> args = new HashMap<>();
65 Map<String, String> args = new HashMap<>();
66 spec.execute(args);
66 spec.execute(args);
67 getLogger().info("add buildArgs {}", args);
67 getLogger().info("add buildArgs {}", args);
68 return args;
68 return args;
69 }));
69 }));
70 }
70 }
71
71
72 public void buildArgs(Closure<Map<String, String>> spec) {
72 public void buildArgs(Closure<Map<String, String>> spec) {
73 buildArgs(Utils.wrapClosure(spec));
73 buildArgs(Utils.wrapClosure(spec));
74 }
74 }
75
75
76 public MapPropertyEntry<String, String> buildArg(String key) {
76 public MapPropertyEntry<String, String> buildArg(String key) {
77 return new MapPropertyEntry<String, String>(getBuildArgs(), key, getProviders());
77 return new MapPropertyEntry<String, String>(getBuildArgs(), key, getProviders());
78 }
78 }
79
79
80 private boolean imageExists(RegularFile file) {
81 return readRefId(file.getAsFile()).map(this::imageExists).orElse(false);
82 }
83
84 private java.util.Optional<String> readRefId(File idFile) {
85 try {
86 return idFile.exists() ? java.util.Optional.of(Utils.readImageRef(idFile).getId())
87 : java.util.Optional.empty();
88 } catch (IOException e) {
89 getLogger().error("Failed to read imageId {}: {}", idFile, e);
90 return java.util.Optional.empty();
91 }
92 }
93
80 @TaskAction
94 @TaskAction
81 public void run() throws Exception {
95 public void run() throws Exception {
82 List<String> args = new ArrayList<>();
96 List<String> args = new ArrayList<>();
83
97
84 // create a temp file to store image id
98 // create a temp file to store image id
85 var iidFile = new File(this.getTemporaryDir(), "iid");
99 var iidFile = new File(this.getTemporaryDir(), "imageid");
86
100
87 // specify where to write image id
101 // specify where to write image id
88 args.addAll(List.of("--iidfile", iidFile.getAbsolutePath()));
102 args.addAll(List.of("--iidfile", iidFile.getAbsolutePath()));
89
103
90 getBuildArgs().get().forEach((k, v) -> {
104 getBuildArgs().get().forEach((k, v) -> {
91 args.add("--build-arg");
105 args.add("--build-arg");
92 args.add(String.format("%s=%s", k, v));
106 args.add(String.format("%s=%s", k, v));
93 });
107 });
94
108
95 // add --target if specified for multi-stage build
109 // add --target if specified for multi-stage build
96 if (getBuildTarget().isPresent()) {
110 if (getBuildTarget().isPresent()) {
97 args.add("--target");
111 args.add("--target");
98 args.add(getBuildTarget().get());
112 args.add(getBuildTarget().get());
99 }
113 }
100
114
101 // add extra parameters
115 // add extra parameters
102 getOptions().get().forEach(args::add);
116 getOptions().get().forEach(args::add);
103
117
104 var imageTag = getImageName().map(Object::toString).get();
118 var imageTag = getImageName().map(Object::toString).get();
105
119
106 // build image
120 // build image
107 docker().buildImage(
121 var spec = docker().imageBuild(
108 imageTag,
122 imageTag,
109 getContextDirectory().map(Directory::getAsFile).get(),
123 getContextDirectory().map(Directory::getAsFile).get(),
110 args);
124 args);
111
125
126 exec(spec);
127
112 // read the built image id and store image ref metadata
128 // read the built image id and store image ref metadata
113 var imageRef = new ImageRef(imageTag, Utils.readAll(iidFile));
129 var imageRef = new ImageRef(imageTag, Utils.readAll(iidFile));
114 Utils.writeJson(getImageIdFile().map(RegularFile::getAsFile).get(), imageRef);
130 Utils.writeJson(getImageIdFile().map(RegularFile::getAsFile).get(), imageRef);
115 }
131 }
116 }
132 }
@@ -1,10 +1,31
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.concurrent.ExecutionException;
6
3 import org.gradle.api.provider.Property;
7 import org.gradle.api.provider.Property;
4 import org.gradle.api.tasks.Internal;
8 import org.gradle.api.tasks.Internal;
9 import org.gradle.api.tasks.TaskAction;
5
10
6 public abstract class ComposeExec {
11 public abstract class ComposeExec extends BaseExecTask implements ComposeTaskMixin {
7
12
8 @Internal
13 @Internal
9 public abstract Property<String> getServiceName();
14 public abstract Property<String> getServiceName();
15
16 public ComposeExec() {
17 applyComposeConvention();
18 }
19
20 @TaskAction
21 public void run() throws InterruptedException, ExecutionException, IOException {
22 var params = new ArrayList<>(getOptions().get());
23
24 if (isDetached())
25 params.add("--detach");
26 if (!getAllocateTty())
27 params.add("--no-TTY");
28
29 exec(docker().compose(this).exec(getServiceName().get(), params, getCommandLine().get()));
30 }
10 }
31 }
@@ -1,23 +1,24
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import java.util.concurrent.ExecutionException;
4 import java.util.concurrent.ExecutionException;
5
5
6 import org.gradle.api.provider.Property;
6 import org.gradle.api.provider.Property;
7 import org.gradle.api.tasks.Internal;
7 import org.gradle.api.tasks.Internal;
8 import org.gradle.api.tasks.TaskAction;
8 import org.gradle.api.tasks.TaskAction;
9
9
10 public abstract class ComposeRm extends ComposeTask {
10 public abstract class ComposeRm extends DockerCliTask implements ComposeTaskMixin {
11
11
12 @Internal
12 @Internal
13 public abstract Property<Boolean> getRemoveVolumes();
13 public abstract Property<Boolean> getRemoveVolumes();
14
14
15 public ComposeRm() {
15 public ComposeRm() {
16 applyComposeConvention();
16 getRemoveVolumes().convention(true);
17 getRemoveVolumes().convention(true);
17 }
18 }
18
19
19 @TaskAction
20 @TaskAction
20 public void run() throws InterruptedException, IOException, ExecutionException {
21 public void run() throws InterruptedException, IOException, ExecutionException {
21 docker().composeRm(getComposeFile().get().getAsFile(), getProfiles().get(), getRemoveVolumes().get());
22 exec(docker().compose(this).rm(getRemoveVolumes().get()));
22 }
23 }
23 }
24 }
@@ -1,14 +1,18
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import java.util.concurrent.ExecutionException;
4 import java.util.concurrent.ExecutionException;
5
5
6 import org.gradle.api.tasks.TaskAction;
6 import org.gradle.api.tasks.TaskAction;
7
7
8 public abstract class ComposeStop extends ComposeTask {
8 public abstract class ComposeStop extends DockerCliTask implements ComposeTaskMixin {
9
10 public ComposeStop() {
11 applyComposeConvention();
12 }
9
13
10 @TaskAction
14 @TaskAction
11 public void run() throws InterruptedException, IOException, ExecutionException {
15 public void run() throws InterruptedException, IOException, ExecutionException {
12 docker().composeStop(getComposeFile().getAsFile().get(), getProfiles().get());
16 exec(docker().compose(this).stop());
13 }
17 }
14 } No newline at end of file
18 }
@@ -1,33 +1,37
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.File;
4 import java.util.Optional;
5
6 import org.gradle.api.file.RegularFile;
3 import org.gradle.api.file.RegularFileProperty;
7 import org.gradle.api.file.RegularFileProperty;
4 import org.gradle.api.provider.SetProperty;
8 import org.gradle.api.provider.SetProperty;
5 import org.gradle.api.tasks.Internal;
9 import org.gradle.api.tasks.Internal;
10 import org.implab.gradle.containers.ComposeExtension;
6
11
7 /**
12 /**
8 * Base task for compose subtasks like 'uo', 'rm', 'stop', etc.
13 * Base task for compose subtasks like 'uo', 'rm', 'stop', etc.
9 */
14 */
10 public abstract class ComposeTask extends DockerCliTask {
15 public interface ComposeTaskMixin extends TaskServices {
11
16
12 /** The list of profiles to use with the compose commands */
17 /** The list of profiles to use with the compose commands */
13 @Internal
18 @Internal
14 public abstract SetProperty<String> getProfiles();
19 SetProperty<String> getProfiles();
15
20
16 /**
21 /**
17 * The primary compose files. This task is executed regardless whether these
22 * The primary compose files. This task is executed regardless whether these
18 * files was changed.
23 * files was changed.
19 */
24 */
20 @Internal
25 @Internal
21 public abstract RegularFileProperty getComposeFile();
26 RegularFileProperty getComposeFile();
22
27
23 protected ComposeTask() {
28 default void applyComposeConvention() {
24 // the task can only be evaluated if the compose file is present
29 onlyIf(task -> getComposeFile().map(RegularFile::getAsFile).map(File::exists).getOrElse(false));
25 setOnlyIf(self -> {
30 Optional.ofNullable(getProject().getExtensions().findByType(ComposeExtension.class))
26 var composeFile = getComposeFile().get().getAsFile();
31 .ifPresent(ext -> {
27 var exists = composeFile.exists();
32 getProfiles().set(ext.getProfiles());
28 getLogger().info("file: {} {}", composeFile.toString(), exists ? "exists" : "doesn't exist");
33 getComposeFile().set(ext.getComposeFile());
29 return exists;
34 });
30 });
35
31 }
36 }
32
33 }
37 }
@@ -1,14 +1,31
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import java.util.concurrent.ExecutionException;
4 import java.util.concurrent.ExecutionException;
5
5
6 import org.gradle.api.tasks.Internal;
6 import org.gradle.api.tasks.TaskAction;
7 import org.gradle.api.tasks.TaskAction;
7
8
8 public abstract class ComposeUp extends ComposeTask {
9 public abstract class ComposeUp extends DockerCliTask implements ComposeTaskMixin {
10
11 private boolean detached = true;
12
13 @Internal
14 public boolean isDetached() {
15 return detached;
16 }
17
18 public void setDetached(boolean value) {
19 detached = value;
20 }
21
22 public ComposeUp() {
23 applyComposeConvention();
24 }
9
25
10 @TaskAction
26 @TaskAction
11 public void run() throws InterruptedException, IOException, ExecutionException {
27 public void run() throws InterruptedException, IOException, ExecutionException {
12 docker().composeUp(getComposeFile().getAsFile().get(), getProfiles().get());
28
29 exec(docker().compose(this).up(isDetached()));
13 }
30 }
14 }
31 }
@@ -1,89 +1,150
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.IOException;
4 import java.util.Optional;
5 import java.util.Optional;
6 import java.util.concurrent.ExecutionException;
5
7
6 import org.gradle.api.DefaultTask;
8 import org.gradle.api.DefaultTask;
7 import org.gradle.api.logging.Logger;
9 import org.gradle.api.GradleException;
10 import org.gradle.api.file.RegularFile;
8 import org.gradle.api.provider.Property;
11 import org.gradle.api.provider.Property;
9 import org.gradle.api.tasks.Input;
12 import org.gradle.api.tasks.Input;
10 import org.gradle.api.tasks.Internal;
13 import org.gradle.api.tasks.Internal;
11 import org.implab.gradle.containers.ContainerExtension;
14 import org.implab.gradle.containers.ContainerExtension;
12 import org.implab.gradle.containers.PropertiesMixin;
15 import org.implab.gradle.containers.PropertiesMixin;
16 import org.implab.gradle.containers.cli.ComposeTraits;
13 import org.implab.gradle.containers.cli.DockerTraits;
17 import org.implab.gradle.containers.cli.DockerTraits;
14 import org.implab.gradle.containers.cli.ProcessSpec;
18 import org.implab.gradle.containers.cli.ProcessSpec;
15 import org.implab.gradle.containers.cli.RedirectFrom;
19 import org.implab.gradle.containers.cli.RedirectFrom;
16 import org.implab.gradle.containers.cli.RedirectTo;
20 import org.implab.gradle.containers.cli.RedirectTo;
21 import org.implab.gradle.containers.cli.Utils;
17
22
18 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin {
23 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin {
19
24
20 @Input
25 @Input
21 public abstract Property<String> getCliCmd();
26 public abstract Property<String> getCliCmd();
22
27
23 /**
28 /**
24 * Returns working directory for docker commands
29 * Returns working directory for docker commands
25 */
30 */
26 @Input
31 @Input
27 @org.gradle.api.tasks.Optional
32 @org.gradle.api.tasks.Optional
28 public abstract Property<File> getWorkingDirectory();
33 public abstract Property<File> getWorkingDirectory();
29
34
30 @Internal
35 @Internal
31 protected ContainerExtension getContainerExtension() {
36 protected ContainerExtension getContainerExtension() {
32 return getProject().getExtensions().getByType(ContainerExtension.class);
37 return getProject().getExtensions().getByType(ContainerExtension.class);
33 }
38 }
34
39
35 public DockerCliTask() {
40 public DockerCliTask() {
36 getCliCmd().convention(getContainerExtension().getCliCmd());
41 getCliCmd().convention(getContainerExtension().getCliCmd());
37
42
38 }
43 }
39
44
40 protected Optional<RedirectTo> loggerInfoRedirect() {
45 protected Optional<RedirectTo> loggerInfoRedirect() {
41 return getLogger().isInfoEnabled() ? Optional.of(RedirectTo.consumer(getLogger()::info)) : Optional.empty();
46 return getLogger().isInfoEnabled() ? Optional.of(RedirectTo.consumer(getLogger()::info)) : Optional.empty();
42 }
47 }
43
48
44 protected Optional<RedirectTo> loggerErrorRedirect() {
49 protected Optional<RedirectTo> loggerErrorRedirect() {
45 return getLogger().isErrorEnabled() ? Optional.of(RedirectTo.consumer(getLogger()::error)) : Optional.empty();
50 return getLogger().isErrorEnabled() ? Optional.of(RedirectTo.consumer(getLogger()::error)) : Optional.empty();
46 }
51 }
47
52
48 protected Optional<RedirectTo> getStdoutRedirection() {
53 protected Optional<RedirectTo> stdoutRedirection() {
49 return loggerInfoRedirect();
54 return loggerInfoRedirect();
50 }
55 }
51
56
52 protected Optional<RedirectTo> getStderrRedirection() {
57 protected Optional<RedirectTo> stderrRedirection() {
53 return loggerErrorRedirect();
58 return loggerErrorRedirect();
54 }
59 }
55
60
56 protected Optional<RedirectFrom> getStdinRedirection() {
61 protected Optional<RedirectFrom> stdinRedirection() {
57 return Optional.empty();
62 return Optional.empty();
58 }
63 }
59
64
60 protected DockerTraits docker() {
65 protected TaskDockerTraits docker() {
61 return new TaskDockerTraits();
66 return new TaskDockerTraits();
62 }
67 }
63
68
64 protected void exec(ProcessSpec spec) {
69 protected boolean imageExists(String imageId) {
70 try {
71 return checkRetCode(docker().imageExists(imageId), 0);
72 } catch (InterruptedException | ExecutionException | IOException e) {
73 // wrap to unchecked exception
74 throw new GradleException("Failed to execute imageExists", e);
75 }
76 }
77
78 protected boolean containerExists(String containerId) {
79 try {
80 return checkRetCode(docker().containerExists(containerId), 0);
81 } catch (InterruptedException | ExecutionException | IOException e) {
82 // wrap to unchecked exception
83 throw new GradleException("Failed to execute imageExists", e);
84 }
85 }
86
87 protected void exec(ProcessSpec spec) throws InterruptedException, ExecutionException, IOException {
88
89 stdoutRedirection().ifPresent(spec::redirectStdout);
90 stderrRedirection().ifPresent(spec::redirectStderr);
91 stdinRedirection().ifPresent(spec::redirectStdin);
92
93 getLogger().info("Staring: {}", spec.command());
94
95 // runs the command and checks the error code
96 spec.exec();
97 }
65
98
66 getLogger().info("Starting: {}", spec.command());
99 protected boolean checkRetCode(ProcessSpec proc, int code)
100 throws InterruptedException, ExecutionException, IOException {
101 if (getLogger().isInfoEnabled()) {
102 proc.redirectStdout(RedirectTo.consumer(getLogger()::info))
103 .redirectStderr(RedirectTo.consumer(getLogger()::info));
104 }
105
106 getLogger().info("Starting: {}", proc.command());
107
108 return proc.start().get() == code;
109 }
110
111 /**
112 * Helper function to read
113 * @param idFile
114 * @return
115 */
116 protected String readId(File idFile) {
117 if (idFile.exists()) {
118 try {
119 return Utils.readImageRef(idFile).getId();
120 } catch (IOException e) {
121 getLogger().error("Failed to read imageId {}: {}", idFile, e);
122 return null;
123 }
124 } else {
125 return null;
126 }
127 }
128
129 protected ProcessSpec commandBuilder(String... command) {
130 var spec = new ProcessSpec().args(getCliCmd().get()).args(command);
131
132 if (getWorkingDirectory().isPresent())
133 spec.directory(getWorkingDirectory().get());
134
135 return spec;
67 }
136 }
68
137
69 class TaskDockerTraits extends DockerTraits {
138 class TaskDockerTraits extends DockerTraits {
70
139
71 @Override
140 @Override
72 public Logger getLogger() {
141 protected ProcessSpec builder(String... command) {
73 return DockerCliTask.this.getLogger();
142 return commandBuilder(command);
74 }
143 }
75
144
76 @Override
145 public ComposeTraits compose(ComposeTaskMixin props) {
77 public Optional<File> getWorkingDir() {
146 return compose(props.getComposeFile().map(RegularFile::getAsFile).get(), props.getProfiles().get());
78 return getWorkingDirectory()
79 .map(Optional::of)
80 .getOrElse(Optional.empty());
81 }
82
83 @Override
84 public String getCliCmd() {
85 return DockerCliTask.this.getCliCmd().get();
86 }
147 }
87
148
88 }
149 }
89 }
150 }
@@ -1,33 +1,28
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import java.util.concurrent.ExecutionException;
4 import java.util.concurrent.ExecutionException;
5
5
6 import org.gradle.api.provider.ListProperty;
7 import org.gradle.api.provider.Property;
6 import org.gradle.api.provider.Property;
8 import org.gradle.api.tasks.Input;
7 import org.gradle.api.tasks.Input;
9 import org.gradle.api.tasks.Optional;
8 import org.gradle.api.tasks.Optional;
10 import org.gradle.api.tasks.TaskAction;
9 import org.gradle.api.tasks.TaskAction;
11 import org.implab.gradle.containers.dsl.OptionsMixin;
10 import org.implab.gradle.containers.dsl.OptionsMixin;
12
11
13 public abstract class PushImage extends DockerCliTask implements OptionsMixin {
12 public abstract class PushImage extends DockerCliTask implements OptionsMixin {
14
13
15 @Input
14 @Input
16 public abstract Property<Object> getImageName();
15 public abstract Property<Object> getImageName();
17
16
18 @Input
17 @Input
19 @Optional
18 @Optional
20 public abstract Property<Object> getTransport();
19 public abstract Property<Object> getTransport();
21
20
22 @Input
23 @Optional
24 public abstract ListProperty<String> getOptions();
25
26 @TaskAction
21 @TaskAction
27 public void run() throws InterruptedException, IOException, ExecutionException {
22 public void run() throws InterruptedException, IOException, ExecutionException {
28
23
29 docker().pushImage(
24 exec(docker().imagePush(
30 getImageName().map(Object::toString).get(),
25 getImageName().map(Object::toString).get(),
31 getOptions().get());
26 getOptions().get()));
32 }
27 }
33 }
28 }
@@ -1,159 +1,125
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.IOException;
4 import java.io.IOException;
5 import java.util.ArrayList;
5 import java.util.ArrayList;
6 import java.util.List;
6 import java.util.List;
7 import java.util.Optional;
7 import java.util.Set;
8 import java.util.concurrent.ExecutionException;
8 import java.util.concurrent.ExecutionException;
9
9
10 import org.gradle.api.Action;
10 import org.gradle.api.Action;
11 import org.gradle.api.GradleException;
11 import org.gradle.api.file.RegularFileProperty;
12 import org.gradle.api.file.RegularFileProperty;
12 import org.gradle.api.provider.ListProperty;
13 import org.gradle.api.provider.Property;
13 import org.gradle.api.provider.Property;
14 import org.gradle.api.tasks.Internal;
14 import org.gradle.api.tasks.Internal;
15 import org.gradle.api.tasks.TaskAction;
15 import org.gradle.api.tasks.TaskAction;
16 import org.implab.gradle.containers.cli.RedirectFrom;
17 import org.implab.gradle.containers.cli.RedirectTo;
18 import org.implab.gradle.containers.dsl.OptionsMixin;
19 import org.implab.gradle.containers.dsl.RedirectFromSpec;
20 import org.implab.gradle.containers.dsl.RedirectToSpec;
21 import org.implab.gradle.containers.dsl.VolumeSpec;
16 import org.implab.gradle.containers.dsl.VolumeSpec;
22
17
23 public abstract class RunContainer extends DockerCliTask implements OptionsMixin {
18 public abstract class RunContainer extends BaseExecTask implements ContainerTaskMixin {
24
25 private final RedirectToSpec redirectStderr = new RedirectToSpec();
26
27 private final RedirectToSpec redirectStdout = new RedirectToSpec();
28
29 private final RedirectFromSpec redirectStdin = new RedirectFromSpec();
30
19
31 private boolean transientContainer = true;
20 private boolean transientContainer = true;
32
21
33 private boolean detached = false;
22 private boolean startIfExists = true;
34
35 private boolean interactive = false;
36
23
37 @Internal
24 @Internal
38 public abstract Property<Object> getImageName();
25 public abstract Property<Object> getImageName();
39
26
40 @Internal
27 @Internal
41 public abstract ListProperty<String> getCommandLine();
28 public abstract Property<String> getContainerName();
42
43 @Internal
44 public RedirectFromSpec getStdin() {
45 return redirectStdin;
46 }
47
29
48 @Internal
30 /**
49 public RedirectToSpec getStdout() {
31 * Transient containers will be removed after completion. Default is `true`
50 return redirectStdout;
32 */
51 }
52
53 @Internal
54 public RedirectToSpec getStderr() {
55 return redirectStderr;
56 }
57
58 @Internal
33 @Internal
59 public boolean isTransientContainer() {
34 public boolean isTransientContainer() {
60 return transientContainer;
35 return transientContainer;
61 }
36 }
62
37
63 public void setTransientContainer(boolean value) {
38 public void setTransientContainer(boolean value) {
64 transientContainer = value;
39 transientContainer = value;
65 }
40 }
66
41
67 @Internal
42 @Internal
68 public boolean isDetached() {
43 public boolean getStartIfExists() {
69 // if IO was redirected the container should run in foreground
44 return startIfExists;
70 if (redirectStdin.isRedirected() || redirectStdout.isRedirected() || redirectStderr.isRedirected())
45 }
71 return false;
72
46
73 return detached;
47 public void setStartIdExists(boolean value) {
48 startIfExists = value;
74 }
49 }
75
50
76 public void setDetached(boolean value) {
51 /**
77 detached = value;
52 * Specified the file where the stared container id will be written.
78 }
53 * Default value is a temporary file.
79
54 *
80 @Internal
55 * <p>
81 public boolean isInteractive() {
56 * This property can be use in tasks where the container id is required
82 // enable interactive mode when processing standard input
57 * to perform operation, for example to StopContainer tasks.
83 return redirectStdin.isRedirected() || interactive;
58 */
84 }
85
86 @Internal
59 @Internal
87 public abstract RegularFileProperty getContainerIdFile();
60 public abstract RegularFileProperty getContainerIdFile();
88
61
89 public RunContainer() {
62 public RunContainer() {
90 getContainerIdFile().convention(() -> new File(getTemporaryDir(), "cid"));
63 getContainerIdFile().convention(() -> new File(getTemporaryDir(), "cid"));
91 }
64 }
92
65
66 /**
67 * Adds volume specification
68 *
69 * @param configure
70 */
93 public void volume(Action<VolumeSpec> configure) {
71 public void volume(Action<VolumeSpec> configure) {
94 getOptions().add("-v");
72 getOptions().add("-v");
95 getOptions().add(provider(() -> {
73 getOptions().add(provider(() -> {
96 var volumeSpec = getObjectFactory().newInstance(VolumeSpec.class);
74 var volumeSpec = getObjectFactory().newInstance(VolumeSpec.class);
97 configure.execute(volumeSpec);
75 configure.execute(volumeSpec);
98 return volumeSpec.resolveSpec();
76 return volumeSpec.resolveSpec();
99 }));
77 }));
100 }
78 }
101
79
102 void commandLine(String... args) {
103 getCommandLine().addAll(args);
104 }
105
106 @Override
107 protected Optional<RedirectTo> getStdoutRedirection() {
108 return redirectStdout.getRedirection().or(super::getStdoutRedirection);
109 }
110
111 @Override
112 protected Optional<RedirectTo> getStderrRedirection() {
113 return redirectStderr.getRedirection().or(super::getStderrRedirection);
114 }
115
116 @Override
117 protected Optional<RedirectFrom> getStdinRedirection() {
118 return redirectStdin.getRedirection();
119 }
120
121 @TaskAction
80 @TaskAction
122 public void run() throws InterruptedException, IOException, ExecutionException {
81 public void run() throws InterruptedException, IOException, ExecutionException {
123 var params = new ArrayList<>(getOptions().get());
82 var params = new ArrayList<>(getOptions().get());
124
83
125 if (isInteractive())
84 if (getContainerId().map(this::containerExists).getOrElse(false)) {
126 params.add("--interactive");
85 var cid = getContainerId().get();
127 if (isTransientContainer())
86 if (getStartIfExists()) {
128 params.add("--rm");
87 getLogger().info("Container {} already created, starting.", cid);
129 if (isDetached())
130 params.add("--detach");
131 if (getContainerIdFile().isPresent()) {
132 getContainerIdFile().getAsFile().get().delete();
133
88
134 params.addAll(List.of("--cidfile", getContainerIdFile().get().getAsFile().toString()));
89 if (isInteractive())
135 }
90 params.add("--interactive");
91 if (!isDetached())
92 params.add("--attach");
93
94 exec(docker().containerStart(Set.of(cid), params));
95 } else {
96 throw new GradleException("Container " + cid + " already exists");
97 }
98 } else {
136
99
137 var spec = docker().runImage(
100 if (isInteractive())
138 getImageName().map(Object::toString).get(),
101 params.add("--interactive");
139 params,
102 if (isTransientContainer())
140 getCommandLine().get());
103 params.add("--rm");
141
104 if (isDetached())
142 // if process will run in foreground, then setup default redirects
105 params.add("--detach");
143 redirectStdout.getRedirection()
106 if (getAllocateTty())
144 .or(this::loggerInfoRedirect)
107 params.add("--tty");
145 .ifPresent(spec::redirectStdout);
108 if (getContainerName().isPresent())
109 params.addAll(List.of("--name", getContainerName().get()));
146
110
147 redirectStderr.getRedirection()
111 if (getContainerIdFile().isPresent()) {
148 .or(this::loggerErrorRedirect)
112 // delete previous container id otherwise the container will fail to start
149 .ifPresent(spec::redirectStderr);
113 getContainerIdFile().getAsFile().get().delete();
150
114
151 redirectStdin.getRedirection().ifPresent(spec::redirectStdin);
115 params.addAll(List.of("--cidfile", getContainerIdFile().get().getAsFile().toString()));
116 }
152
117
153 getLogger().info("Staring: {}", spec.command());
118 exec(docker().containerRun(
154
119 getImageName().map(Object::toString).get(),
155 // runs the command and checks the error code
120 params,
156 spec.exec();
121 getCommandLine().get()));
122 }
157 }
123 }
158
124
159 }
125 }
@@ -1,112 +1,112
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.IOException;
4 import java.io.IOException;
5 import java.util.ArrayList;
5 import java.util.ArrayList;
6 import java.util.Optional;
6 import java.util.Optional;
7 import java.util.concurrent.ExecutionException;
7 import java.util.concurrent.ExecutionException;
8
8
9 import org.gradle.api.file.DirectoryProperty;
9 import org.gradle.api.file.DirectoryProperty;
10 import org.gradle.api.file.FileCollection;
10 import org.gradle.api.file.FileCollection;
11 import org.gradle.api.file.RegularFile;
11 import org.gradle.api.file.RegularFile;
12 import org.gradle.api.provider.Property;
12 import org.gradle.api.provider.Property;
13 import org.gradle.api.provider.Provider;
13 import org.gradle.api.provider.Provider;
14 import org.gradle.api.provider.SetProperty;
14 import org.gradle.api.provider.SetProperty;
15 import org.gradle.api.tasks.Input;
15 import org.gradle.api.tasks.Input;
16 import org.gradle.api.tasks.Internal;
16 import org.gradle.api.tasks.Internal;
17 import org.gradle.api.tasks.OutputFile;
17 import org.gradle.api.tasks.OutputFile;
18 import org.gradle.api.tasks.TaskAction;
18 import org.gradle.api.tasks.TaskAction;
19 import org.gradle.api.tasks.TaskExecutionException;
19 import org.gradle.api.tasks.TaskExecutionException;
20 import org.implab.gradle.containers.cli.Utils;
20 import org.implab.gradle.containers.cli.Utils;
21
21
22 public abstract class SaveImage extends DockerCliTask {
22 public abstract class SaveImage extends DockerCliTask {
23
23
24 @Input
24 @Input
25 public abstract SetProperty<Object> getExportImages();
25 public abstract SetProperty<Object> getExportImages();
26
26
27 @OutputFile
27 @OutputFile
28 public Provider<RegularFile> getArchiveFile() {
28 public Provider<RegularFile> getArchiveFile() {
29 return getDestinationDirectory().file(getArchiveFileName());
29 return getDestinationDirectory().file(getArchiveFileName());
30 }
30 }
31
31
32 @Internal
32 @Internal
33 public abstract DirectoryProperty getDestinationDirectory();
33 public abstract DirectoryProperty getDestinationDirectory();
34
34
35 @Internal
35 @Internal
36 public abstract Property<String> getArchiveFileName();
36 public abstract Property<String> getArchiveFileName();
37
37
38 @Internal
38 @Internal
39 public abstract Property<String> getArchiveBaseName();
39 public abstract Property<String> getArchiveBaseName();
40
40
41 @Internal
41 @Internal
42 public abstract Property<String> getArchiveVersion();
42 public abstract Property<String> getArchiveVersion();
43
43
44 @Internal
44 @Internal
45 public abstract Property<String> getArchiveClassifier();
45 public abstract Property<String> getArchiveClassifier();
46
46
47 @Internal
47 @Internal
48 public abstract Property<String> getArchiveExtension();
48 public abstract Property<String> getArchiveExtension();
49
49
50 String readImageRefTag(File file) throws Exception {
50 String readImageRefTag(File file) throws Exception {
51 getLogger().info("Reading image ref from {}", file);
51 getLogger().info("Reading image ref from {}", file);
52 var imageRef = Utils.readImageRef(file);
52 var imageRef = Utils.readImageRef(file);
53 return imageRef.getTag().orElseThrow(() -> new Exception("The image tag is required to save image"));
53 return imageRef.getTag().orElseThrow(() -> new Exception("The image tag is required to save image"));
54 }
54 }
55
55
56 public void imageRef(File file) {
56 public void imageRef(File file) {
57 getExportImages().add(provider(() -> {
57 getExportImages().add(provider(() -> {
58 return readImageRefTag(file);
58 return readImageRefTag(file);
59 }));
59 }));
60 }
60 }
61
61
62 public void imageRefs(FileCollection files) {
62 public void imageRefs(FileCollection files) {
63 dependsOn(files);
63 dependsOn(files);
64 getExportImages().addAll(provider(() -> {
64 getExportImages().addAll(provider(() -> {
65 var tags = new ArrayList<String>();
65 var tags = new ArrayList<String>();
66 for (File file : files) {
66 for (File file : files) {
67 tags.add(readImageRefTag(file));
67 tags.add(readImageRefTag(file));
68 }
68 }
69 return tags;
69 return tags;
70 }));
70 }));
71 }
71 }
72
72
73 public SaveImage() {
73 public SaveImage() {
74 getArchiveFileName().convention(provider(this::conventionalArchiveFileName));
74 getArchiveFileName().convention(provider(this::conventionalArchiveFileName));
75 getArchiveBaseName().convention(provider(this::conventionalArchiveBaseName));
75 getArchiveBaseName().convention(provider(this::conventionalArchiveBaseName));
76 getArchiveVersion().convention(provider(() -> getProject().getVersion()).map(x -> x.toString()));
76 getArchiveVersion().convention(provider(() -> getProject().getVersion()).map(x -> x.toString()));
77 getDestinationDirectory().convention(getProject().getLayout().getBuildDirectory());
77 getDestinationDirectory().convention(getProject().getLayout().getBuildDirectory());
78 getArchiveExtension().convention("tar");
78 getArchiveExtension().convention("tar");
79 }
79 }
80
80
81 private String conventionalArchiveBaseName() {
81 private String conventionalArchiveBaseName() {
82 ArrayList<String> parts = new ArrayList<>();
82 ArrayList<String> parts = new ArrayList<>();
83 Optional.of(getProject().getGroup()).map(x -> x.toString()).ifPresent(parts::add);
83 Optional.of(getProject().getGroup()).map(x -> x.toString()).ifPresent(parts::add);
84 parts.add(getProject().getName());
84 parts.add(getProject().getName());
85 return String.join("-", parts);
85 return String.join("-", parts);
86 }
86 }
87
87
88 private String conventionalArchiveFileName() {
88 private String conventionalArchiveFileName() {
89 ArrayList<String> parts = new ArrayList<>();
89 ArrayList<String> parts = new ArrayList<>();
90
90
91 if (getArchiveBaseName().isPresent())
91 if (getArchiveBaseName().isPresent())
92 parts.add(getArchiveBaseName().get());
92 parts.add(getArchiveBaseName().get());
93
93
94 if (getArchiveVersion().isPresent())
94 if (getArchiveVersion().isPresent())
95 parts.add(getArchiveVersion().get());
95 parts.add(getArchiveVersion().get());
96
96
97 if (getArchiveClassifier().isPresent())
97 if (getArchiveClassifier().isPresent())
98 parts.add(getArchiveClassifier().get());
98 parts.add(getArchiveClassifier().get());
99
99
100 if (parts.size() == 0)
100 if (parts.size() == 0)
101 throw new TaskExecutionException(this, new Exception("The archiveFileName ism't specified"));
101 throw new TaskExecutionException(this, new Exception("The archiveFileName ism't specified"));
102
102
103 return String.join("-", parts) + "." + getArchiveExtension().get();
103 return String.join("-", parts) + "." + getArchiveExtension().get();
104 }
104 }
105
105
106 @TaskAction
106 @TaskAction
107 public void run() throws InterruptedException, IOException, ExecutionException {
107 public void run() throws InterruptedException, IOException, ExecutionException {
108 docker().saveImage(
108 exec(docker().imageSave(
109 getExportImages().map(Utils::mapToString).get(),
109 getExportImages().map(Utils::mapToString).get(),
110 getArchiveFile().map(RegularFile::getAsFile).get());
110 getArchiveFile().map(RegularFile::getAsFile).get()));
111 }
111 }
112 }
112 }
@@ -1,66 +1,37
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import java.util.ArrayList;
4 import java.util.ArrayList;
5 import java.util.List;
5 import java.util.List;
6 import java.util.concurrent.ExecutionException;
6 import java.util.concurrent.ExecutionException;
7
7
8 import org.gradle.api.GradleException;
9 import org.gradle.api.file.RegularFile;
10 import org.gradle.api.file.RegularFileProperty;
11 import org.gradle.api.provider.Property;
8 import org.gradle.api.provider.Property;
12 import org.gradle.api.provider.Provider;
13 import org.gradle.api.tasks.Internal;
9 import org.gradle.api.tasks.Internal;
14 import org.gradle.api.tasks.TaskAction;
10 import org.gradle.api.tasks.TaskAction;
15 import org.implab.gradle.containers.cli.Utils;
16
11
17 public abstract class StopContainer extends DockerCliTask {
12 public abstract class StopContainer extends DockerCliTask implements ContainerTaskMixin {
18
19 @Internal
20 public abstract RegularFileProperty getContainerIdFile();
21
22 @Internal
23 public abstract Property<String> getContainerName();
24
13
25 @Internal
14 @Internal
26 public abstract Property<String> getStopSignal();
15 public abstract Property<String> getStopSignal();
27
16
28 @Internal
17 @Internal
29 public abstract Property<Integer> getStopTimeout();
18 public abstract Property<Integer> getStopTimeout();
30
19
31 Provider<String> readCid(RegularFile regularFile) {
20 public StopContainer() {
32 var file = regularFile.getAsFile();
21 onlyIfReason("Container doesn't exists", self -> getContainerId().map(this::containerExists).getOrElse(false));
33
34 return provider(() -> {
35 try {
36 return file.isFile() ? Utils.readAll(file) : null;
37 } catch (IOException e) {
38 throw new GradleException("Failed to read container id from file " + file.toString(), e);
39 }
40 });
41 }
22 }
42
23
43 @TaskAction
24 @TaskAction
44 public void run() throws InterruptedException, IOException, ExecutionException {
25 public void run() throws InterruptedException, IOException, ExecutionException {
45 var cid = getContainerIdFile().flatMap(this::readCid).orElse(getContainerName());
46
47 if (!cid.isPresent()) {
48 getLogger().info("The container name or id hasn't been specified");
49 return;
50 }
51
52 var options = new ArrayList<String>();
26 var options = new ArrayList<String>();
53
27
54 if (getStopSignal().isPresent())
28 if (getStopSignal().isPresent())
55 options.addAll(List.of("--signal", getStopSignal().get()));
29 options.addAll(List.of("--signal", getStopSignal().get()));
56
30
57 if (getStopTimeout().isPresent())
31 if (getStopTimeout().isPresent())
58 options.addAll(List.of("--time", getStopTimeout().map(Object::toString).get()));
32 options.addAll(List.of("--time", getStopTimeout().map(Object::toString).get()));
59
33
60 docker().stopContainer(cid.get(), options);
34 exec(docker().containerStop(getContainerId().get(), options));
61
62 if (getContainerIdFile().isPresent())
63 getContainerIdFile().getAsFile().get().delete();
64 }
35 }
65
36
66 }
37 }
@@ -1,47 +1,44
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import java.util.Set;
4 import java.util.Set;
5 import java.util.concurrent.ExecutionException;
5 import java.util.concurrent.ExecutionException;
6
6
7 import org.gradle.api.provider.Property;
7 import org.gradle.api.provider.Property;
8 import org.gradle.api.provider.SetProperty;
8 import org.gradle.api.provider.SetProperty;
9 import org.gradle.api.tasks.Input;
9 import org.gradle.api.tasks.Input;
10 import org.gradle.api.tasks.TaskAction;
10 import org.gradle.api.tasks.TaskAction;
11 import org.implab.gradle.containers.cli.Utils;
11 import org.implab.gradle.containers.cli.Utils;
12
12
13 public abstract class TagImage extends DockerCliTask {
13 public abstract class TagImage extends DockerCliTask {
14 @Input
14 @Input
15 public abstract Property<Object> getSrcImage();
15 public abstract Property<Object> getSrcImage();
16
16
17 @Input
17 @Input
18 public abstract SetProperty<Object> getTags();
18 public abstract SetProperty<Object> getTags();
19
19
20 @Input
20 @Input
21 @Deprecated
21 @Deprecated
22 public abstract Property<Object> getDestImage();
22 public abstract Property<Object> getDestImage();
23
23
24 private Set<String> getImageTags() {
24 private Set<String> getImageTags() {
25 var tags = getTags().map(Utils::mapToString).get();
25 var tags = getTags().map(Utils::mapToString).get();
26 tags.add(getDestImage().map(Object::toString).get());
26 tags.add(getDestImage().map(Object::toString).get());
27 return tags;
27 return tags;
28 }
28 }
29
29
30 public TagImage() {
30 public TagImage() {
31 this.setOnlyIf("No tags were specified", self -> getImageTags().size() > 0);
31 this.setOnlyIf(self -> getImageTags().size() > 0);
32 }
32 }
33
33
34 @TaskAction
34 @TaskAction
35 public void run() throws InterruptedException, IOException, ExecutionException {
35 public void run() throws InterruptedException, IOException, ExecutionException {
36 var tags = getImageTags();
36 var tags = getImageTags();
37 var src = getSrcImage().map(Object::toString).get();
37 var src = getSrcImage().map(Object::toString).get();
38
38
39 if (tags.size() == 0)
40 getLogger().info("No tags were specified");
41
42 for (var tag : tags) {
39 for (var tag : tags) {
43 getLogger().info("Tag: {}", tag);
40 getLogger().info("Tag: {}", tag);
44 docker().tagImage(src, tag);
41 exec(docker().imageTag(src, tag));
45 }
42 }
46 }
43 }
47 }
44 }
General Comments 0
You need to be logged in to leave comments. Login now