##// END OF EJS Templates
Reworked ImageName, added imageName property to the project extension, added TagImage task.
cin -
r4:00ef236c472b v1.1 default
parent child
Show More
@@ -0,0 +1,66
1 package org.implab.gradle.containers;
2
3 import java.io.Serializable;
4 import java.util.Optional;
5
6 public class ImageName implements Serializable {
7
8 private static final long serialVersionUID = -1990105923537254552L;
9
10 final String authority;
11
12 final String name;
13
14 final String tag;
15
16 public ImageName() {
17 name = null;
18 authority = null;
19 tag = null;
20 }
21
22 private ImageName(String authority, String name, String tag) {
23 this.authority = authority;
24 this.name = name;
25 this.tag = tag;
26 }
27
28 public String getAuthority() {
29 return authority;
30 }
31
32 public ImageName authority(String authority) {
33 return new ImageName(authority, name, tag);
34 }
35
36 public String getName() {
37 return name;
38 }
39
40 public ImageName name(String name) {
41 return new ImageName(authority, name, tag);
42 }
43
44 public String getTag() {
45 return tag;
46 }
47
48 public ImageName tag(String tag) {
49 return new ImageName(authority, name, tag);
50 }
51
52 public ImageName copy() {
53 return new ImageName(authority, name, tag);
54 }
55
56 @Override
57 public String toString() {
58 StringBuilder sb = new StringBuilder();
59
60 Optional.ofNullable(authority).ifPresent(s -> sb.append(s).append("/"));
61 Optional.ofNullable(name).ifPresent(s -> sb.append(s));
62 Optional.ofNullable(tag).ifPresent(s-> sb.append(":").append(s));
63
64 return sb.toString();
65 }
66 }
@@ -0,0 +1,42
1 package org.implab.gradle.containers.tasks;
2
3 import java.util.List;
4 import java.util.concurrent.Callable;
5
6 import org.gradle.api.provider.ListProperty;
7 import org.gradle.api.provider.Property;
8 import org.gradle.api.tasks.Input;
9 import org.gradle.api.tasks.Optional;
10 import org.implab.gradle.containers.ImageName;
11
12 public abstract class TagImage extends CliTask {
13
14 @Input
15 public abstract Property<ImageName> getSrcImage();
16
17 @Input
18 public abstract Property<ImageName> getDestImage();
19
20 @Input
21 @Optional
22 public abstract Property<String> getTransport();
23
24 @Input
25 @Optional
26 public abstract ListProperty<String> getOptions();
27
28 public TagImage option(Callable<String> provider) {
29 getOptions().add(getProject().provider(provider));
30 return this;
31 }
32
33 @Override
34 protected void preparingCommand(List<String> commandLine) {
35 super.preparingCommand(commandLine);
36
37 commandLine.add("tag");
38 commandLine.addAll(getOptions().get());
39 commandLine.add(getSrcImage().get().toString());
40 commandLine.add(getDestImage().get().toString());
41 }
42 }
@@ -1,34 +1,27
1 1 plugins {
2 2 id "java-gradle-plugin"
3 id "com.gradle.plugin-publish" version "0.15.0"
3 id "com.gradle.plugin-publish" version "0.16.0"
4 4 }
5 5
6 6
7 7 repositories {
8 8 mavenCentral()
9 9 }
10 10
11 11 gradlePlugin {
12 website = 'https://code.implab.org/implab/gradle-container-plugin'
13 vcsUrl = 'https://code.implab.org/implab/gradle-container-plugin'
14
15 12 plugins {
16 13 containerPlugin {
17 14 id = 'org.implab.gradle-container'
15 displayName = "Container building plugin"
16 description = 'Build and publish container images with docker or podman. Simple wrapper around cli.'
18 17 implementationClass = 'org.implab.gradle.containers.ContainerPlugin'
19 18 }
20 19 }
21 20 }
22 21
23 22 pluginBundle {
24 23 website = 'https://code.implab.org/implab/gradle-container-plugin'
25 24 vcsUrl = 'https://code.implab.org/implab/gradle-container-plugin'
26 25
27 26 tags = ['containers', 'image', 'docker', 'podman']
28
29 plugins {
30 containerPlugin {
31 description = 'Build and publish container images with docker or podman'
32 27 }
33 }
34 }
@@ -1,2 +1,2
1 1 group=org.implab.gradle
2 version=1.0 No newline at end of file
2 version=1.1 No newline at end of file
@@ -1,58 +1,94
1 1 package org.implab.gradle.containers;
2 2
3 import java.util.Optional;
4
5 import org.gradle.api.Project;
3 6 import org.gradle.api.file.DirectoryProperty;
4 7 import org.gradle.api.file.ProjectLayout;
5 8 import org.gradle.api.file.RegularFileProperty;
6 9 import org.gradle.api.model.ObjectFactory;
7 10 import org.gradle.api.provider.Property;
11 import org.gradle.api.provider.Provider;
8 12
9 13 public class ContainerExtension {
10 14
11 15 private final Property<String> cliCmd;
12 16
13 17 private final Property<String> imageAuthority;
14 18
15 19 private final Property<String> imageGroup;
16 20
21 private final Property<String> imageShortName;
22
23 private final Property<String> imageTag;
24
25 private final Property<ImageName> imageName;
26
17 27 private final DirectoryProperty contextDir;
18 28
19 29 private final RegularFileProperty imageIdFile;
20 30
21 public ContainerExtension(ObjectFactory factory, ProjectLayout layout) {
31 public ContainerExtension(ObjectFactory factory, ProjectLayout layout, Project project) {
22 32 contextDir = factory.directoryProperty();
23 33 contextDir.convention(layout.getBuildDirectory().dir("context"));
24 34
25 35 imageIdFile = factory.fileProperty();
26 36 imageIdFile.convention(layout.getBuildDirectory().file("imageId"));
27 37
28 38 cliCmd = factory.property(String.class);
29 39 cliCmd.set("docker");
30 40
31 41 imageAuthority = factory.property(String.class);
32 42 imageGroup = factory.property(String.class);
43 imageShortName = factory.property(String.class);
44
45 imageShortName.convention(project.getName());
46
47 imageTag = factory.property(String.class);
48 imageTag.set(project
49 .provider(() -> Optional.ofNullable(project.getVersion()).map(v -> v.toString()).orElse("latest")));
50
51 Provider<String> imageRepository = imageGroup.map(g -> g + "/" + imageShortName.get()).orElse(imageShortName);
52
53 imageName = factory.property(ImageName.class);
54 imageName.convention(project.provider(
55 () -> new ImageName().authority(imageAuthority.get()).name(imageRepository.get()).tag(imageTag.get())));
33 56 }
34 57
35 58 public Property<String> getCliCmd() {
36 59 return cliCmd;
37 60 }
38 61
39 62 public void setCliCmd(String cliCmd) {
40 63 this.cliCmd.set(cliCmd);
41 64 }
42 65
43 66 public DirectoryProperty getContextDirectory() {
44 67 return contextDir;
45 68 }
46 69
47 70 public RegularFileProperty getImageIdFile() {
48 71 return imageIdFile;
49 72 }
50 73
51 74 public Property<String> getImageAuthority() {
52 75 return imageAuthority;
53 76 }
54 77
55 78 public Property<String> getImageGroup() {
56 79 return imageGroup;
57 80 }
81
82 public Property<ImageName> getImageName() {
83 return imageName;
84 }
85
86 public Property<String> getImageShortName() {
87 return imageShortName;
88 }
89
90 public ImageName createImageName() {
91 return new ImageName();
92 }
93
58 94 } No newline at end of file
@@ -1,102 +1,95
1 1 package org.implab.gradle.containers;
2 2
3 import java.util.Optional;
4
5 3 import org.gradle.api.Plugin;
6 4 import org.gradle.api.Project;
7 5 import org.gradle.api.artifacts.Dependency;
8 6 import org.gradle.api.file.ProjectLayout;
9 7 import org.gradle.api.model.ObjectFactory;
10 8 import org.gradle.api.plugins.ExtraPropertiesExtension;
11 import org.gradle.api.provider.Provider;
12 9 import org.gradle.api.tasks.Copy;
13 10 import org.gradle.api.tasks.Delete;
14 11 import org.gradle.api.tasks.TaskProvider;
15 12 import org.implab.gradle.containers.tasks.BuildImage;
16 13 import org.implab.gradle.containers.tasks.PushImage;
17 14 import org.implab.gradle.containers.tasks.SaveImage;
15 import org.implab.gradle.containers.tasks.TagImage;
18 16
19 17 public class ContainerPlugin implements Plugin<Project> {
20 18
21 19 public static final String BUILD_GROUP = "build";
22 20
23 21 public static final String CONTAINER_EXTENSION_NAME = "container";
24 22
25 23 public static final String ARCHIVE_CONFIGURATION = "archive";
26 24
27 25 private ContainerExtension containerExtension;
28 26
29 27 public void apply(Project project) {
30 28 ObjectFactory factory = project.getObjects();
31 29 ProjectLayout layout = project.getLayout();
32 containerExtension = new ContainerExtension(factory, layout);
30 containerExtension = new ContainerExtension(factory, layout, project);
33 31
34 32 containerExtension.getImageAuthority()
35 33 .convention(project.provider(() -> (String) project.getProperties().get("imagesAuthority")));
36 34
37 35 containerExtension.getImageGroup()
38 36 .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup")));
39 37
40 38 project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension);
41 39
42 40 ExtraPropertiesExtension extras = project.getExtensions().getExtraProperties();
43 41 extras.set(BuildImage.class.getSimpleName(), BuildImage.class);
44 42 extras.set(PushImage.class.getSimpleName(), PushImage.class);
45 43 extras.set(SaveImage.class.getSimpleName(), SaveImage.class);
44 extras.set(TagImage.class.getSimpleName(), TagImage.class);
46 45
47 46 project.getConfigurations().create(Dependency.DEFAULT_CONFIGURATION, c -> {
48 47 c.setCanBeConsumed(true);
49 48 c.setCanBeResolved(false);
50 49 });
51 50
52 51 project.getConfigurations().create(ARCHIVE_CONFIGURATION, c -> {
53 52 c.setCanBeConsumed(true);
54 53 c.setCanBeResolved(false);
55 54 });
56 55
57 56 TaskProvider<Copy> processResourcesTask = project.getTasks().register("processResources", Copy.class, t -> {
58 57 t.setGroup(BUILD_GROUP);
59 58 t.from(layout.getProjectDirectory().dir("src/main"));
60 59 t.into(containerExtension.getContextDirectory());
61 60 });
62 61
63 62 TaskProvider<BuildImage> buildImageTask = project.getTasks().register("buildImage", BuildImage.class, t -> {
64 63 t.setGroup(BUILD_GROUP);
65 64 t.dependsOn(processResourcesTask);
66 65
67 66 t.getContextDirectory().set(containerExtension.getContextDirectory());
68 67 t.getImageIdFile().set(containerExtension.getImageIdFile());
69 68
70 Provider<String> imageName = containerExtension.getImageGroup().map(g -> g + "/" + project.getName())
71 .orElse(project.getName());
72
73 t.getImageAuthority().set(containerExtension.getImageAuthority());
74 t.getImageQName().convention(imageName);
75 t.getImageVersion()
76 .convention(Optional.ofNullable(project.getVersion()).map(v -> v.toString()).orElse("latest"));
69 t.getImageName().set(containerExtension.getImageName());
77 70 });
78 71
79 72 project.getTasks().register("clean", Delete.class, t -> {
80 73 t.delete(layout.getBuildDirectory());
81 74 });
82 75
83 76 project.getTasks().register("build", t -> {
84 77 t.setGroup(BUILD_GROUP);
85 78 t.dependsOn(buildImageTask);
86 79 });
87 80
88 81 project.getTasks().register("pushImage", PushImage.class, t -> {
89 82 t.dependsOn(buildImageTask);
90 t.getSrcImage().set(buildImageTask.flatMap(b -> b.getImageTag()));
83 t.getImageName().set(buildImageTask.flatMap(b -> b.getImageName()));
91 84 });
92 85
93 86 project.getTasks().register("saveImage", SaveImage.class, t -> {
94 87 t.dependsOn(buildImageTask);
95 t.getImage().set(buildImageTask.flatMap(b -> b.getImageTag()));
88 t.getImage().set(buildImageTask.flatMap(b -> b.getImageName()));
96 89 });
97 90
98 91 project.getArtifacts().add(Dependency.DEFAULT_CONFIGURATION, buildImageTask.flatMap(x -> x.getImageIdFile()), t -> {
99 92 t.builtBy(buildImageTask);
100 93 });
101 94 }
102 95 }
@@ -1,62 +1,43
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.util.List;
4 4
5 5 import org.gradle.api.file.DirectoryProperty;
6 6 import org.gradle.api.file.RegularFileProperty;
7 7 import org.gradle.api.provider.Property;
8 import org.gradle.api.provider.Provider;
9 8 import org.gradle.api.tasks.Input;
10 9 import org.gradle.api.tasks.InputDirectory;
11 import org.gradle.api.tasks.Internal;
12 10 import org.gradle.api.tasks.OutputFile;
13 11 import org.gradle.api.tasks.SkipWhenEmpty;
14 12 import org.implab.gradle.Utils;
15 import org.implab.gradle.containers.ImageTag;
13 import org.implab.gradle.containers.ImageName;
16 14
17 15 public abstract class BuildImage extends CliTask {
18 16 @InputDirectory
19 17 @SkipWhenEmpty
20 18 public abstract DirectoryProperty getContextDirectory();
21 19
22 @Internal
23 public abstract Property<String> getImageQName();
24
25 @Internal
26 public abstract Property<String> getImageVersion();
27
28 @Internal
29 public abstract Property<String> getImageAuthority();
30
31 20 @Input
32 public Provider<ImageTag> getImageTag() {
33 return getProject().provider(() -> {
34 ImageTag tag = new ImageTag();
35 tag.setQName(getImageQName().getOrNull());
36 tag.setAuthority(getImageAuthority().getOrNull());
37 tag.setVersion(getImageVersion().getOrNull());
38 return tag;
39 });
40 }
21 public abstract Property<ImageName> getImageName();
41 22
42 23 @OutputFile
43 24 public abstract RegularFileProperty getImageIdFile();
44 25
45 26 public BuildImage() {
46 27
47 28 setWorkingDir(getContextDirectory().getAsFile());
48 29 }
49 30
50 31 @Override
51 32 protected void preparingCommand(List<String> commandLine) {
52 33 super.preparingCommand(commandLine);
53 34
54 35 commandLine.addAll(List.of("build", ".", "-q", "-t"));
55 commandLine.add(getImageTag().get().toString());
36 commandLine.add(getImageName().get().toString());
56 37 }
57 38
58 39 @Override
59 40 protected void processStarted(Process p) {
60 41 Utils.redirectIO(p.getInputStream(), getImageIdFile().get().getAsFile());
61 42 }
62 43 }
@@ -1,51 +1,38
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.util.List;
4 4 import java.util.concurrent.Callable;
5 5
6 6 import org.gradle.api.provider.ListProperty;
7 7 import org.gradle.api.provider.Property;
8 import org.gradle.api.provider.Provider;
9 8 import org.gradle.api.tasks.Input;
10 import org.gradle.api.tasks.Internal;
11 9 import org.gradle.api.tasks.Optional;
12 import org.implab.gradle.containers.ImageTag;
10 import org.implab.gradle.containers.ImageName;
13 11
14 12 public abstract class PushImage extends CliTask {
15 13
16 14 @Input
17 public abstract Property<ImageTag> getSrcImage();
18
19 @Input
20 @Optional
21 public abstract Property<ImageTag> getDestImage();
15 public abstract Property<ImageName> getImageName();
22 16
23 17 @Input
24 18 @Optional
25 19 public abstract Property<String> getTransport();
26 20
27 21 @Input
28 22 @Optional
29 23 public abstract ListProperty<String> getOptions();
30 24
31 25 public PushImage option(Callable<String> provider) {
32 26 getOptions().add(getProject().provider(provider));
33 27 return this;
34 28 }
35 29
36 @Internal
37 public Provider<String> getDestination() {
38 return getDestImage().flatMap(tag -> getTransport().orElse("").map(t -> t + tag.toString()));
39 }
40
41 30 @Override
42 31 protected void preparingCommand(List<String> commandLine) {
43 32 super.preparingCommand(commandLine);
44 33
45 34 commandLine.add("push");
46 35 commandLine.addAll(getOptions().get());
47 commandLine.add(getSrcImage().get().toString());
48 if (getDestination().isPresent())
49 commandLine.add(getDestination().get());
36 commandLine.add(getImageName().get().toString());
50 37 }
51 38 }
@@ -1,91 +1,91
1 1 package org.implab.gradle.containers.tasks;
2 2
3 3 import java.util.ArrayList;
4 4 import java.util.List;
5 5 import java.util.Optional;
6 6
7 7 import org.gradle.api.file.DirectoryProperty;
8 8 import org.gradle.api.file.RegularFile;
9 9 import org.gradle.api.provider.Property;
10 10 import org.gradle.api.provider.Provider;
11 11 import org.gradle.api.tasks.Input;
12 12 import org.gradle.api.tasks.Internal;
13 13 import org.gradle.api.tasks.OutputFile;
14 14 import org.gradle.api.tasks.TaskExecutionException;
15 15 import org.implab.gradle.Utils;
16 import org.implab.gradle.containers.ImageTag;
16 import org.implab.gradle.containers.ImageName;
17 17
18 18 public abstract class SaveImage extends CliTask {
19 19
20 20 @Input
21 public abstract Property<ImageTag> getImage();
21 public abstract Property<ImageName> getImage();
22 22
23 23 @OutputFile
24 24 public Provider<RegularFile> getArchiveFile() {
25 25 return getDestinationDirectory().file(getArchiveFileName());
26 26 }
27 27
28 28 @Internal
29 29 public abstract DirectoryProperty getDestinationDirectory();
30 30
31 31 @Internal
32 32 public abstract Property<String> getArchiveFileName();
33 33
34 34 @Internal
35 35 public abstract Property<String> getArchiveBaseName();
36 36
37 37 @Internal
38 38 public abstract Property<String> getArchiveVersion();
39 39
40 40 @Internal
41 41 public abstract Property<String> getArchiveClassifier();
42 42
43 43 @Internal
44 44 public abstract Property<String> getArchiveExtension();
45 45
46 46 public SaveImage() {
47 47 getArchiveFileName().convention(getProject().provider(this::conventionalArchiveFileName));
48 48 getArchiveBaseName().convention(getProject().provider(this::conventionalArchiveBaseName));
49 49 getArchiveVersion().convention(getProject().provider(() -> getProject().getVersion()).map(x -> x.toString()));
50 50 getDestinationDirectory().convention(getProject().getLayout().getBuildDirectory());
51 51 getArchiveExtension().convention("tar");
52 52 }
53 53
54 54 private String conventionalArchiveBaseName() {
55 55 ArrayList<String> parts = new ArrayList<>();
56 56 Optional.of(getProject().getGroup()).map(x -> x.toString()).ifPresent(parts::add);
57 57 parts.add(getProject().getName());
58 58 return String.join("-", parts);
59 59 }
60 60
61 61 private String conventionalArchiveFileName() {
62 62 ArrayList<String> parts = new ArrayList<>();
63 63
64 64 if (getArchiveBaseName().isPresent())
65 65 parts.add(getArchiveBaseName().get());
66 66
67 67 if (getArchiveVersion().isPresent())
68 68 parts.add(getArchiveVersion().get());
69 69
70 70 if (getArchiveClassifier().isPresent())
71 71 parts.add(getArchiveClassifier().get());
72 72
73 73 if (parts.size() == 0)
74 74 throw new TaskExecutionException(this, new Exception("The archiveFileName ism't specified"));
75 75
76 76 return String.join("-", parts) + "." + getArchiveExtension().get();
77 77 }
78 78
79 79 @Override
80 80 protected void preparingCommand(List<String> commandLine) {
81 81 super.preparingCommand(commandLine);
82 82
83 83 commandLine.add("save");
84 84 commandLine.add(getImage().get().toString());
85 85 }
86 86
87 87 @Override
88 88 protected void processStarted(Process p) {
89 89 Utils.redirectIO(p.getInputStream(), getArchiveFile().get().getAsFile());
90 90 }
91 91 }
@@ -1,83 +1,110
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.0"
8 id 'org.implab.gradle-container' version '1.1'
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 47 building process is deligated to the `Dockerfile` which will run
48 48 in the prepeared build context.
49 49
50 50 ### Project structure
51 51
52 52 * `build/`
53 53 * `context/` - the build context where `docker build` command will run.
54 54 * `imageid` - the file storing the id of the image has been built.
55 55 * `image-name-1.2.3.tgz` - the exported image if `saveImage` has been executed.
56 56 * `src`
57 57 * `main` - the source files which will be copied to the build context.
58 58
59 59 ## Properties
60 60
61 61 `imagesAuthority` - the registry where the image should be published.
62 62 for example `docker.io`
63 63
64 64 `imagesGroup` - the path to the image in the repository.
65 65
66 66 ## Tasks
67 67
68 68 ### buildImage
69 69
70 70 The task builds the image. Wrapper around the `docker build` command.
71 71
72 72 ### saveImage
73 73
74 74 The task exports image as the .tar archive.
75 75
76 76 ### pushImage
77 77
78 78 The task pushes the image to the remote repository.
79 79
80 80 ### processResources
81 81
82 82 The copy task, it prepares the build context. Use it to customize
83 83 the build context.
84
85 ### tagImage
86
87 since: 1.1
88
89 ```gradle
90 task tagLatest(type: TagImage) {
91 srcImage = container.imageName
92 destImage = container.imageName.map { it.tag("latest") }
93 }
94
95 ```
96
97 ## Changes
98
99 ### 1.1
100
101 Warning! This version isn't fully backward compatible with 1.0 version.
102
103 * Added `TagImage` task type
104 * `ImageTag` class is replaced with `ImageName` class
105 * `BuildImage`, `PushImage` tasks are now accepting only `imageName` property
106 * Added `imageName`, `imageShortName`, `imageTag` properties to `container` extension
107
108 ### 1.0
109
110 Initial release. Default tasks to build and publish container images.
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now