diff --git a/container/build.gradle b/container/build.gradle --- a/container/build.gradle +++ b/container/build.gradle @@ -38,6 +38,14 @@ gradlePlugin { implementationClass = 'org.implab.gradle.containers.ContainerBasePlugin' tags.set(['containers', 'image', 'docker', 'podman']) } + + conmposePlugin { + id = 'org.implab.gradle-container-compose' + displayName = "Provdes tasks to start and stop compose" + description = 'Build and publish container images with docker or podman. Simple wrapper around cli.' + implementationClass = 'org.implab.gradle.containers.ComposePlugin' + tags.set(['containers', 'image', 'docker', 'podman']) + } } } diff --git a/container/src/main/java/org/implab/gradle/containers/ContainerBasePlugin.java b/container/src/main/java/org/implab/gradle/containers/ContainerBasePlugin.java --- a/container/src/main/java/org/implab/gradle/containers/ContainerBasePlugin.java +++ b/container/src/main/java/org/implab/gradle/containers/ContainerBasePlugin.java @@ -9,10 +9,10 @@ import org.implab.gradle.containers.task import org.implab.gradle.containers.tasks.SaveImage; import org.implab.gradle.containers.tasks.TagImage; -public class ContainerBasePlugin implements Plugin { +public class ContainerBasePlugin implements Plugin { public static final String CONTAINER_EXTENSION_NAME = "container"; - ContainerExtension containerExtension; + private ContainerExtension containerExtension; ContainerExtension getContainerExtension() { if (containerExtension == null) @@ -37,13 +37,17 @@ public class ContainerBasePlugin impleme containerExtension.getImageGroup() .convention(project.provider(() -> (String) project.getProperties().get("imagesGroup"))); + containerExtension.getCliCmd() + .convention(project + .provider(() -> (String) project.getProperties().get("containerCli")) + .orElse("docker")); + project.getExtensions().add(CONTAINER_EXTENSION_NAME, containerExtension); exportClasses( project, BuildImage.class, PushImage.class, SaveImage.class, TagImage.class, RunImage.class); + } - } - } diff --git a/container/src/main/java/org/implab/gradle/containers/ContainerExtension.java b/container/src/main/java/org/implab/gradle/containers/ContainerExtension.java --- a/container/src/main/java/org/implab/gradle/containers/ContainerExtension.java +++ b/container/src/main/java/org/implab/gradle/containers/ContainerExtension.java @@ -10,21 +10,18 @@ import javax.inject.Inject; import org.gradle.api.Project; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.ProjectLayout; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.implab.gradle.containers.cli.ImageName; import org.implab.gradle.containers.cli.ImageRef; import org.implab.gradle.containers.cli.Utils; -public abstract class ContainerExtension { +public abstract class ContainerExtension implements PropertiesMixin { public abstract Property getCliCmd(); public abstract DirectoryProperty getContextDirectory(); - public abstract RegularFileProperty getImageIdFile(); - /** * Specifies the name of the registry where the image is located * {@code registry.my-company.org} @@ -36,7 +33,9 @@ public abstract class ContainerExtension */ public abstract Property getImageGroup(); - public abstract Property getImageName(); + public Provider getImageName() { + return provider(this::createImageName); + } /** * Specifies local image part like {@code httpd} @@ -57,25 +56,19 @@ public abstract class ContainerExtension public ContainerExtension(ProjectLayout layout, Project project) { getContextDirectory().convention(layout.getBuildDirectory().dir("context")); - getImageIdFile().convention(layout.getBuildDirectory().file("iid.json")); - - getCliCmd().set("docker"); - getImageLocalName().convention(project.getName()); - getImageTag().set(project - .provider(() -> Optional.ofNullable(project.getVersion()).map(v -> v.toString()).orElse("latest"))); - - Provider imageRepository = getImageGroup().map(g -> g + "/" + getImageLocalName().get()) - .orElse(getImageLocalName()); - - getImageName().convention(project.provider( - () -> new ImageName().authority(getImageAuthority().get()).name(imageRepository.get()) - .tag(getImageTag().get()))); + getImageTag().set(provider(() -> Optional.ofNullable(project.getVersion()) + .map(Object::toString) + .orElse("latest"))); } - public ImageName createImageName() { - return new ImageName(); + ImageName createImageName() { + return new ImageName( + getImageAuthority().getOrNull(), + getImageGroup().getOrNull(), + getImageLocalName().get(), + getImageTag().getOrNull()); } public ImageRef readImageRef(File file) throws FileNotFoundException, IOException { diff --git a/container/src/main/java/org/implab/gradle/containers/ContainerPlugin.java b/container/src/main/java/org/implab/gradle/containers/ContainerPlugin.java --- a/container/src/main/java/org/implab/gradle/containers/ContainerPlugin.java +++ b/container/src/main/java/org/implab/gradle/containers/ContainerPlugin.java @@ -19,8 +19,6 @@ public class ContainerPlugin implements public static final String ARCHIVE_CONFIGURATION = "archive"; - ContainerExtension containerExtension; - public void apply(Project project) { ProjectLayout layout = project.getLayout(); @@ -53,9 +51,9 @@ public class ContainerPlugin implements t.dependsOn(processResourcesTask); t.getContextDirectory().set(containerExtension.getContextDirectory()); - t.getImageIdFile().set(containerExtension.getImageIdFile()); + t.getImageIdFile().set(project.getLayout().getBuildDirectory().file("iid.json")); - t.getImageName().set(containerExtension.getImageName()); + t.getImageName().set(containerExtension.getImageName().map(ImageName::toString)); }); project.getTasks().register("clean", Delete.class, t -> { @@ -74,7 +72,7 @@ public class ContainerPlugin implements project.getTasks().register("saveImage", SaveImage.class, t -> { t.dependsOn(buildImageTask); - t.getExportImages().add(buildImageTask.flatMap(BuildImage::getImageName).map(ImageName::toString)); + t.getExportImages().add(buildImageTask.flatMap(BuildImage::getImageName)); }); project.getArtifacts().add(Dependency.DEFAULT_CONFIGURATION, buildImageTask); diff --git a/container/src/main/java/org/implab/gradle/containers/cli/DockerTraits.java b/container/src/main/java/org/implab/gradle/containers/cli/DockerTraits.java --- a/container/src/main/java/org/implab/gradle/containers/cli/DockerTraits.java +++ b/container/src/main/java/org/implab/gradle/containers/cli/DockerTraits.java @@ -2,7 +2,6 @@ package org.implab.gradle.containers.cli import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/container/src/main/java/org/implab/gradle/containers/cli/ImageName.java b/container/src/main/java/org/implab/gradle/containers/cli/ImageName.java --- a/container/src/main/java/org/implab/gradle/containers/cli/ImageName.java +++ b/container/src/main/java/org/implab/gradle/containers/cli/ImageName.java @@ -1,6 +1,7 @@ package org.implab.gradle.containers.cli; import java.io.Serializable; +import java.util.ArrayList; import java.util.Optional; import com.fasterxml.jackson.annotation.JsonCreator; @@ -12,60 +13,71 @@ public class ImageName implements Serial final String authority; - final String name; + final String groupName; + + final String localName; final String tag; - public ImageName() { - name = null; - authority = null; - tag = null; - } + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public ImageName( + @JsonProperty("authority") String authority, + @JsonProperty("groupName") String groupName, + @JsonProperty("localName") String localName, + @JsonProperty("tag") String tag) { + if (localName == null || localName.isBlank()) + throw new IllegalArgumentException("localName can't be null or blank string"); - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - private ImageName(@JsonProperty("authority") String authority, @JsonProperty("name") String name, - @JsonProperty("tag") String tag) { this.authority = authority; - this.name = name; + this.groupName = groupName; + this.localName = localName; this.tag = tag; } - public String getAuthority() { - return authority; + public Optional getAuthority() { + return Optional.ofNullable(authority); } - public ImageName authority(String authority) { - return new ImageName(authority, name, tag); + public ImageName withAuthority(String authority) { + return new ImageName(authority, groupName, localName, tag); } - public String getName() { - return name; + public Optional getGroupName() { + return Optional.ofNullable(groupName); + } + + public ImageName withGroupName(String groupName) { + return new ImageName(authority, groupName, localName, tag); } - public ImageName name(String name) { - return new ImageName(authority, name, tag); + public String getLocalName() { + return localName; } - public String getTag() { - return tag; + public ImageName withLocalName(String localName) { + if (localName == null || localName.isBlank()) + throw new IllegalArgumentException("localName can't be null or blank string"); + return new ImageName(authority, groupName, localName, tag); } - public ImageName tag(String tag) { - return new ImageName(authority, name, tag); + public Optional getTag() { + return Optional.ofNullable(tag); } - public ImageName copy() { - return new ImageName(authority, name, tag); + public ImageName withTag(String tag) { + return new ImageName(authority, groupName, localName, tag); } @Override public String toString() { - StringBuilder sb = new StringBuilder(); + var path = new ArrayList(); - Optional.ofNullable(authority).ifPresent(s -> sb.append(s).append("/")); - Optional.ofNullable(name).ifPresent(s -> sb.append(s)); - Optional.ofNullable(tag).ifPresent(s -> sb.append(":").append(s)); + getAuthority().ifPresent(path::add); + getGroupName().ifPresent(path::add); + path.add(getLocalName()); - return sb.toString(); + var repo = String.join("/", path); + + return getTag().map(tag -> String.join(":", repo, tag)).orElse(repo); } } diff --git a/container/src/main/java/org/implab/gradle/containers/tasks/BuildImage.java b/container/src/main/java/org/implab/gradle/containers/tasks/BuildImage.java --- a/container/src/main/java/org/implab/gradle/containers/tasks/BuildImage.java +++ b/container/src/main/java/org/implab/gradle/containers/tasks/BuildImage.java @@ -22,7 +22,6 @@ import org.gradle.api.tasks.SkipWhenEmpt import org.gradle.api.tasks.TaskAction; import java.io.File; -import org.implab.gradle.containers.cli.ImageName; import org.implab.gradle.containers.cli.ImageRef; import org.implab.gradle.containers.cli.Utils; import org.implab.gradle.containers.dsl.MapPropertyEntry; @@ -31,6 +30,7 @@ import org.implab.gradle.containers.dsl. import groovy.lang.Closure; public abstract class BuildImage extends DockerCliTask implements OptionsMixin { + @InputDirectory @SkipWhenEmpty public abstract DirectoryProperty getContextDirectory(); @@ -43,7 +43,7 @@ public abstract class BuildImage extends public abstract Property getBuildTarget(); @Input - public abstract Property getImageName(); + public abstract Property getImageName(); @Internal public abstract RegularFileProperty getImageIdFile(); @@ -101,7 +101,7 @@ public abstract class BuildImage extends // add extra parameters getOptions().get().forEach(args::add); - var imageTag = getImageName().map(ImageName::toString).get(); + var imageTag = getImageName().get(); // build image docker().buildImage( @@ -111,6 +111,6 @@ public abstract class BuildImage extends // read the built image id and store image ref metadata var imageRef = new ImageRef(imageTag, Utils.readAll(iidFile)); - Utils.writeJson(getImageIdFileOutput().get(), imageRef); + Utils.writeJson(getImageIdFile().map(RegularFile::getAsFile).get(), imageRef); } } diff --git a/container/src/main/java/org/implab/gradle/containers/tasks/PushImage.java b/container/src/main/java/org/implab/gradle/containers/tasks/PushImage.java --- a/container/src/main/java/org/implab/gradle/containers/tasks/PushImage.java +++ b/container/src/main/java/org/implab/gradle/containers/tasks/PushImage.java @@ -7,13 +7,12 @@ import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; -import org.implab.gradle.containers.cli.ImageName; import org.implab.gradle.containers.dsl.OptionsMixin; public abstract class PushImage extends DockerCliTask implements OptionsMixin { @Input - public abstract Property getImageName(); + public abstract Property getImageName(); @Input @Optional @@ -27,7 +26,7 @@ public abstract class PushImage extends public void run() throws InterruptedException, IOException { docker().pushImage( - getImageName().get().toString(), + getImageName().get(), getOptions().get()); } } diff --git a/container/src/main/java/org/implab/gradle/containers/tasks/RunImage.java b/container/src/main/java/org/implab/gradle/containers/tasks/RunImage.java --- a/container/src/main/java/org/implab/gradle/containers/tasks/RunImage.java +++ b/container/src/main/java/org/implab/gradle/containers/tasks/RunImage.java @@ -6,14 +6,13 @@ import org.gradle.api.provider.ListPrope import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; -import org.implab.gradle.containers.cli.ImageName; import org.implab.gradle.containers.dsl.OptionsMixin; import org.implab.gradle.containers.dsl.VolumeSpec; public abstract class RunImage extends DockerCliTask implements OptionsMixin { @Input - public abstract Property getImageName(); + public abstract Property getImageName(); @Input @Optional @@ -35,7 +34,7 @@ public abstract class RunImage extends D public void run() throws InterruptedException, IOException { docker().runImage( - getImageName().get().toString(), + getImageName().get(), getOptions().get(), getCommandLine().get()); } diff --git a/container/src/main/java/org/implab/gradle/containers/tasks/TagImage.java b/container/src/main/java/org/implab/gradle/containers/tasks/TagImage.java --- a/container/src/main/java/org/implab/gradle/containers/tasks/TagImage.java +++ b/container/src/main/java/org/implab/gradle/containers/tasks/TagImage.java @@ -4,19 +4,18 @@ import java.io.IOException; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction; -import org.implab.gradle.containers.cli.ImageName; public abstract class TagImage extends DockerCliTask { @Input - public abstract Property getSrcImage(); + public abstract Property getSrcImage(); @Input - public abstract Property getDestImage(); + public abstract Property getDestImage(); @TaskAction public void run() throws InterruptedException, IOException { docker().tagImage( - getSrcImage().map(ImageName::toString).get(), - getDestImage().map(ImageName::toString).get()); + getSrcImage().get(), + getDestImage().get()); } }