##// END OF EJS Templates
WIP docker compose support
cin -
r11:cf3a5b9d59a9 default
parent child
Show More
@@ -1,57 +1,65
1 plugins {
1 plugins {
2 id "java-gradle-plugin"
2 id "java-gradle-plugin"
3 id "com.gradle.plugin-publish" version "1.2.1"
3 id "com.gradle.plugin-publish" version "1.2.1"
4 // Maven Publish Plugin applied by com.gradle.plugin-publish > 1.0
4 // Maven Publish Plugin applied by com.gradle.plugin-publish > 1.0
5 id "ivy-publish"
5 id "ivy-publish"
6 }
6 }
7
7
8 dependencies {
8 dependencies {
9 implementation "com.fasterxml.jackson.core:jackson-core:2.13.5",
9 implementation "com.fasterxml.jackson.core:jackson-core:2.13.5",
10 "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5"
10 "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5"
11 }
11 }
12
12
13 java {
13 java {
14 targetCompatibility = 11
14 targetCompatibility = 11
15 sourceCompatibility = 11
15 sourceCompatibility = 11
16 }
16 }
17
17
18
18
19 repositories {
19 repositories {
20 mavenCentral()
20 mavenCentral()
21 }
21 }
22
22
23 gradlePlugin {
23 gradlePlugin {
24 website = 'https://code.implab.org/implab/gradle-container-plugin'
24 website = 'https://code.implab.org/implab/gradle-container-plugin'
25 vcsUrl = 'https://code.implab.org/implab/gradle-container-plugin'
25 vcsUrl = 'https://code.implab.org/implab/gradle-container-plugin'
26 plugins {
26 plugins {
27 containerPlugin {
27 containerPlugin {
28 id = 'org.implab.gradle-container'
28 id = 'org.implab.gradle-container'
29 displayName = "Provdes convetional configuration to build a container image"
29 displayName = "Provdes convetional configuration to build a container image"
30 description = 'Build and publish container images with docker or podman. Simple wrapper around cli.'
30 description = 'Build and publish container images with docker or podman. Simple wrapper around cli.'
31 implementationClass = 'org.implab.gradle.containers.ContainerPlugin'
31 implementationClass = 'org.implab.gradle.containers.ContainerPlugin'
32 tags.set(['containers', 'image', 'docker', 'podman'])
32 tags.set(['containers', 'image', 'docker', 'podman'])
33 }
33 }
34 containerBasePlugin {
34 containerBasePlugin {
35 id = 'org.implab.gradle-container-base'
35 id = 'org.implab.gradle-container-base'
36 displayName = "Provides tasks to build manipulate container images"
36 displayName = "Provides tasks to build manipulate container images"
37 description = 'Build and publish container images with docker or podman. Simple wrapper around cli.'
37 description = 'Build and publish container images with docker or podman. Simple wrapper around cli.'
38 implementationClass = 'org.implab.gradle.containers.ContainerBasePlugin'
38 implementationClass = 'org.implab.gradle.containers.ContainerBasePlugin'
39 tags.set(['containers', 'image', 'docker', 'podman'])
39 tags.set(['containers', 'image', 'docker', 'podman'])
40 }
40 }
41
42 conmposePlugin {
43 id = 'org.implab.gradle-container-compose'
44 displayName = "Provdes tasks to start and stop compose"
45 description = 'Build and publish container images with docker or podman. Simple wrapper around cli.'
46 implementationClass = 'org.implab.gradle.containers.ComposePlugin'
47 tags.set(['containers', 'image', 'docker', 'podman'])
48 }
41 }
49 }
42 }
50 }
43
51
44
52
45 task printVersion {
53 task printVersion {
46 doLast {
54 doLast {
47 println "${->jar.archiveFileName.get()}"
55 println "${->jar.archiveFileName.get()}"
48 }
56 }
49 }
57 }
50
58
51 publishing {
59 publishing {
52 repositories {
60 repositories {
53 ivy {
61 ivy {
54 url "${System.properties["user.home"]}/ivy-repo"
62 url "${System.properties["user.home"]}/ivy-repo"
55 }
63 }
56 }
64 }
57 }
65 }
@@ -1,49 +1,53
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 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 containerExtension.getImageAuthority()
34 containerExtension.getImageAuthority()
35 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
35 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
36
36
37 containerExtension.getImageGroup()
37 containerExtension.getImageGroup()
38 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
38 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
39
39
40 containerExtension.getCliCmd()
41 .convention(project
42 .provider(() -> (String) project.getProperties().get("containerCli"))
43 .orElse("docker"));
44
40 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
45 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
41
46
42 exportClasses(
47 exportClasses(
43 project,
48 project,
44 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunImage.class);
49 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunImage.class);
45
50
51 }
46
52
47 }
48
49 }
53 }
@@ -1,85 +1,78
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.io.FileNotFoundException;
4 import java.io.FileNotFoundException;
5 import java.io.IOException;
5 import java.io.IOException;
6 import java.util.Optional;
6 import java.util.Optional;
7
7
8 import javax.inject.Inject;
8 import javax.inject.Inject;
9
9
10 import org.gradle.api.Project;
10 import org.gradle.api.Project;
11 import org.gradle.api.file.DirectoryProperty;
11 import org.gradle.api.file.DirectoryProperty;
12 import org.gradle.api.file.ProjectLayout;
12 import org.gradle.api.file.ProjectLayout;
13 import org.gradle.api.file.RegularFileProperty;
14 import org.gradle.api.provider.Property;
13 import org.gradle.api.provider.Property;
15 import org.gradle.api.provider.Provider;
14 import org.gradle.api.provider.Provider;
16 import org.implab.gradle.containers.cli.ImageName;
15 import org.implab.gradle.containers.cli.ImageName;
17 import org.implab.gradle.containers.cli.ImageRef;
16 import org.implab.gradle.containers.cli.ImageRef;
18 import org.implab.gradle.containers.cli.Utils;
17 import org.implab.gradle.containers.cli.Utils;
19
18
20 public abstract class ContainerExtension {
19 public abstract class ContainerExtension implements PropertiesMixin {
21
20
22 public abstract Property<String> getCliCmd();
21 public abstract Property<String> getCliCmd();
23
22
24 public abstract DirectoryProperty getContextDirectory();
23 public abstract DirectoryProperty getContextDirectory();
25
24
26 public abstract RegularFileProperty getImageIdFile();
27
28 /**
25 /**
29 * Specifies the name of the registry where the image is located
26 * Specifies the name of the registry where the image is located
30 * {@code registry.my-company.org}
27 * {@code registry.my-company.org}
31 */
28 */
32 public abstract Property<String> getImageAuthority();
29 public abstract Property<String> getImageAuthority();
33
30
34 /**
31 /**
35 * Specified the path of the image like {@code my-company}
32 * Specified the path of the image like {@code my-company}
36 */
33 */
37 public abstract Property<String> getImageGroup();
34 public abstract Property<String> getImageGroup();
38
35
39 public abstract Property<ImageName> getImageName();
36 public Provider<ImageName> getImageName() {
37 return provider(this::createImageName);
38 }
40
39
41 /**
40 /**
42 * Specifies local image part like {@code httpd}
41 * Specifies local image part like {@code httpd}
43 */
42 */
44 public abstract Property<String> getImageLocalName();
43 public abstract Property<String> getImageLocalName();
45
44
46 /**
45 /**
47 * This property is deprecated use imageLocalName
46 * This property is deprecated use imageLocalName
48 */
47 */
49 @Deprecated
48 @Deprecated
50 public Property<String> getImageShortName() {
49 public Property<String> getImageShortName() {
51 return getImageLocalName();
50 return getImageLocalName();
52 }
51 }
53
52
54 public abstract Property<String> getImageTag();
53 public abstract Property<String> getImageTag();
55
54
56 @Inject
55 @Inject
57 public ContainerExtension(ProjectLayout layout, Project project) {
56 public ContainerExtension(ProjectLayout layout, Project project) {
58 getContextDirectory().convention(layout.getBuildDirectory().dir("context"));
57 getContextDirectory().convention(layout.getBuildDirectory().dir("context"));
59
58
60 getImageIdFile().convention(layout.getBuildDirectory().file("iid.json"));
61
62 getCliCmd().set("docker");
63
64 getImageLocalName().convention(project.getName());
59 getImageLocalName().convention(project.getName());
65
60
66 getImageTag().set(project
61 getImageTag().set(provider(() -> Optional.ofNullable(project.getVersion())
67 .provider(() -> Optional.ofNullable(project.getVersion()).map(v -> v.toString()).orElse("latest")));
62 .map(Object::toString)
68
63 .orElse("latest")));
69 Provider<String> imageRepository = getImageGroup().map(g -> g + "/" + getImageLocalName().get())
70 .orElse(getImageLocalName());
71
72 getImageName().convention(project.provider(
73 () -> new ImageName().authority(getImageAuthority().get()).name(imageRepository.get())
74 .tag(getImageTag().get())));
75 }
64 }
76
65
77 public ImageName createImageName() {
66 ImageName createImageName() {
78 return new ImageName();
67 return new ImageName(
68 getImageAuthority().getOrNull(),
69 getImageGroup().getOrNull(),
70 getImageLocalName().get(),
71 getImageTag().getOrNull());
79 }
72 }
80
73
81 public ImageRef readImageRef(File file) throws FileNotFoundException, IOException {
74 public ImageRef readImageRef(File file) throws FileNotFoundException, IOException {
82 return Utils.readImageRef(file);
75 return Utils.readImageRef(file);
83 }
76 }
84
77
85 } No newline at end of file
78 }
@@ -1,82 +1,80
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import org.gradle.api.GradleException;
3 import org.gradle.api.GradleException;
4 import org.gradle.api.Plugin;
4 import org.gradle.api.Plugin;
5 import org.gradle.api.Project;
5 import org.gradle.api.Project;
6 import org.gradle.api.artifacts.Dependency;
6 import org.gradle.api.artifacts.Dependency;
7 import org.gradle.api.file.ProjectLayout;
7 import org.gradle.api.file.ProjectLayout;
8 import org.gradle.api.tasks.Copy;
8 import org.gradle.api.tasks.Copy;
9 import org.gradle.api.tasks.Delete;
9 import org.gradle.api.tasks.Delete;
10 import org.gradle.api.tasks.TaskProvider;
10 import org.gradle.api.tasks.TaskProvider;
11 import org.implab.gradle.containers.cli.ImageName;
11 import org.implab.gradle.containers.cli.ImageName;
12 import org.implab.gradle.containers.tasks.BuildImage;
12 import org.implab.gradle.containers.tasks.BuildImage;
13 import org.implab.gradle.containers.tasks.PushImage;
13 import org.implab.gradle.containers.tasks.PushImage;
14 import org.implab.gradle.containers.tasks.SaveImage;
14 import org.implab.gradle.containers.tasks.SaveImage;
15
15
16 public class ContainerPlugin implements Plugin<Project> {
16 public class ContainerPlugin implements Plugin<Project> {
17
17
18 public static final String BUILD_GROUP = "build";
18 public static final String BUILD_GROUP = "build";
19
19
20 public static final String ARCHIVE_CONFIGURATION = "archive";
20 public static final String ARCHIVE_CONFIGURATION = "archive";
21
21
22 ContainerExtension containerExtension;
23
24 public void apply(Project project) {
22 public void apply(Project project) {
25 ProjectLayout layout = project.getLayout();
23 ProjectLayout layout = project.getLayout();
26
24
27 project.getPluginManager().apply(ContainerBasePlugin.class);
25 project.getPluginManager().apply(ContainerBasePlugin.class);
28
26
29 var basePlugin = project.getPlugins().findPlugin(ContainerBasePlugin.class);
27 var basePlugin = project.getPlugins().findPlugin(ContainerBasePlugin.class);
30 if (basePlugin == null)
28 if (basePlugin == null)
31 throw new GradleException("The container-base plugin fails to be applied");
29 throw new GradleException("The container-base plugin fails to be applied");
32
30
33 var containerExtension = basePlugin.getContainerExtension();
31 var containerExtension = basePlugin.getContainerExtension();
34
32
35 project.getConfigurations().create(Dependency.DEFAULT_CONFIGURATION, c -> {
33 project.getConfigurations().create(Dependency.DEFAULT_CONFIGURATION, c -> {
36 c.setCanBeConsumed(true);
34 c.setCanBeConsumed(true);
37 c.setCanBeResolved(false);
35 c.setCanBeResolved(false);
38 });
36 });
39
37
40 project.getConfigurations().create(ARCHIVE_CONFIGURATION, c -> {
38 project.getConfigurations().create(ARCHIVE_CONFIGURATION, c -> {
41 c.setCanBeConsumed(true);
39 c.setCanBeConsumed(true);
42 c.setCanBeResolved(false);
40 c.setCanBeResolved(false);
43 });
41 });
44
42
45 TaskProvider<Copy> processResourcesTask = project.getTasks().register("processResources", Copy.class, t -> {
43 TaskProvider<Copy> processResourcesTask = project.getTasks().register("processResources", Copy.class, t -> {
46 t.setGroup(BUILD_GROUP);
44 t.setGroup(BUILD_GROUP);
47 t.from(layout.getProjectDirectory().dir("src/main"));
45 t.from(layout.getProjectDirectory().dir("src/main"));
48 t.into(containerExtension.getContextDirectory());
46 t.into(containerExtension.getContextDirectory());
49 });
47 });
50
48
51 TaskProvider<BuildImage> buildImageTask = project.getTasks().register("buildImage", BuildImage.class, t -> {
49 TaskProvider<BuildImage> buildImageTask = project.getTasks().register("buildImage", BuildImage.class, t -> {
52 t.setGroup(BUILD_GROUP);
50 t.setGroup(BUILD_GROUP);
53 t.dependsOn(processResourcesTask);
51 t.dependsOn(processResourcesTask);
54
52
55 t.getContextDirectory().set(containerExtension.getContextDirectory());
53 t.getContextDirectory().set(containerExtension.getContextDirectory());
56 t.getImageIdFile().set(containerExtension.getImageIdFile());
54 t.getImageIdFile().set(project.getLayout().getBuildDirectory().file("iid.json"));
57
55
58 t.getImageName().set(containerExtension.getImageName());
56 t.getImageName().set(containerExtension.getImageName().map(ImageName::toString));
59 });
57 });
60
58
61 project.getTasks().register("clean", Delete.class, t -> {
59 project.getTasks().register("clean", Delete.class, t -> {
62 t.delete(layout.getBuildDirectory());
60 t.delete(layout.getBuildDirectory());
63 });
61 });
64
62
65 project.getTasks().register("build", t -> {
63 project.getTasks().register("build", t -> {
66 t.setGroup(BUILD_GROUP);
64 t.setGroup(BUILD_GROUP);
67 t.dependsOn(buildImageTask);
65 t.dependsOn(buildImageTask);
68 });
66 });
69
67
70 project.getTasks().register("pushImage", PushImage.class, t -> {
68 project.getTasks().register("pushImage", PushImage.class, t -> {
71 t.dependsOn(buildImageTask);
69 t.dependsOn(buildImageTask);
72 t.getImageName().set(buildImageTask.flatMap(BuildImage::getImageName));
70 t.getImageName().set(buildImageTask.flatMap(BuildImage::getImageName));
73 });
71 });
74
72
75 project.getTasks().register("saveImage", SaveImage.class, t -> {
73 project.getTasks().register("saveImage", SaveImage.class, t -> {
76 t.dependsOn(buildImageTask);
74 t.dependsOn(buildImageTask);
77 t.getExportImages().add(buildImageTask.flatMap(BuildImage::getImageName).map(ImageName::toString));
75 t.getExportImages().add(buildImageTask.flatMap(BuildImage::getImageName));
78 });
76 });
79
77
80 project.getArtifacts().add(Dependency.DEFAULT_CONFIGURATION, buildImageTask);
78 project.getArtifacts().add(Dependency.DEFAULT_CONFIGURATION, buildImageTask);
81 }
79 }
82 }
80 }
@@ -1,142 +1,141
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.nio.file.Files;
6 import java.util.ArrayList;
5 import java.util.ArrayList;
7 import java.util.Arrays;
6 import java.util.Arrays;
8 import java.util.List;
7 import java.util.List;
9 import java.util.Optional;
8 import java.util.Optional;
10
9
11 import org.gradle.api.logging.Logger;
10 import org.gradle.api.logging.Logger;
12
11
13 public abstract class DockerTraits {
12 public abstract class DockerTraits {
14
13
15 public final String BUILD_COMMAND = "build";
14 public final String BUILD_COMMAND = "build";
16 public final String PUSH_COMMAND = "push";
15 public final String PUSH_COMMAND = "push";
17 public final String RUN_COMMAND = "run";
16 public final String RUN_COMMAND = "run";
18 public final String SAVE_COMMAND = "save";
17 public final String SAVE_COMMAND = "save";
19 public final String INSPECT_COMMAND = "inspect";
18 public final String INSPECT_COMMAND = "inspect";
20 public final String IMAGE_COMMAND = "image";
19 public final String IMAGE_COMMAND = "image";
21 public final String TAG_COMMAND = "tag";
20 public final String TAG_COMMAND = "tag";
22
21
23 public abstract Logger getLogger();
22 public abstract Logger getLogger();
24
23
25 public abstract Optional<File> getWorkingDir();
24 public abstract Optional<File> getWorkingDir();
26
25
27 public abstract String getCliCmd();
26 public abstract String getCliCmd();
28
27
29 Process startProcess(ProcessBuilder builder) throws IOException {
28 Process startProcess(ProcessBuilder builder) throws IOException {
30 getLogger().info("Starting: {}", builder.command());
29 getLogger().info("Starting: {}", builder.command());
31 return builder.start();
30 return builder.start();
32 }
31 }
33
32
34 protected boolean checkRetCode(Process proc, int code) throws InterruptedException {
33 protected boolean checkRetCode(Process proc, int code) throws InterruptedException {
35 if (getLogger().isInfoEnabled()) {
34 if (getLogger().isInfoEnabled()) {
36 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
35 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
37 Utils.redirectIO(proc.getErrorStream(), getLogger()::info);
36 Utils.redirectIO(proc.getErrorStream(), getLogger()::info);
38 }
37 }
39
38
40 return proc.waitFor() == code;
39 return proc.waitFor() == code;
41 }
40 }
42
41
43 protected void complete(Process proc) throws InterruptedException, IOException {
42 protected void complete(Process proc) throws InterruptedException, IOException {
44 if (getLogger().isInfoEnabled())
43 if (getLogger().isInfoEnabled())
45 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
44 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
46
45
47 if (getLogger().isErrorEnabled())
46 if (getLogger().isErrorEnabled())
48 Utils.redirectIO(proc.getErrorStream(), getLogger()::error);
47 Utils.redirectIO(proc.getErrorStream(), getLogger()::error);
49
48
50 var code = proc.waitFor();
49 var code = proc.waitFor();
51 if (code != 0) {
50 if (code != 0) {
52 getLogger().error("The process exited with code {}", code);
51 getLogger().error("The process exited with code {}", code);
53 throw new IOException("The process exited with error code " + code);
52 throw new IOException("The process exited with error code " + code);
54 }
53 }
55 }
54 }
56
55
57 protected ProcessBuilder builder(String... args) {
56 protected ProcessBuilder builder(String... args) {
58 var argsList = new ArrayList<String>(args.length + 1);
57 var argsList = new ArrayList<String>(args.length + 1);
59 Arrays.stream(args).forEach(argsList::add);
58 Arrays.stream(args).forEach(argsList::add);
60
59
61 return builder(argsList);
60 return builder(argsList);
62 }
61 }
63
62
64 protected ProcessBuilder builder(List<String> args) {
63 protected ProcessBuilder builder(List<String> args) {
65 var command = new ArrayList<String>(args.size() + 1);
64 var command = new ArrayList<String>(args.size() + 1);
66
65
67 command.add(getCliCmd());
66 command.add(getCliCmd());
68 args.forEach(command::add);
67 args.forEach(command::add);
69
68
70 var builder = new ProcessBuilder(command);
69 var builder = new ProcessBuilder(command);
71
70
72 getWorkingDir().ifPresent(builder::directory);
71 getWorkingDir().ifPresent(builder::directory);
73 return builder;
72 return builder;
74 }
73 }
75
74
76 public void buildImage(String imageName, File contextDirectory, List<String> options)
75 public void buildImage(String imageName, File contextDirectory, List<String> options)
77 throws IOException, InterruptedException {
76 throws IOException, InterruptedException {
78 var args = new ArrayList<String>();
77 var args = new ArrayList<String>();
79 args.add(BUILD_COMMAND);
78 args.add(BUILD_COMMAND);
80 args.addAll(options);
79 args.addAll(options);
81 args.add("-t");
80 args.add("-t");
82 args.add(imageName);
81 args.add(imageName);
83 args.add(contextDirectory.getAbsolutePath());
82 args.add(contextDirectory.getAbsolutePath());
84 complete(startProcess(builder(args)));
83 complete(startProcess(builder(args)));
85 }
84 }
86
85
87 public void pushImage(String image, List<String> options) throws InterruptedException, IOException {
86 public void pushImage(String image, List<String> options) throws InterruptedException, IOException {
88 var args = new ArrayList<String>();
87 var args = new ArrayList<String>();
89 args.add(PUSH_COMMAND);
88 args.add(PUSH_COMMAND);
90 args.addAll(options);
89 args.addAll(options);
91 args.add(image);
90 args.add(image);
92 complete(startProcess(builder(args)));
91 complete(startProcess(builder(args)));
93 }
92 }
94
93
95 public void runImage(String image, List<String> options, List<String> command)
94 public void runImage(String image, List<String> options, List<String> command)
96 throws InterruptedException, IOException {
95 throws InterruptedException, IOException {
97 var args = new ArrayList<String>();
96 var args = new ArrayList<String>();
98 args.add(RUN_COMMAND);
97 args.add(RUN_COMMAND);
99 args.addAll(options);
98 args.addAll(options);
100 args.add(image);
99 args.add(image);
101 args.addAll(command);
100 args.addAll(command);
102 complete(startProcess(builder(args)));
101 complete(startProcess(builder(args)));
103 }
102 }
104
103
105 public void saveImage(List<String> images, File output) throws InterruptedException, IOException {
104 public void saveImage(List<String> images, File output) throws InterruptedException, IOException {
106 if (output.exists())
105 if (output.exists())
107 output.delete();
106 output.delete();
108
107
109 var args = new ArrayList<String>();
108 var args = new ArrayList<String>();
110 args.add(SAVE_COMMAND);
109 args.add(SAVE_COMMAND);
111 args.add("-o");
110 args.add("-o");
112 args.add(output.getAbsolutePath());
111 args.add(output.getAbsolutePath());
113 images.forEach(args::add);
112 images.forEach(args::add);
114
113
115 complete(startProcess(builder(args)));
114 complete(startProcess(builder(args)));
116 }
115 }
117
116
118 public void tagImage(String source, String target) throws InterruptedException, IOException {
117 public void tagImage(String source, String target) throws InterruptedException, IOException {
119 complete(startProcess(builder(TAG_COMMAND, source, target)));
118 complete(startProcess(builder(TAG_COMMAND, source, target)));
120 }
119 }
121
120
122 public boolean imageExists(String imageId) throws InterruptedException, IOException {
121 public boolean imageExists(String imageId) throws InterruptedException, IOException {
123 getLogger().info("Check image {} exists", imageId);
122 getLogger().info("Check image {} exists", imageId);
124
123
125 return checkRetCode(
124 return checkRetCode(
126 startProcess(builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId)),
125 startProcess(builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId)),
127 0);
126 0);
128 }
127 }
129
128
130 public boolean imageExists(File imageIdFile) {
129 public boolean imageExists(File imageIdFile) {
131 if (imageIdFile.exists()) {
130 if (imageIdFile.exists()) {
132 try {
131 try {
133 var imageId = Utils.readImageRef(imageIdFile);
132 var imageId = Utils.readImageRef(imageIdFile);
134 return imageExists(imageId.getId());
133 return imageExists(imageId.getId());
135 } catch (IOException | InterruptedException e) {
134 } catch (IOException | InterruptedException e) {
136 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
135 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
137 return false;
136 return false;
138 }
137 }
139 }
138 }
140 return false;
139 return false;
141 }
140 }
142 }
141 }
@@ -1,71 +1,83
1 package org.implab.gradle.containers.cli;
1 package org.implab.gradle.containers.cli;
2
2
3 import java.io.Serializable;
3 import java.io.Serializable;
4 import java.util.ArrayList;
4 import java.util.Optional;
5 import java.util.Optional;
5
6
6 import com.fasterxml.jackson.annotation.JsonCreator;
7 import com.fasterxml.jackson.annotation.JsonCreator;
7 import com.fasterxml.jackson.annotation.JsonProperty;
8 import com.fasterxml.jackson.annotation.JsonProperty;
8
9
9 public class ImageName implements Serializable {
10 public class ImageName implements Serializable {
10
11
11 private static final long serialVersionUID = -1990105923537254552L;
12 private static final long serialVersionUID = -1990105923537254552L;
12
13
13 final String authority;
14 final String authority;
14
15
15 final String name;
16 final String groupName;
17
18 final String localName;
16
19
17 final String tag;
20 final String tag;
18
21
19 public ImageName() {
22 @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
20 name = null;
23 public ImageName(
21 authority = null;
24 @JsonProperty("authority") String authority,
22 tag = null;
25 @JsonProperty("groupName") String groupName,
23 }
26 @JsonProperty("localName") String localName,
27 @JsonProperty("tag") String tag) {
28 if (localName == null || localName.isBlank())
29 throw new IllegalArgumentException("localName can't be null or blank string");
24
30
25 @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
26 private ImageName(@JsonProperty("authority") String authority, @JsonProperty("name") String name,
27 @JsonProperty("tag") String tag) {
28 this.authority = authority;
31 this.authority = authority;
29 this.name = name;
32 this.groupName = groupName;
33 this.localName = localName;
30 this.tag = tag;
34 this.tag = tag;
31 }
35 }
32
36
33 public String getAuthority() {
37 public Optional<String> getAuthority() {
34 return authority;
38 return Optional.ofNullable(authority);
35 }
39 }
36
40
37 public ImageName authority(String authority) {
41 public ImageName withAuthority(String authority) {
38 return new ImageName(authority, name, tag);
42 return new ImageName(authority, groupName, localName, tag);
39 }
43 }
40
44
41 public String getName() {
45 public Optional<String> getGroupName() {
42 return name;
46 return Optional.ofNullable(groupName);
47 }
48
49 public ImageName withGroupName(String groupName) {
50 return new ImageName(authority, groupName, localName, tag);
43 }
51 }
44
52
45 public ImageName name(String name) {
53 public String getLocalName() {
46 return new ImageName(authority, name, tag);
54 return localName;
47 }
55 }
48
56
49 public String getTag() {
57 public ImageName withLocalName(String localName) {
50 return tag;
58 if (localName == null || localName.isBlank())
59 throw new IllegalArgumentException("localName can't be null or blank string");
60 return new ImageName(authority, groupName, localName, tag);
51 }
61 }
52
62
53 public ImageName tag(String tag) {
63 public Optional<String> getTag() {
54 return new ImageName(authority, name, tag);
64 return Optional.ofNullable(tag);
55 }
65 }
56
66
57 public ImageName copy() {
67 public ImageName withTag(String tag) {
58 return new ImageName(authority, name, tag);
68 return new ImageName(authority, groupName, localName, tag);
59 }
69 }
60
70
61 @Override
71 @Override
62 public String toString() {
72 public String toString() {
63 StringBuilder sb = new StringBuilder();
73 var path = new ArrayList<String>();
64
74
65 Optional.ofNullable(authority).ifPresent(s -> sb.append(s).append("/"));
75 getAuthority().ifPresent(path::add);
66 Optional.ofNullable(name).ifPresent(s -> sb.append(s));
76 getGroupName().ifPresent(path::add);
67 Optional.ofNullable(tag).ifPresent(s -> sb.append(":").append(s));
77 path.add(getLocalName());
68
78
69 return sb.toString();
79 var repo = String.join("/", path);
80
81 return getTag().map(tag -> String.join(":", repo, tag)).orElse(repo);
70 }
82 }
71 }
83 }
@@ -1,116 +1,116
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.util.ArrayList;
3 import java.util.ArrayList;
4 import java.util.HashMap;
4 import java.util.HashMap;
5 import java.util.List;
5 import java.util.List;
6 import java.util.Map;
6 import java.util.Map;
7
7
8 import org.gradle.api.Action;
8 import org.gradle.api.Action;
9 import org.gradle.api.file.Directory;
9 import org.gradle.api.file.Directory;
10 import org.gradle.api.file.DirectoryProperty;
10 import org.gradle.api.file.DirectoryProperty;
11 import org.gradle.api.file.RegularFile;
11 import org.gradle.api.file.RegularFile;
12 import org.gradle.api.file.RegularFileProperty;
12 import org.gradle.api.file.RegularFileProperty;
13 import org.gradle.api.provider.MapProperty;
13 import org.gradle.api.provider.MapProperty;
14 import org.gradle.api.provider.Property;
14 import org.gradle.api.provider.Property;
15 import org.gradle.api.provider.Provider;
15 import org.gradle.api.provider.Provider;
16 import org.gradle.api.tasks.Input;
16 import org.gradle.api.tasks.Input;
17 import org.gradle.api.tasks.InputDirectory;
17 import org.gradle.api.tasks.InputDirectory;
18 import org.gradle.api.tasks.Internal;
18 import org.gradle.api.tasks.Internal;
19 import org.gradle.api.tasks.Optional;
19 import org.gradle.api.tasks.Optional;
20 import org.gradle.api.tasks.OutputFile;
20 import org.gradle.api.tasks.OutputFile;
21 import org.gradle.api.tasks.SkipWhenEmpty;
21 import org.gradle.api.tasks.SkipWhenEmpty;
22 import org.gradle.api.tasks.TaskAction;
22 import org.gradle.api.tasks.TaskAction;
23 import java.io.File;
23 import java.io.File;
24
24
25 import org.implab.gradle.containers.cli.ImageName;
26 import org.implab.gradle.containers.cli.ImageRef;
25 import org.implab.gradle.containers.cli.ImageRef;
27 import org.implab.gradle.containers.cli.Utils;
26 import org.implab.gradle.containers.cli.Utils;
28 import org.implab.gradle.containers.dsl.MapPropertyEntry;
27 import org.implab.gradle.containers.dsl.MapPropertyEntry;
29 import org.implab.gradle.containers.dsl.OptionsMixin;
28 import org.implab.gradle.containers.dsl.OptionsMixin;
30
29
31 import groovy.lang.Closure;
30 import groovy.lang.Closure;
32
31
33 public abstract class BuildImage extends DockerCliTask implements OptionsMixin {
32 public abstract class BuildImage extends DockerCliTask implements OptionsMixin {
33
34 @InputDirectory
34 @InputDirectory
35 @SkipWhenEmpty
35 @SkipWhenEmpty
36 public abstract DirectoryProperty getContextDirectory();
36 public abstract DirectoryProperty getContextDirectory();
37
37
38 @Input
38 @Input
39 public abstract MapProperty<String, String> getBuildArgs();
39 public abstract MapProperty<String, String> getBuildArgs();
40
40
41 @Input
41 @Input
42 @Optional
42 @Optional
43 public abstract Property<String> getBuildTarget();
43 public abstract Property<String> getBuildTarget();
44
44
45 @Input
45 @Input
46 public abstract Property<ImageName> getImageName();
46 public abstract Property<String> getImageName();
47
47
48 @Internal
48 @Internal
49 public abstract RegularFileProperty getImageIdFile();
49 public abstract RegularFileProperty getImageIdFile();
50
50
51 @OutputFile
51 @OutputFile
52 public Provider<File> getImageIdFileOutput() {
52 public Provider<File> getImageIdFileOutput() {
53 return getImageIdFile().map(RegularFile::getAsFile);
53 return getImageIdFile().map(RegularFile::getAsFile);
54 }
54 }
55
55
56 public BuildImage() {
56 public BuildImage() {
57 getOutputs().upToDateWhen(task -> getImageIdFile()
57 getOutputs().upToDateWhen(task -> getImageIdFile()
58 .map(RegularFile::getAsFile)
58 .map(RegularFile::getAsFile)
59 .map(docker()::imageExists)
59 .map(docker()::imageExists)
60 .getOrElse(false));
60 .getOrElse(false));
61 }
61 }
62
62
63 public void buildArgs(Action<Map<String, String>> spec) {
63 public void buildArgs(Action<Map<String, String>> spec) {
64 getBuildArgs().putAll(provider(() -> {
64 getBuildArgs().putAll(provider(() -> {
65 Map<String, String> args = new HashMap<>();
65 Map<String, String> args = new HashMap<>();
66 spec.execute(args);
66 spec.execute(args);
67 getLogger().info("add buildArgs {}", args);
67 getLogger().info("add buildArgs {}", args);
68 return args;
68 return args;
69 }));
69 }));
70 }
70 }
71
71
72 public void buildArgs(Closure<Map<String, String>> spec) {
72 public void buildArgs(Closure<Map<String, String>> spec) {
73 buildArgs(Utils.wrapClosure(spec));
73 buildArgs(Utils.wrapClosure(spec));
74 }
74 }
75
75
76 public MapPropertyEntry<String, String> buildArg(String key) {
76 public MapPropertyEntry<String, String> buildArg(String key) {
77 return new MapPropertyEntry<String, String>(getBuildArgs(), key, getProviders());
77 return new MapPropertyEntry<String, String>(getBuildArgs(), key, getProviders());
78 }
78 }
79
79
80 @TaskAction
80 @TaskAction
81 public void run() throws Exception {
81 public void run() throws Exception {
82 List<String> args = new ArrayList<>();
82 List<String> args = new ArrayList<>();
83
83
84 // create a temp file to store image id
84 // create a temp file to store image id
85 var iidFile = new File(this.getTemporaryDir(), "iid");
85 var iidFile = new File(this.getTemporaryDir(), "iid");
86
86
87 // specify where to write image id
87 // specify where to write image id
88 args.addAll(List.of("--iidfile", iidFile.getAbsolutePath()));
88 args.addAll(List.of("--iidfile", iidFile.getAbsolutePath()));
89
89
90 getBuildArgs().get().forEach((k, v) -> {
90 getBuildArgs().get().forEach((k, v) -> {
91 args.add("--build-arg");
91 args.add("--build-arg");
92 args.add(String.format("%s=%s", k, v));
92 args.add(String.format("%s=%s", k, v));
93 });
93 });
94
94
95 // add --target if specified for multi-stage build
95 // add --target if specified for multi-stage build
96 if (getBuildTarget().isPresent()) {
96 if (getBuildTarget().isPresent()) {
97 args.add("--target");
97 args.add("--target");
98 args.add(getBuildTarget().get());
98 args.add(getBuildTarget().get());
99 }
99 }
100
100
101 // add extra parameters
101 // add extra parameters
102 getOptions().get().forEach(args::add);
102 getOptions().get().forEach(args::add);
103
103
104 var imageTag = getImageName().map(ImageName::toString).get();
104 var imageTag = getImageName().get();
105
105
106 // build image
106 // build image
107 docker().buildImage(
107 docker().buildImage(
108 imageTag,
108 imageTag,
109 getContextDirectory().map(Directory::getAsFile).get(),
109 getContextDirectory().map(Directory::getAsFile).get(),
110 args);
110 args);
111
111
112 // read the built image id and store image ref metadata
112 // read the built image id and store image ref metadata
113 var imageRef = new ImageRef(imageTag, Utils.readAll(iidFile));
113 var imageRef = new ImageRef(imageTag, Utils.readAll(iidFile));
114 Utils.writeJson(getImageIdFileOutput().get(), imageRef);
114 Utils.writeJson(getImageIdFile().map(RegularFile::getAsFile).get(), imageRef);
115 }
115 }
116 }
116 }
@@ -1,33 +1,32
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4
4
5 import org.gradle.api.provider.ListProperty;
5 import org.gradle.api.provider.ListProperty;
6 import org.gradle.api.provider.Property;
6 import org.gradle.api.provider.Property;
7 import org.gradle.api.tasks.Input;
7 import org.gradle.api.tasks.Input;
8 import org.gradle.api.tasks.Optional;
8 import org.gradle.api.tasks.Optional;
9 import org.gradle.api.tasks.TaskAction;
9 import org.gradle.api.tasks.TaskAction;
10 import org.implab.gradle.containers.cli.ImageName;
11 import org.implab.gradle.containers.dsl.OptionsMixin;
10 import org.implab.gradle.containers.dsl.OptionsMixin;
12
11
13 public abstract class PushImage extends DockerCliTask implements OptionsMixin {
12 public abstract class PushImage extends DockerCliTask implements OptionsMixin {
14
13
15 @Input
14 @Input
16 public abstract Property<ImageName> getImageName();
15 public abstract Property<String> getImageName();
17
16
18 @Input
17 @Input
19 @Optional
18 @Optional
20 public abstract Property<String> getTransport();
19 public abstract Property<String> getTransport();
21
20
22 @Input
21 @Input
23 @Optional
22 @Optional
24 public abstract ListProperty<String> getOptions();
23 public abstract ListProperty<String> getOptions();
25
24
26 @TaskAction
25 @TaskAction
27 public void run() throws InterruptedException, IOException {
26 public void run() throws InterruptedException, IOException {
28
27
29 docker().pushImage(
28 docker().pushImage(
30 getImageName().get().toString(),
29 getImageName().get(),
31 getOptions().get());
30 getOptions().get());
32 }
31 }
33 }
32 }
@@ -1,43 +1,42
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import org.gradle.api.Action;
4 import org.gradle.api.Action;
5 import org.gradle.api.provider.ListProperty;
5 import org.gradle.api.provider.ListProperty;
6 import org.gradle.api.provider.Property;
6 import org.gradle.api.provider.Property;
7 import org.gradle.api.tasks.Input;
7 import org.gradle.api.tasks.Input;
8 import org.gradle.api.tasks.Optional;
8 import org.gradle.api.tasks.Optional;
9 import org.implab.gradle.containers.cli.ImageName;
10 import org.implab.gradle.containers.dsl.OptionsMixin;
9 import org.implab.gradle.containers.dsl.OptionsMixin;
11 import org.implab.gradle.containers.dsl.VolumeSpec;
10 import org.implab.gradle.containers.dsl.VolumeSpec;
12
11
13 public abstract class RunImage extends DockerCliTask implements OptionsMixin {
12 public abstract class RunImage extends DockerCliTask implements OptionsMixin {
14
13
15 @Input
14 @Input
16 public abstract Property<ImageName> getImageName();
15 public abstract Property<String> getImageName();
17
16
18 @Input
17 @Input
19 @Optional
18 @Optional
20 public abstract ListProperty<String> getCommandLine();
19 public abstract ListProperty<String> getCommandLine();
21
20
22
21
23 public void volume(Action<VolumeSpec> configure) {
22 public void volume(Action<VolumeSpec> configure) {
24 getOptions().add("-v");
23 getOptions().add("-v");
25 getOptions().add(provider(() -> {
24 getOptions().add(provider(() -> {
26 var volumeSpec = getObjectFactory().newInstance(VolumeSpec.class);
25 var volumeSpec = getObjectFactory().newInstance(VolumeSpec.class);
27 configure.execute(volumeSpec);
26 configure.execute(volumeSpec);
28 return volumeSpec.resolveSpec();
27 return volumeSpec.resolveSpec();
29 }));
28 }));
30 }
29 }
31
30
32 void commandLine(String ...args) {
31 void commandLine(String ...args) {
33 getCommandLine().addAll(args);
32 getCommandLine().addAll(args);
34 }
33 }
35
34
36 public void run() throws InterruptedException, IOException {
35 public void run() throws InterruptedException, IOException {
37 docker().runImage(
36 docker().runImage(
38 getImageName().get().toString(),
37 getImageName().get(),
39 getOptions().get(),
38 getOptions().get(),
40 getCommandLine().get());
39 getCommandLine().get());
41 }
40 }
42
41
43 }
42 }
@@ -1,22 +1,21
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.IOException;
3 import java.io.IOException;
4 import org.gradle.api.provider.Property;
4 import org.gradle.api.provider.Property;
5 import org.gradle.api.tasks.Input;
5 import org.gradle.api.tasks.Input;
6 import org.gradle.api.tasks.TaskAction;
6 import org.gradle.api.tasks.TaskAction;
7 import org.implab.gradle.containers.cli.ImageName;
8
7
9 public abstract class TagImage extends DockerCliTask {
8 public abstract class TagImage extends DockerCliTask {
10 @Input
9 @Input
11 public abstract Property<ImageName> getSrcImage();
10 public abstract Property<String> getSrcImage();
12
11
13 @Input
12 @Input
14 public abstract Property<ImageName> getDestImage();
13 public abstract Property<String> getDestImage();
15
14
16 @TaskAction
15 @TaskAction
17 public void run() throws InterruptedException, IOException {
16 public void run() throws InterruptedException, IOException {
18 docker().tagImage(
17 docker().tagImage(
19 getSrcImage().map(ImageName::toString).get(),
18 getSrcImage().get(),
20 getDestImage().map(ImageName::toString).get());
19 getDestImage().get());
21 }
20 }
22 }
21 }
General Comments 0
You need to be logged in to leave comments. Login now