##// END OF EJS Templates
More documentation, TagImage task supports multiple tags, set COMPOSE_PROJECT_NAME to project.group by default
cin -
r14:ab28d6aa054e v1.2.1 default
parent child
Show More
@@ -0,0 +1,57
1 # Creating bundle from images
2
3 ## NAME
4
5 Save set of images to the single archive.
6
7 ## SYNOPSIS
8
9 ```gradle
10 plugins {
11 // task types and base extension
12 id "org.implab.gradle-container-base"
13 }
14
15 container {
16 cliCmd = "podman"
17 }
18
19 // create configuration
20 configurations {
21 bundleImages {
22 canBeResolved = true
23 canBeConsumed = false
24 }
25 }
26
27 // add dependencies to the project
28 dependencies {
29 bundleImages project(":images:foo"), project(":images:bar")
30 }
31
32 // create task to export bundle
33 task bundle(type: SaveImage) {
34 // add image refs from artifacts
35 imageRefs configurations.bundleImages
36
37 // add image name
38 exportImages.add "nginx:latest"
39 }
40 ```
41
42 ## DESCRIPTION
43
44 To create an archive with images the task of type `SaveImage` can be used. This
45 task has the following properties:
46
47 | Property | Description |
48 |-|-|
49 | `archiveFileName` | The file name of the bundle archive, defaults to `{archiveBaseName}-{archiveVersion}-{archiveClassifier}.{archiveExtension}`.|
50 | `archiveBaseName` | The base name of the archive, defaults to `{project.group}-{project.name}`. |
51 | `archiveVersion` | The archive version, defaults to `{project.version}`. |
52 | `exportImages` | A set of image names to include in the bundle. |
53
54 | Method | Description |
55 |-|-|
56 | `imageRefs(FileCollection)` | Adds a set of files with image refs to add to the bundle. |
57 | `imageRef(File)` | Adds an image name from the file with image reference. |
@@ -0,0 +1,148
1 # Compose project
2
3 ## NAME
4
5 `org.implab.gradle-container-compose` - docker compose project.
6
7 ## SYNOPSIS
8
9 ```gradle
10 plugins {
11 id "org.implab.gradle-container-compose"
12 }
13
14 dependencies {
15 composeImages project(":images:foo") {
16 ext {
17 // imageName from :images:foo will be written to .env as FOO_IMAGE var
18 composeVar = "FOO_IMAGE"
19 }
20 }
21 }
22
23 writeEnv {
24 // write additional variables to .env
25 env "DEBUG_JAVA" put "yes"
26
27 // set compose name, this variable is set to that value by default
28 env "COMPOSE_PROJECT_NAME" put project.group
29 }
30
31 // base container extension
32 container {
33 cliCmd = "podman"
34 }
35
36 // compose parameters
37 compose {
38 // add compose profiles
39 profiles.add("dev")
40
41 // set compose file name
42 // defaults to compose.yaml
43 composeFileName = "docker-compose.yaml"
44 }
45
46 ```
47
48 ## DESCRIPTION
49
50 This plugin creates a set of conventional tasks to prepare and start compose
51 project. This can be used to create and run sandbox.
52
53 These tasks are:
54
55 * `build` - prepares the context for the compose file, depends on `processResources`
56 and `writeEnv` tasks.
57 * `up` - invokes `compose up -d` in the built context and starts up the project
58 in the background.
59 * `stop` - invokes `compose stop` and stops the projects.
60 * `rm` - invokes `compose rm` and removes containers, by default temporary volumes
61 are removed with containers.
62 * `clean` - cleanups the build directory and removes built context.
63
64 Special configuration `composeImages` should be used to add a dependencies to the
65 images needed by this project. The dependencies must provide a single artifact
66 a json file containing `tag` property. This configuration well be resolved and
67 tag for each dependency will be stored in the environment variable specified with
68 `composeVar` property.
69
70 ```gradle
71 configuration {
72 composeImages file("nginx.json") {
73 ext {
74 composeVar = "NGINX_IMAGE"
75 }
76 }
77 composeImage project(":images:foo") {
78 ext {
79 composeVar = "FOO_IMAGE"
80 }
81 }
82 }
83 ```
84
85 The compose environment variables are written to the `.env` file inside the
86 context directory and will be used by `compose up` command.
87
88 ## Tasks
89
90 ### writeEnv
91
92 `type: WriteEnv, dependsOn: [configurations.composeImages, processResources]`
93
94 Inspects configuration `composeImages`, adds default `COMPOSE_PROJECT_NAME`
95 variable and writes `.env` file to the context directory.
96
97 This task provides a property `environment` of type `MapProperty<String, String>`
98 which can be used to customize the compose environment.
99
100 ```gradle
101 writeEnv {
102 // direct environment property access
103 environment.put("VAR_1", valueOrProvider)
104
105 // syntax sugar to modify environment property
106 env "VAR_2" put "value" // simple value
107 env "VAR_3" put provider { getSomeValue() } // provider
108
109 // map provider will be merged into the
110 env {
111 VAR_4 = "val1"
112 VAR_5 = getAnotherValue()
113 }
114 }
115 ```
116
117 ### processResources
118
119 `type: Copy`
120
121 Copies resources from the source directory `src/main` into the context directory `build/context`
122
123 ### up
124
125 `type: ComposeUp, dependsOn: [buildTask]`
126
127 Starts the compose project. The project is started in the background.
128
129 ### build
130
131 `type: DefaultTask, dependsOn: [writeEnvTask]`
132
133 This is a meta task used to group the set of tasks related to the build target.
134
135 ### stop
136
137 `type: ComposeStop`
138
139 Stops the current compose project.
140
141 ### rm
142
143 `type: ComposeRm`
144
145 Removes containers created from this compose project.
146
147 `removeValues` - boolean property, if set to true the task will remove all
148 temporary volumes left from containers.
@@ -1,159 +1,167
1 1 package org.implab.gradle.containers;
2 2
3 3 import java.io.IOException;
4 4 import java.util.HashMap;
5 5 import java.util.Map;
6 import java.util.Optional;
6 7
7 8 import org.gradle.api.DefaultTask;
8 9 import org.gradle.api.Plugin;
9 10 import org.gradle.api.Project;
10 11 import org.gradle.api.artifacts.Configuration;
11 12 import org.gradle.api.artifacts.Configuration.State;
12 13 import org.gradle.api.logging.Logger;
13 14 import org.gradle.api.tasks.Copy;
14 15 import org.gradle.api.tasks.Delete;
15 16 import org.implab.gradle.containers.cli.Utils;
16 17 import org.implab.gradle.containers.tasks.ComposeRm;
17 18 import org.implab.gradle.containers.tasks.ComposeStop;
18 19 import org.implab.gradle.containers.tasks.ComposeUp;
19 20 import org.implab.gradle.containers.tasks.WriteEnv;
20 21
21 22 public abstract class ComposePlugin implements Plugin<Project>, ProjectMixin {
22 23 public final String COMPOSE_IMAGES_CONFIGURATION = "composeImages";
23 24
25 public final String COMPOSE_PROJECT_NAME = "COMPOSE_PROJECT_NAME";
26
24 27 public final String COMPOSE_EXTENSION = "compose";
25 28
26 29 public final String COMPOSE_UP_TASK = "up";
27 30
28 31 public final String COMPOSE_STOP_TASK = "stop";
29 32
30 33 public final String COMPOSE_RM_TASK = "rm";
31 34
32 35 public final String CLEAN_TASK = "clean";
33 36
34 37 public final String BUILD_TASK = "build";
35 38
36 39 public final String PROCESS_RESOURCES_TASK = "processResources";
37 40
38 41 public final String WRITE_ENV_TASK = "writeEnv";
39 42
40 43 public final String COMPOSE_VAR = "composeVar";
41 44
42 45 public final String ENV_FILE_NAME = ".env";
43 46
44 47 public Logger getLogger() {
45 48 return getProject().getLogger();
46 49 }
47 50
48 51 @Override
49 52 public void apply(Project project) {
50 53 var containerImages = configuration(COMPOSE_IMAGES_CONFIGURATION, Configurations.RESOLVABLE);
51 54
52 55 // basic configuration, register extension
53 56 var basePlugin = plugin(ContainerBasePlugin.class);
54 57 var containerExtension = basePlugin.getContainerExtension();
55 58
56 59 var composeExtension = extension(COMPOSE_EXTENSION, ComposeExtension.class);
57 60
58 61 var composeFile = containerExtension.getContextDirectory()
59 62 .file(composeExtension.getComposeFileName());
60 63 var composeProfiles = composeExtension.getProfiles();
61 64
62 65 var cleanTask = task(CLEAN_TASK, Delete.class, t -> {
63 66 t.delete(containerExtension.getContextDirectory());
64 67 });
65 68
66 69 // copy task from src/main
67 70 var processResources = task(PROCESS_RESOURCES_TASK, Copy.class, t -> {
68 71 t.mustRunAfter(cleanTask);
69 72 t.from(projectDirectory().dir("src/main"));
70 73 t.into(containerExtension.getContextDirectory());
71 74 });
72 75
73 76 // write .env
74 77 var writeEnvTask = task(WRITE_ENV_TASK, WriteEnv.class, t -> {
75 78 t.dependsOn(processResources, containerImages);
76 79 t.getEnvFile().set(containerExtension.getContextDirectory().file(ENV_FILE_NAME));
77 80
81 var group = project.getGroup();
82 if (group != null && group.toString().length() > 0) {
83 t.getEnvironment().put(COMPOSE_PROJECT_NAME, group.toString());
84 }
85
78 86 t.getEnvironment().putAll(containerImages.map(this::extractComposeEnv));
79 87
80 88 });
81 89
82 90 var buildTask = task(BUILD_TASK, DefaultTask.class, t -> {
83 91 t.dependsOn(writeEnvTask);
84 92 });
85 93
86 94 var stopTask = task(COMPOSE_STOP_TASK, ComposeStop.class, t -> {
87 95 // stop must run after build
88 96 t.mustRunAfter(buildTask);
89 97
90 98 t.getProfiles().addAll(composeProfiles);
91 99 t.getComposeFile().set(composeFile);
92 100 });
93 101
94 102 var rmTask = task(COMPOSE_RM_TASK, ComposeRm.class, t -> {
95 103 // rm must run after build and stop
96 104 t.mustRunAfter(buildTask, stopTask);
97 105
98 106 t.getProfiles().addAll(composeProfiles);
99 107 t.getComposeFile().set(composeFile);
100 108 });
101 109
102 110 task(COMPOSE_UP_TASK, ComposeUp.class, t -> {
103 111 t.dependsOn(buildTask);
104 112 // up must run after stop and rm
105 113 t.mustRunAfter(stopTask, rmTask);
106 114
107 115 t.getProfiles().addAll(composeProfiles);
108 116 t.getComposeFile().set(composeFile);
109 117 });
110 118 }
111 119
112 120 /**
113 121 * Processed the configurations, extracts composeVar extra property from
114 122 * each dependency in this configuration and adds a value to the resulting
115 123 * map. The values in the nap will contain image tags.
116 124 */
117 125 private Map<String, String> extractComposeEnv(Configuration config) {
118 126 if (config.getState() != State.UNRESOLVED) {
119 127 getLogger().error("extractComposeEnv: The configuration {} isn't resolved.", config.getName());
120 128 throw new IllegalStateException("The specified configuration isn't resolved");
121 129 }
122 130
123 131 getLogger().info("extractComposeEnv {}", config.getName());
124 132
125 133 var map = new HashMap<String, String>();
126 134
127 135 for (var dependency : config.getDependencies()) {
128 136 // get extra composeVar if present
129 137 extra(dependency, COMPOSE_VAR, String.class).optional().ifPresent(varName -> {
130 138 // if we have a composeVar extra attribute on this dependency
131 139
132 140 // get files for the dependency
133 141 var files = config.files(dependency);
134 142 if (files.size() == 1) {
135 143 // should bw exactly 1 file
136 144 var file = files.stream().findAny().get();
137 145 getLogger().info("Processing {}: {}", dependency, file);
138 146
139 147 try {
140 148 // try read imageRef
141 149 Utils.readImageRef(file).getTag()
142 150 .ifPresentOrElse(
143 151 tag -> map.put(varName, tag),
144 152 () -> getLogger().error("ImageRef doesn't have a tag: {}", file));
145 153
146 154 } catch (IOException e) {
147 155 getLogger().error("Failed to read ImageRef {}: {}", file, e);
148 156 }
149 157
150 158 } else {
151 159 getLogger().warn("Dependency {} must have exactly 1 file", dependency);
152 160 }
153 161 });
154 162 }
155 163
156 164 return map;
157 165 }
158 166
159 167 }
@@ -1,186 +1,186
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.util.ArrayList;
6 6 import java.util.Arrays;
7 7 import java.util.List;
8 8 import java.util.Optional;
9 9 import java.util.Set;
10 10
11 11 import org.gradle.api.logging.Logger;
12 12
13 13 public abstract class DockerTraits {
14 14
15 15 public final String BUILD_COMMAND = "build";
16 16 public final String PUSH_COMMAND = "push";
17 17 public final String RUN_COMMAND = "run";
18 18 public final String SAVE_COMMAND = "save";
19 19 public final String INSPECT_COMMAND = "inspect";
20 20 public final String IMAGE_COMMAND = "image";
21 21 public final String TAG_COMMAND = "tag";
22 22 public final String COMPOSE_COMMAND = "compose";
23 23 public final String UP_COMMAND = "up";
24 24 public final String STOP_COMMAND = "stop";
25 25 public final String RM_COMMAND = "rm";
26 26
27 27 public abstract Logger getLogger();
28 28
29 29 public abstract Optional<File> getWorkingDir();
30 30
31 31 public abstract String getCliCmd();
32 32
33 33 Process startProcess(ProcessBuilder builder) throws IOException {
34 34 getLogger().info("Starting: {}", builder.command());
35 35 return builder.start();
36 36 }
37 37
38 38 protected boolean checkRetCode(Process proc, int code) throws InterruptedException {
39 39 if (getLogger().isInfoEnabled()) {
40 40 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
41 41 Utils.redirectIO(proc.getErrorStream(), getLogger()::info);
42 42 }
43 43
44 44 return proc.waitFor() == code;
45 45 }
46 46
47 47 protected void complete(Process proc) throws InterruptedException, IOException {
48 48 if (getLogger().isInfoEnabled())
49 49 Utils.redirectIO(proc.getInputStream(), getLogger()::info);
50 50
51 51 if (getLogger().isErrorEnabled())
52 52 Utils.redirectIO(proc.getErrorStream(), getLogger()::error);
53 53
54 54 var code = proc.waitFor();
55 55 if (code != 0) {
56 56 getLogger().error("The process exited with code {}", code);
57 57 throw new IOException("The process exited with error code " + code);
58 58 }
59 59 }
60 60
61 61 protected ProcessBuilder builder(String... args) {
62 62 var argsList = new ArrayList<String>(args.length + 1);
63 63 Arrays.stream(args).forEach(argsList::add);
64 64
65 65 return builder(argsList);
66 66 }
67 67
68 68 protected ProcessBuilder builder(List<String> args) {
69 69 var command = new ArrayList<String>(args.size() + 1);
70 70
71 71 command.add(getCliCmd());
72 72 args.forEach(command::add);
73 73
74 74 var builder = new ProcessBuilder(command);
75 75
76 76 getWorkingDir().ifPresent(builder::directory);
77 77 return builder;
78 78 }
79 79
80 80 public void buildImage(String imageName, File contextDirectory, List<String> options)
81 81 throws IOException, InterruptedException {
82 82 var args = new ArrayList<String>();
83 83 args.add(BUILD_COMMAND);
84 84 args.addAll(options);
85 85 args.add("-t");
86 86 args.add(imageName);
87 87 args.add(contextDirectory.getAbsolutePath());
88 88 complete(startProcess(builder(args)));
89 89 }
90 90
91 91 public void pushImage(String image, List<String> options) throws InterruptedException, IOException {
92 92 var args = new ArrayList<String>();
93 93 args.add(PUSH_COMMAND);
94 94 args.addAll(options);
95 95 args.add(image);
96 96 complete(startProcess(builder(args)));
97 97 }
98 98
99 99 public void runImage(String image, List<String> options, List<String> command)
100 100 throws InterruptedException, IOException {
101 101 var args = new ArrayList<String>();
102 102 args.add(RUN_COMMAND);
103 103 args.addAll(options);
104 104 args.add(image);
105 105 args.addAll(command);
106 106 complete(startProcess(builder(args)));
107 107 }
108 108
109 public void saveImage(List<String> images, File output) throws InterruptedException, IOException {
109 public void saveImage(Set<String> images, File output) throws InterruptedException, IOException {
110 110 if (output.exists())
111 111 output.delete();
112 112
113 113 var args = new ArrayList<String>();
114 114 args.add(SAVE_COMMAND);
115 115 args.add("-o");
116 116 args.add(output.getAbsolutePath());
117 117 images.forEach(args::add);
118 118
119 119 complete(startProcess(builder(args)));
120 120 }
121 121
122 122 public void tagImage(String source, String target) throws InterruptedException, IOException {
123 123 complete(startProcess(builder(TAG_COMMAND, source, target)));
124 124 }
125 125
126 126 public boolean imageExists(String imageId) throws InterruptedException, IOException {
127 127 getLogger().info("Check image {} exists", imageId);
128 128
129 129 return checkRetCode(
130 130 startProcess(builder(IMAGE_COMMAND, INSPECT_COMMAND, "--format", "image-exists", imageId)),
131 131 0);
132 132 }
133 133
134 134 public boolean imageExists(File imageIdFile) {
135 135 if (imageIdFile.exists()) {
136 136 try {
137 137 var imageId = Utils.readImageRef(imageIdFile);
138 138 return imageExists(imageId.getId());
139 139 } catch (IOException | InterruptedException e) {
140 140 getLogger().error("Failed to read imageId {}: {}", imageIdFile, e);
141 141 return false;
142 142 }
143 143 }
144 144 return false;
145 145 }
146 146
147 147 List<String> composeArgs(File primaryCompose, Set<String> profiles, String... extra) {
148 148 var args = new ArrayList<String>();
149 149
150 150 args.add(COMPOSE_COMMAND);
151 151 args.add("--file");
152 152 args.add(primaryCompose.getAbsolutePath());
153 153
154 154 if (profiles.size() > 0) {
155 155 for (var profile : profiles) {
156 156 args.add("--profile");
157 157 args.add(profile);
158 158 }
159 159 }
160 160
161 161 args.addAll(List.of(extra));
162 162
163 163 return args;
164 164 }
165 165
166 166 public void composeUp(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException {
167 167 var args = composeArgs(primaryCompose, profiles, UP_COMMAND, "--detach");
168 168
169 169 complete(startProcess(builder(args)));
170 170 }
171 171
172 172 public void composeStop(File primaryCompose, Set<String> profiles) throws InterruptedException, IOException {
173 173 var args = composeArgs(primaryCompose, profiles, STOP_COMMAND);
174 174 complete(startProcess(builder(args)));
175 175 }
176 176
177 177 public void composeRm(File primaryCompose, Set<String> profiles, boolean removeVolumes)
178 178 throws InterruptedException, IOException {
179 179 var args = composeArgs(primaryCompose, profiles, RM_COMMAND, "--force", "--stop");
180 180
181 181 if (removeVolumes)
182 182 args.add("--volumes");
183 183
184 184 complete(startProcess(builder(args)));
185 185 }
186 186 }
@@ -1,111 +1,111
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.io.File;
4 4 import java.io.IOException;
5 5 import java.util.ArrayList;
6 6 import java.util.Optional;
7 7
8 8 import org.gradle.api.file.DirectoryProperty;
9 9 import org.gradle.api.file.FileCollection;
10 10 import org.gradle.api.file.RegularFile;
11 import org.gradle.api.provider.ListProperty;
12 11 import org.gradle.api.provider.Property;
13 12 import org.gradle.api.provider.Provider;
13 import org.gradle.api.provider.SetProperty;
14 14 import org.gradle.api.tasks.Input;
15 15 import org.gradle.api.tasks.Internal;
16 16 import org.gradle.api.tasks.OutputFile;
17 17 import org.gradle.api.tasks.TaskAction;
18 18 import org.gradle.api.tasks.TaskExecutionException;
19 19 import org.implab.gradle.containers.cli.Utils;
20 20
21 21 public abstract class SaveImage extends DockerCliTask {
22 22
23 23 @Input
24 public abstract ListProperty<String> getExportImages();
24 public abstract SetProperty<String> getExportImages();
25 25
26 26 @OutputFile
27 27 public Provider<RegularFile> getArchiveFile() {
28 28 return getDestinationDirectory().file(getArchiveFileName());
29 29 }
30 30
31 31 @Internal
32 32 public abstract DirectoryProperty getDestinationDirectory();
33 33
34 34 @Internal
35 35 public abstract Property<String> getArchiveFileName();
36 36
37 37 @Internal
38 38 public abstract Property<String> getArchiveBaseName();
39 39
40 40 @Internal
41 41 public abstract Property<String> getArchiveVersion();
42 42
43 43 @Internal
44 44 public abstract Property<String> getArchiveClassifier();
45 45
46 46 @Internal
47 47 public abstract Property<String> getArchiveExtension();
48 48
49 49 String readImageRefTag(File file) throws Exception {
50 50 getLogger().info("Reading image ref from {}", file);
51 51 var imageRef = Utils.readImageRef(file);
52 52 return imageRef.getTag().orElseThrow(() -> new Exception("The image tag is required to save image"));
53 53 }
54 54
55 55 public void imageRef(File file) {
56 56 getExportImages().add(provider(() -> {
57 57 return readImageRefTag(file);
58 58 }));
59 59 }
60 60
61 61 public void imageRefs(FileCollection files) {
62 62 dependsOn(files);
63 63 getExportImages().addAll(provider(() -> {
64 64 var tags = new ArrayList<String>();
65 65 for (File file : files) {
66 66 tags.add(readImageRefTag(file));
67 67 }
68 68 return tags;
69 69 }));
70 70 }
71 71
72 72 public SaveImage() {
73 73 getArchiveFileName().convention(provider(this::conventionalArchiveFileName));
74 74 getArchiveBaseName().convention(provider(this::conventionalArchiveBaseName));
75 75 getArchiveVersion().convention(provider(() -> getProject().getVersion()).map(x -> x.toString()));
76 76 getDestinationDirectory().convention(getProject().getLayout().getBuildDirectory());
77 77 getArchiveExtension().convention("tar");
78 78 }
79 79
80 80 private String conventionalArchiveBaseName() {
81 81 ArrayList<String> parts = new ArrayList<>();
82 82 Optional.of(getProject().getGroup()).map(x -> x.toString()).ifPresent(parts::add);
83 83 parts.add(getProject().getName());
84 84 return String.join("-", parts);
85 85 }
86 86
87 87 private String conventionalArchiveFileName() {
88 88 ArrayList<String> parts = new ArrayList<>();
89 89
90 90 if (getArchiveBaseName().isPresent())
91 91 parts.add(getArchiveBaseName().get());
92 92
93 93 if (getArchiveVersion().isPresent())
94 94 parts.add(getArchiveVersion().get());
95 95
96 96 if (getArchiveClassifier().isPresent())
97 97 parts.add(getArchiveClassifier().get());
98 98
99 99 if (parts.size() == 0)
100 100 throw new TaskExecutionException(this, new Exception("The archiveFileName ism't specified"));
101 101
102 102 return String.join("-", parts) + "." + getArchiveExtension().get();
103 103 }
104 104
105 105 @TaskAction
106 106 public void run() throws InterruptedException, IOException {
107 107 docker().saveImage(
108 108 getExportImages().get(),
109 109 getArchiveFile().map(RegularFile::getAsFile).get());
110 110 }
111 111 }
@@ -1,21 +1,46
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.io.IOException;
4 import java.util.HashSet;
5 import java.util.Set;
6
4 7 import org.gradle.api.provider.Property;
8 import org.gradle.api.provider.SetProperty;
5 9 import org.gradle.api.tasks.Input;
6 10 import org.gradle.api.tasks.TaskAction;
7 11
8 12 public abstract class TagImage extends DockerCliTask {
9 13 @Input
10 14 public abstract Property<String> getSrcImage();
11 15
12 16 @Input
17 public abstract SetProperty<String> getTags();
18
19 @Input
20 @Deprecated
13 21 public abstract Property<String> getDestImage();
14 22
23 private Set<String> getImageTags() {
24 var tags = new HashSet<>(getTags().get());
25 tags.add(getDestImage().get());
26 return tags;
27 }
28
29 public TagImage() {
30 this.setOnlyIf("No tags were specified", self -> getImageTags().size() > 0);
31 }
32
15 33 @TaskAction
16 34 public void run() throws InterruptedException, IOException {
17 docker().tagImage(
18 getSrcImage().get(),
19 getDestImage().get());
35 var tags = getImageTags();
36 var src = getSrcImage().get();
37
38 if (tags.size() == 0)
39 getLogger().info("No tags were specified");
40
41 for (var tag : tags) {
42 getLogger().info("Tag: {}", tag);
43 docker().tagImage(src, tag);
44 }
20 45 }
21 46 }
@@ -1,110 +1,220
1 1 # Build and publish images with docker/podman
2 2
3 3 ## SYNOPSIS
4 4
5 5 ```gradle
6 6
7 7 plugins {
8 id 'org.implab.gradle-container' version '1.1'
8 id 'org.implab.gradle-container'
9 9 }
10 10
11 11 container {
12 12 // if you want to use podman
13 13 cliCmd = "podman"
14 14 }
15 15
16 16 configurations {
17 17 app
18 18 }
19 19
20 20 dependencies {
21 21 // the application that needs to be built and packed
22 22 app project(":server")
23 23 }
24 24
25 25 // add custom task to copy application files
26 26 // to the docker build context.
27 27 task copyApp(type: Copy) {
28 28 processResources.dependsOn it
29 29
30 30 into container.contextDirectory.dir("root/opt/myapp")
31 31 from configurations.app
32 32 }
33 33
34 34 task printVersion {
35 35 doLast {
36 36 println "tag: ${buildImage.imageTag.get()}"
37 37 println "archive: ${saveImage.archiveFileName.get()}"
38 38 }
39 39 }
40 40
41 41
42 42 ```
43 43
44 44 ## Description
45 45
46 46 This plugin is a simple wrapper around docker CLI. All the image
47 building process is deligated to the `Dockerfile` which will run
48 in the prepeared build context.
47 building process is delegated to the `Dockerfile` which will run
48 in the prepared build context.
49 49
50 ### Project structure
50 ## Project structure
51 51
52 * `build/`
53 * `context/` - the build context where `docker build` command will run.
54 * `imageid` - the file storing the id of the image has been built.
55 * `image-name-1.2.3.tgz` - the exported image if `saveImage` has been executed.
52 * `build/` - this folder will be created during build, it can be useful while
53 solving Dockerfile problems
54 * `context/` - the build context where `docker build` command will run.
55 * `imageid` - the file storing the id of the image has been built.
56 * `image-name-1.2.3.tgz` - the exported image if `saveImage` has been executed.
56 57 * `src`
57 * `main` - the source files which will be copied to the build context.
58 * `main` - the source files which will be copied to the build context.
59
60 ## Global properties
58 61
59 ## Properties
62 There are several global properties recognized by this plugin in the project.
63 These properties affect images naming and publication and they are useful in
64 multi-project environment.
60 65
61 66 `imagesAuthority` - the registry where the image should be published.
62 67 for example `docker.io`
63 68
64 69 `imagesGroup` - the path to the image in the repository.
65 70
71 `containerCli` - the command line cli, this property corresponds to
72 `container.cliCmd` in the project.
73
74 Properties defined in the project takes precedence over global properties.
75
76 ## Image names
77
78 ```gradle
79 plugins {
80 id "org.implab.gradle-container"
81 }
82
83 container {
84 // image authority, the repository for your images
85 // defaults to global imagesAuthority property or none
86 imageAuthority = "my.registry.org"
87
88 // the image path
89 // defaults to global imagesGroup property or none
90 imageGroup = "my/project"
91
92 // the name of the image
93 // defaults to project.name
94 imageLocalName = "foo"
95 }
96
97 // provider for imageName, returns ImageName object
98 // ImageName consists of "{imageAuthority}/{imageGroup}/{imageLocalName}"
99 def imageNameProvider = container.imageName
100 ```
101
66 102 ## Tasks
67 103
104 Some tasks support passing additional options as additional command line
105 parameters. These task has the property `options` and some additional methods.
106
107 | Property | Description |
108 |--|--|
109 | `options` | A list of additional arguments passed to `docker build` command. |
110
111 | Method | Description |
112 |---|---|
113 | `option(String)` | Adds option to `options`. |
114 | `option(Closure)` | Converts the parameter to provider and adds it to `options`. |
115 | `option(Callable)` | Converts the parameter to provider and adds it to `options`. |
116 | `options(String...)` | Adds specified options to `options`. |
117 | `options(Closure)`| Converts the parameter to provider and adds it to `options`. |
118 | `options(Callable)`| Converts the parameter to provider and adds it to `options` |
119
68 120 ### buildImage
69 121
122 `type: BuildImage`
123
70 124 The task builds the image. Wrapper around the `docker build` command.
71 125
126 | Property | Description |
127 |---|---|
128 | `contextDirectory` | A Dockerfile context directory. Set to `container.context`. |
129 | `buildArgs` | A dictionary with environment variables which are set during build. |
130 | `buildTarget` | A target image for the multi-stage builds. Defaults to none. |
131 | `imageName` | A name (tag) for the resulting image. |
132 | `imageIdFIle` | Output file name where image ref will be written. |
133
134 This task also supports additional command line options.
135
72 136 ### saveImage
73 137
138 `type: SaveImage`
139
74 140 The task exports image as the .tar archive.
75 141
142 | Property | Description |
143 |-|-|
144 | `archiveFileName` | The file name of the bundle archive, defaults to `{archiveBaseName}-{archiveVersion}-{archiveClassifier}.{archiveExtension}`.|
145 | `archiveBaseName` | The base name of the archive, defaults to `{project.group}-{project.name}`. |
146 | `archiveVersion` | The archive version, defaults to `{project.version}`. |
147 | `exportImages` | A set of image names to include in the bundle. |
148
149 | Method | Description |
150 |-|-|
151 | `imageRefs(FileCollection)` | Adds a set of files with image refs to add to the bundle. |
152 | `imageRef(File)` | Adds an image name from the file with image reference. |
153
76 154 ### pushImage
77 155
78 The task pushes the image to the remote repository.
156 The task pushes the image to the remote repository (imageAuthority).
157
158 [Since v1.2] This task also supports additional command line options. You can use them to
159 push all tags for the image.
160
161 ```gradle
162 pushImage {
163 option "--all-tags"
164 }
165 ```
79 166
80 167 ### processResources
81 168
82 The copy task, it prepares the build context. Use it to customize
169 The copy task, it prepares the build context. Use this task to customize
83 170 the build context.
84 171
85 172 ### tagImage
86 173
174 `type: TagImage`
175
87 176 since: 1.1
88 177
89 178 ```gradle
90 179 task tagLatest(type: TagImage) {
180 pushImage.dependsOn it
181
91 182 srcImage = container.imageName
92 destImage = container.imageName.map { it.tag("latest") }
183
184 tags.add(container.imageName.map { it.tag("latest") })
93 185 }
94 186
95 187 ```
96 188
189 | Property | Description |
190 |-|-|
191 | `srcImage` | The source image name to add tag to. |
192 | `tags` | The set of tags to add to the image. |
193
194 ## See also
195
196 * Creating [compose](compose.md) project
197 * Creating [bundle](bundle.md) project
198
97 199 ## Changes
98 200
201 ### 1.2
202
203 Added `org.implab.gradle-container-base`, `org.implab.gradle-container-compose`
204 plugins.
205
206 * `org.implab.gradle-container-base` registers base extension and task types.
207 * `org.implab.gradle-container-compose` registers conventional tasks.
208
99 209 ### 1.1
100 210
101 211 Warning! This version isn't fully backward compatible with 1.0 version.
102 212
103 213 * Added `TagImage` task type
104 214 * `ImageTag` class is replaced with `ImageName` class
105 215 * `BuildImage`, `PushImage` tasks are now accepting only `imageName` property
106 216 * Added `imageName`, `imageShortName`, `imageTag` properties to `container` extension
107 217
108 218 ### 1.0
109 219
110 220 Initial release. Default tasks to build and publish container images.
General Comments 0
You need to be logged in to leave comments. Login now