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