##// 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,19 +1,24
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<>();
@@ -34,8 +39,12 public interface ExecuteMixin {
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);
@@ -1,5 +1,9
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;
@@ -8,19 +12,22 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
@@ -39,6 +46,7 public abstract class BuildImage extends
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
@@ -47,6 +55,13 public abstract class BuildImage extends
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<>();
@@ -59,6 +74,39 public abstract class BuildImage extends
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);
@@ -72,12 +120,10 public abstract class BuildImage extends
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()) {
@@ -7,7 +7,9 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;
@@ -16,6 +18,7 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
@@ -68,7 +71,10 public abstract class DockerCliTask exte
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) {
@@ -78,4 +84,27 public abstract class DockerCliTask exte
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