##// END OF EJS Templates
Working version of containers plugin, extracted container-base plugin for basic types and configuration
cin -
r10:902127e66316 default
parent child
Show More
@@ -0,0 +1,49
1 package org.implab.gradle.containers;
2
3 import org.gradle.api.Plugin;
4 import org.gradle.api.Project;
5 import org.gradle.api.plugins.ExtraPropertiesExtension;
6 import org.implab.gradle.containers.tasks.BuildImage;
7 import org.implab.gradle.containers.tasks.PushImage;
8 import org.implab.gradle.containers.tasks.RunImage;
9 import org.implab.gradle.containers.tasks.SaveImage;
10 import org.implab.gradle.containers.tasks.TagImage;
11
12 public class ContainerBasePlugin implements Plugin<Project> {
13 public static final String CONTAINER_EXTENSION_NAME = "container";
14
15 ContainerExtension containerExtension;
16
17 ContainerExtension getContainerExtension() {
18 if (containerExtension == null)
19 throw new IllegalStateException();
20 return containerExtension;
21 }
22
23 void exportClasses(Project project, Class<?>... classes) {
24 ExtraPropertiesExtension extras = project.getExtensions().getExtraProperties();
25 for (var clazz : classes)
26 extras.set(clazz.getSimpleName(), clazz);
27 }
28
29 @Override
30 public void apply(Project project) {
31
32 containerExtension = project.getObjects().newInstance(ContainerExtension.class);
33
34 containerExtension.getImageAuthority()
35 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
36
37 containerExtension.getImageGroup()
38 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
39
40 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
41
42 exportClasses(
43 project,
44 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunImage.class);
45
46
47 }
48
49 }
@@ -0,0 +1,38
1 package org.implab.gradle.containers.cli;
2
3 import java.io.Serializable;
4 import java.util.Optional;
5
6 import com.fasterxml.jackson.annotation.JsonCreator;
7 import com.fasterxml.jackson.annotation.JsonProperty;
8
9 public class ImageRef implements Serializable {
10
11 private static final long serialVersionUID = 2023111901L;
12
13 final String tag;
14
15 final String id;
16
17 @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
18 public ImageRef(@JsonProperty("tag") String tag, @JsonProperty("id") String id) {
19 this.id = id;
20 this.tag = tag;
21 }
22
23 public String getId() {
24 return id;
25 }
26
27 public ImageRef withId(String id) {
28 return new ImageRef(tag, id);
29 }
30
31 public Optional<String> getTag() {
32 return Optional.ofNullable(tag);
33 }
34
35 public ImageRef withTag(String tag) {
36 return new ImageRef(tag, id);
37 }
38 }
@@ -0,0 +1,39
1 package org.implab.gradle.containers.dsl;
2
3 import java.util.concurrent.Callable;
4
5 import org.gradle.api.provider.ListProperty;
6 import org.gradle.api.tasks.Input;
7 import org.implab.gradle.containers.PropertiesMixin;
8
9 import groovy.lang.Closure;
10
11 public interface OptionsMixin extends PropertiesMixin {
12
13 @Input
14 ListProperty<String> getOptions();
15
16 default void option(String opt) {
17 getOptions().add(opt);
18 }
19
20 default void option(Callable<String> optionProvider) {
21 getOptions().add(provider(optionProvider));
22 }
23
24 default void option(Closure<String> optionProvider) {
25 getOptions().add(provider(optionProvider));
26 }
27
28 default void options(String ...opts) {
29 getOptions().addAll(opts);
30 }
31
32 default void options(Callable<Iterable<String>> optionsProvider) {
33 getOptions().addAll(provider(optionsProvider));
34 }
35
36 default void options(Closure<Iterable<String>> optionsProvider) {
37 getOptions().addAll(provider(optionsProvider));
38 }
39 }
@@ -1,40 +1,57
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 {
9 implementation "com.fasterxml.jackson.core:jackson-core:2.13.5",
10 "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5"
11 }
12
13 java {
14 targetCompatibility = 11
15 sourceCompatibility = 11
16 }
17
8
18
9 repositories {
19 repositories {
10 mavenCentral()
20 mavenCentral()
11 }
21 }
12
22
13 gradlePlugin {
23 gradlePlugin {
14 website = 'https://code.implab.org/implab/gradle-container-plugin'
24 website = 'https://code.implab.org/implab/gradle-container-plugin'
15 vcsUrl = 'https://code.implab.org/implab/gradle-container-plugin'
25 vcsUrl = 'https://code.implab.org/implab/gradle-container-plugin'
16 plugins {
26 plugins {
17 containerPlugin {
27 containerPlugin {
18 id = 'org.implab.gradle-container'
28 id = 'org.implab.gradle-container'
19 displayName = "Container building plugin"
29 displayName = "Provdes convetional configuration to build a container image"
20 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.'
21 implementationClass = 'org.implab.gradle.containers.ContainerPlugin'
31 implementationClass = 'org.implab.gradle.containers.ContainerPlugin'
22 tags.set(['containers', 'image', 'docker', 'podman'])
32 tags.set(['containers', 'image', 'docker', 'podman'])
23 }
33 }
34 containerBasePlugin {
35 id = 'org.implab.gradle-container-base'
36 displayName = "Provides tasks to build manipulate container images"
37 description = 'Build and publish container images with docker or podman. Simple wrapper around cli.'
38 implementationClass = 'org.implab.gradle.containers.ContainerBasePlugin'
39 tags.set(['containers', 'image', 'docker', 'podman'])
40 }
24 }
41 }
25 }
42 }
26
43
27
44
28 task printVersion {
45 task printVersion {
29 doLast {
46 doLast {
30 println "${->jar.archiveFileName.get()}"
47 println "${->jar.archiveFileName.get()}"
31 }
48 }
32 }
49 }
33
50
34 publishing {
51 publishing {
35 repositories {
52 repositories {
36 ivy {
53 ivy {
37 url "${System.properties["user.home"]}/ivy-repo"
54 url "${System.properties["user.home"]}/ivy-repo"
38 }
55 }
39 }
56 }
40 }
57 }
@@ -1,156 +1,85
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers;
2
2
3 import java.io.File;
4 import java.io.FileNotFoundException;
5 import java.io.IOException;
3 import java.util.Optional;
6 import java.util.Optional;
4
7
8 import javax.inject.Inject;
9
5 import org.gradle.api.Project;
10 import org.gradle.api.Project;
6 import org.gradle.api.file.Directory;
7 import org.gradle.api.file.DirectoryProperty;
11 import org.gradle.api.file.DirectoryProperty;
8 import org.gradle.api.file.ProjectLayout;
12 import org.gradle.api.file.ProjectLayout;
9 import org.gradle.api.file.RegularFile;
10 import org.gradle.api.file.RegularFileProperty;
13 import org.gradle.api.file.RegularFileProperty;
11 import org.gradle.api.model.ObjectFactory;
12 import org.gradle.api.provider.Property;
14 import org.gradle.api.provider.Property;
13 import org.gradle.api.provider.Provider;
15 import org.gradle.api.provider.Provider;
14
16 import org.implab.gradle.containers.cli.ImageName;
15 public class ContainerExtension {
17 import org.implab.gradle.containers.cli.ImageRef;
16
18 import org.implab.gradle.containers.cli.Utils;
17 private final Property<String> cliCmd;
18
19
19 private final Property<String> imageAuthority;
20 public abstract class ContainerExtension {
20
21 private final Property<String> imageGroup;
22
21
23 private final Property<String> imageShortName;
22 public abstract Property<String> getCliCmd();
24
25 private final Property<String> imageTag;
26
23
27 private final Property<ImageName> imageName;
24 public abstract DirectoryProperty getContextDirectory();
28
25
29 private final DirectoryProperty contextDir;
26 public abstract RegularFileProperty getImageIdFile();
30
31 private final RegularFileProperty imageIdFile;
32
27
33 public ContainerExtension(ObjectFactory factory, ProjectLayout layout, Project project) {
28 /**
34 contextDir = factory.directoryProperty();
29 * Specifies the name of the registry where the image is located
35 contextDir.convention(layout.getBuildDirectory().dir("context"));
30 * {@code registry.my-company.org}
36
31 */
37 imageIdFile = factory.fileProperty();
32 public abstract Property<String> getImageAuthority();
38 imageIdFile.convention(layout.getBuildDirectory().file("imageId"));
39
40 cliCmd = factory.property(String.class);
41 cliCmd.set("docker");
42
33
43 imageAuthority = factory.property(String.class);
34 /**
44 imageGroup = factory.property(String.class);
35 * Specified the path of the image like {@code my-company}
45 imageShortName = factory.property(String.class);
36 */
46
37 public abstract Property<String> getImageGroup();
47 imageShortName.convention(project.getName());
48
38
49 imageTag = factory.property(String.class);
39 public abstract Property<ImageName> getImageName();
50 imageTag.set(project
51 .provider(() -> Optional.ofNullable(project.getVersion()).map(v -> v.toString()).orElse("latest")));
52
53 Provider<String> imageRepository = imageGroup.map(g -> g + "/" + imageShortName.get()).orElse(imageShortName);
54
40
55 imageName = factory.property(ImageName.class);
41 /**
56 imageName.convention(project.provider(
42 * Specifies local image part like {@code httpd}
57 () -> new ImageName().authority(imageAuthority.get()).name(imageRepository.get()).tag(imageTag.get())));
43 */
58 }
44 public abstract Property<String> getImageLocalName();
59
60 public Property<String> getCliCmd() {
61 return cliCmd;
62 }
63
45
64 public void setCliCmd(String cliCmd) {
46 /**
65 this.cliCmd.set(cliCmd);
47 * This property is deprecated use imageLocalName
66 }
48 */
67
49 @Deprecated
68 public DirectoryProperty getContextDirectory() {
50 public Property<String> getImageShortName() {
69 return contextDir;
51 return getImageLocalName();
70 }
71
72 public void setContextDirectory(Directory dir) {
73 contextDir.set(dir);
74 }
52 }
75
53
76 public void setContextDirectory(Provider<Directory> dir) {
54 public abstract Property<String> getImageTag();
77 contextDir.set(dir);
78 }
79
80 public RegularFileProperty getImageIdFile() {
81 return imageIdFile;
82 }
83
84 public void setImageIdFile(RegularFile file) {
85 imageIdFile.set(file);
86 }
87
88 public void setImageIdFile(Provider<RegularFile> file) {
89 imageIdFile.set(file);
90 }
91
55
92 public Property<String> getImageAuthority() {
56 @Inject
93 return imageAuthority;
57 public ContainerExtension(ProjectLayout layout, Project project) {
94 }
58 getContextDirectory().convention(layout.getBuildDirectory().dir("context"));
95
96 public void setImageAuthority(String value) {
97 imageAuthority.set(value);
98 }
99
59
100 public void setImageAuthority(Provider<String> value) {
60 getImageIdFile().convention(layout.getBuildDirectory().file("iid.json"));
101 imageAuthority.set(value);
102 }
103
61
104 public Property<String> getImageGroup() {
62 getCliCmd().set("docker");
105 return imageGroup;
106 }
107
108 public void setImageGroup(String value) {
109 imageGroup.set(value);
110 }
111
63
112 public void setImageGroup(Provider<String> value) {
64 getImageLocalName().convention(project.getName());
113 imageGroup.set(value);
114 }
115
116 public Property<ImageName> getImageName() {
117 return imageName;
118 }
119
65
120 public void setImageName(ImageName name) {
66 getImageTag().set(project
121 imageName.set(name);
67 .provider(() -> Optional.ofNullable(project.getVersion()).map(v -> v.toString()).orElse("latest")));
122 }
123
124 public void setImageName(Provider<ImageName> name) {
125 imageName.set(name);
126 }
127
68
128 public Property<String> getImageShortName() {
69 Provider<String> imageRepository = getImageGroup().map(g -> g + "/" + getImageLocalName().get())
129 return imageShortName;
70 .orElse(getImageLocalName());
130 }
131
132 public void setImageShortName(String name) {
133 imageShortName.set(name);
134 }
135
71
136 public void setImageShortName(Provider<String> name) {
72 getImageName().convention(project.provider(
137 imageShortName.set(name);
73 () -> new ImageName().authority(getImageAuthority().get()).name(imageRepository.get())
138 }
74 .tag(getImageTag().get())));
139
140 public Property<String> getImageTag() {
141 return imageTag;
142 }
143
144 public void setImageTag(String tag) {
145 imageTag.set(tag);
146 }
147
148 public void setImageTag(Provider<String> tag) {
149 imageTag.set(tag);
150 }
75 }
151
76
152 public ImageName createImageName() {
77 public ImageName createImageName() {
153 return new ImageName();
78 return new ImageName();
154 }
79 }
155
80
81 public ImageRef readImageRef(File file) throws FileNotFoundException, IOException {
82 return Utils.readImageRef(file);
83 }
84
156 } No newline at end of file
85 }
@@ -1,101 +1,82
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.Plugin;
4 import org.gradle.api.Plugin;
4 import org.gradle.api.Project;
5 import org.gradle.api.Project;
5 import org.gradle.api.artifacts.Dependency;
6 import org.gradle.api.artifacts.Dependency;
6 import org.gradle.api.file.ProjectLayout;
7 import org.gradle.api.file.ProjectLayout;
7 import org.gradle.api.model.ObjectFactory;
8 import org.gradle.api.plugins.ExtraPropertiesExtension;
9 import org.gradle.api.tasks.Copy;
8 import org.gradle.api.tasks.Copy;
10 import org.gradle.api.tasks.Delete;
9 import org.gradle.api.tasks.Delete;
11 import org.gradle.api.tasks.TaskProvider;
10 import org.gradle.api.tasks.TaskProvider;
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.RunImage;
15 import org.implab.gradle.containers.tasks.SaveImage;
14 import org.implab.gradle.containers.tasks.SaveImage;
16 import org.implab.gradle.containers.tasks.TagImage;
17
15
18 public class ContainerPlugin implements Plugin<Project> {
16 public class ContainerPlugin implements Plugin<Project> {
19
17
20 public static final String BUILD_GROUP = "build";
18 public static final String BUILD_GROUP = "build";
21
19
22 public static final String CONTAINER_EXTENSION_NAME = "container";
23
24 public static final String ARCHIVE_CONFIGURATION = "archive";
20 public static final String ARCHIVE_CONFIGURATION = "archive";
25
21
26 private ContainerExtension containerExtension;
22 ContainerExtension containerExtension;
27
28 void exportClasses(Project project, Class<?>... classes) {
29 ExtraPropertiesExtension extras = project.getExtensions().getExtraProperties();
30 for (var clazz : classes)
31 extras.set(clazz.getSimpleName(), clazz);
32 }
33
23
34 public void apply(Project project) {
24 public void apply(Project project) {
35 ObjectFactory factory = project.getObjects();
36 ProjectLayout layout = project.getLayout();
25 ProjectLayout layout = project.getLayout();
37 containerExtension = new ContainerExtension(factory, layout, project);
38
26
39 containerExtension.getImageAuthority()
27 project.getPluginManager().apply(ContainerBasePlugin.class);
40 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
41
28
42 containerExtension.getImageGroup()
29 var basePlugin = project.getPlugins().findPlugin(ContainerBasePlugin.class);
43 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
30 if (basePlugin == null)
31 throw new GradleException("The container-base plugin fails to be applied");
44
32
45 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
33 var containerExtension = basePlugin.getContainerExtension();
46
47 exportClasses(
48 project,
49 BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunImage.class);
50
34
51 project.getConfigurations().create(Dependency.DEFAULT_CONFIGURATION, c -> {
35 project.getConfigurations().create(Dependency.DEFAULT_CONFIGURATION, c -> {
52 c.setCanBeConsumed(true);
36 c.setCanBeConsumed(true);
53 c.setCanBeResolved(false);
37 c.setCanBeResolved(false);
54 });
38 });
55
39
56 project.getConfigurations().create(ARCHIVE_CONFIGURATION, c -> {
40 project.getConfigurations().create(ARCHIVE_CONFIGURATION, c -> {
57 c.setCanBeConsumed(true);
41 c.setCanBeConsumed(true);
58 c.setCanBeResolved(false);
42 c.setCanBeResolved(false);
59 });
43 });
60
44
61 TaskProvider<Copy> processResourcesTask = project.getTasks().register("processResources", Copy.class, t -> {
45 TaskProvider<Copy> processResourcesTask = project.getTasks().register("processResources", Copy.class, t -> {
62 t.setGroup(BUILD_GROUP);
46 t.setGroup(BUILD_GROUP);
63 t.from(layout.getProjectDirectory().dir("src/main"));
47 t.from(layout.getProjectDirectory().dir("src/main"));
64 t.into(containerExtension.getContextDirectory());
48 t.into(containerExtension.getContextDirectory());
65 });
49 });
66
50
67 TaskProvider<BuildImage> buildImageTask = project.getTasks().register("buildImage", BuildImage.class, t -> {
51 TaskProvider<BuildImage> buildImageTask = project.getTasks().register("buildImage", BuildImage.class, t -> {
68 t.setGroup(BUILD_GROUP);
52 t.setGroup(BUILD_GROUP);
69 t.dependsOn(processResourcesTask);
53 t.dependsOn(processResourcesTask);
70
54
71 t.getContextDirectory().set(containerExtension.getContextDirectory());
55 t.getContextDirectory().set(containerExtension.getContextDirectory());
72 t.getImageIdFile().set(containerExtension.getImageIdFile());
56 t.getImageIdFile().set(containerExtension.getImageIdFile());
73
57
74 t.getImageName().set(containerExtension.getImageName());
58 t.getImageName().set(containerExtension.getImageName());
75 });
59 });
76
60
77 project.getTasks().register("clean", Delete.class, t -> {
61 project.getTasks().register("clean", Delete.class, t -> {
78 t.delete(layout.getBuildDirectory());
62 t.delete(layout.getBuildDirectory());
79 });
63 });
80
64
81 project.getTasks().register("build", t -> {
65 project.getTasks().register("build", t -> {
82 t.setGroup(BUILD_GROUP);
66 t.setGroup(BUILD_GROUP);
83 t.dependsOn(buildImageTask);
67 t.dependsOn(buildImageTask);
84 });
68 });
85
69
86 project.getTasks().register("pushImage", PushImage.class, t -> {
70 project.getTasks().register("pushImage", PushImage.class, t -> {
87 t.dependsOn(buildImageTask);
71 t.dependsOn(buildImageTask);
88 t.getImageName().set(buildImageTask.flatMap(b -> b.getImageName()));
72 t.getImageName().set(buildImageTask.flatMap(BuildImage::getImageName));
89 });
73 });
90
74
91 project.getTasks().register("saveImage", SaveImage.class, t -> {
75 project.getTasks().register("saveImage", SaveImage.class, t -> {
92 t.dependsOn(buildImageTask);
76 t.dependsOn(buildImageTask);
93 t.getImage().set(buildImageTask.flatMap(b -> b.getImageName()));
77 t.getExportImages().add(buildImageTask.flatMap(BuildImage::getImageName).map(ImageName::toString));
94 });
78 });
95
79
96 project.getArtifacts().add(Dependency.DEFAULT_CONFIGURATION, buildImageTask.flatMap(x -> x.getImageIdFile()),
80 project.getArtifacts().add(Dependency.DEFAULT_CONFIGURATION, buildImageTask);
97 t -> {
98 t.builtBy(buildImageTask);
99 });
100 }
81 }
101 }
82 }
@@ -1,67 +1,142
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;
5 import java.nio.file.Files;
6 import java.util.ArrayList;
6 import java.util.ArrayList;
7 import java.util.Arrays;
7 import java.util.Arrays;
8 import java.util.List;
8 import java.util.List;
9 import java.util.Optional;
9 import java.util.Optional;
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
15 public final String BUILD_COMMAND = "build";
16 public final String PUSH_COMMAND = "push";
17 public final String RUN_COMMAND = "run";
18 public final String SAVE_COMMAND = "save";
19 public final String INSPECT_COMMAND = "inspect";
20 public final String IMAGE_COMMAND = "image";
21 public final String TAG_COMMAND = "tag";
22
14 public abstract Logger getLogger();
23 public abstract Logger getLogger();
15
24
16 public abstract Optional<File> getWorkingDir();
25 public abstract Optional<File> getWorkingDir();
17
26
18 public abstract String getCliCmd();
27 public abstract String getCliCmd();
19
28
20 boolean execute(ProcessBuilder builder, int code) throws IOException, InterruptedException {
29 Process startProcess(ProcessBuilder builder) throws IOException {
21 var proc = builder.start();
30 getLogger().info("Starting: {}", builder.command());
31 return builder.start();
32 }
33
34 protected boolean checkRetCode(Process proc, int code) throws InterruptedException {
35 if (getLogger().isInfoEnabled()) {
36 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
37 Utils.redirectIO(proc.getErrorStream(), getLogger()::info);
38 }
39
22 return proc.waitFor() == code;
40 return proc.waitFor() == code;
23 }
41 }
24
42
43 protected void complete(Process proc) throws InterruptedException, IOException {
44 if (getLogger().isInfoEnabled())
45 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
46
47 if (getLogger().isErrorEnabled())
48 Utils.redirectIO(proc.getErrorStream(), getLogger()::error);
49
50 var code = proc.waitFor();
51 if (code != 0) {
52 getLogger().error("The process exited with code {}", code);
53 throw new IOException("The process exited with error code " + code);
54 }
55 }
56
25 protected ProcessBuilder builder(String... args) {
57 protected ProcessBuilder builder(String... args) {
26 var command = new ArrayList<String>(args.length + 1);
58 var argsList = new ArrayList<String>(args.length + 1);
59 Arrays.stream(args).forEach(argsList::add);
60
61 return builder(argsList);
62 }
63
64 protected ProcessBuilder builder(List<String> args) {
65 var command = new ArrayList<String>(args.size() + 1);
66
27 command.add(getCliCmd());
67 command.add(getCliCmd());
28 Arrays.stream(args).forEach(command::add);
68 args.forEach(command::add);
29
69
30 var builder = new ProcessBuilder(command);
70 var builder = new ProcessBuilder(command);
31
71
32 getWorkingDir().ifPresent(builder::directory);
72 getWorkingDir().ifPresent(builder::directory);
33 return builder();
73 return builder;
74 }
75
76 public void buildImage(String imageName, File contextDirectory, List<String> options)
77 throws IOException, InterruptedException {
78 var args = new ArrayList<String>();
79 args.add(BUILD_COMMAND);
80 args.addAll(options);
81 args.add("-t");
82 args.add(imageName);
83 args.add(contextDirectory.getAbsolutePath());
84 complete(startProcess(builder(args)));
85 }
86
87 public void pushImage(String image, List<String> options) throws InterruptedException, IOException {
88 var args = new ArrayList<String>();
89 args.add(PUSH_COMMAND);
90 args.addAll(options);
91 args.add(image);
92 complete(startProcess(builder(args)));
34 }
93 }
35
94
36 protected ProcessBuilder builder(List<String> command) {
95 public void runImage(String image, List<String> options, List<String> command)
37 var builder = new ProcessBuilder(command);
96 throws InterruptedException, IOException {
38
97 var args = new ArrayList<String>();
39 getWorkingDir().ifPresent(builder::directory);
98 args.add(RUN_COMMAND);
40 return builder();
99 args.addAll(options);
100 args.add(image);
101 args.addAll(command);
102 complete(startProcess(builder(args)));
41 }
103 }
42
104
43 public void build(List<String> args) {
105 public void saveImage(List<String> images, File output) throws InterruptedException, IOException {
106 if (output.exists())
107 output.delete();
44
108
109 var args = new ArrayList<String>();
110 args.add(SAVE_COMMAND);
111 args.add("-o");
112 args.add(output.getAbsolutePath());
113 images.forEach(args::add);
114
115 complete(startProcess(builder(args)));
116 }
117
118 public void tagImage(String source, String target) throws InterruptedException, IOException {
119 complete(startProcess(builder(TAG_COMMAND, source, target)));
45 }
120 }
46
121
47 public boolean imageExists(String imageId) throws InterruptedException, IOException {
122 public boolean imageExists(String imageId) throws InterruptedException, IOException {
48 getLogger().info("Check image {} exists", imageId);
123 getLogger().info("Check image {} exists", imageId);
49
124
50 var builder = builder("image", "inspect", "--format", "image-exists", imageId);
125 return checkRetCode(
51
126 startProcess(builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId)),
52 return execute(builder, 0);
127 0);
53 }
128 }
54
129
55 public boolean imageExists(File imageIdFile) {
130 public boolean imageExists(File imageIdFile) {
56 if (imageIdFile.exists()) {
131 if (imageIdFile.exists()) {
57 try {
132 try {
58 var imageId = Files.readString(imageIdFile.toPath());
133 var imageId = Utils.readImageRef(imageIdFile);
59 return imageExists(imageId);
134 return imageExists(imageId.getId());
60 } catch (IOException | InterruptedException e) {
135 } catch (IOException | InterruptedException e) {
61 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
136 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
62 return false;
137 return false;
63 }
138 }
64 }
139 }
65 return false;
140 return false;
66 }
141 }
67 }
142 }
@@ -1,66 +1,71
1 package org.implab.gradle.containers;
1 package org.implab.gradle.containers.cli;
2
2
3 import java.io.Serializable;
3 import java.io.Serializable;
4 import java.util.Optional;
4 import java.util.Optional;
5
5
6 import com.fasterxml.jackson.annotation.JsonCreator;
7 import com.fasterxml.jackson.annotation.JsonProperty;
8
6 public class ImageName implements Serializable {
9 public class ImageName implements Serializable {
7
10
8 private static final long serialVersionUID = -1990105923537254552L;
11 private static final long serialVersionUID = -1990105923537254552L;
9
12
10 final String authority;
13 final String authority;
11
14
12 final String name;
15 final String name;
13
16
14 final String tag;
17 final String tag;
15
18
16 public ImageName() {
19 public ImageName() {
17 name = null;
20 name = null;
18 authority = null;
21 authority = null;
19 tag = null;
22 tag = null;
20 }
23 }
21
24
22 private ImageName(String authority, String name, String tag) {
25 @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
26 private ImageName(@JsonProperty("authority") String authority, @JsonProperty("name") String name,
27 @JsonProperty("tag") String tag) {
23 this.authority = authority;
28 this.authority = authority;
24 this.name = name;
29 this.name = name;
25 this.tag = tag;
30 this.tag = tag;
26 }
31 }
27
32
28 public String getAuthority() {
33 public String getAuthority() {
29 return authority;
34 return authority;
30 }
35 }
31
36
32 public ImageName authority(String authority) {
37 public ImageName authority(String authority) {
33 return new ImageName(authority, name, tag);
38 return new ImageName(authority, name, tag);
34 }
39 }
35
40
36 public String getName() {
41 public String getName() {
37 return name;
42 return name;
38 }
43 }
39
44
40 public ImageName name(String name) {
45 public ImageName name(String name) {
41 return new ImageName(authority, name, tag);
46 return new ImageName(authority, name, tag);
42 }
47 }
43
48
44 public String getTag() {
49 public String getTag() {
45 return tag;
50 return tag;
46 }
51 }
47
52
48 public ImageName tag(String tag) {
53 public ImageName tag(String tag) {
49 return new ImageName(authority, name, tag);
54 return new ImageName(authority, name, tag);
50 }
55 }
51
56
52 public ImageName copy() {
57 public ImageName copy() {
53 return new ImageName(authority, name, tag);
58 return new ImageName(authority, name, tag);
54 }
59 }
55
60
56 @Override
61 @Override
57 public String toString() {
62 public String toString() {
58 StringBuilder sb = new StringBuilder();
63 StringBuilder sb = new StringBuilder();
59
64
60 Optional.ofNullable(authority).ifPresent(s -> sb.append(s).append("/"));
65 Optional.ofNullable(authority).ifPresent(s -> sb.append(s).append("/"));
61 Optional.ofNullable(name).ifPresent(s -> sb.append(s));
66 Optional.ofNullable(name).ifPresent(s -> sb.append(s));
62 Optional.ofNullable(tag).ifPresent(s-> sb.append(":").append(s));
67 Optional.ofNullable(tag).ifPresent(s -> sb.append(":").append(s));
63
68
64 return sb.toString();
69 return sb.toString();
65 }
70 }
66 }
71 }
@@ -1,93 +1,139
1 package org.implab.gradle.containers.cli;
1 package org.implab.gradle.containers.cli;
2
2
3 import java.io.ByteArrayOutputStream;
3 import java.io.ByteArrayOutputStream;
4 import java.io.Closeable;
4 import java.io.Closeable;
5 import java.io.File;
5 import java.io.File;
6 import java.io.FileInputStream;
6 import java.io.FileNotFoundException;
7 import java.io.FileOutputStream;
7 import java.io.FileOutputStream;
8 import java.io.FileReader;
8 import java.io.IOException;
9 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.InputStream;
10 import java.io.OutputStream;
11 import java.io.OutputStream;
11 import java.nio.file.Files;
12 import java.io.Reader;
12 import java.util.Scanner;
13 import java.util.Scanner;
14 import java.util.stream.StreamSupport;
15 import java.nio.file.Files;
16 import java.util.List;
17 import org.gradle.api.Action;
18 import org.gradle.internal.impldep.org.bouncycastle.util.Iterable;
13
19
14 import org.gradle.api.Action;
20 import com.fasterxml.jackson.core.exc.StreamWriteException;
21 import com.fasterxml.jackson.databind.DatabindException;
22 import com.fasterxml.jackson.databind.ObjectMapper;
23 import com.fasterxml.jackson.databind.SerializationFeature;
24 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
15
25
16 import groovy.json.JsonGenerator;
26 import groovy.json.JsonGenerator;
17 import groovy.json.JsonOutput;
27 import groovy.json.JsonOutput;
18 import groovy.json.JsonGenerator.Converter;
28 import groovy.json.JsonGenerator.Converter;
19 import groovy.lang.Closure;
29 import groovy.lang.Closure;
20
30
21 public final class Utils {
31 public final class Utils {
22 public static void redirectIO(final InputStream src, final Action<String> consumer) {
32 public static void redirectIO(final InputStream src, final Action<String> consumer) {
23 new Thread(() -> {
33 new Thread(() -> {
24 try (Scanner sc = new Scanner(src)) {
34 try (Scanner sc = new Scanner(src)) {
25 while (sc.hasNextLine()) {
35 while (sc.hasNextLine()) {
26 consumer.execute(sc.nextLine());
36 consumer.execute(sc.nextLine());
27 }
37 }
28 }
38 }
29 }).start();
39 }).start();
30 }
40 }
31
41
32 public static void redirectIO(final InputStream src, final File file) {
42 public static void redirectIO(final InputStream src, final File file) {
33 new Thread(() -> {
43 new Thread(() -> {
34 try (OutputStream out = new FileOutputStream(file)) {
44 try (OutputStream out = new FileOutputStream(file)) {
35 src.transferTo(out);
45 src.transferTo(out);
36 } catch (Exception e) {
46 } catch (Exception e) {
37 // silence!
47 // silence!
38 }
48 }
39 }).start();
49 }).start();
40 }
50 }
41
51
42 public static void closeSilent(Closeable handle) {
52 public static void closeSilent(Closeable handle) {
43 try {
53 try {
44 handle.close();
54 handle.close();
45 } catch (Exception e) {
55 } catch (Exception e) {
46 // silence!
56 // silence!
47 }
57 }
48 }
58 }
49
59
50 public static String readAll(final InputStream src) throws IOException {
60 public static String readAll(final InputStream src) throws IOException {
51 ByteArrayOutputStream out = new ByteArrayOutputStream();
61 ByteArrayOutputStream out = new ByteArrayOutputStream();
52 src.transferTo(out);
62 src.transferTo(out);
53 return out.toString();
63 return out.toString();
54 }
64 }
55
65
66 public static <T> T readJson(final Reader reader, Class<T> type) throws IOException {
67 ObjectMapper objectMapper = new ObjectMapper()
68 .registerModule(new Jdk8Module());
69 return objectMapper.readValue(reader, type);
70 }
71
72 public static void writeJson(final File file, Object value)
73 throws StreamWriteException, DatabindException, IOException {
74 ObjectMapper objectMapper = new ObjectMapper()
75 .enable(SerializationFeature.INDENT_OUTPUT)
76 .registerModule(new Jdk8Module());
77 objectMapper.writeValue(file, value);
78 }
79
80 public static ImageRef readImageRef(final File file) throws FileNotFoundException, IOException {
81 try (var reader = new FileReader(file)) {
82 return readJson(reader, ImageRef.class);
83 }
84 }
85
86 public static String readAll(final File src) throws IOException {
87 return Files.readString(src.toPath());
88 }
89
90 public static List<String> readAll(final Iterable<? extends File> files) {
91 return StreamSupport.stream(files.spliterator(), false)
92 .map(file -> {
93 try {
94 return Utils.readAll(file);
95 } catch (IOException e) {
96 throw new RuntimeException(e);
97 }
98 })
99 .toList();
100 }
101
56 public static String readAll(final InputStream src, String charset) throws IOException {
102 public static String readAll(final InputStream src, String charset) throws IOException {
57 ByteArrayOutputStream out = new ByteArrayOutputStream();
103 ByteArrayOutputStream out = new ByteArrayOutputStream();
58 src.transferTo(out);
104 src.transferTo(out);
59 return out.toString(charset);
105 return out.toString(charset);
60 }
106 }
61
107
62 public static JsonGenerator createDefaultJsonGenerator() {
108 public static JsonGenerator createDefaultJsonGenerator() {
63 return new JsonGenerator.Options()
109 return new JsonGenerator.Options()
64 .excludeNulls()
110 .excludeNulls()
65 .addConverter(new Converter() {
111 .addConverter(new Converter() {
66 public boolean handles(Class<?> type) {
112 public boolean handles(Class<?> type) {
67 return (File.class == type);
113 return (File.class == type);
68 }
114 }
69
115
70 public Object convert(Object value, String key) {
116 public Object convert(Object value, String key) {
71 return ((File) value).getPath();
117 return ((File) value).getPath();
72 }
118 }
73 })
119 })
74 .build();
120 .build();
75 }
121 }
76
122
77 public static String toJsonPretty(Object value) {
123 public static String toJsonPretty(Object value) {
78 return JsonOutput.prettyPrint(createDefaultJsonGenerator().toJson(value));
124 return JsonOutput.prettyPrint(createDefaultJsonGenerator().toJson(value));
79 }
125 }
80
126
81 public static boolean isNullOrEmptyString(String value) {
127 public static boolean isNullOrEmptyString(String value) {
82 return (value == null || value.length() == 0);
128 return (value == null || value.length() == 0);
83 }
129 }
84
130
85 public static <T> Action<T> wrapClosure(Closure<?> closure) {
131 public static <T> Action<T> wrapClosure(Closure<?> closure) {
86 return x -> {
132 return x -> {
87 closure.setDelegate(x);
133 closure.setDelegate(x);
88 closure.setResolveStrategy(Closure.DELEGATE_FIRST);
134 closure.setResolveStrategy(Closure.OWNER_FIRST);
89 closure.call(x);
135 closure.call(x);
90 };
136 };
91 }
137 }
92
138
93 } No newline at end of file
139 }
@@ -1,10 +1,10
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.dsl;
2
2
3 public class MountSpec {
3 public class MountSpec {
4
4
5 String destination;
5 String destination;
6
6
7 String source;
7 String source;
8
8
9 boolean readOnly;
9 boolean readOnly;
10 }
10 }
@@ -1,34 +1,34
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.dsl;
2
2
3 import java.util.ArrayList;
3 import java.util.ArrayList;
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
7
8 public abstract class VolumeSpec {
8 public abstract class VolumeSpec {
9
9
10 public abstract Property<String> getSource();
10 public abstract Property<String> getSource();
11
11
12 public abstract Property<String> getTarget();
12 public abstract Property<String> getTarget();
13
13
14 public abstract ListProperty<String> getOptions();
14 public abstract ListProperty<String> getOptions();
15
15
16 public void ro() {
16 public void ro() {
17 getOptions().add("ro");
17 getOptions().add("ro");
18 }
18 }
19
19
20 public String resolveSpec() {
20 public String resolveSpec() {
21 var parts = new ArrayList<String>();
21 var parts = new ArrayList<String>();
22
22
23 if (getSource().isPresent())
23 if (getSource().isPresent())
24 parts.add(getSource().get());
24 parts.add(getSource().get());
25
25
26 parts.add(getTarget().get());
26 parts.add(getTarget().get());
27
27
28 if (getOptions().isPresent())
28 if (getOptions().isPresent())
29 parts.add(String.join(",", getOptions().get()));
29 parts.add(String.join(",", getOptions().get()));
30
30
31 return String.join(":", parts);
31 return String.join(":", parts);
32 }
32 }
33
33
34 }
34 }
@@ -1,142 +1,116
1 package org.implab.gradle.containers.tasks;
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;
7 import java.util.ArrayList;
3 import java.util.ArrayList;
8 import java.util.Collection;
9 import java.util.Collections;
10 import java.util.HashMap;
4 import java.util.HashMap;
11 import java.util.List;
5 import java.util.List;
12 import java.util.Map;
6 import java.util.Map;
13 import java.util.Optional;
14
7
15 import org.apache.tools.ant.taskdefs.UpToDate;
16 import org.gradle.api.Action;
8 import org.gradle.api.Action;
9 import org.gradle.api.file.Directory;
17 import org.gradle.api.file.DirectoryProperty;
10 import org.gradle.api.file.DirectoryProperty;
18 import org.gradle.api.file.RegularFile;
11 import org.gradle.api.file.RegularFile;
19 import org.gradle.api.file.RegularFileProperty;
12 import org.gradle.api.file.RegularFileProperty;
20 import org.gradle.api.provider.ListProperty;
21 import org.gradle.api.provider.MapProperty;
13 import org.gradle.api.provider.MapProperty;
22 import org.gradle.api.provider.Property;
14 import org.gradle.api.provider.Property;
15 import org.gradle.api.provider.Provider;
23 import org.gradle.api.tasks.Input;
16 import org.gradle.api.tasks.Input;
24 import org.gradle.api.tasks.InputDirectory;
17 import org.gradle.api.tasks.InputDirectory;
18 import org.gradle.api.tasks.Internal;
19 import org.gradle.api.tasks.Optional;
25 import org.gradle.api.tasks.OutputFile;
20 import org.gradle.api.tasks.OutputFile;
26 import org.gradle.api.tasks.SkipWhenEmpty;
21 import org.gradle.api.tasks.SkipWhenEmpty;
27 import org.gradle.api.tasks.TaskAction;
22 import org.gradle.api.tasks.TaskAction;
28 import org.implab.gradle.containers.ImageName;
23 import java.io.File;
24
25 import org.implab.gradle.containers.cli.ImageName;
26 import org.implab.gradle.containers.cli.ImageRef;
29 import org.implab.gradle.containers.cli.Utils;
27 import org.implab.gradle.containers.cli.Utils;
30 import org.implab.gradle.containers.dsl.MapPropertyEntry;
28 import org.implab.gradle.containers.dsl.MapPropertyEntry;
29 import org.implab.gradle.containers.dsl.OptionsMixin;
31
30
32 import groovy.lang.Closure;
31 import groovy.lang.Closure;
33
32
34 public abstract class BuildImage extends DockerCliTask {
33 public abstract class BuildImage extends DockerCliTask implements OptionsMixin {
35
36 public final String BUILD_COMMAND = "build";
37
38 @InputDirectory
34 @InputDirectory
39 @SkipWhenEmpty
35 @SkipWhenEmpty
40 public abstract DirectoryProperty getContextDirectory();
36 public abstract DirectoryProperty getContextDirectory();
41
37
42 @Input
38 @Input
43 public abstract MapProperty<String, String> getBuildArgs();
39 public abstract MapProperty<String, String> getBuildArgs();
44
40
45 @Input
41 @Input
46 public abstract ListProperty<String> getExtraCommandArgs();
42 @Optional
47
48 @Input
49 @org.gradle.api.tasks.Optional
50 public abstract Property<String> getBuildTarget();
43 public abstract Property<String> getBuildTarget();
51
44
52 @Input
45 @Input
53 public abstract Property<ImageName> getImageName();
46 public abstract Property<ImageName> getImageName();
54
47
55 @OutputFile
48 @Internal
56 public abstract RegularFileProperty getImageIdFile();
49 public abstract RegularFileProperty getImageIdFile();
57
50
58 protected BuildImage() {
51 @OutputFile
52 public Provider<File> getImageIdFileOutput() {
53 return getImageIdFile().map(RegularFile::getAsFile);
54 }
55
56 public BuildImage() {
59 getOutputs().upToDateWhen(task -> getImageIdFile()
57 getOutputs().upToDateWhen(task -> getImageIdFile()
60 .map(RegularFile::getAsFile)
58 .map(RegularFile::getAsFile)
61 .map(docker()::imageExists)
59 .map(docker()::imageExists)
62 .getOrElse(false));
60 .getOrElse(false));
63 }
61 }
64
62
65 public void buildArgs(Action<Map<String, String>> spec) {
63 public void buildArgs(Action<Map<String, String>> spec) {
66 getBuildArgs().putAll(provider(() -> {
64 getBuildArgs().putAll(provider(() -> {
67 Map<String, String> args = new HashMap<>();
65 Map<String, String> args = new HashMap<>();
68 spec.execute(args);
66 spec.execute(args);
67 getLogger().info("add buildArgs {}", args);
69 return args;
68 return args;
70 }));
69 }));
71 }
70 }
72
71
73 public void buildArgs(Closure<Map<String, String>> spec) {
72 public void buildArgs(Closure<Map<String, String>> spec) {
74 buildArgs(Utils.wrapClosure(spec));
73 buildArgs(Utils.wrapClosure(spec));
75 }
74 }
76
75
77 public MapPropertyEntry<String, String> buildArg(String key) {
76 public MapPropertyEntry<String, String> buildArg(String key) {
78 return new MapPropertyEntry<String, String>(getBuildArgs(), key, getProviders());
77 return new MapPropertyEntry<String, String>(getBuildArgs(), key, getProviders());
79 }
78 }
80
79
81 @TaskAction
80 @TaskAction
82 public void Run() throws Exception {
81 public void run() throws Exception {
83 List<String> args = new ArrayList<>();
82 List<String> args = new ArrayList<>();
84
83
85 args.addAll(List.of(
84 // create a temp file to store image id
86 "-t", getImageName().get().toString(),
85 var iidFile = new File(this.getTemporaryDir(), "iid");
87 "--iidfile", getImageIdFile().getAsFile().get().toString()));
86
87 // specify where to write image id
88 args.addAll(List.of("--iidfile", iidFile.getAbsolutePath()));
88
89
89 getBuildArgs().get().forEach((k, v) -> {
90 getBuildArgs().get().forEach((k, v) -> {
90 args.add("--build-arg");
91 args.add("--build-arg");
91 args.add(String.format("%s=%s", k, v));
92 args.add(String.format("%s=%s", k, v));
92 });
93 });
93
94
94 // add --target if specified for multi-stage build
95 // add --target if specified for multi-stage build
95 if (getBuildTarget().isPresent()) {
96 if (getBuildTarget().isPresent()) {
96 args.add("--target");
97 args.add("--target");
97 args.add(getBuildTarget().get());
98 args.add(getBuildTarget().get());
98 }
99 }
99
100
100 // add extra parameters
101 // add extra parameters
101 getExtraCommandArgs().getOrElse(Collections.emptyList())
102 getOptions().get().forEach(args::add);
102 .forEach(args::add);
103
104 args.add(getContextDirectory().getAsFile().get().toString());
105
106 docker().build(args);
107 }
108
109
103
110 @Override
104 var imageTag = getImageName().map(ImageName::toString).get();
111 protected Optional<String> getSubCommand() {
112 return Optional.of(BUILD_COMMAND);
113 }
114
115 @Override
116 protected Collection<String> getSubCommandArguments() {
117 List<String> args = new ArrayList<>();
118
105
119 args.addAll(List.of(
106 // build image
120 "-t", getImageName().get().toString(),
107 docker().buildImage(
121 "--iidfile", getImageIdFile().getAsFile().get().toString()));
108 imageTag,
122
109 getContextDirectory().map(Directory::getAsFile).get(),
123 getBuildArgs().get().forEach((k, v) -> {
110 args);
124 args.add("--build-arg");
125 args.add(String.format("%s=%s", k, v));
126 });
127
111
128 // add --target if specified for multi-stage build
112 // read the built image id and store image ref metadata
129 if (getBuildTarget().isPresent()) {
113 var imageRef = new ImageRef(imageTag, Utils.readAll(iidFile));
130 args.add("--target");
114 Utils.writeJson(getImageIdFileOutput().get(), imageRef);
131 args.add(getBuildTarget().get());
132 }
133
134 // add extra parameters
135 getExtraCommandArgs().getOrElse(Collections.emptyList())
136 .forEach(args::add);
137
138 args.add(getContextDirectory().getAsFile().get().toString());
139
140 return args;
141 }
115 }
142 }
116 }
@@ -1,110 +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.Collection;
5 import java.util.Collections;
6 import java.util.List;
7 import java.util.Optional;
4 import java.util.Optional;
8
5
9 import org.gradle.api.DefaultTask;
6 import org.gradle.api.DefaultTask;
10 import org.gradle.api.file.Directory;
7 import org.gradle.api.file.Directory;
11 import org.gradle.api.file.DirectoryProperty;
8 import org.gradle.api.file.DirectoryProperty;
12 import org.gradle.api.logging.Logger;
9 import org.gradle.api.logging.Logger;
13 import org.gradle.api.provider.Property;
10 import org.gradle.api.provider.Property;
14 import org.gradle.api.provider.Provider;
15 import org.gradle.api.tasks.Input;
11 import org.gradle.api.tasks.Input;
16 import org.gradle.api.tasks.Internal;
12 import org.gradle.api.tasks.Internal;
17 import org.gradle.api.tasks.TaskAction;
18 import org.implab.gradle.containers.ContainerExtension;
13 import org.implab.gradle.containers.ContainerExtension;
19 import org.implab.gradle.containers.ExecuteMixin;
20 import org.implab.gradle.containers.PropertiesMixin;
14 import org.implab.gradle.containers.PropertiesMixin;
21 import org.implab.gradle.containers.cli.DockerTraits;
15 import org.implab.gradle.containers.cli.DockerTraits;
22
16
23 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin, ExecuteMixin {
17 public abstract class DockerCliTask extends DefaultTask implements PropertiesMixin {
24
25 private final Property<String> cliCmd;
26
27 private final DirectoryProperty workingDir;
28
29 public DockerCliTask() {
30 cliCmd = property(String.class)
31 .convention(getContainerExtension().getCliCmd());
32
33 workingDir = directoryProperty();
34 }
35
36 @Internal
37 protected Optional<String> getSubCommand() {
38 return Optional.empty();
39 }
40
41 @Internal
42 protected Collection<String> getSubCommandArguments() {
43 return Collections.emptyList();
44 }
45
18
46 @Input
19 @Input
47 public Property<String> getCliCmd() {
20 public abstract Property<String> getCliCmd();
48 return cliCmd;
49 }
50
21
51 public void setCliCmd(String cliCmd) {
22 @Input
52 this.cliCmd.set(cliCmd);
23 @org.gradle.api.tasks.Optional
53 }
24 public abstract DirectoryProperty getWorkingDirectory();
54
25
55 @Internal
26 @Internal
56 protected ContainerExtension getContainerExtension() {
27 protected ContainerExtension getContainerExtension() {
57 return getProject().getExtensions().getByType(ContainerExtension.class);
28 return getProject().getExtensions().getByType(ContainerExtension.class);
58 }
29 }
59
30
60 @TaskAction
31 public DockerCliTask() {
61 public void Run() throws Exception {
32 getCliCmd().convention(getContainerExtension().getCliCmd());
62 Execute();
63 }
64
65 public void preparingCommand(List<String> commandLine) {
66 commandLine.add(cliCmd.get());
67 getSubCommand().ifPresent(commandLine::add);
68 getSubCommandArguments().forEach(commandLine::add);
69 }
70
33
71 @Override
72 @Internal
73 public Optional<File> getWorkingDir() {
74 return workingDir
75 .map(Directory::getAsFile)
76 .map(Optional::of)
77 .getOrElse(Optional.empty());
78 }
79
80 protected void setWorkingDir(Provider<File> workingDir) {
81 this.workingDir.fileProvider(workingDir);
82 }
83
84 protected void setWorkingDir(File workingDir) {
85 this.workingDir.set(workingDir);
86 }
34 }
87
35
88 protected DockerTraits docker() {
36 protected DockerTraits docker() {
89 return new TaskDockerTraits();
37 return new TaskDockerTraits();
90 }
38 }
91
39
92 class TaskDockerTraits extends DockerTraits {
40 class TaskDockerTraits extends DockerTraits {
93
41
94 @Override
42 @Override
95 public Logger getLogger() {
43 public Logger getLogger() {
96 return DockerCliTask.this.getLogger();
44 return DockerCliTask.this.getLogger();
97 }
45 }
98
46
99 @Override
47 @Override
100 public Optional<File> getWorkingDir() {
48 public Optional<File> getWorkingDir() {
101 return DockerCliTask.this.getWorkingDir();
49 return getWorkingDirectory()
50 .map(Directory::getAsFile)
51 .map(Optional::of)
52 .getOrElse(Optional.empty());
102 }
53 }
103
54
104 @Override
55 @Override
105 public String getCliCmd() {
56 public String getCliCmd() {
106 return cliCmd.get();
57 return DockerCliTask.this.getCliCmd().get();
107 }
58 }
108
59
109 }
60 }
110 }
61 }
@@ -1,48 +1,33
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.util.ArrayList;
3 import java.io.IOException;
4 import java.util.Collection;
5 import java.util.List;
6 import java.util.concurrent.Callable;
7
4
8 import org.gradle.api.provider.ListProperty;
5 import org.gradle.api.provider.ListProperty;
9 import org.gradle.api.provider.Property;
6 import org.gradle.api.provider.Property;
10 import org.gradle.api.tasks.Input;
7 import org.gradle.api.tasks.Input;
11 import org.gradle.api.tasks.Optional;
8 import org.gradle.api.tasks.Optional;
12 import org.implab.gradle.containers.ImageName;
9 import org.gradle.api.tasks.TaskAction;
10 import org.implab.gradle.containers.cli.ImageName;
11 import org.implab.gradle.containers.dsl.OptionsMixin;
13
12
14 public abstract class PushImage extends DockerCliTask {
13 public abstract class PushImage extends DockerCliTask implements OptionsMixin {
15
16 public final String PUSH_COMMAND = "push";
17
14
18 @Input
15 @Input
19 public abstract Property<ImageName> getImageName();
16 public abstract Property<ImageName> getImageName();
20
17
21 @Input
18 @Input
22 @Optional
19 @Optional
23 public abstract Property<String> getTransport();
20 public abstract Property<String> getTransport();
24
21
25 @Input
22 @Input
26 @Optional
23 @Optional
27 public abstract ListProperty<String> getOptions();
24 public abstract ListProperty<String> getOptions();
28
25
29 public PushImage option(Callable<String> provider) {
26 @TaskAction
30 getOptions().add(getProject().provider(provider));
27 public void run() throws InterruptedException, IOException {
31 return this;
32 }
33
28
34 @Override
29 docker().pushImage(
35 protected java.util.Optional<String> getSubCommand() {
30 getImageName().get().toString(),
36 return java.util.Optional.of(PUSH_COMMAND);
31 getOptions().get());
37 }
38
39 @Override
40 protected Collection<String> getSubCommandArguments() {
41 List<String> args = new ArrayList<>();
42
43 args.addAll(getOptions().get());
44 args.add(getImageName().get().toString());
45
46 return args;
47 }
32 }
48 }
33 }
@@ -1,54 +1,43
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.util.ArrayList;
3 import java.io.IOException;
4 import java.util.Collection;
5 import java.util.List;
6
7 import org.gradle.api.Action;
4 import org.gradle.api.Action;
8 import org.gradle.api.provider.ListProperty;
5 import org.gradle.api.provider.ListProperty;
9 import org.gradle.api.provider.Property;
6 import org.gradle.api.provider.Property;
10 import org.gradle.api.tasks.Input;
7 import org.gradle.api.tasks.Input;
11 import org.gradle.api.tasks.Optional;
8 import org.gradle.api.tasks.Optional;
12 import org.implab.gradle.containers.ImageName;
9 import org.implab.gradle.containers.cli.ImageName;
13
10 import org.implab.gradle.containers.dsl.OptionsMixin;
14 public abstract class RunImage extends DockerCliTask {
11 import org.implab.gradle.containers.dsl.VolumeSpec;
15
12
16 public final String RUN_COMMAND = "run";
13 public abstract class RunImage extends DockerCliTask implements OptionsMixin {
17
18 @Input
19 @Optional
20 public abstract ListProperty<String> getOptions();
21
14
22 @Input
15 @Input
23 public abstract Property<ImageName> getImageName();
16 public abstract Property<ImageName> getImageName();
24
17
25 @Input
18 @Input
26 @Optional
19 @Optional
27 public abstract ListProperty<String> getCommandLine();
20 public abstract ListProperty<String> getCommandLine();
28
21
29 @Override
30 protected java.util.Optional<String> getSubCommand() {
31 return java.util.Optional.of(RUN_COMMAND);
32 }
33
34 @Override
35 protected Collection<String> getSubCommandArguments() {
36 List<String> args = new ArrayList<String>();
37
38 args.addAll(getOptions().get());
39 args.add(getImageName().get().toString());
40 args.addAll(getCommandLine().get());
41
42 return args;
43 }
44
22
45 public void volume(Action<VolumeSpec> configure) {
23 public void volume(Action<VolumeSpec> configure) {
46 getOptions().add("-v");
24 getOptions().add("-v");
47 getOptions().add(provider(() -> {
25 getOptions().add(provider(() -> {
48 var volumeSpec = getObjectFactory().newInstance(VolumeSpec.class);
26 var volumeSpec = getObjectFactory().newInstance(VolumeSpec.class);
49 configure.execute(volumeSpec);
27 configure.execute(volumeSpec);
50 return volumeSpec.resolveSpec();
28 return volumeSpec.resolveSpec();
51 }));
29 }));
52 }
30 }
53
31
32 void commandLine(String ...args) {
33 getCommandLine().addAll(args);
54 }
34 }
35
36 public void run() throws InterruptedException, IOException {
37 docker().runImage(
38 getImageName().get().toString(),
39 getOptions().get(),
40 getCommandLine().get());
41 }
42
43 }
@@ -1,92 +1,111
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.io.File;
4 import java.io.IOException;
3 import java.util.ArrayList;
5 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.List;
6 import java.util.Optional;
6 import java.util.Optional;
7
7
8 import org.gradle.api.file.DirectoryProperty;
8 import org.gradle.api.file.DirectoryProperty;
9 import org.gradle.api.file.FileCollection;
9 import org.gradle.api.file.RegularFile;
10 import org.gradle.api.file.RegularFile;
11 import org.gradle.api.provider.ListProperty;
10 import org.gradle.api.provider.Property;
12 import org.gradle.api.provider.Property;
11 import org.gradle.api.provider.Provider;
13 import org.gradle.api.provider.Provider;
12 import org.gradle.api.tasks.Input;
14 import org.gradle.api.tasks.Input;
13 import org.gradle.api.tasks.Internal;
15 import org.gradle.api.tasks.Internal;
14 import org.gradle.api.tasks.OutputFile;
16 import org.gradle.api.tasks.OutputFile;
17 import org.gradle.api.tasks.TaskAction;
15 import org.gradle.api.tasks.TaskExecutionException;
18 import org.gradle.api.tasks.TaskExecutionException;
16 import org.implab.gradle.containers.ImageName;
19 import org.implab.gradle.containers.cli.Utils;
17
20
18 public abstract class SaveImage extends DockerCliTask {
21 public abstract class SaveImage extends DockerCliTask {
19 public final String SAVE_COMMAND = "save";
20
22
21 @Input
23 @Input
22 public abstract Property<ImageName> getImage();
24 public abstract ListProperty<String> getExportImages();
23
25
24 @OutputFile
26 @OutputFile
25 public Provider<RegularFile> getArchiveFile() {
27 public Provider<RegularFile> getArchiveFile() {
26 return getDestinationDirectory().file(getArchiveFileName());
28 return getDestinationDirectory().file(getArchiveFileName());
27 }
29 }
28
30
29 @Internal
31 @Internal
30 public abstract DirectoryProperty getDestinationDirectory();
32 public abstract DirectoryProperty getDestinationDirectory();
31
33
32 @Internal
34 @Internal
33 public abstract Property<String> getArchiveFileName();
35 public abstract Property<String> getArchiveFileName();
34
36
35 @Internal
37 @Internal
36 public abstract Property<String> getArchiveBaseName();
38 public abstract Property<String> getArchiveBaseName();
37
39
38 @Internal
40 @Internal
39 public abstract Property<String> getArchiveVersion();
41 public abstract Property<String> getArchiveVersion();
40
42
41 @Internal
43 @Internal
42 public abstract Property<String> getArchiveClassifier();
44 public abstract Property<String> getArchiveClassifier();
43
45
44 @Internal
46 @Internal
45 public abstract Property<String> getArchiveExtension();
47 public abstract Property<String> getArchiveExtension();
46
48
49 String readImageRefTag(File file) throws Exception {
50 getLogger().info("Reading image ref from {}", file);
51 var imageRef = Utils.readImageRef(file);
52 return imageRef.getTag().orElseThrow(() -> new Exception("The image tag is required to save image"));
53 }
54
55 public void imageRef(File file) {
56 getExportImages().add(provider(() -> {
57 return readImageRefTag(file);
58 }));
59 }
60
61 public void imageRefs(FileCollection files) {
62 dependsOn(files);
63 getExportImages().addAll(provider(() -> {
64 var tags = new ArrayList<String>();
65 for (File file : files) {
66 tags.add(readImageRefTag(file));
67 }
68 return tags;
69 }));
70 }
71
47 public SaveImage() {
72 public SaveImage() {
48 getArchiveFileName().convention(provider(this::conventionalArchiveFileName));
73 getArchiveFileName().convention(provider(this::conventionalArchiveFileName));
49 getArchiveBaseName().convention(provider(this::conventionalArchiveBaseName));
74 getArchiveBaseName().convention(provider(this::conventionalArchiveBaseName));
50 getArchiveVersion().convention(provider(() -> getProject().getVersion()).map(x -> x.toString()));
75 getArchiveVersion().convention(provider(() -> getProject().getVersion()).map(x -> x.toString()));
51 getDestinationDirectory().convention(getProject().getLayout().getBuildDirectory());
76 getDestinationDirectory().convention(getProject().getLayout().getBuildDirectory());
52 getArchiveExtension().convention("tar");
77 getArchiveExtension().convention("tar");
53 }
78 }
54
79
55 private String conventionalArchiveBaseName() {
80 private String conventionalArchiveBaseName() {
56 ArrayList<String> parts = new ArrayList<>();
81 ArrayList<String> parts = new ArrayList<>();
57 Optional.of(getProject().getGroup()).map(x -> x.toString()).ifPresent(parts::add);
82 Optional.of(getProject().getGroup()).map(x -> x.toString()).ifPresent(parts::add);
58 parts.add(getProject().getName());
83 parts.add(getProject().getName());
59 return String.join("-", parts);
84 return String.join("-", parts);
60 }
85 }
61
86
62 private String conventionalArchiveFileName() {
87 private String conventionalArchiveFileName() {
63 ArrayList<String> parts = new ArrayList<>();
88 ArrayList<String> parts = new ArrayList<>();
64
89
65 if (getArchiveBaseName().isPresent())
90 if (getArchiveBaseName().isPresent())
66 parts.add(getArchiveBaseName().get());
91 parts.add(getArchiveBaseName().get());
67
92
68 if (getArchiveVersion().isPresent())
93 if (getArchiveVersion().isPresent())
69 parts.add(getArchiveVersion().get());
94 parts.add(getArchiveVersion().get());
70
95
71 if (getArchiveClassifier().isPresent())
96 if (getArchiveClassifier().isPresent())
72 parts.add(getArchiveClassifier().get());
97 parts.add(getArchiveClassifier().get());
73
98
74 if (parts.size() == 0)
99 if (parts.size() == 0)
75 throw new TaskExecutionException(this, new Exception("The archiveFileName ism't specified"));
100 throw new TaskExecutionException(this, new Exception("The archiveFileName ism't specified"));
76
101
77 return String.join("-", parts) + "." + getArchiveExtension().get();
102 return String.join("-", parts) + "." + getArchiveExtension().get();
78 }
103 }
79
104
80 @Override
105 @TaskAction
81 protected Optional<String> getSubCommand() {
106 public void run() throws InterruptedException, IOException {
82 return Optional.of(SAVE_COMMAND);
107 docker().saveImage(
83 }
108 getExportImages().get(),
84
109 getArchiveFile().map(RegularFile::getAsFile).get());
85 @Override
86 protected Collection<String> getSubCommandArguments() {
87 return List.of(
88 getImage().get().toString(),
89 getArchiveFile().get().getAsFile().toString()
90 );
91 }
110 }
92 }
111 }
@@ -1,51 +1,22
1 package org.implab.gradle.containers.tasks;
1 package org.implab.gradle.containers.tasks;
2
2
3 import java.util.ArrayList;
3 import java.io.IOException;
4 import java.util.Collection;
5 import java.util.List;
6 import java.util.concurrent.Callable;
7
8 import org.gradle.api.provider.ListProperty;
9 import org.gradle.api.provider.Property;
4 import org.gradle.api.provider.Property;
10 import org.gradle.api.tasks.Input;
5 import org.gradle.api.tasks.Input;
11 import org.gradle.api.tasks.Optional;
6 import org.gradle.api.tasks.TaskAction;
12 import org.implab.gradle.containers.ImageName;
7 import org.implab.gradle.containers.cli.ImageName;
13
8
14 public abstract class TagImage extends DockerCliTask {
9 public abstract class TagImage extends DockerCliTask {
15 public final String TAG_COMMAND = "tag";
16
17 @Input
10 @Input
18 public abstract Property<ImageName> getSrcImage();
11 public abstract Property<ImageName> getSrcImage();
19
12
20 @Input
13 @Input
21 public abstract Property<ImageName> getDestImage();
14 public abstract Property<ImageName> getDestImage();
22
15
23 @Input
16 @TaskAction
24 @Optional
17 public void run() throws InterruptedException, IOException {
25 public abstract Property<String> getTransport();
18 docker().tagImage(
26
19 getSrcImage().map(ImageName::toString).get(),
27 @Input
20 getDestImage().map(ImageName::toString).get());
28 @Optional
29 public abstract ListProperty<String> getOptions();
30
31 public TagImage option(Callable<String> provider) {
32 getOptions().add(getProject().provider(provider));
33 return this;
34 }
35
36 @Override
37 protected java.util.Optional<String> getSubCommand() {
38 return java.util.Optional.of(TAG_COMMAND);
39 }
40
41 @Override
42 protected Collection<String> getSubCommandArguments() {
43 List<String> args = new ArrayList<>();
44
45 args.addAll(getOptions().get());
46 args.add(getSrcImage().get().toString());
47 args.add(getDestImage().get().toString());
48
49 return args;
50 }
21 }
51 }
22 }
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now