diff --git a/bundle.md b/bundle.md new file mode 100644 --- /dev/null +++ b/bundle.md @@ -0,0 +1,57 @@ +# Creating bundle from images + +## NAME + +Save set of images to the single archive. + +## SYNOPSIS + +```gradle +plugins { + // task types and base extension + id "org.implab.gradle-container-base" +} + +container { + cliCmd = "podman" +} + +// create configuration +configurations { + bundleImages { + canBeResolved = true + canBeConsumed = false + } +} + +// add dependencies to the project +dependencies { + bundleImages project(":images:foo"), project(":images:bar") +} + +// create task to export bundle +task bundle(type: SaveImage) { + // add image refs from artifacts + imageRefs configurations.bundleImages + + // add image name + exportImages.add "nginx:latest" +} +``` + +## DESCRIPTION + +To create an archive with images the task of type `SaveImage` can be used. This +task has the following properties: + +| Property | Description | +|-|-| +| `archiveFileName` | The file name of the bundle archive, defaults to `{archiveBaseName}-{archiveVersion}-{archiveClassifier}.{archiveExtension}`.| +| `archiveBaseName` | The base name of the archive, defaults to `{project.group}-{project.name}`. | +| `archiveVersion` | The archive version, defaults to `{project.version}`. | +| `exportImages` | A set of image names to include in the bundle. | + +| Method | Description | +|-|-| +| `imageRefs(FileCollection)` | Adds a set of files with image refs to add to the bundle. | +| `imageRef(File)` | Adds an image name from the file with image reference. | diff --git a/compose.md b/compose.md new file mode 100644 --- /dev/null +++ b/compose.md @@ -0,0 +1,148 @@ +# Compose project + +## NAME + +`org.implab.gradle-container-compose` - docker compose project. + +## SYNOPSIS + +```gradle +plugins { + id "org.implab.gradle-container-compose" +} + +dependencies { + composeImages project(":images:foo") { + ext { + // imageName from :images:foo will be written to .env as FOO_IMAGE var + composeVar = "FOO_IMAGE" + } + } +} + +writeEnv { + // write additional variables to .env + env "DEBUG_JAVA" put "yes" + + // set compose name, this variable is set to that value by default + env "COMPOSE_PROJECT_NAME" put project.group +} + +// base container extension +container { + cliCmd = "podman" +} + +// compose parameters +compose { + // add compose profiles + profiles.add("dev") + + // set compose file name + // defaults to compose.yaml + composeFileName = "docker-compose.yaml" +} + +``` + +## DESCRIPTION + +This plugin creates a set of conventional tasks to prepare and start compose +project. This can be used to create and run sandbox. + +These tasks are: + +* `build` - prepares the context for the compose file, depends on `processResources` + and `writeEnv` tasks. +* `up` - invokes `compose up -d` in the built context and starts up the project + in the background. +* `stop` - invokes `compose stop` and stops the projects. +* `rm` - invokes `compose rm` and removes containers, by default temporary volumes + are removed with containers. +* `clean` - cleanups the build directory and removes built context. + +Special configuration `composeImages` should be used to add a dependencies to the +images needed by this project. The dependencies must provide a single artifact +a json file containing `tag` property. This configuration well be resolved and +tag for each dependency will be stored in the environment variable specified with +`composeVar` property. + +```gradle +configuration { + composeImages file("nginx.json") { + ext { + composeVar = "NGINX_IMAGE" + } + } + composeImage project(":images:foo") { + ext { + composeVar = "FOO_IMAGE" + } + } +} +``` + +The compose environment variables are written to the `.env` file inside the +context directory and will be used by `compose up` command. + +## Tasks + +### writeEnv + +`type: WriteEnv, dependsOn: [configurations.composeImages, processResources]` + +Inspects configuration `composeImages`, adds default `COMPOSE_PROJECT_NAME` +variable and writes `.env` file to the context directory. + +This task provides a property `environment` of type `MapProperty` +which can be used to customize the compose environment. + +```gradle +writeEnv { + // direct environment property access + environment.put("VAR_1", valueOrProvider) + + // syntax sugar to modify environment property + env "VAR_2" put "value" // simple value + env "VAR_3" put provider { getSomeValue() } // provider + + // map provider will be merged into the + env { + VAR_4 = "val1" + VAR_5 = getAnotherValue() + } +} +``` + +### processResources + +`type: Copy` + +Copies resources from the source directory `src/main` into the context directory `build/context` + +### up + +`type: ComposeUp, dependsOn: [buildTask]` + +Starts the compose project. The project is started in the background. + +### build + +`type: DefaultTask, dependsOn: [writeEnvTask]` + +This is a meta task used to group the set of tasks related to the build target. + +### stop + +`type: ComposeStop` + +Stops the current compose project. + +### rm + +`type: ComposeRm` + +Removes containers created from this compose project. + +`removeValues` - boolean property, if set to true the task will remove all +temporary volumes left from containers. diff --git a/container/src/main/java/org/implab/gradle/containers/ComposePlugin.java b/container/src/main/java/org/implab/gradle/containers/ComposePlugin.java --- a/container/src/main/java/org/implab/gradle/containers/ComposePlugin.java +++ b/container/src/main/java/org/implab/gradle/containers/ComposePlugin.java @@ -3,6 +3,7 @@ package org.implab.gradle.containers; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import org.gradle.api.DefaultTask; import org.gradle.api.Plugin; @@ -21,6 +22,8 @@ import org.implab.gradle.containers.task public abstract class ComposePlugin implements Plugin, ProjectMixin { public final String COMPOSE_IMAGES_CONFIGURATION = "composeImages"; + public final String COMPOSE_PROJECT_NAME = "COMPOSE_PROJECT_NAME"; + public final String COMPOSE_EXTENSION = "compose"; public final String COMPOSE_UP_TASK = "up"; @@ -75,6 +78,11 @@ public abstract class ComposePlugin impl t.dependsOn(processResources, containerImages); t.getEnvFile().set(containerExtension.getContextDirectory().file(ENV_FILE_NAME)); + var group = project.getGroup(); + if (group != null && group.toString().length() > 0) { + t.getEnvironment().put(COMPOSE_PROJECT_NAME, group.toString()); + } + t.getEnvironment().putAll(containerImages.map(this::extractComposeEnv)); }); 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 @@ -106,7 +106,7 @@ public abstract class DockerTraits { complete(startProcess(builder(args))); } - public void saveImage(List images, File output) throws InterruptedException, IOException { + public void saveImage(Set images, File output) throws InterruptedException, IOException { if (output.exists()) output.delete(); diff --git a/container/src/main/java/org/implab/gradle/containers/tasks/SaveImage.java b/container/src/main/java/org/implab/gradle/containers/tasks/SaveImage.java --- a/container/src/main/java/org/implab/gradle/containers/tasks/SaveImage.java +++ b/container/src/main/java/org/implab/gradle/containers/tasks/SaveImage.java @@ -8,9 +8,9 @@ import java.util.Optional; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFile; -import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.OutputFile; @@ -21,7 +21,7 @@ import org.implab.gradle.containers.cli. public abstract class SaveImage extends DockerCliTask { @Input - public abstract ListProperty getExportImages(); + public abstract SetProperty getExportImages(); @OutputFile public Provider getArchiveFile() { 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 @@ -1,7 +1,11 @@ package org.implab.gradle.containers.tasks; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction; @@ -10,12 +14,33 @@ public abstract class TagImage extends D public abstract Property getSrcImage(); @Input + public abstract SetProperty getTags(); + + @Input + @Deprecated public abstract Property getDestImage(); + private Set getImageTags() { + var tags = new HashSet<>(getTags().get()); + tags.add(getDestImage().get()); + return tags; + } + + public TagImage() { + this.setOnlyIf("No tags were specified", self -> getImageTags().size() > 0); + } + @TaskAction public void run() throws InterruptedException, IOException { - docker().tagImage( - getSrcImage().get(), - getDestImage().get()); + var tags = getImageTags(); + var src = getSrcImage().get(); + + if (tags.size() == 0) + getLogger().info("No tags were specified"); + + for (var tag : tags) { + getLogger().info("Tag: {}", tag); + docker().tagImage(src, tag); + } } } diff --git a/readme.md b/readme.md --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ ```gradle plugins { - id 'org.implab.gradle-container' version '1.1' + id 'org.implab.gradle-container' } container { @@ -44,58 +44,168 @@ task printVersion { ## Description This plugin is a simple wrapper around docker CLI. All the image -building process is deligated to the `Dockerfile` which will run -in the prepeared build context. +building process is delegated to the `Dockerfile` which will run +in the prepared build context. -### Project structure +## Project structure -* `build/` - * `context/` - the build context where `docker build` command will run. - * `imageid` - the file storing the id of the image has been built. - * `image-name-1.2.3.tgz` - the exported image if `saveImage` has been executed. +* `build/` - this folder will be created during build, it can be useful while + solving Dockerfile problems + * `context/` - the build context where `docker build` command will run. + * `imageid` - the file storing the id of the image has been built. + * `image-name-1.2.3.tgz` - the exported image if `saveImage` has been executed. * `src` - * `main` - the source files which will be copied to the build context. + * `main` - the source files which will be copied to the build context. + +## Global properties -## Properties +There are several global properties recognized by this plugin in the project. +These properties affect images naming and publication and they are useful in +multi-project environment. `imagesAuthority` - the registry where the image should be published. for example `docker.io` `imagesGroup` - the path to the image in the repository. +`containerCli` - the command line cli, this property corresponds to +`container.cliCmd` in the project. + +Properties defined in the project takes precedence over global properties. + +## Image names + +```gradle +plugins { + id "org.implab.gradle-container" +} + +container { + // image authority, the repository for your images + // defaults to global imagesAuthority property or none + imageAuthority = "my.registry.org" + + // the image path + // defaults to global imagesGroup property or none + imageGroup = "my/project" + + // the name of the image + // defaults to project.name + imageLocalName = "foo" +} + +// provider for imageName, returns ImageName object +// ImageName consists of "{imageAuthority}/{imageGroup}/{imageLocalName}" +def imageNameProvider = container.imageName +``` + ## Tasks +Some tasks support passing additional options as additional command line +parameters. These task has the property `options` and some additional methods. + +| Property | Description | +|--|--| +| `options` | A list of additional arguments passed to `docker build` command. | + +| Method | Description | +|---|---| +| `option(String)` | Adds option to `options`. | +| `option(Closure)` | Converts the parameter to provider and adds it to `options`. | +| `option(Callable)` | Converts the parameter to provider and adds it to `options`. | +| `options(String...)` | Adds specified options to `options`. | +| `options(Closure)`| Converts the parameter to provider and adds it to `options`. | +| `options(Callable)`| Converts the parameter to provider and adds it to `options` | + ### buildImage +`type: BuildImage` + The task builds the image. Wrapper around the `docker build` command. +| Property | Description | +|---|---| +| `contextDirectory` | A Dockerfile context directory. Set to `container.context`. | +| `buildArgs` | A dictionary with environment variables which are set during build. | +| `buildTarget` | A target image for the multi-stage builds. Defaults to none. | +| `imageName` | A name (tag) for the resulting image. | +| `imageIdFIle` | Output file name where image ref will be written. | + +This task also supports additional command line options. + ### saveImage +`type: SaveImage` + The task exports image as the .tar archive. +| Property | Description | +|-|-| +| `archiveFileName` | The file name of the bundle archive, defaults to `{archiveBaseName}-{archiveVersion}-{archiveClassifier}.{archiveExtension}`.| +| `archiveBaseName` | The base name of the archive, defaults to `{project.group}-{project.name}`. | +| `archiveVersion` | The archive version, defaults to `{project.version}`. | +| `exportImages` | A set of image names to include in the bundle. | + +| Method | Description | +|-|-| +| `imageRefs(FileCollection)` | Adds a set of files with image refs to add to the bundle. | +| `imageRef(File)` | Adds an image name from the file with image reference. | + ### pushImage -The task pushes the image to the remote repository. +The task pushes the image to the remote repository (imageAuthority). + +[Since v1.2] This task also supports additional command line options. You can use them to +push all tags for the image. + +```gradle +pushImage { + option "--all-tags" +} +``` ### processResources -The copy task, it prepares the build context. Use it to customize +The copy task, it prepares the build context. Use this task to customize the build context. ### tagImage +`type: TagImage` + since: 1.1 ```gradle task tagLatest(type: TagImage) { + pushImage.dependsOn it + srcImage = container.imageName - destImage = container.imageName.map { it.tag("latest") } + + tags.add(container.imageName.map { it.tag("latest") }) } ``` +| Property | Description | +|-|-| +| `srcImage` | The source image name to add tag to. | +| `tags` | The set of tags to add to the image. | + +## See also + +* Creating [compose](compose.md) project +* Creating [bundle](bundle.md) project + ## Changes +### 1.2 + +Added `org.implab.gradle-container-base`, `org.implab.gradle-container-compose` +plugins. + +* `org.implab.gradle-container-base` registers base extension and task types. +* `org.implab.gradle-container-compose` registers conventional tasks. + ### 1.1 Warning! This version isn't fully backward compatible with 1.0 version.