##// END OF EJS Templates
Implemented compose
cin -
r12:c7d470187afa v1.2.0 default
parent child
Show More
@@ -0,0 +1,17
1 package org.implab.gradle.containers;
2
3 import org.gradle.api.provider.Property;
4 import org.gradle.api.provider.SetProperty;
5
6 public abstract class ComposeExtension {
7 public final String COMPOSE_FILE = "compose.yaml";
8
9 public abstract SetProperty<String> getProfiles();
10
11 public abstract Property<String> getComposeFileName();
12
13 public ComposeExtension() {
14 getComposeFileName().convention(COMPOSE_FILE);
15 }
16
17 }
@@ -0,0 +1,159
1 package org.implab.gradle.containers;
2
3 import java.io.IOException;
4 import java.util.HashMap;
5 import java.util.Map;
6
7 import org.gradle.api.DefaultTask;
8 import org.gradle.api.Plugin;
9 import org.gradle.api.Project;
10 import org.gradle.api.artifacts.Configuration;
11 import org.gradle.api.artifacts.Configuration.State;
12 import org.gradle.api.logging.Logger;
13 import org.gradle.api.tasks.Copy;
14 import org.gradle.api.tasks.Delete;
15 import org.implab.gradle.containers.cli.Utils;
16 import org.implab.gradle.containers.tasks.ComposeRm;
17 import org.implab.gradle.containers.tasks.ComposeStop;
18 import org.implab.gradle.containers.tasks.ComposeUp;
19 import org.implab.gradle.containers.tasks.WriteEnv;
20
21 public abstract class ComposePlugin implements Plugin<Project>, ProjectMixin {
22 public final String COMPOSE_IMAGES_CONFIGURATION = "composeImages";
23
24 public final String COMPOSE_EXTENSION = "compose";
25
26 public final String COMPOSE_UP_TASK = "up";
27
28 public final String COMPOSE_STOP_TASK = "stop";
29
30 public final String COMPOSE_RM_TASK = "rm";
31
32 public final String CLEAN_TASK = "clean";
33
34 public final String BUILD_TASK = "build";
35
36 public final String PROCESS_RESOURCES_TASK = "processResources";
37
38 public final String WRITE_ENV_TASK = "writeEnv";
39
40 public final String COMPOSE_VAR = "composeVar";
41
42 public final String ENV_FILE_NAME = ".env";
43
44 public Logger getLogger() {
45 return getProject().getLogger();
46 }
47
48 @Override
49 public void apply(Project project) {
50 var containerImages = configuration(COMPOSE_IMAGES_CONFIGURATION, Configurations.RESOLVABLE);
51
52 // basic configuration, register extension
53 var basePlugin = plugin(ContainerBasePlugin.class);
54 var containerExtension = basePlugin.getContainerExtension();
55
56 var composeExtension = extension(COMPOSE_EXTENSION, ComposeExtension.class);
57
58 var composeFile = containerExtension.getContextDirectory()
59 .file(composeExtension.getComposeFileName());
60 var composeProfiles = composeExtension.getProfiles();
61
62 var cleanTask = task(CLEAN_TASK, Delete.class, t -> {
63 t.delete(containerExtension.getContextDirectory());
64 });
65
66 // copy task from src/main
67 var processResources = task(PROCESS_RESOURCES_TASK, Copy.class, t -> {
68 t.mustRunAfter(cleanTask);
69 t.from(projectDirectory().dir("src/main"));
70 t.into(containerExtension.getContextDirectory());
71 });
72
73 // write .env
74 var writeEnvTask = task(WRITE_ENV_TASK, WriteEnv.class, t -> {
75 t.dependsOn(processResources, containerImages);
76 t.getEnvFile().set(containerExtension.getContextDirectory().file(ENV_FILE_NAME));
77
78 t.getEnvironment().putAll(containerImages.map(this::extractComposeEnv));
79
80 });
81
82 var buildTask = task(BUILD_TASK, DefaultTask.class, t -> {
83 t.dependsOn(writeEnvTask);
84 });
85
86 var stopTask = task(COMPOSE_STOP_TASK, ComposeStop.class, t -> {
87 // stop must run after build
88 t.mustRunAfter(buildTask);
89
90 t.getProfiles().addAll(composeProfiles);
91 t.getComposeFile().set(composeFile);
92 });
93
94 var rmTask = task(COMPOSE_RM_TASK, ComposeRm.class, t -> {
95 // rm must run after build and stop
96 t.mustRunAfter(buildTask, stopTask);
97
98 t.getProfiles().addAll(composeProfiles);
99 t.getComposeFile().set(composeFile);
100 });
101
102 task(COMPOSE_UP_TASK, ComposeUp.class, t -> {
103 t.dependsOn(buildTask);
104 // up must run after stop and rm
105 t.mustRunAfter(stopTask, rmTask);
106
107 t.getProfiles().addAll(composeProfiles);
108 t.getComposeFile().set(composeFile);
109 });
110 }
111
112 /**
113 * Processed the configurations, extracts composeVar extra property from
114 * each dependency in this configuration and adds a value to the resulting
115 * map. The values in the nap will contain image tags.
116 */
117 private Map<String, String> extractComposeEnv(Configuration config) {
118 if (config.getState() != State.UNRESOLVED) {
119 getLogger().error("extractComposeEnv: The configuration {} isn't resolved.", config.getName());
120 throw new IllegalStateException("The specified configuration isn't resolved");
121 }
122
123 getLogger().info("extractComposeEnv {}", config.getName());
124
125 var map = new HashMap<String, String>();
126
127 for (var dependency : config.getDependencies()) {
128 // get extra composeVar if present
129 extra(dependency, COMPOSE_VAR, String.class).optional().ifPresent(varName -> {
130 // if we have a composeVar extra attribute on this dependency
131
132 // get files for the dependency
133 var files = config.files(dependency);
134 if (files.size() == 1) {
135 // should bw exactly 1 file
136 var file = files.stream().findAny().get();
137 getLogger().info("Processing {}: {}", dependency, file);
138
139 try {
140 // try read imageRef
141 Utils.readImageRef(file).getTag()
142 .ifPresentOrElse(
143 tag -> map.put(varName, tag),
144 () -> getLogger().error("ImageRef doesn't have a tag: {}", file));
145
146 } catch (IOException e) {
147 getLogger().error("Failed to read ImageRef {}: {}", file, e);
148 }
149
150 } else {
151 getLogger().warn("Dependency {} must have exactly 1 file", dependency);
152 }
153 });
154 }
155
156 return map;
157 }
158
159 }
@@ -0,0 +1,24
1 package org.implab.gradle.containers;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.artifacts.Configuration;
5
6 public final class Configurations {
7
8 public static final Action<Configuration> RESOLVABLE = Configurations::resolvable;
9
10 public static final Action<Configuration> CONSUMABLE = Configurations::consumable;
11
12 private Configurations() {
13 }
14
15 private static void resolvable(Configuration configuration) {
16 configuration.setCanBeResolved(true);
17 configuration.setCanBeConsumed(false);
18 }
19
20 private static void consumable(Configuration configuration) {
21 configuration.setCanBeResolved(false);
22 configuration.setCanBeConsumed(true);
23 }
24 }
@@ -0,0 +1,69
1 package org.implab.gradle.containers;
2
3 import java.util.Collections;
4 import java.util.Optional;
5
6 import javax.inject.Inject;
7
8 import org.gradle.api.Action;
9 import org.gradle.api.NamedDomainObjectProvider;
10 import org.gradle.api.Plugin;
11 import org.gradle.api.Project;
12 import org.gradle.api.Task;
13 import org.gradle.api.artifacts.Configuration;
14 import org.gradle.api.file.Directory;
15 import org.gradle.api.tasks.TaskProvider;
16 import org.implab.gradle.containers.dsl.ExtraProps;
17 import org.implab.gradle.containers.dsl.MapEntry;
18
19 /** Project configuration traits */
20 public interface ProjectMixin {
21 @Inject
22 Project getProject();
23
24 /** registers the new task */
25 default <T extends Task> TaskProvider<T> task(String name, Class<T> clazz, Action<? super T> configure) {
26 return getProject().getTasks().register(name, clazz, configure);
27 }
28
29 /** Registers the new configuration */
30 default NamedDomainObjectProvider<Configuration> configuration(String name, Action<? super Configuration> configure) {
31 return getProject().getConfigurations().register(name, configure);
32 }
33
34 /** Returns the project directory */
35 default Directory projectDirectory() {
36 return getProject().getLayout().getProjectDirectory();
37 }
38
39 /** Applies and returns the specified plugin, plugin is applied only once. */
40 default <T extends Plugin<Project>> T plugin(Class<T> clazz) {
41 getProject().getPluginManager().apply(clazz);
42 return getProject().getPlugins().findPlugin(clazz);
43 }
44
45 /** Creates and register a new project extension.
46 *
47 * @param <T> The type of the extension
48 * @param extensionName The name of the extension in the project
49 * @param clazz The class of the extension
50 * @return the newly created extension
51 */
52 default <T> T extension(String extensionName, Class<T> clazz) {
53 T extension = getProject().getObjects().newInstance(clazz);
54 getProject().getExtensions().add(extensionName, extension);
55 return extension;
56 }
57
58 /** Return extra properties container for the specified object */
59 default Optional<ExtraProps> extra(Object target) {
60 return ExtraProps.extra(target);
61 }
62
63 /** Returns accessor for the specified extra property name */
64 default <T> MapEntry<T> extra(Object target, String prop, Class<T> clazz) {
65 return ExtraProps.extra(target)
66 .map(x -> x.prop(prop, clazz))
67 .orElseGet(() -> new MapEntry<T>(Collections.emptyMap(), prop, clazz));
68 }
69 }
@@ -0,0 +1,42
1 package org.implab.gradle.containers.dsl;
2
3 import java.util.Optional;
4
5 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
6 import org.gradle.api.plugins.ExtensionAware;
7 import org.gradle.api.plugins.ExtraPropertiesExtension;
8
9 public class ExtraProps {
10 private final ExtraPropertiesExtension extraProps;
11
12 protected ExtraProps(ExtraPropertiesExtension extraProps) {
13 this.extraProps = extraProps;
14 }
15
16 public <T> Optional<T> get(String key, Class<T> clazz) {
17 if (!extraProps.has(key))
18 return Optional.empty();
19
20 return Optional.ofNullable(DefaultGroovyMethods.asType(extraProps.get(key), clazz));
21 }
22
23 public <T> void put(String key, T value) {
24 extraProps.set(key, value);
25 }
26
27 public void delete(String key) {
28 extraProps.getProperties().remove(key);
29 }
30
31 public <T> MapEntry<T> prop(String key, Class<T> clazz) {
32 return new MapEntry<>(extraProps.getProperties(), key, clazz);
33 }
34
35 public static Optional<ExtraProps> extra(Object target) {
36 return target instanceof ExtensionAware
37 ? Optional.of(new ExtraProps(
38 ((ExtensionAware) target).getExtensions().getExtraProperties()))
39 : Optional.empty();
40
41 }
42 }
@@ -0,0 +1,39
1 package org.implab.gradle.containers.dsl;
2
3 import java.util.Map;
4 import java.util.Optional;
5
6 public class MapEntry<T> {
7 private final Map<String, Object> map;
8 private final String key;
9 private final Class<T> clazz;
10
11 public MapEntry(Map<String, Object> map, String key, Class<T> clazz) {
12 this.map = map;
13 this.key = key;
14 this.clazz = clazz;
15 }
16
17 public boolean has() {
18 var value = map.get(key);
19 return value != null;
20 }
21
22 public T get() {
23 if (!has())
24 throw new IllegalStateException();
25 return clazz.cast(map.get(key));
26 }
27
28 public void put(T value) {
29 map.put(key, value);
30 }
31
32 public void remove() {
33 map.remove(key);
34 }
35
36 public Optional<T> optional() {
37 return has() ? Optional.of(get()) : Optional.empty();
38 }
39 }
@@ -0,0 +1,22
1 package org.implab.gradle.containers.tasks;
2
3 import java.io.IOException;
4
5 import org.gradle.api.provider.Property;
6 import org.gradle.api.tasks.Internal;
7 import org.gradle.api.tasks.TaskAction;
8
9 public abstract class ComposeRm extends ComposeStop {
10
11 @Internal
12 public abstract Property<Boolean> getRemoveVolumes();
13
14 public ComposeRm() {
15 getRemoveVolumes().convention(true);
16 }
17
18 @TaskAction
19 public void run() throws InterruptedException, IOException {
20 docker().composeRm(getComposeFile().get().getAsFile(), getProfiles().get(), getRemoveVolumes().get());
21 }
22 }
@@ -0,0 +1,13
1 package org.implab.gradle.containers.tasks;
2
3 import java.io.IOException;
4
5 import org.gradle.api.tasks.TaskAction;
6
7 public abstract class ComposeStop extends ComposeTask {
8
9 @TaskAction
10 public void run() throws InterruptedException, IOException {
11 docker().composeStop(getComposeFile().getAsFile().get(), getProfiles().get());
12 }
13 } No newline at end of file
@@ -0,0 +1,33
1 package org.implab.gradle.containers.tasks;
2
3 import org.gradle.api.file.RegularFileProperty;
4 import org.gradle.api.provider.SetProperty;
5 import org.gradle.api.tasks.Internal;
6
7 /**
8 * Base task for compose subtasks like 'uo', 'rm', 'stop', etc.
9 */
10 public abstract class ComposeTask extends DockerCliTask {
11
12 /** The list of profiles to use with the compose commands */
13 @Internal
14 public abstract SetProperty<String> getProfiles();
15
16 /**
17 * The primary compose files. This task is executed regardless whether these
18 * files was changed.
19 */
20 @Internal
21 public abstract RegularFileProperty getComposeFile();
22
23 protected ComposeTask() {
24 // the task can only be evaluated if the compose file is present
25 setOnlyIf(self -> {
26 var composeFile = getComposeFile().get().getAsFile();
27 var exists = composeFile.exists();
28 getLogger().info("file: {} {}", composeFile.toString(), exists ? "exists" : "doesn't exist");
29 return exists;
30 });
31 }
32
33 }
@@ -0,0 +1,13
1 package org.implab.gradle.containers.tasks;
2
3 import java.io.IOException;
4
5 import org.gradle.api.tasks.TaskAction;
6
7 public abstract class ComposeUp extends ComposeTask {
8
9 @TaskAction
10 public void run() throws InterruptedException, IOException {
11 docker().composeUp(getComposeFile().getAsFile().get(), getProfiles().get());
12 }
13 }
@@ -0,0 +1,59
1 package org.implab.gradle.containers.tasks;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.DefaultTask;
5 import org.gradle.api.file.RegularFileProperty;
6 import org.gradle.api.provider.MapProperty;
7 import org.gradle.api.tasks.Input;
8 import org.gradle.api.tasks.OutputFile;
9 import org.gradle.api.tasks.TaskAction;
10 import org.implab.gradle.containers.PropertiesMixin;
11 import org.implab.gradle.containers.cli.Utils;
12 import org.implab.gradle.containers.dsl.MapPropertyEntry;
13
14 import java.io.FileWriter;
15 import java.io.IOException;
16 import java.util.HashMap;
17 import java.util.Map;
18
19 import groovy.lang.Closure;
20
21 public abstract class WriteEnv extends DefaultTask implements PropertiesMixin {
22
23 @Input
24 public abstract MapProperty<String, String> getEnvironment();
25
26 @OutputFile
27 public abstract RegularFileProperty getEnvFile();
28
29 public void env(Action<Map<String, String>> spec) {
30 getEnvironment().putAll(provider(() -> {
31 Map<String, String> args = new HashMap<>();
32 spec.execute(args);
33 getLogger().info("add buildArgs {}", args);
34 return args;
35 }));
36 }
37
38 public void env(Closure<Map<String, String>> spec) {
39 env(Utils.wrapClosure(spec));
40 }
41
42 public MapPropertyEntry<String, String> env(String key) {
43 return new MapPropertyEntry<String, String>(getEnvironment(), key, getProviders());
44 }
45
46 @TaskAction
47 public void run() throws IOException {
48 try (var writer = new FileWriter(getEnvFile().get().getAsFile())) {
49 for (var entry : getEnvironment().get().entrySet()) {
50 writer.write(entry.getKey());
51 writer.write("=");
52 writer.write(entry.getValue().replace("\n", "\\n"));
53 writer.write("\n");
54 }
55 }
56 }
57
58
59 }
@@ -1,2 +1,2
1 group=org.implab.gradle
1 group=org.implab.gradle
2 version=1.1.2 No newline at end of file
2 version=1.2.0 No newline at end of file
@@ -1,53 +1,54
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import org.gradle.api.Plugin;
3 import org.gradle.api.Plugin;
4 import org.gradle.api.Project;
4 import org.gradle.api.Project;
5 import org.gradle.api.plugins.ExtraPropertiesExtension;
5 import org.gradle.api.plugins.ExtraPropertiesExtension;
6 import org.implab.gradle.containers.tasks.BuildImage;
6 import org.implab.gradle.containers.tasks.BuildImage;
7 import org.implab.gradle.containers.tasks.PushImage;
7 import org.implab.gradle.containers.tasks.PushImage;
8 import org.implab.gradle.containers.tasks.RunImage;
8 import org.implab.gradle.containers.tasks.RunImage;
9 import org.implab.gradle.containers.tasks.SaveImage;
9 import org.implab.gradle.containers.tasks.SaveImage;
10 import org.implab.gradle.containers.tasks.TagImage;
10 import org.implab.gradle.containers.tasks.TagImage;
11
11
12 public class ContainerBasePlugin implements Plugin<Project> {
12 public class ContainerBasePlugin implements Plugin<Project> {
13 public static final String CONTAINER_EXTENSION_NAME = "container";
13 public static final String CONTAINER_EXTENSION_NAME = "container";
14
14
15 private ContainerExtension containerExtension;
15 private ContainerExtension containerExtension;
16
16
17 ContainerExtension getContainerExtension() {
17 ContainerExtension getContainerExtension() {
18 if (containerExtension == null)
18 if (containerExtension == null)
19 throw new IllegalStateException();
19 throw new IllegalStateException();
20 return containerExtension;
20 return containerExtension;
21 }
21 }
22
22
23 void exportClasses(Project project, Class<?>... classes) {
23 void exportClasses(Project project, Class<?>... classes) {
24 ExtraPropertiesExtension extras = project.getExtensions().getExtraProperties();
24 ExtraPropertiesExtension extras = project.getExtensions().getExtraProperties();
25 for (var clazz : classes)
25 for (var clazz : classes)
26 extras.set(clazz.getSimpleName(), clazz);
26 extras.set(clazz.getSimpleName(), clazz);
27 }
27 }
28
28
29 @Override
29 @Override
30 public void apply(Project project) {
30 public void apply(Project project) {
31
31
32 containerExtension = project.getObjects().newInstance(ContainerExtension.class);
32 containerExtension = project.getObjects().newInstance(ContainerExtension.class);
33
33
34 // TODO: move properties initialization into the constructor
34 containerExtension.getImageAuthority()
35 containerExtension.getImageAuthority()
35 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
36 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
36
37
37 containerExtension.getImageGroup()
38 containerExtension.getImageGroup()
38 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
39 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
39
40
40 containerExtension.getCliCmd()
41 containerExtension.getCliCmd()
41 .convention(project
42 .convention(project
42 .provider(() -> (String) project.getProperties().get("containerCli"))
43 .provider(() -> (String) project.getProperties().get("containerCli"))
43 .orElse("docker"));
44 .orElse("docker"));
44
45
45 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
46 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
46
47
47 exportClasses(
48 exportClasses(
48 project,
49 project,
49 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunImage.class);
50 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunImage.class);
50
51
51 }
52 }
52
53
53 }
54 }
@@ -1,141 +1,186
1 package org.implab.gradle.containers.cli;
1 package org.implab.gradle.containers.cli;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.IOException;
4 import java.io.IOException;
5 import java.util.ArrayList;
5 import java.util.ArrayList;
6 import java.util.Arrays;
6 import java.util.Arrays;
7 import java.util.List;
7 import java.util.List;
8 import java.util.Optional;
8 import java.util.Optional;
9 import java.util.Set;
9
10
10 import org.gradle.api.logging.Logger;
11 import org.gradle.api.logging.Logger;
11
12
12 public abstract class DockerTraits {
13 public abstract class DockerTraits {
13
14
14 public final String BUILD_COMMAND = "build";
15 public final String BUILD_COMMAND = "build";
15 public final String PUSH_COMMAND = "push";
16 public final String PUSH_COMMAND = "push";
16 public final String RUN_COMMAND = "run";
17 public final String RUN_COMMAND = "run";
17 public final String SAVE_COMMAND = "save";
18 public final String SAVE_COMMAND = "save";
18 public final String INSPECT_COMMAND = "inspect";
19 public final String INSPECT_COMMAND = "inspect";
19 public final String IMAGE_COMMAND = "image";
20 public final String IMAGE_COMMAND = "image";
20 public final String TAG_COMMAND = "tag";
21 public final String TAG_COMMAND = "tag";
22 public final String COMPOSE_COMMAND = "compose";
23 public final String UP_COMMAND = "up";
24 public final String STOP_COMMAND = "stop";
25 public final String RM_COMMAND = "rm";
21
26
22 public abstract Logger getLogger();
27 public abstract Logger getLogger();
23
28
24 public abstract Optional<File> getWorkingDir();
29 public abstract Optional<File> getWorkingDir();
25
30
26 public abstract String getCliCmd();
31 public abstract String getCliCmd();
27
32
28 Process startProcess(ProcessBuilder builder) throws IOException {
33 Process startProcess(ProcessBuilder builder) throws IOException {
29 getLogger().info("Starting: {}", builder.command());
34 getLogger().info("Starting: {}", builder.command());
30 return builder.start();
35 return builder.start();
31 }
36 }
32
37
33 protected boolean checkRetCode(Process proc, int code) throws InterruptedException {
38 protected boolean checkRetCode(Process proc, int code) throws InterruptedException {
34 if (getLogger().isInfoEnabled()) {
39 if (getLogger().isInfoEnabled()) {
35 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
40 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
36 Utils.redirectIO(proc.getErrorStream(), getLogger()::info);
41 Utils.redirectIO(proc.getErrorStream(), getLogger()::info);
37 }
42 }
38
43
39 return proc.waitFor() == code;
44 return proc.waitFor() == code;
40 }
45 }
41
46
42 protected void complete(Process proc) throws InterruptedException, IOException {
47 protected void complete(Process proc) throws InterruptedException, IOException {
43 if (getLogger().isInfoEnabled())
48 if (getLogger().isInfoEnabled())
44 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
49 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
45
50
46 if (getLogger().isErrorEnabled())
51 if (getLogger().isErrorEnabled())
47 Utils.redirectIO(proc.getErrorStream(), getLogger()::error);
52 Utils.redirectIO(proc.getErrorStream(), getLogger()::error);
48
53
49 var code = proc.waitFor();
54 var code = proc.waitFor();
50 if (code != 0) {
55 if (code != 0) {
51 getLogger().error("The process exited with code {}", code);
56 getLogger().error("The process exited with code {}", code);
52 throw new IOException("The process exited with error code " + code);
57 throw new IOException("The process exited with error code " + code);
53 }
58 }
54 }
59 }
55
60
56 protected ProcessBuilder builder(String... args) {
61 protected ProcessBuilder builder(String... args) {
57 var argsList = new ArrayList<String>(args.length + 1);
62 var argsList = new ArrayList<String>(args.length + 1);
58 Arrays.stream(args).forEach(argsList::add);
63 Arrays.stream(args).forEach(argsList::add);
59
64
60 return builder(argsList);
65 return builder(argsList);
61 }
66 }
62
67
63 protected ProcessBuilder builder(List<String> args) {
68 protected ProcessBuilder builder(List<String> args) {
64 var command = new ArrayList<String>(args.size() + 1);
69 var command = new ArrayList<String>(args.size() + 1);
65
70
66 command.add(getCliCmd());
71 command.add(getCliCmd());
67 args.forEach(command::add);
72 args.forEach(command::add);
68
73
69 var builder = new ProcessBuilder(command);
74 var builder = new ProcessBuilder(command);
70
75
71 getWorkingDir().ifPresent(builder::directory);
76 getWorkingDir().ifPresent(builder::directory);
72 return builder;
77 return builder;
73 }
78 }
74
79
75 public void buildImage(String imageName, File contextDirectory, List<String> options)
80 public void buildImage(String imageName, File contextDirectory, List<String> options)
76 throws IOException, InterruptedException {
81 throws IOException, InterruptedException {
77 var args = new ArrayList<String>();
82 var args = new ArrayList<String>();
78 args.add(BUILD_COMMAND);
83 args.add(BUILD_COMMAND);
79 args.addAll(options);
84 args.addAll(options);
80 args.add("-t");
85 args.add("-t");
81 args.add(imageName);
86 args.add(imageName);
82 args.add(contextDirectory.getAbsolutePath());
87 args.add(contextDirectory.getAbsolutePath());
83 complete(startProcess(builder(args)));
88 complete(startProcess(builder(args)));
84 }
89 }
85
90
86 public void pushImage(String image, List<String> options) throws InterruptedException, IOException {
91 public void pushImage(String image, List<String> options) throws InterruptedException, IOException {
87 var args = new ArrayList<String>();
92 var args = new ArrayList<String>();
88 args.add(PUSH_COMMAND);
93 args.add(PUSH_COMMAND);
89 args.addAll(options);
94 args.addAll(options);
90 args.add(image);
95 args.add(image);
91 complete(startProcess(builder(args)));
96 complete(startProcess(builder(args)));
92 }
97 }
93
98
94 public void runImage(String image, List<String> options, List<String> command)
99 public void runImage(String image, List<String> options, List<String> command)
95 throws InterruptedException, IOException {
100 throws InterruptedException, IOException {
96 var args = new ArrayList<String>();
101 var args = new ArrayList<String>();
97 args.add(RUN_COMMAND);
102 args.add(RUN_COMMAND);
98 args.addAll(options);
103 args.addAll(options);
99 args.add(image);
104 args.add(image);
100 args.addAll(command);
105 args.addAll(command);
101 complete(startProcess(builder(args)));
106 complete(startProcess(builder(args)));
102 }
107 }
103
108
104 public void saveImage(List<String> images, File output) throws InterruptedException, IOException {
109 public void saveImage(List<String> images, File output) throws InterruptedException, IOException {
105 if (output.exists())
110 if (output.exists())
106 output.delete();
111 output.delete();
107
112
108 var args = new ArrayList<String>();
113 var args = new ArrayList<String>();
109 args.add(SAVE_COMMAND);
114 args.add(SAVE_COMMAND);
110 args.add("-o");
115 args.add("-o");
111 args.add(output.getAbsolutePath());
116 args.add(output.getAbsolutePath());
112 images.forEach(args::add);
117 images.forEach(args::add);
113
118
114 complete(startProcess(builder(args)));
119 complete(startProcess(builder(args)));
115 }
120 }
116
121
117 public void tagImage(String source, String target) throws InterruptedException, IOException {
122 public void tagImage(String source, String target) throws InterruptedException, IOException {
118 complete(startProcess(builder(TAG_COMMAND, source, target)));
123 complete(startProcess(builder(TAG_COMMAND, source, target)));
119 }
124 }
120
125
121 public boolean imageExists(String imageId) throws InterruptedException, IOException {
126 public boolean imageExists(String imageId) throws InterruptedException, IOException {
122 getLogger().info("Check image {} exists", imageId);
127 getLogger().info("Check image {} exists", imageId);
123
128
124 return checkRetCode(
129 return checkRetCode(
125 startProcess(builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId)),
130 startProcess(builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId)),
126 0);
131 0);
127 }
132 }
128
133
129 public boolean imageExists(File imageIdFile) {
134 public boolean imageExists(File imageIdFile) {
130 if (imageIdFile.exists()) {
135 if (imageIdFile.exists()) {
131 try {
136 try {
132 var imageId = Utils.readImageRef(imageIdFile);
137 var imageId = Utils.readImageRef(imageIdFile);
133 return imageExists(imageId.getId());
138 return imageExists(imageId.getId());
134 } catch (IOException | InterruptedException e) {
139 } catch (IOException | InterruptedException e) {
135 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
140 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
136 return false;
141 return false;
137 }
142 }
138 }
143 }
139 return false;
144 return false;
140 }
145 }
146
147 List<String> composeArgs(File primaryCompose, Set<String> profiles, String... extra) {
148 var args = new ArrayList<String>();
149
150 args.add(COMPOSE_COMMAND);
151 args.add("--file");
152 args.add(primaryCompose.getAbsolutePath());
153
154 if (profiles.size() > 0) {
155 for (var profile : profiles) {
156 args.add("--profile");
157 args.add(profile);
141 }
158 }
159 }
160
161 args.addAll(List.of(extra));
162
163 return args;
164 }
165
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)));
175 }
176
177 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
181 if (removeVolumes)
182 args.add("--volumes");
183
184 complete(startProcess(builder(args)));
185 }
186 }
@@ -1,61 +1,61
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.util.Optional;
4 import java.util.Optional;
5
5
6 import org.gradle.api.DefaultTask;
6 import org.gradle.api.DefaultTask;
7 import org.gradle.api.file.Directory;
8 import org.gradle.api.file.DirectoryProperty;
9 import org.gradle.api.logging.Logger;
7 import org.gradle.api.logging.Logger;
10 import org.gradle.api.provider.Property;
8 import org.gradle.api.provider.Property;
11 import org.gradle.api.tasks.Input;
9 import org.gradle.api.tasks.Input;
12 import org.gradle.api.tasks.Internal;
10 import org.gradle.api.tasks.Internal;
13 import org.implab.gradle.containers.ContainerExtension;
11 import org.implab.gradle.containers.ContainerExtension;
14 import org.implab.gradle.containers.PropertiesMixin;
12 import org.implab.gradle.containers.PropertiesMixin;
15 import org.implab.gradle.containers.cli.DockerTraits;
13 import org.implab.gradle.containers.cli.DockerTraits;
16
14
17 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin {
15 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin {
18
16
19 @Input
17 @Input
20 public abstract Property<String> getCliCmd();
18 public abstract Property<String> getCliCmd();
21
19
20 /**
21 * Returns working directory for docker commands
22 */
22 @Input
23 @Input
23 @org.gradle.api.tasks.Optional
24 @org.gradle.api.tasks.Optional
24 public abstract DirectoryProperty getWorkingDirectory();
25 public abstract Property<File> getWorkingDirectory();
25
26
26 @Internal
27 @Internal
27 protected ContainerExtension getContainerExtension() {
28 protected ContainerExtension getContainerExtension() {
28 return getProject().getExtensions().getByType(ContainerExtension.class);
29 return getProject().getExtensions().getByType(ContainerExtension.class);
29 }
30 }
30
31
31 public DockerCliTask() {
32 public DockerCliTask() {
32 getCliCmd().convention(getContainerExtension().getCliCmd());
33 getCliCmd().convention(getContainerExtension().getCliCmd());
33
34
34 }
35 }
35
36
36 protected DockerTraits docker() {
37 protected DockerTraits docker() {
37 return new TaskDockerTraits();
38 return new TaskDockerTraits();
38 }
39 }
39
40
40 class TaskDockerTraits extends DockerTraits {
41 class TaskDockerTraits extends DockerTraits {
41
42
42 @Override
43 @Override
43 public Logger getLogger() {
44 public Logger getLogger() {
44 return DockerCliTask.this.getLogger();
45 return DockerCliTask.this.getLogger();
45 }
46 }
46
47
47 @Override
48 @Override
48 public Optional<File> getWorkingDir() {
49 public Optional<File> getWorkingDir() {
49 return getWorkingDirectory()
50 return getWorkingDirectory()
50 .map(Directory::getAsFile)
51 .map(Optional::of)
51 .map(Optional::of)
52 .getOrElse(Optional.empty());
52 .getOrElse(Optional.empty());
53 }
53 }
54
54
55 @Override
55 @Override
56 public String getCliCmd() {
56 public String getCliCmd() {
57 return DockerCliTask.this.getCliCmd().get();
57 return DockerCliTask.this.getCliCmd().get();
58 }
58 }
59
59
60 }
60 }
61 }
61 }
General Comments 0
You need to be logged in to leave comments. Login now