##// 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 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 3 import java.io.IOException;
4 4 import java.util.HashMap;
5 5 import java.util.Map;
6 import java.util.Optional;
7 6
8 7 import org.gradle.api.DefaultTask;
9 8 import org.gradle.api.Plugin;
@@ -20,6 +19,8 import org.implab.gradle.containers.task
20 19 import org.implab.gradle.containers.tasks.WriteEnv;
21 20
22 21 public abstract class ComposePlugin implements Plugin<Project>, ProjectMixin {
22 public static final String BUILD_GROUP = "build";
23
23 24 public final String COMPOSE_IMAGES_CONFIGURATION = "composeImages";
24 25
25 26 public final String COMPOSE_PROJECT_NAME = "COMPOSE_PROJECT_NAME";
@@ -88,6 +89,7 public abstract class ComposePlugin impl
88 89 });
89 90
90 91 var buildTask = task(BUILD_TASK, DefaultTask.class, t -> {
92 t.setGroup(BUILD_GROUP);
91 93 t.dependsOn(writeEnvTask);
92 94 });
93 95
@@ -5,8 +5,9 import org.gradle.api.Project;
5 5 import org.gradle.api.plugins.ExtraPropertiesExtension;
6 6 import org.implab.gradle.containers.tasks.BuildImage;
7 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 9 import org.implab.gradle.containers.tasks.SaveImage;
10 import org.implab.gradle.containers.tasks.StopContainer;
10 11 import org.implab.gradle.containers.tasks.TagImage;
11 12
12 13 public class ContainerBasePlugin implements Plugin<Project> {
@@ -47,7 +48,8 public class ContainerBasePlugin impleme
47 48
48 49 exportClasses(
49 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 3 import java.io.File;
4 4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 5 import java.util.List;
8 6 import java.util.Optional;
9 7 import java.util.Set;
8 import java.util.concurrent.ExecutionException;
10 9
11 10 import org.gradle.api.logging.Logger;
12 11
@@ -30,104 +29,93 public abstract class DockerTraits {
30 29
31 30 public abstract String getCliCmd();
32 31
33 Process startProcess(ProcessBuilder builder) throws IOException {
34 getLogger().info("Starting: {}", builder.command());
35 return builder.start();
36 }
37
38 protected boolean checkRetCode(Process proc, int code) throws InterruptedException {
32 protected boolean checkRetCode(ProcessSpec proc, int code)
33 throws InterruptedException, ExecutionException, IOException {
39 34 if (getLogger().isInfoEnabled()) {
40 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
41 Utils.redirectIO(proc.getErrorStream(), getLogger()::info);
35 proc.redirectStdout(RedirectTo.consumer(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 45 if (getLogger().isInfoEnabled())
49 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
46 proc.redirectStdout(RedirectTo.consumer(getLogger()::info));
50 47
51 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 54 if (code != 0) {
56 55 getLogger().error("The process exited with code {}", code);
57 56 throw new IOException("The process exited with error code " + code);
58 57 }
59 58 }
60 59
61 protected ProcessBuilder builder(String... args) {
62 var argsList = new ArrayList<String>(args.length + 1);
63 Arrays.stream(args).forEach(argsList::add);
64
65 return builder(argsList);
66 }
60 protected ProcessSpec builder(String... args) {
61 var spec = new ProcessSpec().args(getCliCmd()).args(args);
67 62
68 protected ProcessBuilder builder(List<String> args) {
69 var command = new ArrayList<String>(args.size() + 1);
63 getWorkingDir().ifPresent(spec::directory);
70 64
71 command.add(getCliCmd());
72 args.forEach(command::add);
73
74 var builder = new ProcessBuilder(command);
75
76 getWorkingDir().ifPresent(builder::directory);
77 return builder;
65 return spec;
78 66 }
79 67
80 68 public void buildImage(String imageName, File contextDirectory, List<String> options)
81 throws IOException, InterruptedException {
82 var args = new ArrayList<String>();
83 args.add(BUILD_COMMAND);
84 args.addAll(options);
85 args.add("-t");
86 args.add(imageName);
87 args.add(contextDirectory.getAbsolutePath());
88 complete(startProcess(builder(args)));
69 throws IOException, InterruptedException, ExecutionException {
70 var spec = builder(BUILD_COMMAND)
71 .args(options)
72 .args("-t", imageName, contextDirectory.getAbsolutePath());
73
74 exec(spec);
89 75 }
90 76
91 public void pushImage(String image, List<String> options) throws InterruptedException, IOException {
92 var args = new ArrayList<String>();
93 args.add(PUSH_COMMAND);
94 args.addAll(options);
95 args.add(image);
96 complete(startProcess(builder(args)));
77 public void pushImage(String image, List<String> options)
78 throws InterruptedException, IOException, ExecutionException {
79 var spec = builder(PUSH_COMMAND)
80 .args(options)
81 .args(image);
82
83 exec(spec);
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 87 throws InterruptedException, IOException {
101 var args = new ArrayList<String>();
102 args.add(RUN_COMMAND);
103 args.addAll(options);
104 args.add(image);
105 args.addAll(command);
106 complete(startProcess(builder(args)));
88 return builder(RUN_COMMAND)
89 .args(options)
90 .args(image)
91 .args(command);
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 100 if (output.exists())
111 101 output.delete();
112 102
113 var args = new ArrayList<String>();
114 args.add(SAVE_COMMAND);
115 args.add("-o");
116 args.add(output.getAbsolutePath());
117 images.forEach(args::add);
103 var spec = builder(SAVE_COMMAND)
104 .args("-o", output.getAbsolutePath())
105 .args(images);
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 {
123 complete(startProcess(builder(TAG_COMMAND, source, target)));
110 public void tagImage(String source, String target) throws InterruptedException, IOException, ExecutionException {
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 115 getLogger().info("Check image {} exists", imageId);
128 116
129 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 119 0);
132 120 }
133 121
@@ -136,7 +124,7 public abstract class DockerTraits {
136 124 try {
137 125 var imageId = Utils.readImageRef(imageIdFile);
138 126 return imageExists(imageId.getId());
139 } catch (IOException | InterruptedException e) {
127 } catch (IOException | InterruptedException | ExecutionException e) {
140 128 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
141 129 return false;
142 130 }
@@ -144,43 +132,31 public abstract class DockerTraits {
144 132 return false;
145 133 }
146 134
147 List<String> composeArgs(File primaryCompose, Set<String> profiles, String... extra) {
148 var args = new ArrayList<String>();
135 ProcessSpec compose(File primaryCompose, Set<String> profiles, String... extra) {
136 var spec = builder(COMPOSE_COMMAND, "--file", primaryCompose.getAbsolutePath());
149 137
150 args.add(COMPOSE_COMMAND);
151 args.add("--file");
152 args.add(primaryCompose.getAbsolutePath());
138 for (var profile : profiles)
139 spec.args("--profile", profile);
153 140
154 if (profiles.size() > 0) {
155 for (var profile : profiles) {
156 args.add("--profile");
157 args.add(profile);
158 }
141 spec.args(extra);
142
143 return spec;
159 144 }
160 145
161 args.addAll(List.of(extra));
162
163 return args;
146 public void composeUp(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException, ExecutionException {
147 exec(compose(primaryCompose, profiles, UP_COMMAND, "--detach"));
164 148 }
165 149
166 public void composeUp(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException {
167 var args = composeArgs(primaryCompose, profiles, UP_COMMAND, "--detach");
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)));
150 public void composeStop(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException, ExecutionException {
151 exec(compose(primaryCompose, profiles, STOP_COMMAND));
175 152 }
176 153
177 154 public void composeRm(File primaryCompose, Set<String> profiles, boolean removeVolumes)
178 throws InterruptedException, IOException {
179 var args = composeArgs(primaryCompose, profiles, RM_COMMAND, "--force", "--stop");
180
155 throws InterruptedException, IOException, ExecutionException {
156 var spec = compose(primaryCompose, profiles, RM_COMMAND, "--force", "--stop");
181 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 11 import java.io.OutputStream;
12 12 import java.io.Reader;
13 13 import java.util.Scanner;
14 import java.util.Set;
15 import java.util.concurrent.CompletableFuture;
16 import java.util.stream.Collectors;
14 17 import java.util.stream.StreamSupport;
15 18 import java.nio.file.Files;
16 19 import java.util.List;
@@ -29,24 +32,34 import groovy.json.JsonGenerator.Convert
29 32 import groovy.lang.Closure;
30 33
31 34 public final class Utils {
32 public static void redirectIO(final InputStream src, final Action<String> consumer) {
33 new Thread(() -> {
35 public static CompletableFuture<Void> redirectIO(final InputStream src, final Action<String> consumer) {
36 return CompletableFuture.runAsync(() -> {
34 37 try (Scanner sc = new Scanner(src)) {
35 38 while (sc.hasNextLine()) {
36 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) {
43 new Thread(() -> {
45 public static CompletableFuture<Void> redirectIO(final InputStream src, final File file) {
46 return CompletableFuture.runAsync(() -> {
44 47 try (OutputStream out = new FileOutputStream(file)) {
45 48 src.transferTo(out);
46 49 } catch (Exception e) {
47 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 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 156 } No newline at end of file
@@ -43,7 +43,7 public abstract class BuildImage extends
43 43 public abstract Property<String> getBuildTarget();
44 44
45 45 @Input
46 public abstract Property<String> getImageName();
46 public abstract Property<Object> getImageName();
47 47
48 48 @Internal
49 49 public abstract RegularFileProperty getImageIdFile();
@@ -101,7 +101,7 public abstract class BuildImage extends
101 101 // add extra parameters
102 102 getOptions().get().forEach(args::add);
103 103
104 var imageTag = getImageName().get();
104 var imageTag = getImageName().map(Object::toString).get();
105 105
106 106 // build image
107 107 docker().buildImage(
@@ -1,12 +1,13
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.io.IOException;
4 import java.util.concurrent.ExecutionException;
4 5
5 6 import org.gradle.api.provider.Property;
6 7 import org.gradle.api.tasks.Internal;
7 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 12 @Internal
12 13 public abstract Property<Boolean> getRemoveVolumes();
@@ -16,7 +17,7 public abstract class ComposeRm extends
16 17 }
17 18
18 19 @TaskAction
19 public void run() throws InterruptedException, IOException {
20 public void run() throws InterruptedException, IOException, ExecutionException {
20 21 docker().composeRm(getComposeFile().get().getAsFile(), getProfiles().get(), getRemoveVolumes().get());
21 22 }
22 23 }
@@ -1,13 +1,14
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.io.IOException;
4 import java.util.concurrent.ExecutionException;
4 5
5 6 import org.gradle.api.tasks.TaskAction;
6 7
7 8 public abstract class ComposeStop extends ComposeTask {
8 9
9 10 @TaskAction
10 public void run() throws InterruptedException, IOException {
11 public void run() throws InterruptedException, IOException, ExecutionException {
11 12 docker().composeStop(getComposeFile().getAsFile().get(), getProfiles().get());
12 13 }
13 14 } No newline at end of file
@@ -1,13 +1,14
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.io.IOException;
4 import java.util.concurrent.ExecutionException;
4 5
5 6 import org.gradle.api.tasks.TaskAction;
6 7
7 8 public abstract class ComposeUp extends ComposeTask {
8 9
9 10 @TaskAction
10 public void run() throws InterruptedException, IOException {
11 public void run() throws InterruptedException, IOException, ExecutionException {
11 12 docker().composeUp(getComposeFile().getAsFile().get(), getProfiles().get());
12 13 }
13 14 }
@@ -11,6 +11,9 import org.gradle.api.tasks.Internal;
11 11 import org.implab.gradle.containers.ContainerExtension;
12 12 import org.implab.gradle.containers.PropertiesMixin;
13 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 18 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin {
16 19
@@ -34,10 +37,35 public abstract class DockerCliTask exte
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 60 protected DockerTraits docker() {
38 61 return new TaskDockerTraits();
39 62 }
40 63
64 protected void exec(ProcessSpec spec) {
65
66 getLogger().info("Starting: {}", spec.command());
67 }
68
41 69 class TaskDockerTraits extends DockerTraits {
42 70
43 71 @Override
@@ -1,6 +1,7
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.io.IOException;
4 import java.util.concurrent.ExecutionException;
4 5
5 6 import org.gradle.api.provider.ListProperty;
6 7 import org.gradle.api.provider.Property;
@@ -12,21 +13,21 import org.implab.gradle.containers.dsl.
12 13 public abstract class PushImage extends DockerCliTask implements OptionsMixin {
13 14
14 15 @Input
15 public abstract Property<String> getImageName();
16 public abstract Property<Object> getImageName();
16 17
17 18 @Input
18 19 @Optional
19 public abstract Property<String> getTransport();
20 public abstract Property<Object> getTransport();
20 21
21 22 @Input
22 23 @Optional
23 24 public abstract ListProperty<String> getOptions();
24 25
25 26 @TaskAction
26 public void run() throws InterruptedException, IOException {
27 public void run() throws InterruptedException, IOException, ExecutionException {
27 28
28 29 docker().pushImage(
29 getImageName().get(),
30 getImageName().map(Object::toString).get(),
30 31 getOptions().get());
31 32 }
32 33 }
@@ -1,24 +1,94
1 1 package org.implab.gradle.containers.tasks;
2 2
3 import java.io.File;
3 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 10 import org.gradle.api.Action;
11 import org.gradle.api.file.RegularFileProperty;
5 12 import org.gradle.api.provider.ListProperty;
6 13 import org.gradle.api.provider.Property;
7 import org.gradle.api.tasks.Input;
8 import org.gradle.api.tasks.Optional;
14 import org.gradle.api.tasks.Internal;
9 15 import org.gradle.api.tasks.TaskAction;
16 import org.implab.gradle.containers.cli.RedirectFrom;
17 import org.implab.gradle.containers.cli.RedirectTo;
10 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 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
16 public abstract Property<String> getImageName();
31 private boolean transientContainer = true;
32
33 private boolean detached = false;
17 34
18 @Input
19 @Optional
35 private boolean interactive = false;
36
37 @Internal
38 public abstract Property<Object> getImageName();
39
40 @Internal
20 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 93 public void volume(Action<VolumeSpec> configure) {
24 94 getOptions().add("-v");
@@ -33,12 +103,57 public abstract class RunImage extends D
33 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 121 @TaskAction
37 public void run() throws InterruptedException, IOException {
38 docker().runImage(
39 getImageName().get(),
40 getOptions().get(),
122 public void run() throws InterruptedException, IOException, ExecutionException {
123 var params = new ArrayList<>(getOptions().get());
124
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 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 4 import java.io.IOException;
5 5 import java.util.ArrayList;
6 6 import java.util.Optional;
7 import java.util.concurrent.ExecutionException;
7 8
8 9 import org.gradle.api.file.DirectoryProperty;
9 10 import org.gradle.api.file.FileCollection;
@@ -21,7 +22,7 import org.implab.gradle.containers.cli.
21 22 public abstract class SaveImage extends DockerCliTask {
22 23
23 24 @Input
24 public abstract SetProperty<String> getExportImages();
25 public abstract SetProperty<Object> getExportImages();
25 26
26 27 @OutputFile
27 28 public Provider<RegularFile> getArchiveFile() {
@@ -103,9 +104,9 public abstract class SaveImage extends
103 104 }
104 105
105 106 @TaskAction
106 public void run() throws InterruptedException, IOException {
107 public void run() throws InterruptedException, IOException, ExecutionException {
107 108 docker().saveImage(
108 getExportImages().get(),
109 getExportImages().map(Utils::mapToString).get(),
109 110 getArchiveFile().map(RegularFile::getAsFile).get());
110 111 }
111 112 }
@@ -1,28 +1,29
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.io.IOException;
4 import java.util.HashSet;
5 4 import java.util.Set;
5 import java.util.concurrent.ExecutionException;
6 6
7 7 import org.gradle.api.provider.Property;
8 8 import org.gradle.api.provider.SetProperty;
9 9 import org.gradle.api.tasks.Input;
10 10 import org.gradle.api.tasks.TaskAction;
11 import org.implab.gradle.containers.cli.Utils;
11 12
12 13 public abstract class TagImage extends DockerCliTask {
13 14 @Input
14 public abstract Property<String> getSrcImage();
15 public abstract Property<Object> getSrcImage();
15 16
16 17 @Input
17 public abstract SetProperty<String> getTags();
18 public abstract SetProperty<Object> getTags();
18 19
19 20 @Input
20 21 @Deprecated
21 public abstract Property<String> getDestImage();
22 public abstract Property<Object> getDestImage();
22 23
23 24 private Set<String> getImageTags() {
24 var tags = new HashSet<>(getTags().get());
25 tags.add(getDestImage().get());
25 var tags = getTags().map(Utils::mapToString).get();
26 tags.add(getDestImage().map(Object::toString).get());
26 27 return tags;
27 28 }
28 29
@@ -31,9 +32,9 public abstract class TagImage extends D
31 32 }
32 33
33 34 @TaskAction
34 public void run() throws InterruptedException, IOException {
35 public void run() throws InterruptedException, IOException, ExecutionException {
35 36 var tags = getImageTags();
36 var src = getSrcImage().get();
37 var src = getSrcImage().map(Object::toString).get();
37 38
38 39 if (tags.size() == 0)
39 40 getLogger().info("No tags were specified");
General Comments 0
You need to be logged in to leave comments. Login now