@@ -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 |
p |
|
|
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 |
|
|
|
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( |
|
|
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(). |
|
|
77 | t.getExportImages().add(buildImageTask.flatMap(BuildImage::getImageName).map(ImageName::toString)); | |
|
94 | 78 | }); |
|
95 | 79 | |
|
96 |
project.getArtifacts().add(Dependency.DEFAULT_CONFIGURATION, buildImageTask |
|
|
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 |
|
|
|
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 |
|
|
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 |
|
|
|
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 = |
|
|
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.File |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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. |
|
|
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< |
|
|
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. |
|
|
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