##// END OF EJS Templates
WIP docker exec, run commands
cin -
r18:ec532699ca7d default
parent child
Show More
@@ -0,0 +1,106
1 package org.implab.gradle.containers.cli;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Collection;
7 import java.util.List;
8 import java.util.concurrent.CompletableFuture;
9 import java.util.concurrent.ExecutionException;
10
11 public class ProcessSpec {
12 private final ProcessBuilder builder;
13
14 private RedirectFrom inputRedirect;
15
16 private RedirectTo outputRedirect;
17
18 private RedirectTo errorRedirect;
19
20 private List<String> command = new ArrayList<>();
21
22 private File directory;
23
24 public ProcessSpec() {
25 builder = new ProcessBuilder();
26 }
27
28 public CompletableFuture<Integer> start() throws IOException {
29 var tasks = new ArrayList<CompletableFuture<?>>();
30
31 builder.command(this.command);
32 builder.directory(directory);
33
34 var proc = builder.start();
35
36 tasks.add(proc.onExit());
37
38 if (inputRedirect != null)
39 tasks.add(inputRedirect.redirect(proc.getOutputStream()));
40
41 if (outputRedirect != null)
42 tasks.add(outputRedirect.redirect(proc.getInputStream()));
43
44 if (errorRedirect != null)
45 tasks.add(errorRedirect.redirect(proc.getErrorStream()));
46
47 return CompletableFuture
48 .allOf(tasks.toArray(new CompletableFuture<?>[0]))
49 .thenApply(t -> proc.exitValue());
50 }
51
52 public void exec() throws InterruptedException, ExecutionException, IOException {
53 var code = start().get();
54 if (code != 0)
55 throw new IOException("The process exited with error code " + code);
56 }
57
58 public List<String> command() {
59 return this.command;
60 }
61
62 public ProcessSpec command(String... args) {
63 this.command = new ArrayList<>();
64 args(args);
65 return this;
66 }
67
68 public ProcessSpec directory(File workingDir) {
69 this.directory = workingDir;
70 return this;
71 }
72
73 public ProcessSpec command(Collection<String> args) {
74 this.command = new ArrayList<>();
75 args(args);
76 return this;
77 }
78
79 public ProcessSpec args(String... args) {
80 for (String arg : args)
81 this.command.add(arg);
82
83 return this;
84 }
85
86 public ProcessSpec args(Collection<String> args) {
87 this.command.addAll(args);
88
89 return this;
90 }
91
92 public ProcessSpec redirectStderr(RedirectTo to) {
93 this.errorRedirect = to;
94 return this;
95 }
96
97 public ProcessSpec redirectStdin(RedirectFrom from) {
98 this.inputRedirect = from;
99 return this;
100 }
101
102 public ProcessSpec redirectStdout(RedirectTo to) {
103 this.outputRedirect = to;
104 return this;
105 }
106 }
@@ -0,0 +1,32
1 package org.implab.gradle.containers.cli;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.InputStream;
6 import java.io.OutputStream;
7 import java.util.concurrent.Callable;
8 import java.util.concurrent.CompletableFuture;
9
10 public interface RedirectFrom {
11 CompletableFuture<Void> redirect(OutputStream to);
12
13 public static RedirectFrom file(final File file) {
14 return to -> CompletableFuture.runAsync(() -> {
15 try (var from = new FileInputStream(file); to) {
16 from.transferTo(to);
17 } catch (Exception e) {
18 // silence!
19 }
20 });
21 }
22
23 public static RedirectFrom stream(final InputStream from) {
24 return to -> CompletableFuture.runAsync(() -> {
25 try (from; to) {
26 from.transferTo(to);
27 } catch (Exception e) {
28 // silence!
29 }
30 });
31 }
32 }
@@ -0,0 +1,47
1 package org.implab.gradle.containers.cli;
2
3 import java.io.File;
4 import java.io.FileOutputStream;
5 import java.io.InputStream;
6 import java.io.OutputStream;
7 import java.util.Scanner;
8 import java.util.concurrent.CompletableFuture;
9 import java.util.function.Consumer;
10
11 /**
12 * RedirectSpec
13 */
14 public interface RedirectTo {
15 CompletableFuture<Void> redirect(InputStream from);
16
17 public static RedirectTo consumer(final Consumer<String> consumer) {
18
19 return (src) -> CompletableFuture.runAsync(() -> {
20 try (Scanner sc = new Scanner(src)) {
21 while (sc.hasNextLine()) {
22 consumer.accept(sc.nextLine());
23 }
24 }
25 });
26 }
27
28 public static RedirectTo file(final File file) {
29 return src -> CompletableFuture.runAsync(() -> {
30 try (OutputStream out = new FileOutputStream(file)) {
31 src.transferTo(out);
32 } catch (Exception e) {
33 // silence!
34 }
35 });
36 }
37
38 public static RedirectTo stream(final OutputStream dest) {
39 return src -> CompletableFuture.runAsync(() -> {
40 try (dest; src) {
41 src.transferTo(dest);
42 } catch (Exception e) {
43 // silence!
44 }
45 });
46 }
47 } No newline at end of file
@@ -0,0 +1,37
1 package org.implab.gradle.containers.dsl;
2
3 import java.io.File;
4 import java.io.InputStream;
5 import java.util.Optional;
6 import java.util.function.Supplier;
7
8 import org.gradle.api.provider.Provider;
9 import org.implab.gradle.containers.cli.RedirectFrom;
10
11 public class RedirectFromSpec {
12 private Supplier<RedirectFrom> streamRedirect;
13
14 public boolean isRedirected() {
15 return streamRedirect != null;
16 }
17
18 public Optional<RedirectFrom> getRedirection() {
19 return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty();
20 }
21
22 public void fromFile(File file) {
23 this.streamRedirect = () -> RedirectFrom.file(file);
24 }
25
26 public void fromFile(Provider<File> file) {
27 this.streamRedirect = file.map(RedirectFrom::file)::get;
28 }
29
30 public void fromStream(InputStream stream) {
31 this.streamRedirect = () -> RedirectFrom.stream(stream);
32 }
33
34 public void fromStream(Provider<InputStream> stream) {
35 this.streamRedirect = stream.map(RedirectFrom::stream)::get;
36 }
37 }
@@ -0,0 +1,42
1 package org.implab.gradle.containers.dsl;
2
3 import java.io.File;
4 import java.io.OutputStream;
5 import java.util.Optional;
6 import java.util.function.Consumer;
7 import java.util.function.Supplier;
8
9 import org.gradle.api.provider.Provider;
10 import org.implab.gradle.containers.cli.RedirectTo;
11
12 public class RedirectToSpec {
13 private Supplier<RedirectTo> streamRedirect;
14
15 public boolean isRedirected() {
16 return streamRedirect != null;
17 }
18
19 public Optional<RedirectTo> getRedirection() {
20 return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty();
21 }
22
23 public void toFile(File file) {
24 this.streamRedirect = () -> RedirectTo.file(file);
25 }
26
27 public void toFile(Provider<File> file) {
28 this.streamRedirect = file.map(RedirectTo::file)::get;
29 }
30
31 public void toStream(OutputStream stream) {
32 this.streamRedirect = () -> RedirectTo.stream(stream);
33 }
34
35 public void toStream(Provider<OutputStream> stream) {
36 this.streamRedirect = stream.map(RedirectTo::stream)::get;
37 }
38
39 public void toConsumer(Consumer<String> consumer) {
40 this.streamRedirect = () -> RedirectTo.consumer(consumer);
41 }
42 }
@@ -0,0 +1,10
1 package org.implab.gradle.containers.tasks;
2
3 import org.gradle.api.provider.Property;
4 import org.gradle.api.tasks.Internal;
5
6 public abstract class ComposeExec {
7
8 @Internal
9 public abstract Property<String> getServiceName();
10 }
@@ -0,0 +1,66
1 package org.implab.gradle.containers.tasks;
2
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.List;
6 import java.util.concurrent.ExecutionException;
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;
12 import org.gradle.api.provider.Provider;
13 import org.gradle.api.tasks.Internal;
14 import org.gradle.api.tasks.TaskAction;
15 import org.implab.gradle.containers.cli.Utils;
16
17 public abstract class StopContainer extends DockerCliTask {
18
19 @Internal
20 public abstract RegularFileProperty getContainerIdFile();
21
22 @Internal
23 public abstract Property<String> getContainerName();
24
25 @Internal
26 public abstract Property<String> getStopSignal();
27
28 @Internal
29 public abstract Property<Integer> getStopTimeout();
30
31 Provider<String> readCid(RegularFile regularFile) {
32 var file = regularFile.getAsFile();
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 }
42
43 @TaskAction
44 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>();
53
54 if (getStopSignal().isPresent())
55 options.addAll(List.of("--signal", getStopSignal().get()));
56
57 if (getStopTimeout().isPresent())
58 options.addAll(List.of("--time", getStopTimeout().map(Object::toString).get()));
59
60 docker().stopContainer(cid.get(), options);
61
62 if (getContainerIdFile().isPresent())
63 getContainerIdFile().getAsFile().get().delete();
64 }
65
66 }
@@ -1,2 +1,2
1 group=org.implab.gradle
1 group=org.implab.gradle
2 version=1.2.2 No newline at end of file
2 version=1.3.0 No newline at end of file
@@ -1,167 +1,169
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 import java.util.Optional;
7
6
8 import org.gradle.api.DefaultTask;
7 import org.gradle.api.DefaultTask;
9 import org.gradle.api.Plugin;
8 import org.gradle.api.Plugin;
10 import org.gradle.api.Project;
9 import org.gradle.api.Project;
11 import org.gradle.api.artifacts.Configuration;
10 import org.gradle.api.artifacts.Configuration;
12 import org.gradle.api.artifacts.Configuration.State;
11 import org.gradle.api.artifacts.Configuration.State;
13 import org.gradle.api.logging.Logger;
12 import org.gradle.api.logging.Logger;
14 import org.gradle.api.tasks.Copy;
13 import org.gradle.api.tasks.Copy;
15 import org.gradle.api.tasks.Delete;
14 import org.gradle.api.tasks.Delete;
16 import org.implab.gradle.containers.cli.Utils;
15 import org.implab.gradle.containers.cli.Utils;
17 import org.implab.gradle.containers.tasks.ComposeRm;
16 import org.implab.gradle.containers.tasks.ComposeRm;
18 import org.implab.gradle.containers.tasks.ComposeStop;
17 import org.implab.gradle.containers.tasks.ComposeStop;
19 import org.implab.gradle.containers.tasks.ComposeUp;
18 import org.implab.gradle.containers.tasks.ComposeUp;
20 import org.implab.gradle.containers.tasks.WriteEnv;
19 import org.implab.gradle.containers.tasks.WriteEnv;
21
20
22 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";
23
23 public final String COMPOSE_IMAGES_CONFIGURATION = "composeImages";
24 public final String COMPOSE_IMAGES_CONFIGURATION = "composeImages";
24
25
25 public final String COMPOSE_PROJECT_NAME = "COMPOSE_PROJECT_NAME";
26 public final String COMPOSE_PROJECT_NAME = "COMPOSE_PROJECT_NAME";
26
27
27 public final String COMPOSE_EXTENSION = "compose";
28 public final String COMPOSE_EXTENSION = "compose";
28
29
29 public final String COMPOSE_UP_TASK = "up";
30 public final String COMPOSE_UP_TASK = "up";
30
31
31 public final String COMPOSE_STOP_TASK = "stop";
32 public final String COMPOSE_STOP_TASK = "stop";
32
33
33 public final String COMPOSE_RM_TASK = "rm";
34 public final String COMPOSE_RM_TASK = "rm";
34
35
35 public final String CLEAN_TASK = "clean";
36 public final String CLEAN_TASK = "clean";
36
37
37 public final String BUILD_TASK = "build";
38 public final String BUILD_TASK = "build";
38
39
39 public final String PROCESS_RESOURCES_TASK = "processResources";
40 public final String PROCESS_RESOURCES_TASK = "processResources";
40
41
41 public final String WRITE_ENV_TASK = "writeEnv";
42 public final String WRITE_ENV_TASK = "writeEnv";
42
43
43 public final String COMPOSE_VAR = "composeVar";
44 public final String COMPOSE_VAR = "composeVar";
44
45
45 public final String ENV_FILE_NAME = ".env";
46 public final String ENV_FILE_NAME = ".env";
46
47
47 public Logger getLogger() {
48 public Logger getLogger() {
48 return getProject().getLogger();
49 return getProject().getLogger();
49 }
50 }
50
51
51 @Override
52 @Override
52 public void apply(Project project) {
53 public void apply(Project project) {
53 var containerImages = configuration(COMPOSE_IMAGES_CONFIGURATION, Configurations.RESOLVABLE);
54 var containerImages = configuration(COMPOSE_IMAGES_CONFIGURATION, Configurations.RESOLVABLE);
54
55
55 // basic configuration, register extension
56 // basic configuration, register extension
56 var basePlugin = plugin(ContainerBasePlugin.class);
57 var basePlugin = plugin(ContainerBasePlugin.class);
57 var containerExtension = basePlugin.getContainerExtension();
58 var containerExtension = basePlugin.getContainerExtension();
58
59
59 var composeExtension = extension(COMPOSE_EXTENSION, ComposeExtension.class);
60 var composeExtension = extension(COMPOSE_EXTENSION, ComposeExtension.class);
60
61
61 var composeFile = containerExtension.getContextDirectory()
62 var composeFile = containerExtension.getContextDirectory()
62 .file(composeExtension.getComposeFileName());
63 .file(composeExtension.getComposeFileName());
63 var composeProfiles = composeExtension.getProfiles();
64 var composeProfiles = composeExtension.getProfiles();
64
65
65 var cleanTask = task(CLEAN_TASK, Delete.class, t -> {
66 var cleanTask = task(CLEAN_TASK, Delete.class, t -> {
66 t.delete(containerExtension.getContextDirectory());
67 t.delete(containerExtension.getContextDirectory());
67 });
68 });
68
69
69 // copy task from src/main
70 // copy task from src/main
70 var processResources = task(PROCESS_RESOURCES_TASK, Copy.class, t -> {
71 var processResources = task(PROCESS_RESOURCES_TASK, Copy.class, t -> {
71 t.mustRunAfter(cleanTask);
72 t.mustRunAfter(cleanTask);
72 t.from(projectDirectory().dir("src/main"));
73 t.from(projectDirectory().dir("src/main"));
73 t.into(containerExtension.getContextDirectory());
74 t.into(containerExtension.getContextDirectory());
74 });
75 });
75
76
76 // write .env
77 // write .env
77 var writeEnvTask = task(WRITE_ENV_TASK, WriteEnv.class, t -> {
78 var writeEnvTask = task(WRITE_ENV_TASK, WriteEnv.class, t -> {
78 t.dependsOn(processResources, containerImages);
79 t.dependsOn(processResources, containerImages);
79 t.getEnvFile().set(containerExtension.getContextDirectory().file(ENV_FILE_NAME));
80 t.getEnvFile().set(containerExtension.getContextDirectory().file(ENV_FILE_NAME));
80
81
81 var group = project.getGroup();
82 var group = project.getGroup();
82 if (group != null && group.toString().length() > 0) {
83 if (group != null && group.toString().length() > 0) {
83 t.getEnvironment().put(COMPOSE_PROJECT_NAME, group.toString());
84 t.getEnvironment().put(COMPOSE_PROJECT_NAME, group.toString());
84 }
85 }
85
86
86 t.getEnvironment().putAll(containerImages.map(this::extractComposeEnv));
87 t.getEnvironment().putAll(containerImages.map(this::extractComposeEnv));
87
88
88 });
89 });
89
90
90 var buildTask = task(BUILD_TASK, DefaultTask.class, t -> {
91 var buildTask = task(BUILD_TASK, DefaultTask.class, t -> {
92 t.setGroup(BUILD_GROUP);
91 t.dependsOn(writeEnvTask);
93 t.dependsOn(writeEnvTask);
92 });
94 });
93
95
94 var stopTask = task(COMPOSE_STOP_TASK, ComposeStop.class, t -> {
96 var stopTask = task(COMPOSE_STOP_TASK, ComposeStop.class, t -> {
95 // stop must run after build
97 // stop must run after build
96 t.mustRunAfter(buildTask);
98 t.mustRunAfter(buildTask);
97
99
98 t.getProfiles().addAll(composeProfiles);
100 t.getProfiles().addAll(composeProfiles);
99 t.getComposeFile().set(composeFile);
101 t.getComposeFile().set(composeFile);
100 });
102 });
101
103
102 var rmTask = task(COMPOSE_RM_TASK, ComposeRm.class, t -> {
104 var rmTask = task(COMPOSE_RM_TASK, ComposeRm.class, t -> {
103 // rm must run after build and stop
105 // rm must run after build and stop
104 t.mustRunAfter(buildTask, stopTask);
106 t.mustRunAfter(buildTask, stopTask);
105
107
106 t.getProfiles().addAll(composeProfiles);
108 t.getProfiles().addAll(composeProfiles);
107 t.getComposeFile().set(composeFile);
109 t.getComposeFile().set(composeFile);
108 });
110 });
109
111
110 task(COMPOSE_UP_TASK, ComposeUp.class, t -> {
112 task(COMPOSE_UP_TASK, ComposeUp.class, t -> {
111 t.dependsOn(buildTask);
113 t.dependsOn(buildTask);
112 // up must run after stop and rm
114 // up must run after stop and rm
113 t.mustRunAfter(stopTask, rmTask);
115 t.mustRunAfter(stopTask, rmTask);
114
116
115 t.getProfiles().addAll(composeProfiles);
117 t.getProfiles().addAll(composeProfiles);
116 t.getComposeFile().set(composeFile);
118 t.getComposeFile().set(composeFile);
117 });
119 });
118 }
120 }
119
121
120 /**
122 /**
121 * Processed the configurations, extracts composeVar extra property from
123 * Processed the configurations, extracts composeVar extra property from
122 * each dependency in this configuration and adds a value to the resulting
124 * each dependency in this configuration and adds a value to the resulting
123 * map. The values in the nap will contain image tags.
125 * map. The values in the nap will contain image tags.
124 */
126 */
125 private Map<String, String> extractComposeEnv(Configuration config) {
127 private Map<String, String> extractComposeEnv(Configuration config) {
126 if (config.getState() != State.UNRESOLVED) {
128 if (config.getState() != State.UNRESOLVED) {
127 getLogger().error("extractComposeEnv: The configuration {} isn't resolved.", config.getName());
129 getLogger().error("extractComposeEnv: The configuration {} isn't resolved.", config.getName());
128 throw new IllegalStateException("The specified configuration isn't resolved");
130 throw new IllegalStateException("The specified configuration isn't resolved");
129 }
131 }
130
132
131 getLogger().info("extractComposeEnv {}", config.getName());
133 getLogger().info("extractComposeEnv {}", config.getName());
132
134
133 var map = new HashMap<String, String>();
135 var map = new HashMap<String, String>();
134
136
135 for (var dependency : config.getDependencies()) {
137 for (var dependency : config.getDependencies()) {
136 // get extra composeVar if present
138 // get extra composeVar if present
137 extra(dependency, COMPOSE_VAR, String.class).optional().ifPresent(varName -> {
139 extra(dependency, COMPOSE_VAR, String.class).optional().ifPresent(varName -> {
138 // if we have a composeVar extra attribute on this dependency
140 // if we have a composeVar extra attribute on this dependency
139
141
140 // get files for the dependency
142 // get files for the dependency
141 var files = config.files(dependency);
143 var files = config.files(dependency);
142 if (files.size() == 1) {
144 if (files.size() == 1) {
143 // should bw exactly 1 file
145 // should bw exactly 1 file
144 var file = files.stream().findAny().get();
146 var file = files.stream().findAny().get();
145 getLogger().info("Processing {}: {}", dependency, file);
147 getLogger().info("Processing {}: {}", dependency, file);
146
148
147 try {
149 try {
148 // try read imageRef
150 // try read imageRef
149 Utils.readImageRef(file).getTag()
151 Utils.readImageRef(file).getTag()
150 .ifPresentOrElse(
152 .ifPresentOrElse(
151 tag -> map.put(varName, tag),
153 tag -> map.put(varName, tag),
152 () -> getLogger().error("ImageRef doesn't have a tag: {}", file));
154 () -> getLogger().error("ImageRef doesn't have a tag: {}", file));
153
155
154 } catch (IOException e) {
156 } catch (IOException e) {
155 getLogger().error("Failed to read ImageRef {}: {}", file, e);
157 getLogger().error("Failed to read ImageRef {}: {}", file, e);
156 }
158 }
157
159
158 } else {
160 } else {
159 getLogger().warn("Dependency {} must have exactly 1 file", dependency);
161 getLogger().warn("Dependency {} must have exactly 1 file", dependency);
160 }
162 }
161 });
163 });
162 }
164 }
163
165
164 return map;
166 return map;
165 }
167 }
166
168
167 }
169 }
@@ -1,54 +1,56
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.PushImage;
7 import org.implab.gradle.containers.tasks.PushImage;
8 import org.implab.gradle.containers.tasks.RunImage;
8 import org.implab.gradle.containers.tasks.RunContainer;
9 import org.implab.gradle.containers.tasks.SaveImage;
9 import org.implab.gradle.containers.tasks.SaveImage;
10 import org.implab.gradle.containers.tasks.StopContainer;
10 import org.implab.gradle.containers.tasks.TagImage;
11 import org.implab.gradle.containers.tasks.TagImage;
11
12
12 public class ContainerBasePlugin implements Plugin<Project> {
13 public class ContainerBasePlugin implements Plugin<Project> {
13 public static final String CONTAINER_EXTENSION_NAME = "container";
14 public static final String CONTAINER_EXTENSION_NAME = "container";
14
15
15 private ContainerExtension containerExtension;
16 private ContainerExtension containerExtension;
16
17
17 ContainerExtension getContainerExtension() {
18 ContainerExtension getContainerExtension() {
18 if (containerExtension == null)
19 if (containerExtension == null)
19 throw new IllegalStateException();
20 throw new IllegalStateException();
20 return containerExtension;
21 return containerExtension;
21 }
22 }
22
23
23 void exportClasses(Project project, Class<?>... classes) {
24 void exportClasses(Project project, Class<?>... classes) {
24 ExtraPropertiesExtension extras = project.getExtensions().getExtraProperties();
25 ExtraPropertiesExtension extras = project.getExtensions().getExtraProperties();
25 for (var clazz : classes)
26 for (var clazz : classes)
26 extras.set(clazz.getSimpleName(), clazz);
27 extras.set(clazz.getSimpleName(), clazz);
27 }
28 }
28
29
29 @Override
30 @Override
30 public void apply(Project project) {
31 public void apply(Project project) {
31
32
32 containerExtension = project.getObjects().newInstance(ContainerExtension.class);
33 containerExtension = project.getObjects().newInstance(ContainerExtension.class);
33
34
34 // TODO: move properties initialization into the constructor
35 // TODO: move properties initialization into the constructor
35 containerExtension.getImageAuthority()
36 containerExtension.getImageAuthority()
36 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
37 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
37
38
38 containerExtension.getImageGroup()
39 containerExtension.getImageGroup()
39 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
40 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
40
41
41 containerExtension.getCliCmd()
42 containerExtension.getCliCmd()
42 .convention(project
43 .convention(project
43 .provider(() -> (String) project.getProperties().get("containerCli"))
44 .provider(() -> (String) project.getProperties().get("containerCli"))
44 .orElse("docker"));
45 .orElse("docker"));
45
46
46 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
47 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
47
48
48 exportClasses(
49 exportClasses(
49 project,
50 project,
50 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunImage.class);
51 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunContainer.class,
52 StopContainer.class);
51
53
52 }
54 }
53
55
54 }
56 }
@@ -1,186 +1,162
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.ArrayList;
6 import java.util.Arrays;
7 import java.util.List;
5 import java.util.List;
8 import java.util.Optional;
6 import java.util.Optional;
9 import java.util.Set;
7 import java.util.Set;
8 import java.util.concurrent.ExecutionException;
10
9
11 import org.gradle.api.logging.Logger;
10 import org.gradle.api.logging.Logger;
12
11
13 public abstract class DockerTraits {
12 public abstract class DockerTraits {
14
13
15 public final String BUILD_COMMAND = "build";
14 public final String BUILD_COMMAND = "build";
16 public final String PUSH_COMMAND = "push";
15 public final String PUSH_COMMAND = "push";
17 public final String RUN_COMMAND = "run";
16 public final String RUN_COMMAND = "run";
18 public final String SAVE_COMMAND = "save";
17 public final String SAVE_COMMAND = "save";
19 public final String INSPECT_COMMAND = "inspect";
18 public final String INSPECT_COMMAND = "inspect";
20 public final String IMAGE_COMMAND = "image";
19 public final String IMAGE_COMMAND = "image";
21 public final String TAG_COMMAND = "tag";
20 public final String TAG_COMMAND = "tag";
22 public final String COMPOSE_COMMAND = "compose";
21 public final String COMPOSE_COMMAND = "compose";
23 public final String UP_COMMAND = "up";
22 public final String UP_COMMAND = "up";
24 public final String STOP_COMMAND = "stop";
23 public final String STOP_COMMAND = "stop";
25 public final String RM_COMMAND = "rm";
24 public final String RM_COMMAND = "rm";
26
25
27 public abstract Logger getLogger();
26 public abstract Logger getLogger();
28
27
29 public abstract Optional<File> getWorkingDir();
28 public abstract Optional<File> getWorkingDir();
30
29
31 public abstract String getCliCmd();
30 public abstract String getCliCmd();
32
31
33 Process startProcess(ProcessBuilder builder) throws IOException {
32 protected boolean checkRetCode(ProcessSpec proc, int code)
34 getLogger().info("Starting: {}", builder.command());
33 throws InterruptedException, ExecutionException, IOException {
35 return builder.start();
36 }
37
38 protected boolean checkRetCode(Process proc, int code) throws InterruptedException {
39 if (getLogger().isInfoEnabled()) {
34 if (getLogger().isInfoEnabled()) {
40 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
35 proc.redirectStdout(RedirectTo.consumer(getLogger()::info))
41 Utils.redirectIO(proc.getErrorStream(), getLogger()::info);
36 .redirectStderr(RedirectTo.consumer(getLogger()::info));
42 }
37 }
43
38
44 return proc.waitFor() == code;
39 getLogger().info("Starting: {}", proc.command());
40
41 return proc.start().get() == code;
45 }
42 }
46
43
47 protected void complete(Process proc) throws InterruptedException, IOException {
44 protected void exec(ProcessSpec proc) throws InterruptedException, IOException, ExecutionException {
48 if (getLogger().isInfoEnabled())
45 if (getLogger().isInfoEnabled())
49 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
46 proc.redirectStdout(RedirectTo.consumer(getLogger()::info));
50
47
51 if (getLogger().isErrorEnabled())
48 if (getLogger().isErrorEnabled())
52 Utils.redirectIO(proc.getErrorStream(), getLogger()::error);
49 proc.redirectStderr(RedirectTo.consumer(getLogger()::error));
53
50
54 var code = proc.waitFor();
51 getLogger().info("Starting: {}", proc.command());
52
53 var code = proc.start().get();
55 if (code != 0) {
54 if (code != 0) {
56 getLogger().error("The process exited with code {}", code);
55 getLogger().error("The process exited with code {}", code);
57 throw new IOException("The process exited with error code " + code);
56 throw new IOException("The process exited with error code " + code);
58 }
57 }
59 }
58 }
60
59
61 protected ProcessBuilder builder(String... args) {
60 protected ProcessSpec builder(String... args) {
62 var argsList = new ArrayList<String>(args.length + 1);
61 var spec = new ProcessSpec().args(getCliCmd()).args(args);
63 Arrays.stream(args).forEach(argsList::add);
64
65 return builder(argsList);
66 }
67
62
68 protected ProcessBuilder builder(List<String> args) {
63 getWorkingDir().ifPresent(spec::directory);
69 var command = new ArrayList<String>(args.size() + 1);
70
64
71 command.add(getCliCmd());
65 return spec;
72 args.forEach(command::add);
73
74 var builder = new ProcessBuilder(command);
75
76 getWorkingDir().ifPresent(builder::directory);
77 return builder;
78 }
66 }
79
67
80 public void buildImage(String imageName, File contextDirectory, List<String> options)
68 public void buildImage(String imageName, File contextDirectory, List<String> options)
81 throws IOException, InterruptedException {
69 throws IOException, InterruptedException, ExecutionException {
82 var args = new ArrayList<String>();
70 var spec = builder(BUILD_COMMAND)
83 args.add(BUILD_COMMAND);
71 .args(options)
84 args.addAll(options);
72 .args("-t", imageName, contextDirectory.getAbsolutePath());
85 args.add("-t");
73
86 args.add(imageName);
74 exec(spec);
87 args.add(contextDirectory.getAbsolutePath());
88 complete(startProcess(builder(args)));
89 }
75 }
90
76
91 public void pushImage(String image, List<String> options) throws InterruptedException, IOException {
77 public void pushImage(String image, List<String> options)
92 var args = new ArrayList<String>();
78 throws InterruptedException, IOException, ExecutionException {
93 args.add(PUSH_COMMAND);
79 var spec = builder(PUSH_COMMAND)
94 args.addAll(options);
80 .args(options)
95 args.add(image);
81 .args(image);
96 complete(startProcess(builder(args)));
82
83 exec(spec);
97 }
84 }
98
85
99 public void runImage(String image, List<String> options, List<String> command)
86 public ProcessSpec runImage(String image, List<String> options, List<String> command)
100 throws InterruptedException, IOException {
87 throws InterruptedException, IOException {
101 var args = new ArrayList<String>();
88 return builder(RUN_COMMAND)
102 args.add(RUN_COMMAND);
89 .args(options)
103 args.addAll(options);
90 .args(image)
104 args.add(image);
91 .args(command);
105 args.addAll(command);
106 complete(startProcess(builder(args)));
107 }
92 }
108
93
109 public void saveImage(Set<String> images, File output) throws InterruptedException, IOException {
94 public void stopContainer(String containerId, List<String> options) throws InterruptedException, IOException, ExecutionException {
95 exec(builder(STOP_COMMAND, containerId).args(options));
96 }
97
98 public void saveImage(Set<String> images, File output)
99 throws InterruptedException, IOException, ExecutionException {
110 if (output.exists())
100 if (output.exists())
111 output.delete();
101 output.delete();
112
102
113 var args = new ArrayList<String>();
103 var spec = builder(SAVE_COMMAND)
114 args.add(SAVE_COMMAND);
104 .args("-o", output.getAbsolutePath())
115 args.add("-o");
105 .args(images);
116 args.add(output.getAbsolutePath());
117 images.forEach(args::add);
118
106
119 complete(startProcess(builder(args)));
107 exec(spec);
120 }
108 }
121
109
122 public void tagImage(String source, String target) throws InterruptedException, IOException {
110 public void tagImage(String source, String target) throws InterruptedException, IOException, ExecutionException {
123 complete(startProcess(builder(TAG_COMMAND, source, target)));
111 exec(builder(TAG_COMMAND, source, target));
124 }
112 }
125
113
126 public boolean imageExists(String imageId) throws InterruptedException, IOException {
114 public boolean imageExists(String imageId) throws InterruptedException, IOException, ExecutionException {
127 getLogger().info("Check image {} exists", imageId);
115 getLogger().info("Check image {} exists", imageId);
128
116
129 return checkRetCode(
117 return checkRetCode(
130 startProcess(builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId)),
118 builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId),
131 0);
119 0);
132 }
120 }
133
121
134 public boolean imageExists(File imageIdFile) {
122 public boolean imageExists(File imageIdFile) {
135 if (imageIdFile.exists()) {
123 if (imageIdFile.exists()) {
136 try {
124 try {
137 var imageId = Utils.readImageRef(imageIdFile);
125 var imageId = Utils.readImageRef(imageIdFile);
138 return imageExists(imageId.getId());
126 return imageExists(imageId.getId());
139 } catch (IOException | InterruptedException e) {
127 } catch (IOException | InterruptedException | ExecutionException e) {
140 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
128 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
141 return false;
129 return false;
142 }
130 }
143 }
131 }
144 return false;
132 return false;
145 }
133 }
146
134
147 List<String> composeArgs(File primaryCompose, Set<String> profiles, String... extra) {
135 ProcessSpec compose(File primaryCompose, Set<String> profiles, String... extra) {
148 var args = new ArrayList<String>();
136 var spec = builder(COMPOSE_COMMAND, "--file", primaryCompose.getAbsolutePath());
149
137
150 args.add(COMPOSE_COMMAND);
138 for (var profile : profiles)
151 args.add("--file");
139 spec.args("--profile", profile);
152 args.add(primaryCompose.getAbsolutePath());
153
140
154 if (profiles.size() > 0) {
141 spec.args(extra);
155 for (var profile : profiles) {
142
156 args.add("--profile");
143 return spec;
157 args.add(profile);
158 }
159 }
144 }
160
145
161 args.addAll(List.of(extra));
146 public void composeUp(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException, ExecutionException {
162
147 exec(compose(primaryCompose, profiles, UP_COMMAND, "--detach"));
163 return args;
164 }
148 }
165
149
166 public void composeUp(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException {
150 public void composeStop(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException, ExecutionException {
167 var args = composeArgs(primaryCompose, profiles, UP_COMMAND, "--detach");
151 exec(compose(primaryCompose, profiles, STOP_COMMAND));
168
169 complete(startProcess(builder(args)));
170 }
171
172 public void composeStop(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException {
173 var args = composeArgs(primaryCompose, profiles, STOP_COMMAND);
174 complete(startProcess(builder(args)));
175 }
152 }
176
153
177 public void composeRm(File primaryCompose, Set<String> profiles, boolean removeVolumes)
154 public void composeRm(File primaryCompose, Set<String> profiles, boolean removeVolumes)
178 throws InterruptedException, IOException {
155 throws InterruptedException, IOException, ExecutionException {
179 var args = composeArgs(primaryCompose, profiles, RM_COMMAND, "--force", "--stop");
156 var spec = compose(primaryCompose, profiles, RM_COMMAND, "--force", "--stop");
180
181 if (removeVolumes)
157 if (removeVolumes)
182 args.add("--volumes");
158 spec.args("--volumes");
183
159
184 complete(startProcess(builder(args)));
160 exec(spec);
185 }
161 }
186 }
162 }
@@ -1,139 +1,156
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;
15 import java.util.concurrent.CompletableFuture;
16 import java.util.stream.Collectors;
14 import java.util.stream.StreamSupport;
17 import java.util.stream.StreamSupport;
15 import java.nio.file.Files;
18 import java.nio.file.Files;
16 import java.util.List;
19 import java.util.List;
17 import org.gradle.api.Action;
20 import org.gradle.api.Action;
18 import org.gradle.internal.impldep.org.bouncycastle.util.Iterable;
21 import org.gradle.internal.impldep.org.bouncycastle.util.Iterable;
19
22
20 import com.fasterxml.jackson.core.exc.StreamWriteException;
23 import com.fasterxml.jackson.core.exc.StreamWriteException;
21 import com.fasterxml.jackson.databind.DatabindException;
24 import com.fasterxml.jackson.databind.DatabindException;
22 import com.fasterxml.jackson.databind.ObjectMapper;
25 import com.fasterxml.jackson.databind.ObjectMapper;
23 import com.fasterxml.jackson.databind.SerializationFeature;
26 import com.fasterxml.jackson.databind.SerializationFeature;
24 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
27 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
25
28
26 import groovy.json.JsonGenerator;
29 import groovy.json.JsonGenerator;
27 import groovy.json.JsonOutput;
30 import groovy.json.JsonOutput;
28 import groovy.json.JsonGenerator.Converter;
31 import groovy.json.JsonGenerator.Converter;
29 import groovy.lang.Closure;
32 import groovy.lang.Closure;
30
33
31 public final class Utils {
34 public final class Utils {
32 public static void redirectIO(final InputStream src, final Action<String> consumer) {
35 public static CompletableFuture<Void> redirectIO(final InputStream src, final Action<String> consumer) {
33 new Thread(() -> {
36 return CompletableFuture.runAsync(() -> {
34 try (Scanner sc = new Scanner(src)) {
37 try (Scanner sc = new Scanner(src)) {
35 while (sc.hasNextLine()) {
38 while (sc.hasNextLine()) {
36 consumer.execute(sc.nextLine());
39 consumer.execute(sc.nextLine());
37 }
40 }
38 }
41 }
39 }).start();
42 });
40 }
43 }
41
44
42 public static void redirectIO(final InputStream src, final File file) {
45 public static CompletableFuture<Void> redirectIO(final InputStream src, final File file) {
43 new Thread(() -> {
46 return CompletableFuture.runAsync(() -> {
44 try (OutputStream out = new FileOutputStream(file)) {
47 try (OutputStream out = new FileOutputStream(file)) {
45 src.transferTo(out);
48 src.transferTo(out);
46 } catch (Exception e) {
49 } catch (Exception e) {
47 // silence!
50 // silence!
48 }
51 }
49 }).start();
52 });
53 }
54
55 public static CompletableFuture<Void> redirectIO(final InputStream src, final OutputStream dst) {
56 return CompletableFuture.runAsync(() -> {
57 try (dst) {
58 src.transferTo(dst);
59 } catch (Exception e) {
60 // silence!
61 }
62 });
50 }
63 }
51
64
52 public static void closeSilent(Closeable handle) {
65 public static void closeSilent(Closeable handle) {
53 try {
66 try {
54 handle.close();
67 handle.close();
55 } catch (Exception e) {
68 } catch (Exception e) {
56 // silence!
69 // silence!
57 }
70 }
58 }
71 }
59
72
60 public static String readAll(final InputStream src) throws IOException {
73 public static String readAll(final InputStream src) throws IOException {
61 ByteArrayOutputStream out = new ByteArrayOutputStream();
74 ByteArrayOutputStream out = new ByteArrayOutputStream();
62 src.transferTo(out);
75 src.transferTo(out);
63 return out.toString();
76 return out.toString();
64 }
77 }
65
78
66 public static <T> T readJson(final Reader reader, Class<T> type) throws IOException {
79 public static <T> T readJson(final Reader reader, Class<T> type) throws IOException {
67 ObjectMapper objectMapper = new ObjectMapper()
80 ObjectMapper objectMapper = new ObjectMapper()
68 .registerModule(new Jdk8Module());
81 .registerModule(new Jdk8Module());
69 return objectMapper.readValue(reader, type);
82 return objectMapper.readValue(reader, type);
70 }
83 }
71
84
72 public static void writeJson(final File file, Object value)
85 public static void writeJson(final File file, Object value)
73 throws StreamWriteException, DatabindException, IOException {
86 throws StreamWriteException, DatabindException, IOException {
74 ObjectMapper objectMapper = new ObjectMapper()
87 ObjectMapper objectMapper = new ObjectMapper()
75 .enable(SerializationFeature.INDENT_OUTPUT)
88 .enable(SerializationFeature.INDENT_OUTPUT)
76 .registerModule(new Jdk8Module());
89 .registerModule(new Jdk8Module());
77 objectMapper.writeValue(file, value);
90 objectMapper.writeValue(file, value);
78 }
91 }
79
92
80 public static ImageRef readImageRef(final File file) throws FileNotFoundException, IOException {
93 public static ImageRef readImageRef(final File file) throws FileNotFoundException, IOException {
81 try (var reader = new FileReader(file)) {
94 try (var reader = new FileReader(file)) {
82 return readJson(reader, ImageRef.class);
95 return readJson(reader, ImageRef.class);
83 }
96 }
84 }
97 }
85
98
86 public static String readAll(final File src) throws IOException {
99 public static String readAll(final File src) throws IOException {
87 return Files.readString(src.toPath());
100 return Files.readString(src.toPath());
88 }
101 }
89
102
90 public static List<String> readAll(final Iterable<? extends File> files) {
103 public static List<String> readAll(final Iterable<? extends File> files) {
91 return StreamSupport.stream(files.spliterator(), false)
104 return StreamSupport.stream(files.spliterator(), false)
92 .map(file -> {
105 .map(file -> {
93 try {
106 try {
94 return Utils.readAll(file);
107 return Utils.readAll(file);
95 } catch (IOException e) {
108 } catch (IOException e) {
96 throw new RuntimeException(e);
109 throw new RuntimeException(e);
97 }
110 }
98 })
111 })
99 .toList();
112 .toList();
100 }
113 }
101
114
102 public static String readAll(final InputStream src, String charset) throws IOException {
115 public static String readAll(final InputStream src, String charset) throws IOException {
103 ByteArrayOutputStream out = new ByteArrayOutputStream();
116 ByteArrayOutputStream out = new ByteArrayOutputStream();
104 src.transferTo(out);
117 src.transferTo(out);
105 return out.toString(charset);
118 return out.toString(charset);
106 }
119 }
107
120
108 public static JsonGenerator createDefaultJsonGenerator() {
121 public static JsonGenerator createDefaultJsonGenerator() {
109 return new JsonGenerator.Options()
122 return new JsonGenerator.Options()
110 .excludeNulls()
123 .excludeNulls()
111 .addConverter(new Converter() {
124 .addConverter(new Converter() {
112 public boolean handles(Class<?> type) {
125 public boolean handles(Class<?> type) {
113 return (File.class == type);
126 return (File.class == type);
114 }
127 }
115
128
116 public Object convert(Object value, String key) {
129 public Object convert(Object value, String key) {
117 return ((File) value).getPath();
130 return ((File) value).getPath();
118 }
131 }
119 })
132 })
120 .build();
133 .build();
121 }
134 }
122
135
123 public static String toJsonPretty(Object value) {
136 public static String toJsonPretty(Object value) {
124 return JsonOutput.prettyPrint(createDefaultJsonGenerator().toJson(value));
137 return JsonOutput.prettyPrint(createDefaultJsonGenerator().toJson(value));
125 }
138 }
126
139
127 public static boolean isNullOrEmptyString(String value) {
140 public static boolean isNullOrEmptyString(String value) {
128 return (value == null || value.length() == 0);
141 return (value == null || value.length() == 0);
129 }
142 }
130
143
131 public static <T> Action<T> wrapClosure(Closure<?> closure) {
144 public static <T> Action<T> wrapClosure(Closure<?> closure) {
132 return x -> {
145 return x -> {
133 closure.setDelegate(x);
146 closure.setDelegate(x);
134 closure.setResolveStrategy(Closure.OWNER_FIRST);
147 closure.setResolveStrategy(Closure.OWNER_FIRST);
135 closure.call(x);
148 closure.call(x);
136 };
149 };
137 }
150 }
138
151
152 public static Set<String> mapToString(Set<Object> set) {
153 return set.stream().map(Object::toString).collect(Collectors.toSet());
154 }
155
139 } No newline at end of file
156 }
@@ -1,116 +1,116
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
24
25 import org.implab.gradle.containers.cli.ImageRef;
25 import org.implab.gradle.containers.cli.ImageRef;
26 import org.implab.gradle.containers.cli.Utils;
26 import org.implab.gradle.containers.cli.Utils;
27 import org.implab.gradle.containers.dsl.MapPropertyEntry;
27 import org.implab.gradle.containers.dsl.MapPropertyEntry;
28 import org.implab.gradle.containers.dsl.OptionsMixin;
28 import org.implab.gradle.containers.dsl.OptionsMixin;
29
29
30 import groovy.lang.Closure;
30 import groovy.lang.Closure;
31
31
32 public abstract class BuildImage extends DockerCliTask implements OptionsMixin {
32 public abstract class BuildImage extends DockerCliTask implements OptionsMixin {
33
33
34 @InputDirectory
34 @InputDirectory
35 @SkipWhenEmpty
35 @SkipWhenEmpty
36 public abstract DirectoryProperty getContextDirectory();
36 public abstract DirectoryProperty getContextDirectory();
37
37
38 @Input
38 @Input
39 public abstract MapProperty<String, String> getBuildArgs();
39 public abstract MapProperty<String, String> getBuildArgs();
40
40
41 @Input
41 @Input
42 @Optional
42 @Optional
43 public abstract Property<String> getBuildTarget();
43 public abstract Property<String> getBuildTarget();
44
44
45 @Input
45 @Input
46 public abstract Property<String> getImageName();
46 public abstract Property<Object> getImageName();
47
47
48 @Internal
48 @Internal
49 public abstract RegularFileProperty getImageIdFile();
49 public abstract RegularFileProperty getImageIdFile();
50
50
51 @OutputFile
51 @OutputFile
52 public Provider<File> getImageIdFileOutput() {
52 public Provider<File> getImageIdFileOutput() {
53 return getImageIdFile().map(RegularFile::getAsFile);
53 return getImageIdFile().map(RegularFile::getAsFile);
54 }
54 }
55
55
56 public BuildImage() {
56 public BuildImage() {
57 getOutputs().upToDateWhen(task -> getImageIdFile()
57 getOutputs().upToDateWhen(task -> getImageIdFile()
58 .map(RegularFile::getAsFile)
58 .map(RegularFile::getAsFile)
59 .map(docker()::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 @TaskAction
80 @TaskAction
81 public void run() throws Exception {
81 public void run() throws Exception {
82 List<String> args = new ArrayList<>();
82 List<String> args = new ArrayList<>();
83
83
84 // create a temp file to store image id
84 // create a temp file to store image id
85 var iidFile = new File(this.getTemporaryDir(), "iid");
85 var iidFile = new File(this.getTemporaryDir(), "iid");
86
86
87 // specify where to write image id
87 // specify where to write image id
88 args.addAll(List.of("--iidfile", iidFile.getAbsolutePath()));
88 args.addAll(List.of("--iidfile", iidFile.getAbsolutePath()));
89
89
90 getBuildArgs().get().forEach((k, v) -> {
90 getBuildArgs().get().forEach((k, v) -> {
91 args.add("--build-arg");
91 args.add("--build-arg");
92 args.add(String.format("%s=%s", k, v));
92 args.add(String.format("%s=%s", k, v));
93 });
93 });
94
94
95 // add --target if specified for multi-stage build
95 // add --target if specified for multi-stage build
96 if (getBuildTarget().isPresent()) {
96 if (getBuildTarget().isPresent()) {
97 args.add("--target");
97 args.add("--target");
98 args.add(getBuildTarget().get());
98 args.add(getBuildTarget().get());
99 }
99 }
100
100
101 // add extra parameters
101 // add extra parameters
102 getOptions().get().forEach(args::add);
102 getOptions().get().forEach(args::add);
103
103
104 var imageTag = getImageName().get();
104 var imageTag = getImageName().map(Object::toString).get();
105
105
106 // build image
106 // build image
107 docker().buildImage(
107 docker().buildImage(
108 imageTag,
108 imageTag,
109 getContextDirectory().map(Directory::getAsFile).get(),
109 getContextDirectory().map(Directory::getAsFile).get(),
110 args);
110 args);
111
111
112 // read the built image id and store image ref metadata
112 // read the built image id and store image ref metadata
113 var imageRef = new ImageRef(imageTag, Utils.readAll(iidFile));
113 var imageRef = new ImageRef(imageTag, Utils.readAll(iidFile));
114 Utils.writeJson(getImageIdFile().map(RegularFile::getAsFile).get(), imageRef);
114 Utils.writeJson(getImageIdFile().map(RegularFile::getAsFile).get(), imageRef);
115 }
115 }
116 }
116 }
@@ -1,22 +1,23
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
5
5 import org.gradle.api.provider.Property;
6 import org.gradle.api.provider.Property;
6 import org.gradle.api.tasks.Internal;
7 import org.gradle.api.tasks.Internal;
7 import org.gradle.api.tasks.TaskAction;
8 import org.gradle.api.tasks.TaskAction;
8
9
9 public abstract class ComposeRm extends ComposeStop {
10 public abstract class ComposeRm extends ComposeTask {
10
11
11 @Internal
12 @Internal
12 public abstract Property<Boolean> getRemoveVolumes();
13 public abstract Property<Boolean> getRemoveVolumes();
13
14
14 public ComposeRm() {
15 public ComposeRm() {
15 getRemoveVolumes().convention(true);
16 getRemoveVolumes().convention(true);
16 }
17 }
17
18
18 @TaskAction
19 @TaskAction
19 public void run() throws InterruptedException, IOException {
20 public void run() throws InterruptedException, IOException, ExecutionException {
20 docker().composeRm(getComposeFile().get().getAsFile(), getProfiles().get(), getRemoveVolumes().get());
21 docker().composeRm(getComposeFile().get().getAsFile(), getProfiles().get(), getRemoveVolumes().get());
21 }
22 }
22 }
23 }
@@ -1,13 +1,14
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
5
5 import org.gradle.api.tasks.TaskAction;
6 import org.gradle.api.tasks.TaskAction;
6
7
7 public abstract class ComposeStop extends ComposeTask {
8 public abstract class ComposeStop extends ComposeTask {
8
9
9 @TaskAction
10 @TaskAction
10 public void run() throws InterruptedException, IOException {
11 public void run() throws InterruptedException, IOException, ExecutionException {
11 docker().composeStop(getComposeFile().getAsFile().get(), getProfiles().get());
12 docker().composeStop(getComposeFile().getAsFile().get(), getProfiles().get());
12 }
13 }
13 } No newline at end of file
14 }
@@ -1,13 +1,14
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
5
5 import org.gradle.api.tasks.TaskAction;
6 import org.gradle.api.tasks.TaskAction;
6
7
7 public abstract class ComposeUp extends ComposeTask {
8 public abstract class ComposeUp extends ComposeTask {
8
9
9 @TaskAction
10 @TaskAction
10 public void run() throws InterruptedException, IOException {
11 public void run() throws InterruptedException, IOException, ExecutionException {
11 docker().composeUp(getComposeFile().getAsFile().get(), getProfiles().get());
12 docker().composeUp(getComposeFile().getAsFile().get(), getProfiles().get());
12 }
13 }
13 }
14 }
@@ -1,61 +1,89
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.util.Optional;
4 import java.util.Optional;
5
5
6 import org.gradle.api.DefaultTask;
6 import org.gradle.api.DefaultTask;
7 import org.gradle.api.logging.Logger;
7 import org.gradle.api.logging.Logger;
8 import org.gradle.api.provider.Property;
8 import org.gradle.api.provider.Property;
9 import org.gradle.api.tasks.Input;
9 import org.gradle.api.tasks.Input;
10 import org.gradle.api.tasks.Internal;
10 import org.gradle.api.tasks.Internal;
11 import org.implab.gradle.containers.ContainerExtension;
11 import org.implab.gradle.containers.ContainerExtension;
12 import org.implab.gradle.containers.PropertiesMixin;
12 import org.implab.gradle.containers.PropertiesMixin;
13 import org.implab.gradle.containers.cli.DockerTraits;
13 import org.implab.gradle.containers.cli.DockerTraits;
14 import org.implab.gradle.containers.cli.ProcessSpec;
15 import org.implab.gradle.containers.cli.RedirectFrom;
16 import org.implab.gradle.containers.cli.RedirectTo;
14
17
15 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin {
18 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin {
16
19
17 @Input
20 @Input
18 public abstract Property<String> getCliCmd();
21 public abstract Property<String> getCliCmd();
19
22
20 /**
23 /**
21 * Returns working directory for docker commands
24 * Returns working directory for docker commands
22 */
25 */
23 @Input
26 @Input
24 @org.gradle.api.tasks.Optional
27 @org.gradle.api.tasks.Optional
25 public abstract Property<File> getWorkingDirectory();
28 public abstract Property<File> getWorkingDirectory();
26
29
27 @Internal
30 @Internal
28 protected ContainerExtension getContainerExtension() {
31 protected ContainerExtension getContainerExtension() {
29 return getProject().getExtensions().getByType(ContainerExtension.class);
32 return getProject().getExtensions().getByType(ContainerExtension.class);
30 }
33 }
31
34
32 public DockerCliTask() {
35 public DockerCliTask() {
33 getCliCmd().convention(getContainerExtension().getCliCmd());
36 getCliCmd().convention(getContainerExtension().getCliCmd());
34
37
35 }
38 }
36
39
40 protected Optional<RedirectTo> loggerInfoRedirect() {
41 return getLogger().isInfoEnabled() ? Optional.of(RedirectTo.consumer(getLogger()::info)) : Optional.empty();
42 }
43
44 protected Optional<RedirectTo> loggerErrorRedirect() {
45 return getLogger().isErrorEnabled() ? Optional.of(RedirectTo.consumer(getLogger()::error)) : Optional.empty();
46 }
47
48 protected Optional<RedirectTo> getStdoutRedirection() {
49 return loggerInfoRedirect();
50 }
51
52 protected Optional<RedirectTo> getStderrRedirection() {
53 return loggerErrorRedirect();
54 }
55
56 protected Optional<RedirectFrom> getStdinRedirection() {
57 return Optional.empty();
58 }
59
37 protected DockerTraits docker() {
60 protected DockerTraits docker() {
38 return new TaskDockerTraits();
61 return new TaskDockerTraits();
39 }
62 }
40
63
64 protected void exec(ProcessSpec spec) {
65
66 getLogger().info("Starting: {}", spec.command());
67 }
68
41 class TaskDockerTraits extends DockerTraits {
69 class TaskDockerTraits extends DockerTraits {
42
70
43 @Override
71 @Override
44 public Logger getLogger() {
72 public Logger getLogger() {
45 return DockerCliTask.this.getLogger();
73 return DockerCliTask.this.getLogger();
46 }
74 }
47
75
48 @Override
76 @Override
49 public Optional<File> getWorkingDir() {
77 public Optional<File> getWorkingDir() {
50 return getWorkingDirectory()
78 return getWorkingDirectory()
51 .map(Optional::of)
79 .map(Optional::of)
52 .getOrElse(Optional.empty());
80 .getOrElse(Optional.empty());
53 }
81 }
54
82
55 @Override
83 @Override
56 public String getCliCmd() {
84 public String getCliCmd() {
57 return DockerCliTask.this.getCliCmd().get();
85 return DockerCliTask.this.getCliCmd().get();
58 }
86 }
59
87
60 }
88 }
61 }
89 }
@@ -1,32 +1,33
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
5
5 import org.gradle.api.provider.ListProperty;
6 import org.gradle.api.provider.ListProperty;
6 import org.gradle.api.provider.Property;
7 import org.gradle.api.provider.Property;
7 import org.gradle.api.tasks.Input;
8 import org.gradle.api.tasks.Input;
8 import org.gradle.api.tasks.Optional;
9 import org.gradle.api.tasks.Optional;
9 import org.gradle.api.tasks.TaskAction;
10 import org.gradle.api.tasks.TaskAction;
10 import org.implab.gradle.containers.dsl.OptionsMixin;
11 import org.implab.gradle.containers.dsl.OptionsMixin;
11
12
12 public abstract class PushImage extends DockerCliTask implements OptionsMixin {
13 public abstract class PushImage extends DockerCliTask implements OptionsMixin {
13
14
14 @Input
15 @Input
15 public abstract Property<String> getImageName();
16 public abstract Property<Object> getImageName();
16
17
17 @Input
18 @Input
18 @Optional
19 @Optional
19 public abstract Property<String> getTransport();
20 public abstract Property<Object> getTransport();
20
21
21 @Input
22 @Input
22 @Optional
23 @Optional
23 public abstract ListProperty<String> getOptions();
24 public abstract ListProperty<String> getOptions();
24
25
25 @TaskAction
26 @TaskAction
26 public void run() throws InterruptedException, IOException {
27 public void run() throws InterruptedException, IOException, ExecutionException {
27
28
28 docker().pushImage(
29 docker().pushImage(
29 getImageName().get(),
30 getImageName().map(Object::toString).get(),
30 getOptions().get());
31 getOptions().get());
31 }
32 }
32 }
33 }
@@ -1,44 +1,159
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.IOException;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.List;
7 import java.util.Optional;
8 import java.util.concurrent.ExecutionException;
9
4 import org.gradle.api.Action;
10 import org.gradle.api.Action;
11 import org.gradle.api.file.RegularFileProperty;
5 import org.gradle.api.provider.ListProperty;
12 import org.gradle.api.provider.ListProperty;
6 import org.gradle.api.provider.Property;
13 import org.gradle.api.provider.Property;
7 import org.gradle.api.tasks.Input;
14 import org.gradle.api.tasks.Internal;
8 import org.gradle.api.tasks.Optional;
9 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;
10 import org.implab.gradle.containers.dsl.OptionsMixin;
18 import org.implab.gradle.containers.dsl.OptionsMixin;
19 import org.implab.gradle.containers.dsl.RedirectFromSpec;
20 import org.implab.gradle.containers.dsl.RedirectToSpec;
11 import org.implab.gradle.containers.dsl.VolumeSpec;
21 import org.implab.gradle.containers.dsl.VolumeSpec;
12
22
13 public abstract class RunImage extends DockerCliTask implements OptionsMixin {
23 public abstract class RunContainer extends DockerCliTask implements OptionsMixin {
24
25 private final RedirectToSpec redirectStderr = new RedirectToSpec();
26
27 private final RedirectToSpec redirectStdout = new RedirectToSpec();
28
29 private final RedirectFromSpec redirectStdin = new RedirectFromSpec();
14
30
15 @Input
31 private boolean transientContainer = true;
16 public abstract Property<String> getImageName();
32
33 private boolean detached = false;
17
34
18 @Input
35 private boolean interactive = false;
19 @Optional
36
37 @Internal
38 public abstract Property<Object> getImageName();
39
40 @Internal
20 public abstract ListProperty<String> getCommandLine();
41 public abstract ListProperty<String> getCommandLine();
21
42
43 @Internal
44 public RedirectFromSpec getStdin() {
45 return redirectStdin;
46 }
47
48 @Internal
49 public RedirectToSpec getStdout() {
50 return redirectStdout;
51 }
52
53 @Internal
54 public RedirectToSpec getStderr() {
55 return redirectStderr;
56 }
57
58 @Internal
59 public boolean isTransientContainer() {
60 return transientContainer;
61 }
62
63 public void setTransientContainer(boolean value) {
64 transientContainer = value;
65 }
66
67 @Internal
68 public boolean isDetached() {
69 // if IO was redirected the container should run in foreground
70 if (redirectStdin.isRedirected() || redirectStdout.isRedirected() || redirectStderr.isRedirected())
71 return false;
72
73 return detached;
74 }
75
76 public void setDetached(boolean value) {
77 detached = value;
78 }
79
80 @Internal
81 public boolean isInteractive() {
82 // enable interactive mode when processing standard input
83 return redirectStdin.isRedirected() || interactive;
84 }
85
86 @Internal
87 public abstract RegularFileProperty getContainerIdFile();
88
89 public RunContainer() {
90 getContainerIdFile().convention(() -> new File(getTemporaryDir(), "cid"));
91 }
22
92
23 public void volume(Action<VolumeSpec> configure) {
93 public void volume(Action<VolumeSpec> configure) {
24 getOptions().add("-v");
94 getOptions().add("-v");
25 getOptions().add(provider(() -> {
95 getOptions().add(provider(() -> {
26 var volumeSpec = getObjectFactory().newInstance(VolumeSpec.class);
96 var volumeSpec = getObjectFactory().newInstance(VolumeSpec.class);
27 configure.execute(volumeSpec);
97 configure.execute(volumeSpec);
28 return volumeSpec.resolveSpec();
98 return volumeSpec.resolveSpec();
29 }));
99 }));
30 }
100 }
31
101
32 void commandLine(String ...args) {
102 void commandLine(String... args) {
33 getCommandLine().addAll(args);
103 getCommandLine().addAll(args);
34 }
104 }
35
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
36 @TaskAction
121 @TaskAction
37 public void run() throws InterruptedException, IOException {
122 public void run() throws InterruptedException, IOException, ExecutionException {
38 docker().runImage(
123 var params = new ArrayList<>(getOptions().get());
39 getImageName().get(),
124
40 getOptions().get(),
125 if (isInteractive())
126 params.add("--interactive");
127 if (isTransientContainer())
128 params.add("--rm");
129 if (isDetached())
130 params.add("--detach");
131 if (getContainerIdFile().isPresent()) {
132 getContainerIdFile().getAsFile().get().delete();
133
134 params.addAll(List.of("--cidfile", getContainerIdFile().get().getAsFile().toString()));
135 }
136
137 var spec = docker().runImage(
138 getImageName().map(Object::toString).get(),
139 params,
41 getCommandLine().get());
140 getCommandLine().get());
141
142 // if process will run in foreground, then setup default redirects
143 redirectStdout.getRedirection()
144 .or(this::loggerInfoRedirect)
145 .ifPresent(spec::redirectStdout);
146
147 redirectStderr.getRedirection()
148 .or(this::loggerErrorRedirect)
149 .ifPresent(spec::redirectStderr);
150
151 redirectStdin.getRedirection().ifPresent(spec::redirectStdin);
152
153 getLogger().info("Staring: {}", spec.command());
154
155 // runs the command and checks the error code
156 spec.exec();
42 }
157 }
43
158
44 }
159 }
@@ -1,111 +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
8
8 import org.gradle.api.file.DirectoryProperty;
9 import org.gradle.api.file.DirectoryProperty;
9 import org.gradle.api.file.FileCollection;
10 import org.gradle.api.file.FileCollection;
10 import org.gradle.api.file.RegularFile;
11 import org.gradle.api.file.RegularFile;
11 import org.gradle.api.provider.Property;
12 import org.gradle.api.provider.Property;
12 import org.gradle.api.provider.Provider;
13 import org.gradle.api.provider.Provider;
13 import org.gradle.api.provider.SetProperty;
14 import org.gradle.api.provider.SetProperty;
14 import org.gradle.api.tasks.Input;
15 import org.gradle.api.tasks.Input;
15 import org.gradle.api.tasks.Internal;
16 import org.gradle.api.tasks.Internal;
16 import org.gradle.api.tasks.OutputFile;
17 import org.gradle.api.tasks.OutputFile;
17 import org.gradle.api.tasks.TaskAction;
18 import org.gradle.api.tasks.TaskAction;
18 import org.gradle.api.tasks.TaskExecutionException;
19 import org.gradle.api.tasks.TaskExecutionException;
19 import org.implab.gradle.containers.cli.Utils;
20 import org.implab.gradle.containers.cli.Utils;
20
21
21 public abstract class SaveImage extends DockerCliTask {
22 public abstract class SaveImage extends DockerCliTask {
22
23
23 @Input
24 @Input
24 public abstract SetProperty<String> getExportImages();
25 public abstract SetProperty<Object> getExportImages();
25
26
26 @OutputFile
27 @OutputFile
27 public Provider<RegularFile> getArchiveFile() {
28 public Provider<RegularFile> getArchiveFile() {
28 return getDestinationDirectory().file(getArchiveFileName());
29 return getDestinationDirectory().file(getArchiveFileName());
29 }
30 }
30
31
31 @Internal
32 @Internal
32 public abstract DirectoryProperty getDestinationDirectory();
33 public abstract DirectoryProperty getDestinationDirectory();
33
34
34 @Internal
35 @Internal
35 public abstract Property<String> getArchiveFileName();
36 public abstract Property<String> getArchiveFileName();
36
37
37 @Internal
38 @Internal
38 public abstract Property<String> getArchiveBaseName();
39 public abstract Property<String> getArchiveBaseName();
39
40
40 @Internal
41 @Internal
41 public abstract Property<String> getArchiveVersion();
42 public abstract Property<String> getArchiveVersion();
42
43
43 @Internal
44 @Internal
44 public abstract Property<String> getArchiveClassifier();
45 public abstract Property<String> getArchiveClassifier();
45
46
46 @Internal
47 @Internal
47 public abstract Property<String> getArchiveExtension();
48 public abstract Property<String> getArchiveExtension();
48
49
49 String readImageRefTag(File file) throws Exception {
50 String readImageRefTag(File file) throws Exception {
50 getLogger().info("Reading image ref from {}", file);
51 getLogger().info("Reading image ref from {}", file);
51 var imageRef = Utils.readImageRef(file);
52 var imageRef = Utils.readImageRef(file);
52 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"));
53 }
54 }
54
55
55 public void imageRef(File file) {
56 public void imageRef(File file) {
56 getExportImages().add(provider(() -> {
57 getExportImages().add(provider(() -> {
57 return readImageRefTag(file);
58 return readImageRefTag(file);
58 }));
59 }));
59 }
60 }
60
61
61 public void imageRefs(FileCollection files) {
62 public void imageRefs(FileCollection files) {
62 dependsOn(files);
63 dependsOn(files);
63 getExportImages().addAll(provider(() -> {
64 getExportImages().addAll(provider(() -> {
64 var tags = new ArrayList<String>();
65 var tags = new ArrayList<String>();
65 for (File file : files) {
66 for (File file : files) {
66 tags.add(readImageRefTag(file));
67 tags.add(readImageRefTag(file));
67 }
68 }
68 return tags;
69 return tags;
69 }));
70 }));
70 }
71 }
71
72
72 public SaveImage() {
73 public SaveImage() {
73 getArchiveFileName().convention(provider(this::conventionalArchiveFileName));
74 getArchiveFileName().convention(provider(this::conventionalArchiveFileName));
74 getArchiveBaseName().convention(provider(this::conventionalArchiveBaseName));
75 getArchiveBaseName().convention(provider(this::conventionalArchiveBaseName));
75 getArchiveVersion().convention(provider(() -> getProject().getVersion()).map(x -> x.toString()));
76 getArchiveVersion().convention(provider(() -> getProject().getVersion()).map(x -> x.toString()));
76 getDestinationDirectory().convention(getProject().getLayout().getBuildDirectory());
77 getDestinationDirectory().convention(getProject().getLayout().getBuildDirectory());
77 getArchiveExtension().convention("tar");
78 getArchiveExtension().convention("tar");
78 }
79 }
79
80
80 private String conventionalArchiveBaseName() {
81 private String conventionalArchiveBaseName() {
81 ArrayList<String> parts = new ArrayList<>();
82 ArrayList<String> parts = new ArrayList<>();
82 Optional.of(getProject().getGroup()).map(x -> x.toString()).ifPresent(parts::add);
83 Optional.of(getProject().getGroup()).map(x -> x.toString()).ifPresent(parts::add);
83 parts.add(getProject().getName());
84 parts.add(getProject().getName());
84 return String.join("-", parts);
85 return String.join("-", parts);
85 }
86 }
86
87
87 private String conventionalArchiveFileName() {
88 private String conventionalArchiveFileName() {
88 ArrayList<String> parts = new ArrayList<>();
89 ArrayList<String> parts = new ArrayList<>();
89
90
90 if (getArchiveBaseName().isPresent())
91 if (getArchiveBaseName().isPresent())
91 parts.add(getArchiveBaseName().get());
92 parts.add(getArchiveBaseName().get());
92
93
93 if (getArchiveVersion().isPresent())
94 if (getArchiveVersion().isPresent())
94 parts.add(getArchiveVersion().get());
95 parts.add(getArchiveVersion().get());
95
96
96 if (getArchiveClassifier().isPresent())
97 if (getArchiveClassifier().isPresent())
97 parts.add(getArchiveClassifier().get());
98 parts.add(getArchiveClassifier().get());
98
99
99 if (parts.size() == 0)
100 if (parts.size() == 0)
100 throw new TaskExecutionException(this, new Exception("The archiveFileName ism't specified"));
101 throw new TaskExecutionException(this, new Exception("The archiveFileName ism't specified"));
101
102
102 return String.join("-", parts) + "." + getArchiveExtension().get();
103 return String.join("-", parts) + "." + getArchiveExtension().get();
103 }
104 }
104
105
105 @TaskAction
106 @TaskAction
106 public void run() throws InterruptedException, IOException {
107 public void run() throws InterruptedException, IOException, ExecutionException {
107 docker().saveImage(
108 docker().saveImage(
108 getExportImages().get(),
109 getExportImages().map(Utils::mapToString).get(),
109 getArchiveFile().map(RegularFile::getAsFile).get());
110 getArchiveFile().map(RegularFile::getAsFile).get());
110 }
111 }
111 }
112 }
@@ -1,46 +1,47
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.HashSet;
5 import java.util.Set;
4 import java.util.Set;
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
12
12 public abstract class TagImage extends DockerCliTask {
13 public abstract class TagImage extends DockerCliTask {
13 @Input
14 @Input
14 public abstract Property<String> getSrcImage();
15 public abstract Property<Object> getSrcImage();
15
16
16 @Input
17 @Input
17 public abstract SetProperty<String> getTags();
18 public abstract SetProperty<Object> getTags();
18
19
19 @Input
20 @Input
20 @Deprecated
21 @Deprecated
21 public abstract Property<String> getDestImage();
22 public abstract Property<Object> getDestImage();
22
23
23 private Set<String> getImageTags() {
24 private Set<String> getImageTags() {
24 var tags = new HashSet<>(getTags().get());
25 var tags = getTags().map(Utils::mapToString).get();
25 tags.add(getDestImage().get());
26 tags.add(getDestImage().map(Object::toString).get());
26 return tags;
27 return tags;
27 }
28 }
28
29
29 public TagImage() {
30 public TagImage() {
30 this.setOnlyIf("No tags were specified", self -> getImageTags().size() > 0);
31 this.setOnlyIf("No tags were specified", self -> getImageTags().size() > 0);
31 }
32 }
32
33
33 @TaskAction
34 @TaskAction
34 public void run() throws InterruptedException, IOException {
35 public void run() throws InterruptedException, IOException, ExecutionException {
35 var tags = getImageTags();
36 var tags = getImageTags();
36 var src = getSrcImage().get();
37 var src = getSrcImage().map(Object::toString).get();
37
38
38 if (tags.size() == 0)
39 if (tags.size() == 0)
39 getLogger().info("No tags were specified");
40 getLogger().info("No tags were specified");
40
41
41 for (var tag : tags) {
42 for (var tag : tags) {
42 getLogger().info("Tag: {}", tag);
43 getLogger().info("Tag: {}", tag);
43 docker().tagImage(src, tag);
44 docker().tagImage(src, tag);
44 }
45 }
45 }
46 }
46 }
47 }
General Comments 0
You need to be logged in to leave comments. Login now