##// END OF EJS Templates
moving docker commands to DockerTraits, refacotring
cin -
r9:04caf9830434 default
parent child
Show More
@@ -0,0 +1,67
1 package org.implab.gradle.containers.cli;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.nio.file.Files;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.List;
9 import java.util.Optional;
10 import org.gradle.api.logging.Logger;
11
12 public abstract class DockerTraits {
13
14 public abstract Logger getLogger();
15
16 public abstract Optional<File> getWorkingDir();
17
18 public abstract String getCliCmd();
19
20 boolean execute(ProcessBuilder builder, int code) throws IOException, InterruptedException {
21 var proc = builder.start();
22 return proc.waitFor() == code;
23 }
24
25 protected ProcessBuilder builder(String... args) {
26 var command = new ArrayList<String>(args.length + 1);
27 command.add(getCliCmd());
28 Arrays.stream(args).forEach(command::add);
29
30 var builder = new ProcessBuilder(command);
31
32 getWorkingDir().ifPresent(builder::directory);
33 return builder();
34 }
35
36 protected ProcessBuilder builder(List<String> command) {
37 var builder = new ProcessBuilder(command);
38
39 getWorkingDir().ifPresent(builder::directory);
40 return builder();
41 }
42
43 public void build(List<String> args) {
44
45 }
46
47 public boolean imageExists(String imageId) throws InterruptedException, IOException {
48 getLogger().info("Check image {} exists", imageId);
49
50 var builder = builder("image", "inspect", "--format", "image-exists", imageId);
51
52 return execute(builder, 0);
53 }
54
55 public boolean imageExists(File imageIdFile) {
56 if (imageIdFile.exists()) {
57 try {
58 var imageId = Files.readString(imageIdFile.toPath());
59 return imageExists(imageId);
60 } catch (IOException | InterruptedException e) {
61 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
62 return false;
63 }
64 }
65 return false;
66 }
67 }
@@ -0,0 +1,93
1 package org.implab.gradle.containers.cli;
2
3 import java.io.ByteArrayOutputStream;
4 import java.io.Closeable;
5 import java.io.File;
6 import java.io.FileInputStream;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.OutputStream;
11 import java.nio.file.Files;
12 import java.util.Scanner;
13
14 import org.gradle.api.Action;
15
16 import groovy.json.JsonGenerator;
17 import groovy.json.JsonOutput;
18 import groovy.json.JsonGenerator.Converter;
19 import groovy.lang.Closure;
20
21 public final class Utils {
22 public static void redirectIO(final InputStream src, final Action<String> consumer) {
23 new Thread(() -> {
24 try (Scanner sc = new Scanner(src)) {
25 while (sc.hasNextLine()) {
26 consumer.execute(sc.nextLine());
27 }
28 }
29 }).start();
30 }
31
32 public static void redirectIO(final InputStream src, final File file) {
33 new Thread(() -> {
34 try (OutputStream out = new FileOutputStream(file)) {
35 src.transferTo(out);
36 } catch (Exception e) {
37 // silence!
38 }
39 }).start();
40 }
41
42 public static void closeSilent(Closeable handle) {
43 try {
44 handle.close();
45 } catch (Exception e) {
46 // silence!
47 }
48 }
49
50 public static String readAll(final InputStream src) throws IOException {
51 ByteArrayOutputStream out = new ByteArrayOutputStream();
52 src.transferTo(out);
53 return out.toString();
54 }
55
56 public static String readAll(final InputStream src, String charset) throws IOException {
57 ByteArrayOutputStream out = new ByteArrayOutputStream();
58 src.transferTo(out);
59 return out.toString(charset);
60 }
61
62 public static JsonGenerator createDefaultJsonGenerator() {
63 return new JsonGenerator.Options()
64 .excludeNulls()
65 .addConverter(new Converter() {
66 public boolean handles(Class<?> type) {
67 return (File.class == type);
68 }
69
70 public Object convert(Object value, String key) {
71 return ((File) value).getPath();
72 }
73 })
74 .build();
75 }
76
77 public static String toJsonPretty(Object value) {
78 return JsonOutput.prettyPrint(createDefaultJsonGenerator().toJson(value));
79 }
80
81 public static boolean isNullOrEmptyString(String value) {
82 return (value == null || value.length() == 0);
83 }
84
85 public static <T> Action<T> wrapClosure(Closure<?> closure) {
86 return x -> {
87 closure.setDelegate(x);
88 closure.setResolveStrategy(Closure.DELEGATE_FIRST);
89 closure.call(x);
90 };
91 }
92
93 } No newline at end of file
@@ -0,0 +1,35
1 package org.implab.gradle.containers.dsl;
2
3 import java.util.concurrent.Callable;
4 import org.gradle.api.provider.MapProperty;
5 import org.gradle.api.provider.Provider;
6 import org.gradle.api.provider.ProviderFactory;
7
8 public class MapPropertyEntry<K,V> {
9
10 private final MapProperty<K,V> map;
11
12 private final K key;
13
14 private final ProviderFactory providerFactory;
15
16 public MapPropertyEntry(MapProperty<K,V> map, K key, ProviderFactory providerFactory) {
17 this.map = map;
18 this.key = key;
19 this.providerFactory = providerFactory;
20 }
21
22 void put(Callable<V> value) {
23 map.put(key, providerFactory.provider(value));
24 }
25
26 void put(V value) {
27 map.put(key, value);
28 }
29
30 void put(Provider<V> value) {
31 map.put(key, value);
32 }
33
34
35 }
@@ -1,57 +1,66
1 1 package org.implab.gradle.containers;
2 2
3 3 import java.io.File;
4 import java.io.IOException;
4 5 import java.util.ArrayList;
5 6 import java.util.List;
6 7 import java.util.Optional;
7 8
8 9 import org.gradle.api.logging.Logger;
10 import org.gradle.api.tasks.Internal;
11 import org.implab.gradle.containers.cli.Utils;
9 12
10 13 public interface ExecuteMixin {
11 14
15 @Internal
12 16 Logger getLogger();
13 17
18 @Internal
14 19 Optional<File> getWorkingDir();
15 20
16 default void Execute() throws Exception {
21 default void Execute() throws IOException, InterruptedException {
17 22 final Logger log = getLogger();
18 23
19 24 List<String> command = new ArrayList<>();
20 25
21 26 preparingCommand(command);
22 27
23 28 ProcessBuilder builder = new ProcessBuilder(command);
24 29 getWorkingDir().ifPresent(workingDir -> builder.directory(workingDir));
25 30
26 31 log.info("Starting: {}", builder.command());
27 32
28 33 Process p = builder.start();
29 34
30 35 processStarted(p);
31 36
32 37 if (log.isErrorEnabled()) {
33 38 Utils.redirectIO(p.getErrorStream(), log::error);
34 39 }
35 40
36 41 int code = p.waitFor();
37 if (code != 0)
38 throw new Exception("Process exited with the error: " + code);
42 if (code != 0) {
43 log.error("Process: {}", builder.command());
44 log.error("Process exited with code {}", code);
45
46 throw new IOException("Process exited with code: " + code);
47 }
39 48 }
40 49
41 50 void preparingCommand(List<String> command);
42 51
43 52
44 53 /**
45 54 * Invoked after the process is started, can be used to handle the process
46 55 * output.
47 56 *
48 57 * Default implementation redirects output to the logger as INFO messages.
49 58 *
50 59 * @param p The current process.
51 60 */
52 61
53 62 default void processStarted(Process p) {
54 63 final Logger log = getLogger();
55 64 Utils.redirectIO(p.getInputStream(), log::info);
56 65 }
57 66 }
@@ -1,96 +1,142
1 1 package org.implab.gradle.containers.tasks;
2 2
3 import java.io.FileInputStream;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.nio.file.Files;
3 7 import java.util.ArrayList;
4 8 import java.util.Collection;
5 9 import java.util.Collections;
6 10 import java.util.HashMap;
7 11 import java.util.List;
8 12 import java.util.Map;
9 13 import java.util.Optional;
10 14
15 import org.apache.tools.ant.taskdefs.UpToDate;
11 16 import org.gradle.api.Action;
12 17 import org.gradle.api.file.DirectoryProperty;
18 import org.gradle.api.file.RegularFile;
13 19 import org.gradle.api.file.RegularFileProperty;
14 20 import org.gradle.api.provider.ListProperty;
15 21 import org.gradle.api.provider.MapProperty;
16 22 import org.gradle.api.provider.Property;
17 import org.gradle.api.provider.Provider;
18 23 import org.gradle.api.tasks.Input;
19 24 import org.gradle.api.tasks.InputDirectory;
20 25 import org.gradle.api.tasks.OutputFile;
21 26 import org.gradle.api.tasks.SkipWhenEmpty;
27 import org.gradle.api.tasks.TaskAction;
22 28 import org.implab.gradle.containers.ImageName;
23 import org.implab.gradle.containers.Utils;
29 import org.implab.gradle.containers.cli.Utils;
30 import org.implab.gradle.containers.dsl.MapPropertyEntry;
24 31
25 32 import groovy.lang.Closure;
26 33
27 34 public abstract class BuildImage extends DockerCliTask {
28 35
29 36 public final String BUILD_COMMAND = "build";
30 37
31 38 @InputDirectory
32 39 @SkipWhenEmpty
33 40 public abstract DirectoryProperty getContextDirectory();
34 41
35 42 @Input
36 43 public abstract MapProperty<String, String> getBuildArgs();
37 44
38 45 @Input
39 46 public abstract ListProperty<String> getExtraCommandArgs();
40 47
41 48 @Input
49 @org.gradle.api.tasks.Optional
42 50 public abstract Property<String> getBuildTarget();
43 51
44 52 @Input
45 53 public abstract Property<ImageName> getImageName();
46 54
47 55 @OutputFile
48 56 public abstract RegularFileProperty getImageIdFile();
49 57
58 protected BuildImage() {
59 getOutputs().upToDateWhen(task -> getImageIdFile()
60 .map(RegularFile::getAsFile)
61 .map(docker()::imageExists)
62 .getOrElse(false));
63 }
64
50 65 public void buildArgs(Action<Map<String, String>> spec) {
51 66 getBuildArgs().putAll(provider(() -> {
52 67 Map<String, String> args = new HashMap<>();
53 68 spec.execute(args);
54 69 return args;
55 70 }));
56 71 }
57 72
58 73 public void buildArgs(Closure<Map<String, String>> spec) {
59 74 buildArgs(Utils.wrapClosure(spec));
60 75 }
61 76
77 public MapPropertyEntry<String, String> buildArg(String key) {
78 return new MapPropertyEntry<String, String>(getBuildArgs(), key, getProviders());
79 }
80
81 @TaskAction
82 public void Run() throws Exception {
83 List<String> args = new ArrayList<>();
84
85 args.addAll(List.of(
86 "-t", getImageName().get().toString(),
87 "--iidfile", getImageIdFile().getAsFile().get().toString()));
88
89 getBuildArgs().get().forEach((k, v) -> {
90 args.add("--build-arg");
91 args.add(String.format("%s=%s", k, v));
92 });
93
94 // add --target if specified for multi-stage build
95 if (getBuildTarget().isPresent()) {
96 args.add("--target");
97 args.add(getBuildTarget().get());
98 }
99
100 // add extra parameters
101 getExtraCommandArgs().getOrElse(Collections.emptyList())
102 .forEach(args::add);
103
104 args.add(getContextDirectory().getAsFile().get().toString());
105
106 docker().build(args);
107 }
108
109
62 110 @Override
63 111 protected Optional<String> getSubCommand() {
64 112 return Optional.of(BUILD_COMMAND);
65 113 }
66 114
67 115 @Override
68 116 protected Collection<String> getSubCommandArguments() {
69 117 List<String> args = new ArrayList<>();
70 118
71 119 args.addAll(List.of(
72 120 "-t", getImageName().get().toString(),
73 121 "--iidfile", getImageIdFile().getAsFile().get().toString()));
74 122
75 if (imageBuildArgs.isPresent()) {
76 imageBuildArgs.get().forEach((k, v) -> {
77 args.add("--build-arg");
78 args.add(String.format("%s=%s", k, v));
79 });
80 }
123 getBuildArgs().get().forEach((k, v) -> {
124 args.add("--build-arg");
125 args.add(String.format("%s=%s", k, v));
126 });
81 127
82 128 // add --target if specified for multi-stage build
83 129 if (getBuildTarget().isPresent()) {
84 130 args.add("--target");
85 131 args.add(getBuildTarget().get());
86 132 }
87 133
88 134 // add extra parameters
89 135 getExtraCommandArgs().getOrElse(Collections.emptyList())
90 136 .forEach(args::add);
91 137
92 138 args.add(getContextDirectory().getAsFile().get().toString());
93 139
94 140 return args;
95 141 }
96 142 }
@@ -1,81 +1,110
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.io.File;
4 4 import java.util.Collection;
5 5 import java.util.Collections;
6 6 import java.util.List;
7 7 import java.util.Optional;
8 8
9 9 import org.gradle.api.DefaultTask;
10 import org.gradle.api.file.Directory;
10 11 import org.gradle.api.file.DirectoryProperty;
12 import org.gradle.api.logging.Logger;
11 13 import org.gradle.api.provider.Property;
12 14 import org.gradle.api.provider.Provider;
13 15 import org.gradle.api.tasks.Input;
14 16 import org.gradle.api.tasks.Internal;
15 17 import org.gradle.api.tasks.TaskAction;
16 18 import org.implab.gradle.containers.ContainerExtension;
17 19 import org.implab.gradle.containers.ExecuteMixin;
18 20 import org.implab.gradle.containers.PropertiesMixin;
21 import org.implab.gradle.containers.cli.DockerTraits;
19 22
20 23 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin, ExecuteMixin {
21 24
22 25 private final Property<String> cliCmd;
23 26
24 27 private final DirectoryProperty workingDir;
25 28
26 29 public DockerCliTask() {
27 30 cliCmd = property(String.class)
28 31 .convention(getContainerExtension().getCliCmd());
29 32
30 33 workingDir = directoryProperty();
31 34 }
32 35
33 36 @Internal
34 37 protected Optional<String> getSubCommand() {
35 38 return Optional.empty();
36 39 }
37 40
38 41 @Internal
39 42 protected Collection<String> getSubCommandArguments() {
40 43 return Collections.emptyList();
41 44 }
42 45
43 46 @Input
44 47 public Property<String> getCliCmd() {
45 48 return cliCmd;
46 49 }
47 50
48 51 public void setCliCmd(String cliCmd) {
49 52 this.cliCmd.set(cliCmd);
50 53 }
51 54
52 55 @Internal
53 56 protected ContainerExtension getContainerExtension() {
54 57 return getProject().getExtensions().getByType(ContainerExtension.class);
55 58 }
56 59
57 60 @TaskAction
58 61 public void Run() throws Exception {
59 62 Execute();
60 63 }
61 64
62 65 public void preparingCommand(List<String> commandLine) {
63 66 commandLine.add(cliCmd.get());
64 67 getSubCommand().ifPresent(commandLine::add);
65 68 getSubCommandArguments().forEach(commandLine::add);
66 69 }
67 70
68 71 @Override
69 72 @Internal
70 73 public Optional<File> getWorkingDir() {
71 return Optional.ofNullable(workingDir.get().getAsFile());
74 return workingDir
75 .map(Directory::getAsFile)
76 .map(Optional::of)
77 .getOrElse(Optional.empty());
72 78 }
73 79
74 80 protected void setWorkingDir(Provider<File> workingDir) {
75 81 this.workingDir.fileProvider(workingDir);
76 82 }
77 83
78 84 protected void setWorkingDir(File workingDir) {
79 85 this.workingDir.set(workingDir);
80 86 }
87
88 protected DockerTraits docker() {
89 return new TaskDockerTraits();
90 }
91
92 class TaskDockerTraits extends DockerTraits {
93
94 @Override
95 public Logger getLogger() {
96 return DockerCliTask.this.getLogger();
97 }
98
99 @Override
100 public Optional<File> getWorkingDir() {
101 return DockerCliTask.this.getWorkingDir();
102 }
103
104 @Override
105 public String getCliCmd() {
106 return cliCmd.get();
107 }
108
109 }
81 110 }
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now