##// 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
@@ -3,7 +3,6 package org.implab.gradle.containers;
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;
@@ -20,6 +19,8 import org.implab.gradle.containers.task
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";
@@ -59,7 +60,7 public abstract class ComposePlugin impl
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 -> {
@@ -88,6 +89,7 public abstract class ComposePlugin impl
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
@@ -5,8 +5,9 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> {
@@ -47,7 +48,8 public class ContainerBasePlugin impleme
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
@@ -2,11 +2,10 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
@@ -30,104 +29,93 public abstract class DockerTraits {
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
@@ -136,7 +124,7 public abstract class DockerTraits {
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 }
@@ -144,43 +132,31 public abstract class DockerTraits {
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
150 args.add(COMPOSE_COMMAND);
151 args.add("--file");
152 args.add(primaryCompose.getAbsolutePath());
153
137
154 if (profiles.size() > 0) {
138 for (var profile : profiles)
155 for (var profile : profiles) {
139 spec.args("--profile", profile);
156 args.add("--profile");
157 args.add(profile);
158 }
159 }
160
140
161 args.addAll(List.of(extra));
141 spec.args(extra);
162
142
163 return args;
143 return spec;
164 }
144 }
165
145
166 public void composeUp(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException {
146 public void composeUp(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException, ExecutionException {
167 var args = composeArgs(primaryCompose, profiles, UP_COMMAND, "--detach");
147 exec(compose(primaryCompose, profiles, UP_COMMAND, "--detach"));
168
169 complete(startProcess(builder(args)));
170 }
148 }
171
149
172 public void composeStop(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException {
150 public void composeStop(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException, ExecutionException {
173 var args = composeArgs(primaryCompose, profiles, STOP_COMMAND);
151 exec(compose(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 }
@@ -11,6 +11,9 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;
@@ -29,24 +32,34 import groovy.json.JsonGenerator.Convert
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) {
@@ -136,4 +149,8 public final class Utils {
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 }
@@ -43,7 +43,7 public abstract class BuildImage extends
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();
@@ -101,7 +101,7 public abstract class BuildImage extends
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(
@@ -1,12 +1,13
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();
@@ -16,7 +17,7 public abstract class ComposeRm extends
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 }
@@ -11,6 +11,9 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
@@ -33,11 +36,36 public abstract class DockerCliTask exte
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
@@ -48,8 +76,8 public abstract class DockerCliTask exte
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
@@ -1,6 +1,7
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;
@@ -12,21 +13,21 import org.implab.gradle.containers.dsl.
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,24 +1,94
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();
30
31 private boolean transientContainer = true;
32
33 private boolean detached = false;
34
35 private boolean interactive = false;
36
37 @Internal
38 public abstract Property<Object> getImageName();
14
39
15 @Input
40 @Internal
16 public abstract Property<String> getImageName();
41 public abstract ListProperty<String> getCommandLine();
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 }
17
57
18 @Input
58 @Internal
19 @Optional
59 public boolean isTransientContainer() {
20 public abstract ListProperty<String> getCommandLine();
60 return transientContainer;
21
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");
@@ -29,16 +99,61 public abstract class RunImage extends D
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 }
@@ -4,6 +4,7 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;
@@ -21,7 +22,7 import org.implab.gradle.containers.cli.
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() {
@@ -103,9 +104,9 public abstract class SaveImage extends
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,28 +1,29
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
@@ -31,9 +32,9 public abstract class TagImage extends D
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");
General Comments 0
You need to be logged in to leave comments. Login now