├── .gitattributes ├── plugin ├── src │ ├── test │ │ ├── resources │ │ │ ├── docker │ │ │ │ ├── subdirectory │ │ │ │ │ └── file-to-be-included.txt │ │ │ │ └── Dockerfile │ │ │ ├── env-files │ │ │ │ └── env-test.properties │ │ │ └── logback-test.xml │ │ ├── groovy │ │ │ └── de │ │ │ │ └── gesellix │ │ │ │ └── gradle │ │ │ │ └── docker │ │ │ │ ├── tasks │ │ │ │ ├── TestTask.groovy │ │ │ │ ├── DockerRmiTaskSpec.groovy │ │ │ │ ├── DockerKillTaskSpec.groovy │ │ │ │ ├── DockerStopTaskSpec.groovy │ │ │ │ ├── DockerPauseTaskSpec.groovy │ │ │ │ ├── DockerWaitTaskSpec.groovy │ │ │ │ ├── DockerRestartTaskSpec.groovy │ │ │ │ ├── DockerStartTaskSpec.groovy │ │ │ │ ├── DockerUnpauseTaskSpec.groovy │ │ │ │ ├── DockerTagTaskSpec.groovy │ │ │ │ ├── DockerServiceRmTaskSpec.groovy │ │ │ │ ├── DockerVolumeRmTaskSpec.groovy │ │ │ │ ├── DockerSwarmLeaveTaskSpec.groovy │ │ │ │ ├── DockerNetworkConnectTaskSpec.groovy │ │ │ │ ├── DockerNetworkDisconnectTaskSpec.groovy │ │ │ │ ├── DockerPingTaskSpec.groovy │ │ │ │ ├── DockerPsTaskSpec.groovy │ │ │ │ ├── DockerRenameTaskSpec.groovy │ │ │ │ ├── DockerNetworksTaskSpec.groovy │ │ │ │ ├── DockerInfoTaskSpec.groovy │ │ │ │ ├── DockerCopyToContainerTaskSpec.groovy │ │ │ │ ├── DockerRmTaskSpec.groovy │ │ │ │ ├── DockerVersionTaskSpec.groovy │ │ │ │ ├── DockerLogsTaskSpec.groovy │ │ │ │ ├── DockerSwarmJoinTaskSpec.groovy │ │ │ │ ├── DockerImagesTaskSpec.groovy │ │ │ │ ├── DockerCopyFromContainerTaskSpec.groovy │ │ │ │ ├── DockerInspectContainerTaskSpec.groovy │ │ │ │ ├── DockerSwarmInitTaskSpec.groovy │ │ │ │ ├── DockerInspectImageTaskSpec.groovy │ │ │ │ ├── DockerCommitTaskSpec.groovy │ │ │ │ ├── DockerPullTaskSpec.groovy │ │ │ │ ├── DockerVolumeCreateTaskSpec.groovy │ │ │ │ ├── DockerNetworkRmTaskSpec.groovy │ │ │ │ ├── DockerNetworkCreateTaskSpec.groovy │ │ │ │ ├── DockerServiceCreateTaskSpec.groovy │ │ │ │ ├── DockerVolumesTaskSpec.groovy │ │ │ │ ├── DockerPushTaskSpec.groovy │ │ │ │ ├── DockerExecTaskSpec.groovy │ │ │ │ ├── GenericDockerTaskSpec.groovy │ │ │ │ ├── DockerCreateTaskSpec.groovy │ │ │ │ ├── DockerRunTaskSpec.groovy │ │ │ │ ├── DockerDisposeContainerTaskSpec.groovy │ │ │ │ ├── DockerBuildTaskFunctionalTest.groovy │ │ │ │ └── DockerBuildTaskSpec.groovy │ │ │ │ └── DockerPluginSpec.groovy │ │ └── java │ │ │ └── de │ │ │ └── gesellix │ │ │ └── gradle │ │ │ └── docker │ │ │ └── testutil │ │ │ └── TestImage.java │ └── main │ │ └── java │ │ └── de │ │ └── gesellix │ │ └── gradle │ │ └── docker │ │ ├── worker │ │ ├── BuildcontextArchiverWorkParameters.java │ │ └── BuildcontextArchiver.java │ │ ├── tasks │ │ ├── DockerPingTask.java │ │ ├── DockerRmiTask.java │ │ ├── DockerKillTask.java │ │ ├── DockerStopTask.java │ │ ├── DockerPauseTask.java │ │ ├── DockerVolumeRmTask.java │ │ ├── DockerStartTask.java │ │ ├── DockerRestartTask.java │ │ ├── DockerServiceRmTask.java │ │ ├── DockerUnpauseTask.java │ │ ├── DockerInfoTask.java │ │ ├── DockerVersionTask.java │ │ ├── DockerSwarmLeaveTask.java │ │ ├── DockerNetworksTask.java │ │ ├── DockerPsTask.java │ │ ├── DockerImagesTask.java │ │ ├── DockerSwarmJoinTask.java │ │ ├── DockerTagTask.java │ │ ├── DockerRenameTask.java │ │ ├── DockerNetworkConnectTask.java │ │ ├── DockerNetworkDisconnectTask.java │ │ ├── DockerInspectImageTask.java │ │ ├── DockerSwarmInitTask.java │ │ ├── DockerInspectContainerTask.java │ │ ├── DockerRmTask.java │ │ ├── DockerVolumesTask.java │ │ ├── DockerServiceCreateTask.java │ │ ├── DockerVolumeCreateTask.java │ │ ├── DockerCopyToContainerTask.java │ │ ├── DockerCopyFromContainerTask.java │ │ ├── DockerNetworkRmTask.java │ │ ├── DockerWaitTask.java │ │ ├── DockerExecTask.java │ │ ├── GenericDockerTask.java │ │ ├── DockerNetworkCreateTask.java │ │ ├── DockerCommitTask.java │ │ ├── DockerLogsTask.java │ │ ├── DockerPullTask.java │ │ ├── DockerDisposeContainerTask.java │ │ ├── DockerPushTask.java │ │ ├── DockerRunTask.java │ │ └── DockerCreateTask.java │ │ ├── DockerPlugin.java │ │ └── DockerPluginExtension.java └── build.gradle.kts ├── img ├── docker-logo.png └── gradle-logo.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .gitignore ├── .editorconfig ├── settings.gradle.kts ├── gradle.properties ├── .github ├── workflows │ ├── update-gradle-wrapper.yml │ ├── publish-test-results.yml │ ├── ci.yml │ ├── cd.yml │ └── release.yml └── dependabot.yml ├── RELEASE.md ├── LICENSE ├── debug.gradle.kts ├── README.md ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── supported-api.md └── gradlew /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | gradlew text eol=lf -------------------------------------------------------------------------------- /plugin/src/test/resources/docker/subdirectory/file-to-be-included.txt: -------------------------------------------------------------------------------- 1 | hello world -------------------------------------------------------------------------------- /plugin/src/test/resources/env-files/env-test.properties: -------------------------------------------------------------------------------- 1 | THE_WIND=CAUGHT_IT 2 | FOO=BAR Baz 3 | -------------------------------------------------------------------------------- /img/docker-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gesellix/gradle-docker-plugin/HEAD/img/docker-logo.png -------------------------------------------------------------------------------- /img/gradle-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gesellix/gradle-docker-plugin/HEAD/img/gradle-logo.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gesellix/gradle-docker-plugin/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | *.iml 4 | build/ 5 | out/ 6 | .DS_Store 7 | .project 8 | .classpath 9 | .settings 10 | bin 11 | local-plugins 12 | -------------------------------------------------------------------------------- /plugin/src/test/resources/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM test:build-base 2 | LABEL de.gesellix.gradle-docker-plugin.test="1" 3 | COPY ./subdirectory/file-to-be-included.txt /file-to-be-included.txt 4 | CMD ["true"] 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_size = 2 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | continuation_indent_size = 4 11 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "gradle-docker-plugin" 2 | include("plugin") 3 | 4 | // https://docs.gradle.org/current/userguide/toolchains.html#sec:provisioning 5 | plugins { 6 | id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=df67a32e86e3276d011735facb1535f64d0d88df84fa87521e90becc2d735444 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/TestTask.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import org.gradle.api.model.ObjectFactory 4 | import org.gradle.api.tasks.TaskAction 5 | 6 | import javax.inject.Inject 7 | 8 | class TestTask extends GenericDockerTask { 9 | 10 | @Inject 11 | TestTask(ObjectFactory objectFactory) { 12 | super(objectFactory) 13 | } 14 | 15 | @TaskAction 16 | def run() { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/worker/BuildcontextArchiverWorkParameters.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.worker; 2 | 3 | import org.gradle.api.file.DirectoryProperty; 4 | import org.gradle.api.file.RegularFileProperty; 5 | import org.gradle.workers.WorkParameters; 6 | 7 | public interface BuildcontextArchiverWorkParameters extends WorkParameters { 8 | 9 | DirectoryProperty getSourceDirectory(); 10 | 11 | RegularFileProperty getArchivedTargetFile(); 12 | } 13 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=true 2 | 3 | group=de.gesellix 4 | 5 | github.package-registry.owner=gesellix 6 | github.package-registry.repository=gradle-docker-plugin 7 | github.package-registry.username= 8 | github.package-registry.password= 9 | 10 | sonatype.snapshot.url=https://oss.sonatype.org/content/repositories/snapshots/ 11 | sonatype.staging.url=https://oss.sonatype.org/service/local/staging/deploy/maven2/ 12 | sonatype.staging.profile.id= 13 | sonatype.username= 14 | sonatype.password= 15 | 16 | gradle.publish.key= 17 | gradle.publish.secret= 18 | -------------------------------------------------------------------------------- /plugin/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/update-gradle-wrapper.yml: -------------------------------------------------------------------------------- 1 | name: Update Gradle Wrapper 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # "weekly" https://crontab.guru/every-week 7 | - cron: "0 0 * * 0" 8 | 9 | jobs: 10 | update-gradle-wrapper: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | - name: Update Gradle Wrapper 15 | uses: gradle-update/update-gradle-wrapper-action@v2 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | - uses: gradle/wrapper-validation-action@v3 19 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerRmiTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerRmiTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerRmi', DockerRmiTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.imageId = "4712" 22 | 23 | when: 24 | task.rmi() 25 | 26 | then: 27 | 1 * dockerClient.rmi("4712") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerKillTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerKillTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerKill', DockerKillTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.containerId = "4711" 22 | 23 | when: 24 | task.kill() 25 | 26 | then: 27 | 1 * dockerClient.kill("4711") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerStopTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerStopTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerStop', DockerStopTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.containerId = "4711" 22 | 23 | when: 24 | task.stop() 25 | 26 | then: 27 | 1 * dockerClient.stop("4711") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerPauseTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerPauseTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerPause', DockerPauseTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.containerId = "4711" 22 | 23 | when: 24 | task.pause() 25 | 26 | then: 27 | 1 * dockerClient.pause("4711") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerWaitTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerWaitTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerWait', DockerWaitTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.containerId = "4711" 22 | 23 | when: 24 | task.awaitStop() 25 | 26 | then: 27 | 1 * dockerClient.wait("4711") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerRestartTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerRestartTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerRestart', DockerRestartTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.containerId = "4711" 22 | 23 | when: 24 | task.restart() 25 | 26 | then: 27 | 1 * dockerClient.restart("4711") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerStartTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerStartTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerStart', DockerStartTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.containerId = "4711" 22 | 23 | when: 24 | task.start() 25 | 26 | then: 27 | 1 * dockerClient.startContainer("4711") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerUnpauseTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerUnpauseTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerUnpause', DockerUnpauseTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.containerId = "4711" 22 | 23 | when: 24 | task.unpause() 25 | 26 | then: 27 | 1 * dockerClient.unpause("4711") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Publishing 2 | 3 | Packages are automatically published from the `main` branch to the GitHub Package Registry. 4 | Manually cut releases will be published to both GitHub Package Registry and Maven Central. 5 | 6 | ## Release Workflow 7 | 8 | There are multiple GitHub Action Workflows for the different steps in the package's lifecycle: 9 | 10 | - CI: Builds and checks incoming changes on a pull request 11 | - triggered on every push to a non-default branch 12 | - CD: Publishes the Gradle artifacts to GitHub Package Registry 13 | - triggered only on pushes to the default branch 14 | - Release: Publishes Gradle artifacts to Sonatype and releases them to Maven Central 15 | - triggered on a published GitHub release using the underlying tag as artifact version, e.g. via `git tag -m "$MESSAGE" v$(date +"%Y-%m-%dT%H-%M-%S")` 16 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerTagTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerTagTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerTag', DockerTagTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.imageId = "4711" 22 | task.imageTag = "aTag" 23 | 24 | when: 25 | task.tag() 26 | 27 | then: 28 | 1 * dockerClient.tag("4711", "aTag") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerServiceRmTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerServiceRmTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('rmService', DockerServiceRmTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient and saves result"() { 20 | given: 21 | task.serviceName = "a-service" 22 | 23 | when: 24 | task.rmService() 25 | 26 | then: 27 | 1 * dockerClient.rmService("a-service") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerVolumeRmTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerVolumeRmTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerRmVolume', DockerVolumeRmTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient and saves result"() { 20 | given: 21 | task.configure { 22 | volumeName = "foo" 23 | } 24 | 25 | when: 26 | task.rmVolume() 27 | 28 | then: 29 | 1 * dockerClient.rmVolume("foo") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerSwarmLeaveTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.api.tasks.TaskProvider 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import spock.lang.Specification 7 | 8 | class DockerSwarmLeaveTaskSpec extends Specification { 9 | 10 | def project 11 | def task 12 | def dockerClient = Mock(DockerClient) 13 | 14 | def setup() { 15 | project = ProjectBuilder.builder().build() 16 | task = project.tasks.register('leaveSwarm', DockerSwarmLeaveTask).get() 17 | task.dockerClient = dockerClient 18 | } 19 | 20 | def "delegates to dockerClient and saves result"() { 21 | given: 22 | task.force = true 23 | 24 | when: 25 | task.leaveSwarm() 26 | 27 | then: 28 | 1 * dockerClient.leaveSwarm(true) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerPingTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import org.gradle.api.model.ObjectFactory; 5 | import org.gradle.api.tasks.Internal; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerPingTask extends GenericDockerTask { 11 | 12 | private EngineResponseContent result; 13 | 14 | @Internal 15 | public EngineResponseContent getResult() { 16 | return result; 17 | } 18 | 19 | @Inject 20 | public DockerPingTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Ping the docker server"); 23 | } 24 | 25 | @TaskAction 26 | public EngineResponseContent ping() { 27 | getLogger().info("docker ping"); 28 | return result = getDockerClient().ping(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerRmiTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerRmiTask extends GenericDockerTask { 11 | 12 | private final Property imageId; 13 | 14 | @Input 15 | public Property getImageId() { 16 | return imageId; 17 | } 18 | 19 | @Inject 20 | public DockerRmiTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Remove one or more images"); 23 | 24 | imageId = objectFactory.property(String.class); 25 | } 26 | 27 | @TaskAction 28 | public void rmi() { 29 | getLogger().info("docker rmi"); 30 | getDockerClient().rmi(getImageId().get()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerNetworkConnectTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerNetworkConnectTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('connectNetwork', DockerNetworkConnectTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient and saves result"() { 20 | given: 21 | task.networkName = "a-network" 22 | task.containerName = "a-container" 23 | 24 | when: 25 | task.connectNetwork() 26 | 27 | then: 28 | 1 * dockerClient.connectNetwork("a-network", "a-container") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerKillTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerKillTask extends GenericDockerTask { 11 | 12 | private final Property containerId; 13 | 14 | @Input 15 | public Property getContainerId() { 16 | return containerId; 17 | } 18 | 19 | @Inject 20 | public DockerKillTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Kill a running container"); 23 | 24 | containerId = objectFactory.property(String.class); 25 | } 26 | 27 | @TaskAction 28 | public void kill() { 29 | getLogger().info("docker kill"); 30 | getDockerClient().kill(getContainerId().get()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerStopTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerStopTask extends GenericDockerTask { 11 | 12 | private final Property containerId; 13 | 14 | @Input 15 | public Property getContainerId() { 16 | return containerId; 17 | } 18 | 19 | @Inject 20 | public DockerStopTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Stop a running container"); 23 | 24 | containerId = objectFactory.property(String.class); 25 | } 26 | 27 | @TaskAction 28 | public void stop() { 29 | getLogger().info("docker stop"); 30 | getDockerClient().stop(getContainerId().get()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerPauseTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerPauseTask extends GenericDockerTask { 11 | 12 | private final Property containerId; 13 | 14 | @Input 15 | public Property getContainerId() { 16 | return containerId; 17 | } 18 | 19 | @Inject 20 | public DockerPauseTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Pause a running container"); 23 | 24 | containerId = objectFactory.property(String.class); 25 | } 26 | 27 | @TaskAction 28 | public void pause() { 29 | getLogger().info("docker pause"); 30 | getDockerClient().pause(getContainerId().get()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerVolumeRmTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerVolumeRmTask extends GenericDockerTask { 11 | 12 | private final Property volumeName; 13 | 14 | @Input 15 | public Property getVolumeName() { 16 | return volumeName; 17 | } 18 | 19 | @Inject 20 | public DockerVolumeRmTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Remove a volume"); 23 | 24 | volumeName = objectFactory.property(String.class); 25 | } 26 | 27 | @TaskAction 28 | public void rmVolume() { 29 | getLogger().info("docker volume rm"); 30 | getDockerClient().rmVolume(getVolumeName().get()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerNetworkDisconnectTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerNetworkDisconnectTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('disconnectNetwork', DockerNetworkDisconnectTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient and saves result"() { 20 | given: 21 | task.networkName = "a-network" 22 | task.containerName = "a-container" 23 | 24 | when: 25 | task.disconnectNetwork() 26 | 27 | then: 28 | 1 * dockerClient.disconnectNetwork("a-network", "a-container") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerPingTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import spock.lang.Specification 7 | 8 | class DockerPingTaskSpec extends Specification { 9 | 10 | def project 11 | def task 12 | def dockerClient = Mock(DockerClient) 13 | 14 | def setup() { 15 | project = ProjectBuilder.builder().build() 16 | task = project.tasks.register('dockerPing', DockerPingTask).get() 17 | task.dockerClient = dockerClient 18 | } 19 | 20 | def "delegates to dockerClient and saves result"() { 21 | given: 22 | def response = new EngineResponseContent("OK") 23 | 24 | when: 25 | task.ping() 26 | 27 | then: 28 | 1 * dockerClient.ping() >> response 29 | 30 | and: 31 | task.result == response 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerStartTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerStartTask extends GenericDockerTask { 11 | 12 | private final Property containerId; 13 | 14 | @Input 15 | public Property getContainerId() { 16 | return containerId; 17 | } 18 | 19 | @Inject 20 | public DockerStartTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Start a stopped container"); 23 | 24 | containerId = objectFactory.property(String.class); 25 | } 26 | 27 | @TaskAction 28 | public void start() { 29 | getLogger().info("docker start"); 30 | getDockerClient().startContainer(getContainerId().get()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerPsTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import spock.lang.Specification 7 | 8 | class DockerPsTaskSpec extends Specification { 9 | 10 | def project 11 | def task 12 | def dockerClient = Mock(DockerClient) 13 | 14 | def setup() { 15 | project = ProjectBuilder.builder().build() 16 | task = project.tasks.register('dockerPs', DockerPsTask).get() 17 | task.dockerClient = dockerClient 18 | } 19 | 20 | def "delegates to dockerClient and saves result"() { 21 | given: 22 | def expectedResult = new EngineResponseContent([]) 23 | 24 | when: 25 | task.ps() 26 | 27 | then: 28 | 1 * dockerClient.ps() >> expectedResult 29 | and: 30 | task.containers == expectedResult 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerRestartTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerRestartTask extends GenericDockerTask { 11 | 12 | private final Property containerId; 13 | 14 | @Input 15 | public Property getContainerId() { 16 | return containerId; 17 | } 18 | 19 | @Inject 20 | public DockerRestartTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Restart a running container"); 23 | 24 | containerId = objectFactory.property(String.class); 25 | } 26 | 27 | @TaskAction 28 | public void restart() { 29 | getLogger().info("docker restart"); 30 | getDockerClient().restart(getContainerId().get()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerServiceRmTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerServiceRmTask extends GenericDockerTask { 11 | 12 | private final Property serviceName; 13 | 14 | @Input 15 | public Property getServiceName() { 16 | return serviceName; 17 | } 18 | 19 | @Inject 20 | public DockerServiceRmTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Remove a service"); 23 | 24 | serviceName = objectFactory.property(String.class); 25 | } 26 | 27 | @TaskAction 28 | public void rmService() { 29 | getLogger().info("docker service rm"); 30 | getDockerClient().rmService(getServiceName().get()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerUnpauseTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerUnpauseTask extends GenericDockerTask { 11 | 12 | private final Property containerId; 13 | 14 | @Input 15 | public Property getContainerId() { 16 | return containerId; 17 | } 18 | 19 | @Inject 20 | public DockerUnpauseTask(ObjectFactory objectFactory) { 21 | super(objectFactory); 22 | setDescription("Unpause a paused container"); 23 | 24 | containerId = objectFactory.property(String.class); 25 | } 26 | 27 | @TaskAction 28 | public void unpause() { 29 | getLogger().info("docker unpause"); 30 | getDockerClient().unpause(getContainerId().get()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerRenameTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerRenameTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerRename', DockerRenameTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates rename command to dockerClient and saves result"() { 20 | given: 21 | def containerId = 'oldName' 22 | task.containerId = containerId 23 | 24 | def newName = 'anotherName' 25 | task.newName = newName 26 | 27 | when: 28 | task.rename() 29 | 30 | then: 31 | 1 * dockerClient.rename(containerId, newName) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerNetworksTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import spock.lang.Specification 7 | 8 | class DockerNetworksTaskSpec extends Specification { 9 | 10 | def project 11 | def task 12 | def dockerClient = Mock(DockerClient) 13 | 14 | def setup() { 15 | project = ProjectBuilder.builder().build() 16 | task = project.tasks.register('dockerNetworks', DockerNetworksTask).get() 17 | task.dockerClient = dockerClient 18 | } 19 | 20 | def "delegates to dockerClient and saves result"() { 21 | given: 22 | def response = new EngineResponseContent([]) 23 | 24 | when: 25 | task.networks() 26 | 27 | then: 28 | 1 * dockerClient.networks() >> response 29 | 30 | and: 31 | task.networks == response 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerInfoTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.SystemInfo; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.tasks.Internal; 7 | import org.gradle.api.tasks.TaskAction; 8 | 9 | import javax.inject.Inject; 10 | 11 | public class DockerInfoTask extends GenericDockerTask { 12 | 13 | private EngineResponseContent info; 14 | 15 | @Internal 16 | public EngineResponseContent getInfo() { 17 | return info; 18 | } 19 | 20 | @Inject 21 | public DockerInfoTask(ObjectFactory objectFactory) { 22 | super(objectFactory); 23 | setDescription("Display system-wide information"); 24 | } 25 | 26 | @TaskAction 27 | public EngineResponseContent info() { 28 | getLogger().info("docker info"); 29 | return info = getDockerClient().info(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerVersionTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.SystemVersion; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.tasks.Internal; 7 | import org.gradle.api.tasks.TaskAction; 8 | 9 | import javax.inject.Inject; 10 | 11 | public class DockerVersionTask extends GenericDockerTask { 12 | 13 | private EngineResponseContent version; 14 | 15 | @Internal 16 | public EngineResponseContent getVersion() { 17 | return version; 18 | } 19 | 20 | @Inject 21 | public DockerVersionTask(ObjectFactory objectFactory) { 22 | super(objectFactory); 23 | setDescription("Show the Docker version information"); 24 | } 25 | 26 | @TaskAction 27 | public void version() { 28 | getLogger().info("docker version"); 29 | version = getDockerClient().version(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerSwarmLeaveTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.Optional; 7 | import org.gradle.api.tasks.TaskAction; 8 | 9 | import javax.inject.Inject; 10 | 11 | public class DockerSwarmLeaveTask extends GenericDockerTask { 12 | 13 | private final Property force; 14 | 15 | @Input 16 | @Optional 17 | public Property getForce() { 18 | return force; 19 | } 20 | 21 | @Inject 22 | public DockerSwarmLeaveTask(ObjectFactory objectFactory) { 23 | super(objectFactory); 24 | setDescription("Leave the swarm"); 25 | 26 | force = objectFactory.property(Boolean.class); 27 | } 28 | 29 | @TaskAction 30 | public void leaveSwarm() { 31 | getLogger().info("docker swarm leave"); 32 | getDockerClient().leaveSwarm(getForce().get()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerNetworksTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.Network; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.tasks.Internal; 7 | import org.gradle.api.tasks.TaskAction; 8 | 9 | import javax.inject.Inject; 10 | import java.util.List; 11 | 12 | public class DockerNetworksTask extends GenericDockerTask { 13 | 14 | private EngineResponseContent> networks; 15 | 16 | @Internal 17 | public EngineResponseContent> getNetworks() { 18 | return networks; 19 | } 20 | 21 | @Inject 22 | public DockerNetworksTask(ObjectFactory objectFactory) { 23 | super(objectFactory); 24 | setDescription("Lists all networks"); 25 | } 26 | 27 | @TaskAction 28 | public void networks() { 29 | getLogger().info("docker network ls"); 30 | networks = getDockerClient().networks(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerPsTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.ContainerSummary; 5 | 6 | import org.gradle.api.model.ObjectFactory; 7 | import org.gradle.api.tasks.Internal; 8 | import org.gradle.api.tasks.TaskAction; 9 | 10 | import javax.inject.Inject; 11 | import java.util.List; 12 | 13 | public class DockerPsTask extends GenericDockerTask { 14 | 15 | private EngineResponseContent> containers; 16 | 17 | @Internal 18 | public EngineResponseContent> getContainers() { 19 | return containers; 20 | } 21 | 22 | @Inject 23 | public DockerPsTask(ObjectFactory objectFactory) { 24 | super(objectFactory); 25 | setDescription("List containers"); 26 | } 27 | 28 | @TaskAction 29 | public void ps() { 30 | getLogger().info("docker ps"); 31 | containers = getDockerClient().ps(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerInfoTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.SystemInfo 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import spock.lang.Specification 8 | 9 | class DockerInfoTaskSpec extends Specification { 10 | 11 | def project 12 | def task 13 | def dockerClient = Mock(DockerClient) 14 | 15 | def setup() { 16 | project = ProjectBuilder.builder().build() 17 | task = project.tasks.register('dockerInfo', DockerInfoTask).get() 18 | task.dockerClient = dockerClient 19 | } 20 | 21 | def "delegates to dockerClient and saves result"() { 22 | given: 23 | def response = new EngineResponseContent(new SystemInfo()) 24 | 25 | when: 26 | task.info() 27 | 28 | then: 29 | 1 * dockerClient.info() >> response 30 | 31 | and: 32 | task.info == response 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerCopyToContainerTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerCopyToContainerTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerCpToContainer', DockerCopyToContainerTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates archive upload to dockerClient"() { 20 | given: 21 | task.container = "4711" 22 | task.targetPath = "/tmp/." 23 | 24 | def stream = new ByteArrayInputStream('--'.bytes) 25 | task.tarInputStream = stream 26 | 27 | when: 28 | task.copyToContainer() 29 | 30 | then: 31 | 1 * dockerClient.putArchive("4711", "/tmp/.", stream) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerImagesTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.ImageSummary; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.tasks.Internal; 7 | import org.gradle.api.tasks.TaskAction; 8 | 9 | import javax.inject.Inject; 10 | import java.util.List; 11 | 12 | public class DockerImagesTask extends GenericDockerTask { 13 | 14 | private EngineResponseContent> images; 15 | 16 | @Internal 17 | public EngineResponseContent> getImages() { 18 | return images; 19 | } 20 | 21 | @Inject 22 | public DockerImagesTask(ObjectFactory objectFactory) { 23 | super(objectFactory); 24 | setDescription("List images"); 25 | } 26 | 27 | @TaskAction 28 | public EngineResponseContent> images() { 29 | getLogger().info("docker images"); 30 | return images = getDockerClient().images(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerRmTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerRmTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerRm', DockerRmTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.containerId = "4712" 22 | 23 | when: 24 | task.rm() 25 | 26 | then: 27 | 1 * dockerClient.rm("4712", [v: 0]) 28 | } 29 | 30 | def "allows to removeVolumes"() { 31 | given: 32 | task.containerId = "4712" 33 | task.removeVolumes = true 34 | 35 | when: 36 | task.rm() 37 | 38 | then: 39 | 1 * dockerClient.rm("4712", [v: 1]) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerVersionTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.SystemVersion 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import spock.lang.Specification 8 | 9 | class DockerVersionTaskSpec extends Specification { 10 | 11 | def project 12 | def task 13 | def dockerClient = Mock(DockerClient) 14 | 15 | def setup() { 16 | project = ProjectBuilder.builder().build() 17 | task = project.tasks.register('dockerVersion', DockerVersionTask).get() 18 | task.dockerClient = dockerClient 19 | } 20 | 21 | def "delegates to dockerClient and saves result"() { 22 | given: 23 | def response = new EngineResponseContent(new SystemVersion()) 24 | 25 | when: 26 | task.version() 27 | 28 | then: 29 | 1 * dockerClient.version() >> response 30 | 31 | and: 32 | task.version == response 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "gradle" 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | open-pull-requests-limit: 20 17 | groups: 18 | # https://github.blog/2023-08-24-a-faster-way-to-manage-version-updates-with-dependabot/ 19 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups 20 | kotlin: 21 | patterns: 22 | - "org.jetbrains.kotlin:*" 23 | okio: 24 | patterns: 25 | - "com.squareup.okio:*" 26 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerLogsTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | import java.time.Duration 8 | import java.time.temporal.ChronoUnit 9 | 10 | class DockerLogsTaskSpec extends Specification { 11 | 12 | def project 13 | def task 14 | def dockerClient = Mock(DockerClient) 15 | 16 | def setup() { 17 | project = ProjectBuilder.builder().build() 18 | task = project.tasks.register('dockerLogs', DockerLogsTask).get() 19 | task.dockerClient = dockerClient 20 | task.logsTimeout = Duration.of(1, ChronoUnit.SECONDS) 21 | } 22 | 23 | def "delegates to dockerClient"() { 24 | given: 25 | task.containerId = "4711" 26 | task.logOptions.put("timestamps", true) 27 | 28 | when: 29 | task.logs() 30 | 31 | then: 32 | 1 * dockerClient.logs("4711", 33 | ["follow": false, "timestamps": true], 34 | _, _) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerSwarmJoinTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.remote.api.SwarmJoinRequest; 4 | import org.gradle.api.model.ObjectFactory; 5 | import org.gradle.api.provider.Property; 6 | import org.gradle.api.tasks.Input; 7 | import org.gradle.api.tasks.Optional; 8 | import org.gradle.api.tasks.TaskAction; 9 | 10 | import javax.inject.Inject; 11 | 12 | public class DockerSwarmJoinTask extends GenericDockerTask { 13 | 14 | private final Property config; 15 | 16 | @Input 17 | @Optional 18 | public Property getConfig() { 19 | return config; 20 | } 21 | 22 | @Inject 23 | public DockerSwarmJoinTask(ObjectFactory objectFactory) { 24 | super(objectFactory); 25 | setDescription("Join a swarm as a node and/or manager"); 26 | 27 | config = objectFactory.property(SwarmJoinRequest.class); 28 | } 29 | 30 | @TaskAction 31 | public void joinSwarm() { 32 | getLogger().info("docker swarm join"); 33 | getDockerClient().joinSwarm(getConfig().get()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tobias Gesellchen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerSwarmJoinTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.remote.api.SwarmJoinRequest 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import spock.lang.Specification 7 | 8 | class DockerSwarmJoinTaskSpec extends Specification { 9 | 10 | def project 11 | DockerSwarmJoinTask task 12 | def dockerClient = Mock(DockerClient) 13 | private SwarmJoinRequest swarmJoinRequest 14 | 15 | def setup() { 16 | project = ProjectBuilder.builder().build() 17 | task = project.tasks.register('joinSwarm', DockerSwarmJoinTask).get() 18 | task.dockerClient = dockerClient 19 | } 20 | 21 | def "delegates to dockerClient and saves result"() { 22 | given: 23 | swarmJoinRequest = new SwarmJoinRequest( 24 | "0.0.0.0:4500", 25 | null, 26 | null, 27 | ["node1:4500"], 28 | null) 29 | task.config.set(swarmJoinRequest) 30 | 31 | when: 32 | task.joinSwarm() 33 | 34 | then: 35 | 1 * dockerClient.joinSwarm(swarmJoinRequest) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerImagesTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.ImageSummary 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import spock.lang.Specification 8 | 9 | class DockerImagesTaskSpec extends Specification { 10 | 11 | def project 12 | def task 13 | def dockerClient = Mock(DockerClient) 14 | 15 | def setup() { 16 | project = ProjectBuilder.builder().build() 17 | task = project.tasks.register('dockerImages', DockerImagesTask).get() 18 | task.dockerClient = dockerClient 19 | } 20 | 21 | def "delegates to dockerClient and saves result"() { 22 | given: 23 | def summary = new ImageSummary( 24 | "image", "parent", 25 | -1, 1, 10, 0, 26 | null, null, null 27 | ) 28 | 29 | when: 30 | task.images() 31 | 32 | then: 33 | 1 * dockerClient.images() >> new EngineResponseContent([summary]) 34 | 35 | and: 36 | task.images.content.id == ["image"] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerTagTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerTagTask extends GenericDockerTask { 11 | 12 | private final Property imageId; 13 | 14 | @Input 15 | public Property getImageId() { 16 | return imageId; 17 | } 18 | 19 | private final Property imageTag; 20 | 21 | @Input 22 | public Property getImageTag() { 23 | return imageTag; 24 | } 25 | 26 | @Inject 27 | public DockerTagTask(ObjectFactory objectFactory) { 28 | super(objectFactory); 29 | setDescription("Tag an image into a repository"); 30 | 31 | imageId = objectFactory.property(String.class); 32 | imageTag = objectFactory.property(String.class); 33 | } 34 | 35 | @TaskAction 36 | public void tag() { 37 | getLogger().info("docker tag"); 38 | getDockerClient().tag(getImageId().get(), getImageTag().get()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerCopyFromContainerTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import spock.lang.Specification 7 | 8 | class DockerCopyFromContainerTaskSpec extends Specification { 9 | 10 | def project 11 | def task 12 | def dockerClient = Mock(DockerClient) 13 | 14 | def setup() { 15 | project = ProjectBuilder.builder().build() 16 | task = project.tasks.register('dockerCpFromContainer', DockerCopyFromContainerTask).get() 17 | task.dockerClient = dockerClient 18 | } 19 | 20 | def "delegates archive download from dockerClient and saves result"() { 21 | given: 22 | task.container = "4711" 23 | task.sourcePath = "/file.txt" 24 | def expectedResponse = new EngineResponseContent("file-content") 25 | 26 | when: 27 | task.copyFromContainer() 28 | 29 | then: 30 | 1 * dockerClient.getArchive("4711", "/file.txt") >> expectedResponse 31 | and: 32 | task.content == expectedResponse 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/worker/BuildcontextArchiver.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.worker; 2 | 3 | import de.gesellix.docker.builder.BuildContextBuilder; 4 | import org.gradle.workers.WorkAction; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | public abstract class BuildcontextArchiver implements WorkAction { 12 | 13 | private static final Logger log = LoggerFactory.getLogger(BuildcontextArchiver.class); 14 | 15 | @Override 16 | public void execute() { 17 | final File sourceDirectory = getParameters().getSourceDirectory().getAsFile().get(); 18 | final File targetFile = getParameters().getArchivedTargetFile().getAsFile().get(); 19 | log.info("archiving " + sourceDirectory + " into " + targetFile + "..."); 20 | targetFile.getParentFile().mkdirs(); 21 | try { 22 | BuildContextBuilder.archiveTarFilesRecursively(sourceDirectory, targetFile); 23 | } 24 | catch (IOException e) { 25 | throw new RuntimeException("Archiving failed", e); 26 | } 27 | log.info("archiving finished"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerRenameTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerRenameTask extends GenericDockerTask { 11 | 12 | private final Property containerId; 13 | 14 | @Input 15 | public Property getContainerId() { 16 | return containerId; 17 | } 18 | 19 | private final Property newName; 20 | 21 | @Input 22 | public Property getNewName() { 23 | return newName; 24 | } 25 | 26 | @Inject 27 | public DockerRenameTask(ObjectFactory objectFactory) { 28 | super(objectFactory); 29 | setDescription("Rename an existing container"); 30 | 31 | containerId = objectFactory.property(String.class); 32 | newName = objectFactory.property(String.class); 33 | } 34 | 35 | @TaskAction 36 | public void rename() { 37 | getLogger().info("docker rename"); 38 | getDockerClient().rename(getContainerId().get(), getNewName().get()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerInspectContainerTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.ContainerInspectResponse 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import spock.lang.Specification 8 | 9 | class DockerInspectContainerTaskSpec extends Specification { 10 | 11 | def project 12 | def task 13 | def dockerClient = Mock(DockerClient) 14 | 15 | def setup() { 16 | project = ProjectBuilder.builder().build() 17 | task = project.tasks.register('dockerInspect', DockerInspectContainerTask).get() 18 | task.dockerClient = dockerClient 19 | } 20 | 21 | def "delegates to dockerClient and returns result"() { 22 | given: 23 | task.containerId = "4711" 24 | def expectedResponse = new EngineResponseContent(new ContainerInspectResponse().tap { id = "123" }) 25 | 26 | when: 27 | task.inspect() 28 | 29 | then: 30 | 1 * dockerClient.inspectContainer("4711") >> expectedResponse 31 | and: 32 | task.containerInfo == expectedResponse 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerSwarmInitTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.SwarmInitRequest 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import spock.lang.Specification 8 | 9 | class DockerSwarmInitTaskSpec extends Specification { 10 | 11 | def project 12 | def task 13 | def dockerClient = Mock(DockerClient) 14 | 15 | def setup() { 16 | project = ProjectBuilder.builder().build() 17 | task = project.tasks.register('initSwarm', DockerSwarmInitTask).get() 18 | task.dockerClient = dockerClient 19 | } 20 | 21 | def "delegates to dockerClient and saves result"() { 22 | given: 23 | def swarmConfig = new SwarmInitRequest().tap { 24 | listenAddr = "0.0.0.0:80" 25 | } 26 | task.swarmconfig.set(swarmConfig) 27 | 28 | when: 29 | task.initSwarm() 30 | 31 | then: 32 | 1 * dockerClient.initSwarm(swarmConfig) >> new EngineResponseContent("swarm-result") 33 | 34 | and: 35 | task.response.content == "swarm-result" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerInspectImageTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.ImageInspect 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import spock.lang.Specification 8 | 9 | class DockerInspectImageTaskSpec extends Specification { 10 | 11 | def project 12 | def task 13 | def dockerClient = Mock(DockerClient) 14 | 15 | def setup() { 16 | project = ProjectBuilder.builder().build() 17 | task = project.tasks.register('dockerInspect', DockerInspectImageTask).get() 18 | task.dockerClient = dockerClient 19 | } 20 | 21 | def "delegates to dockerClient and returns result"() { 22 | given: 23 | task.imageId = "my.image:dev" 24 | def inspect = new ImageInspect().tap { 25 | it.id = "sha256:1234" 26 | it.parent = "parent" 27 | } 28 | def expectedResponse = new EngineResponseContent(inspect) 29 | 30 | when: 31 | task.inspect() 32 | 33 | then: 34 | 1 * dockerClient.inspectImage("my.image:dev") >> expectedResponse 35 | and: 36 | task.imageInfo == expectedResponse 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugin/src/test/java/de/gesellix/gradle/docker/testutil/TestImage.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.testutil; 2 | 3 | import de.gesellix.docker.client.DockerClient; 4 | 5 | import java.util.Objects; 6 | 7 | public class TestImage { 8 | 9 | private final DockerClient dockerClient; 10 | private final String repository; 11 | private final String tag; 12 | private final boolean isWindows; 13 | 14 | public TestImage(DockerClient dockerClient) { 15 | this.dockerClient = dockerClient; 16 | 17 | this.isWindows = Objects.requireNonNull(dockerClient.version().getContent().getOs()).equalsIgnoreCase("windows"); 18 | this.repository = "gesellix/echo-server"; 19 | this.tag = "2025-07-27T22-12-00"; 20 | 21 | // TODO consider NOT calling prepare inside the constructor 22 | prepare(); 23 | } 24 | 25 | public void prepare() { 26 | dockerClient.pull(null, null, getImageName(), getImageTag()); 27 | } 28 | 29 | public boolean isWindows() { 30 | return isWindows; 31 | } 32 | 33 | public String getImageWithTag() { 34 | return getImageName() + ":" + getImageTag(); 35 | } 36 | 37 | public String getImageName() { 38 | return repository; 39 | } 40 | 41 | public String getImageTag() { 42 | return tag; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerNetworkConnectTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerNetworkConnectTask extends GenericDockerTask { 11 | 12 | private final Property networkName; 13 | 14 | @Input 15 | public Property getNetworkName() { 16 | return networkName; 17 | } 18 | 19 | private final Property containerName; 20 | 21 | @Input 22 | public Property getContainerName() { 23 | return containerName; 24 | } 25 | 26 | @Inject 27 | public DockerNetworkConnectTask(ObjectFactory objectFactory) { 28 | super(objectFactory); 29 | setDescription("Connects a container to a network"); 30 | 31 | networkName = objectFactory.property(String.class); 32 | containerName = objectFactory.property(String.class); 33 | } 34 | 35 | @TaskAction 36 | public void connectNetwork() { 37 | getLogger().info("docker network connect"); 38 | getDockerClient().connectNetwork(getNetworkName().get(), getContainerName().get()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerNetworkDisconnectTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class DockerNetworkDisconnectTask extends GenericDockerTask { 11 | 12 | private final Property networkName; 13 | 14 | @Input 15 | public Property getNetworkName() { 16 | return networkName; 17 | } 18 | 19 | private final Property containerName; 20 | 21 | @Input 22 | public Property getContainerName() { 23 | return containerName; 24 | } 25 | 26 | @Inject 27 | public DockerNetworkDisconnectTask(ObjectFactory objectFactory) { 28 | super(objectFactory); 29 | setDescription("Disconnects container from a network"); 30 | 31 | networkName = objectFactory.property(String.class); 32 | containerName = objectFactory.property(String.class); 33 | } 34 | 35 | @TaskAction 36 | public void disconnectNetwork() { 37 | getLogger().info("docker network disconnect"); 38 | getDockerClient().disconnectNetwork(getNetworkName().get(), getContainerName().get()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerInspectImageTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.ImageInspect; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.provider.Property; 7 | import org.gradle.api.tasks.Input; 8 | import org.gradle.api.tasks.Internal; 9 | import org.gradle.api.tasks.TaskAction; 10 | 11 | import javax.inject.Inject; 12 | 13 | public class DockerInspectImageTask extends GenericDockerTask { 14 | 15 | private final Property imageId; 16 | 17 | @Input 18 | public Property getImageId() { 19 | return imageId; 20 | } 21 | 22 | private EngineResponseContent imageInfo; 23 | 24 | @Internal 25 | public EngineResponseContent getImageInfo() { 26 | return imageInfo; 27 | } 28 | 29 | @Inject 30 | public DockerInspectImageTask(ObjectFactory objectFactory) { 31 | super(objectFactory); 32 | setDescription("Return low-level information on image"); 33 | 34 | imageId = objectFactory.property(String.class); 35 | } 36 | 37 | @TaskAction 38 | public void inspect() { 39 | getLogger().info("docker inspect"); 40 | imageInfo = getDockerClient().inspectImage(getImageId().get()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerCommitTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class DockerCommitTaskSpec extends Specification { 8 | 9 | def project 10 | def task 11 | def dockerClient = Mock(DockerClient) 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | task = project.tasks.register('dockerCommit', DockerCommitTask).get() 16 | task.dockerClient = dockerClient 17 | } 18 | 19 | def "delegates to dockerClient"() { 20 | given: 21 | task.repo = "your.local.repo" 22 | task.tag = "container-changed:1.0" 23 | task.containerId = "a-container" 24 | task.author = "Tue Dissing " 25 | task.comment = "a test" 26 | task.changes.set("change description") 27 | task.pauseContainer = true 28 | 29 | when: 30 | task.commit() 31 | 32 | then: 33 | 1 * dockerClient.commit("a-container", [ 34 | repo : 'your.local.repo', 35 | tag : 'container-changed:1.0', 36 | comment: 'a test', 37 | author : 'Tue Dissing ', 38 | changes: "change description", 39 | pause : true 40 | ]) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerPullTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.authentication.AuthConfig 4 | import de.gesellix.docker.client.DockerClient 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import spock.lang.Specification 7 | 8 | import java.time.Duration 9 | import java.time.temporal.ChronoUnit 10 | 11 | class DockerPullTaskSpec extends Specification { 12 | 13 | def project 14 | def task 15 | def dockerClient = Mock(DockerClient) 16 | 17 | def setup() { 18 | project = ProjectBuilder.builder().build() 19 | task = project.tasks.register('dockerPull', DockerPullTask).get() 20 | task.dockerClient = dockerClient 21 | task.pullTimeout = Duration.of(1, ChronoUnit.SECONDS) 22 | } 23 | 24 | def "delegates to dockerClient"() { 25 | given: 26 | task.authConfig = new AuthConfig(username: "user", password: "pass") 27 | task.imageName = "imageName" 28 | task.imageTag = "latest" 29 | task.registry = "registry.example.com:4711" 30 | 31 | when: 32 | task.pull() 33 | 34 | then: 35 | 1 * dockerClient.encodeAuthConfig(new AuthConfig(username: "user", password: "pass")) >> "-foo-" 36 | then: 37 | 1 * dockerClient.pull(_, _, "registry.example.com:4711/imageName", "latest", "-foo-") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerVolumeCreateTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.Volume 6 | import de.gesellix.docker.remote.api.VolumeCreateOptions 7 | import org.gradle.testfixtures.ProjectBuilder 8 | import spock.lang.Specification 9 | 10 | class DockerVolumeCreateTaskSpec extends Specification { 11 | 12 | def project 13 | def task 14 | def dockerClient = Mock(DockerClient) 15 | 16 | def setup() { 17 | project = ProjectBuilder.builder().build() 18 | task = project.tasks.register('dockerCreateVolume', DockerVolumeCreateTask).get() 19 | task.dockerClient = dockerClient 20 | } 21 | 22 | def "delegates to dockerClient and saves result"() { 23 | given: 24 | def config = new VolumeCreateOptions().tap { 25 | it.name = "foo" 26 | } 27 | task.configure { 28 | volumeConfig = config 29 | } 30 | def expectedResult = new EngineResponseContent(new Volume( 31 | "foo", "overlay", "", null, null, null, null, null, null, null 32 | )) 33 | 34 | when: 35 | task.createVolume() 36 | 37 | then: 38 | 1 * dockerClient.createVolume(config) >> expectedResult 39 | 40 | and: 41 | task.response == expectedResult 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerSwarmInitTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.SwarmInitRequest; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.provider.Property; 7 | import org.gradle.api.tasks.Input; 8 | import org.gradle.api.tasks.Internal; 9 | import org.gradle.api.tasks.TaskAction; 10 | 11 | import javax.inject.Inject; 12 | 13 | public class DockerSwarmInitTask extends GenericDockerTask { 14 | 15 | private final Property swarmconfig; 16 | 17 | @Input 18 | public Property getSwarmconfig() { 19 | return swarmconfig; 20 | } 21 | 22 | private EngineResponseContent response; 23 | 24 | @Internal 25 | public EngineResponseContent getResponse() { 26 | return response; 27 | } 28 | 29 | @Inject 30 | public DockerSwarmInitTask(ObjectFactory objectFactory) { 31 | super(objectFactory); 32 | setDescription("Initialize a swarm"); 33 | 34 | swarmconfig = objectFactory.property(SwarmInitRequest.class); 35 | } 36 | 37 | @TaskAction 38 | public EngineResponseContent initSwarm() { 39 | getLogger().info("docker swarm init"); 40 | response = getDockerClient().initSwarm(getSwarmconfig().get()); 41 | return response; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerInspectContainerTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.ContainerInspectResponse; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.provider.Property; 7 | import org.gradle.api.tasks.Input; 8 | import org.gradle.api.tasks.Internal; 9 | import org.gradle.api.tasks.TaskAction; 10 | 11 | import javax.inject.Inject; 12 | 13 | public class DockerInspectContainerTask extends GenericDockerTask { 14 | 15 | private final Property containerId; 16 | 17 | @Input 18 | public Property getContainerId() { 19 | return containerId; 20 | } 21 | 22 | private EngineResponseContent containerInfo; 23 | 24 | @Internal 25 | public EngineResponseContent getContainerInfo() { 26 | return containerInfo; 27 | } 28 | 29 | @Inject 30 | public DockerInspectContainerTask(ObjectFactory objectFactory) { 31 | super(objectFactory); 32 | setDescription("Return low-level information on a container"); 33 | 34 | containerId = objectFactory.property(String.class); 35 | } 36 | 37 | @TaskAction 38 | public void inspect() { 39 | getLogger().info("docker inspect"); 40 | containerInfo = getDockerClient().inspectContainer(getContainerId().get()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerRmTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.Optional; 7 | import org.gradle.api.tasks.TaskAction; 8 | 9 | import javax.inject.Inject; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class DockerRmTask extends GenericDockerTask { 14 | 15 | private final Property containerId; 16 | 17 | @Input 18 | public Property getContainerId() { 19 | return containerId; 20 | } 21 | 22 | private final Property removeVolumes; 23 | 24 | @Input 25 | @Optional 26 | public Property getRemoveVolumes() { 27 | return removeVolumes; 28 | } 29 | 30 | @Inject 31 | public DockerRmTask(ObjectFactory objectFactory) { 32 | super(objectFactory); 33 | setDescription("Remove one or more containers"); 34 | 35 | containerId = objectFactory.property(String.class); 36 | removeVolumes = objectFactory.property(Boolean.class); 37 | removeVolumes.convention(false); 38 | } 39 | 40 | @TaskAction 41 | public void rm() { 42 | getLogger().info("docker rm"); 43 | Map query = new HashMap<>(1); 44 | query.put("v", getRemoveVolumes().get() ? 1 : 0); 45 | getDockerClient().rm(getContainerId().get(), query); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /debug.gradle.kts: -------------------------------------------------------------------------------- 1 | import de.gesellix.docker.client.DockerClientImpl 2 | 3 | buildscript { 4 | repositories { 5 | // mavenLocal() 6 | // fun findProperty(s: String) = project.findProperty(s) as String? 7 | // listOf( 8 | // "docker-client/*", 9 | // "gesellix/*" 10 | // ).forEach { repo -> 11 | // maven { 12 | // name = "github" 13 | // setUrl("https://maven.pkg.github.com/$repo") 14 | // credentials { 15 | // username = System.getenv("PACKAGE_REGISTRY_USER") ?: findProperty("github.package-registry.username") 16 | // password = System.getenv("PACKAGE_REGISTRY_TOKEN") ?: findProperty("github.package-registry.password") 17 | // } 18 | // } 19 | // } 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | classpath("de.gesellix:docker-client:[2025-01-01T01-01-01,)") 25 | } 26 | } 27 | 28 | tasks.register("checkDockerAvailability") { 29 | group = "docker" 30 | val client = DockerClientImpl() 31 | 32 | doFirst { 33 | logger.lifecycle("Docker Host:\n|> ${client.env.dockerHost} <|") 34 | } 35 | doLast { 36 | try { 37 | logger.lifecycle("Docker Ping:\n|> ${client.ping().content} <|") 38 | logger.lifecycle("Docker Version:\n|> ${client.version().content} <|") 39 | logger.lifecycle("Docker Info:\n|> ${client.info().content} <|") 40 | } catch (e: Exception) { 41 | logger.warn("Docker Engine not available?", e) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerVolumesTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.VolumeListResponse; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.provider.MapProperty; 7 | import org.gradle.api.tasks.Input; 8 | import org.gradle.api.tasks.Internal; 9 | import org.gradle.api.tasks.Optional; 10 | import org.gradle.api.tasks.TaskAction; 11 | 12 | import javax.inject.Inject; 13 | import java.util.HashMap; 14 | 15 | public class DockerVolumesTask extends GenericDockerTask { 16 | 17 | private final MapProperty query; 18 | 19 | @Input 20 | @Optional 21 | public MapProperty getQuery() { 22 | return query; 23 | } 24 | 25 | private EngineResponseContent volumes; 26 | 27 | @Internal 28 | public EngineResponseContent getVolumes() { 29 | return volumes; 30 | } 31 | 32 | @Inject 33 | public DockerVolumesTask(ObjectFactory objectFactory) { 34 | super(objectFactory); 35 | setDescription("List volumes from all volume drivers"); 36 | 37 | query = objectFactory.mapProperty(String.class, Object.class); 38 | } 39 | 40 | @TaskAction 41 | public void volumes() { 42 | getLogger().info("docker volume ls"); 43 | volumes = getDockerClient().volumes(new HashMap<>(getQuery().get())); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerServiceCreateTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.ServiceCreateRequest; 5 | import de.gesellix.docker.remote.api.ServiceCreateResponse; 6 | import org.gradle.api.model.ObjectFactory; 7 | import org.gradle.api.provider.Property; 8 | import org.gradle.api.tasks.Input; 9 | import org.gradle.api.tasks.Internal; 10 | import org.gradle.api.tasks.TaskAction; 11 | 12 | import javax.inject.Inject; 13 | 14 | public class DockerServiceCreateTask extends GenericDockerTask { 15 | 16 | private final Property serviceConfig; 17 | 18 | @Input 19 | public Property getServiceConfig() { 20 | return serviceConfig; 21 | } 22 | 23 | private EngineResponseContent response; 24 | 25 | @Internal 26 | public EngineResponseContent getResponse() { 27 | return response; 28 | } 29 | 30 | @Inject 31 | public DockerServiceCreateTask(ObjectFactory objectFactory) { 32 | super(objectFactory); 33 | setDescription("Create a service"); 34 | 35 | serviceConfig = objectFactory.property(ServiceCreateRequest.class); 36 | } 37 | 38 | @TaskAction 39 | public void createService() { 40 | getLogger().info("docker service create"); 41 | response = getDockerClient().createService(getServiceConfig().get()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerNetworkRmTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.FailsWith 6 | import spock.lang.Specification 7 | 8 | class DockerNetworkRmTaskSpec extends Specification { 9 | 10 | def project 11 | def task 12 | def dockerClient = Mock(DockerClient) 13 | 14 | def setup() { 15 | project = ProjectBuilder.builder().build() 16 | task = project.tasks.register('rmNetwork', DockerNetworkRmTask).get() 17 | task.dockerClient = dockerClient 18 | } 19 | 20 | def "delegates to dockerClient and saves result"() { 21 | given: 22 | task.networkName = "a-network" 23 | 24 | when: 25 | task.rmNetwork() 26 | 27 | then: 28 | 1 * dockerClient.rmNetwork("a-network") 29 | } 30 | 31 | @FailsWith(RuntimeException) 32 | def "fails on error"() { 33 | given: 34 | task.networkName = "a-network" 35 | 36 | when: 37 | task.rmNetwork() 38 | 39 | then: 40 | 1 * dockerClient.rmNetwork("a-network") >> { throw new RuntimeException("expected error") } 41 | } 42 | 43 | def "can ignore errors"() { 44 | given: 45 | task.networkName = "a-network" 46 | task.ignoreError = true 47 | 48 | when: 49 | task.rmNetwork() 50 | 51 | then: 52 | 1 * dockerClient.rmNetwork("a-network") >> { throw new RuntimeException("expected error") } 53 | notThrown(Exception) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerVolumeCreateTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.Volume; 5 | import de.gesellix.docker.remote.api.VolumeCreateOptions; 6 | 7 | import org.gradle.api.model.ObjectFactory; 8 | import org.gradle.api.provider.Property; 9 | import org.gradle.api.tasks.Input; 10 | import org.gradle.api.tasks.Internal; 11 | import org.gradle.api.tasks.Optional; 12 | import org.gradle.api.tasks.TaskAction; 13 | 14 | import javax.inject.Inject; 15 | 16 | public class DockerVolumeCreateTask extends GenericDockerTask { 17 | 18 | private final Property volumeConfig; 19 | 20 | @Input 21 | @Optional 22 | public Property getVolumeConfig() { 23 | return volumeConfig; 24 | } 25 | 26 | private EngineResponseContent response; 27 | 28 | @Internal 29 | public EngineResponseContent getResponse() { 30 | return response; 31 | } 32 | 33 | @Inject 34 | public DockerVolumeCreateTask(ObjectFactory objectFactory) { 35 | super(objectFactory); 36 | setDescription("Create a volume"); 37 | 38 | volumeConfig = objectFactory.property(VolumeCreateOptions.class); 39 | } 40 | 41 | @TaskAction 42 | public EngineResponseContent createVolume() { 43 | getLogger().info("docker volume create"); 44 | 45 | response = getDockerClient().createVolume(volumeConfig.get()); 46 | return response; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerCopyToContainerTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import javax.inject.Inject; 9 | import java.io.InputStream; 10 | 11 | public class DockerCopyToContainerTask extends GenericDockerTask { 12 | 13 | private final Property container; 14 | 15 | @Input 16 | public Property getContainer() { 17 | return container; 18 | } 19 | 20 | private final Property targetPath; 21 | 22 | @Input 23 | public Property getTargetPath() { 24 | return targetPath; 25 | } 26 | 27 | private final Property tarInputStream; 28 | 29 | @Input 30 | public Property getTarInputStream() { 31 | return tarInputStream; 32 | } 33 | 34 | @Inject 35 | public DockerCopyToContainerTask(ObjectFactory objectFactory) { 36 | super(objectFactory); 37 | setDescription("Copy files/folders from your host to a container."); 38 | 39 | container = objectFactory.property(String.class); 40 | targetPath = objectFactory.property(String.class); 41 | tarInputStream = objectFactory.property(InputStream.class); 42 | } 43 | 44 | @TaskAction 45 | public void copyToContainer() { 46 | getLogger().info("docker cp to container"); 47 | getDockerClient().putArchive(getContainer().get(), getTargetPath().get(), getTarInputStream().get()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerNetworkCreateTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.IPAM 6 | import de.gesellix.docker.remote.api.NetworkCreateRequest 7 | import org.gradle.testfixtures.ProjectBuilder 8 | import spock.lang.Specification 9 | 10 | class DockerNetworkCreateTaskSpec extends Specification { 11 | 12 | def project 13 | def task 14 | def dockerClient = Mock(DockerClient) 15 | 16 | def setup() { 17 | project = ProjectBuilder.builder().build() 18 | task = project.tasks.register('createNetwork', DockerNetworkCreateTask).get() 19 | task.dockerClient = dockerClient 20 | } 21 | 22 | def "delegates to dockerClient and saves result"() { 23 | given: 24 | task.networkName = "a-network" 25 | task.networkConfig = new NetworkCreateRequest( 26 | "a-network", null, 27 | "overlay", null, null, null, null, null, null, 28 | new IPAM("default", null, null), 29 | null, null, null) 30 | def expectedResult = new EngineResponseContent("result") 31 | 32 | when: 33 | task.createNetwork() 34 | 35 | then: 36 | 1 * dockerClient.createNetwork(new NetworkCreateRequest( 37 | "a-network", null, 38 | "overlay", null, null, null, null, null, null, 39 | new IPAM("default", null, null), 40 | null, null, null)) >> expectedResult 41 | 42 | and: 43 | task.response == expectedResult 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerServiceCreateTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.ServiceCreateRequest 6 | import de.gesellix.docker.remote.api.ServiceCreateResponse 7 | import de.gesellix.docker.remote.api.TaskSpec 8 | import de.gesellix.docker.remote.api.TaskSpecContainerSpec 9 | import org.gradle.testfixtures.ProjectBuilder 10 | import spock.lang.Specification 11 | 12 | class DockerServiceCreateTaskSpec extends Specification { 13 | 14 | def project 15 | def task 16 | def dockerClient = Mock(DockerClient) 17 | 18 | def setup() { 19 | project = ProjectBuilder.builder().build() 20 | task = project.tasks.register('createService', DockerServiceCreateTask).get() 21 | task.dockerClient = dockerClient 22 | } 23 | 24 | def "delegates to dockerClient and saves result"() { 25 | given: 26 | def response = new EngineResponseContent(new ServiceCreateResponse()) 27 | def serviceSpec = new ServiceCreateRequest().tap { 28 | name = "a-service" 29 | taskTemplate = new TaskSpec().tap { 30 | containerSpec = new TaskSpecContainerSpec().tap { 31 | image = "nginx" 32 | } 33 | } 34 | } 35 | task.serviceConfig = serviceSpec 36 | 37 | when: 38 | task.createService() 39 | 40 | then: 41 | 1 * dockerClient.createService(serviceSpec) >> response 42 | 43 | and: 44 | task.response == response 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerVolumesTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.VolumeListResponse 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import spock.lang.Specification 8 | 9 | class DockerVolumesTaskSpec extends Specification { 10 | 11 | def project 12 | def task 13 | def dockerClient = Mock(DockerClient) 14 | 15 | def setup() { 16 | project = ProjectBuilder.builder().build() 17 | task = project.tasks.register('dockerVolumes', DockerVolumesTask).get() 18 | task.dockerClient = dockerClient 19 | } 20 | 21 | def "delegates to dockerClient and saves result"() { 22 | given: 23 | def expectedResult = new EngineResponseContent(new VolumeListResponse( 24 | [], [] 25 | )) 26 | 27 | when: 28 | task.volumes() 29 | 30 | then: 31 | 1 * dockerClient.volumes([:]) >> expectedResult 32 | 33 | and: 34 | task.volumes == expectedResult 35 | } 36 | 37 | def "delegates with query to dockerClient and saves result"() { 38 | given: 39 | def expectedResult = new EngineResponseContent(new VolumeListResponse( 40 | [], [] 41 | )) 42 | 43 | when: 44 | task.configure { 45 | query = [filters: [dangling: ["true"]]] 46 | } 47 | task.volumes() 48 | 49 | then: 50 | 1 * dockerClient.volumes([filters: [dangling: ["true"]]]) >> expectedResult 51 | 52 | and: 53 | task.volumes == expectedResult 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/DockerPluginSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker 2 | 3 | import de.gesellix.docker.authentication.AuthConfig 4 | import de.gesellix.gradle.docker.tasks.TestTask 5 | import org.gradle.api.Project 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import spock.lang.Specification 8 | 9 | class DockerPluginSpec extends Specification { 10 | 11 | private Project project 12 | 13 | def setup() { 14 | project = ProjectBuilder.builder().build() 15 | } 16 | 17 | def "DockerPluginExtension is added to project"() { 18 | when: 19 | project.apply plugin: 'de.gesellix.docker' 20 | then: 21 | project["docker"] instanceof DockerPluginExtension 22 | } 23 | 24 | def "configuration is passed to tasks"() { 25 | given: 26 | project.apply plugin: 'de.gesellix.docker' 27 | project.docker.dockerHost = "https://example.org:2376" 28 | project.docker.certPath = 'foo' 29 | project.docker.authConfig = new AuthConfig(username: "plain example") 30 | 31 | when: 32 | def task = project.tasks.register("testTask", TestTask).get() 33 | 34 | then: 35 | task.dockerHost.get() == "https://example.org:2376" 36 | task.certPath.get() == project.file('foo').absolutePath 37 | task.authConfig.get().username == "plain example" 38 | } 39 | 40 | def "returns the absolute certification path"() { 41 | given: 42 | project.apply plugin: 'de.gesellix.docker' 43 | 44 | when: 45 | project.docker.certPath = 'foo' 46 | 47 | then: 48 | project.docker.certPath == project.file('foo').absolutePath 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/DockerPlugin.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker; 2 | 3 | import de.gesellix.gradle.docker.tasks.GenericDockerTask; 4 | import org.gradle.api.Plugin; 5 | import org.gradle.api.Project; 6 | 7 | public class DockerPlugin implements Plugin { 8 | 9 | public static final String EXTENSION_NAME = "docker"; 10 | 11 | @Override 12 | public void apply(Project project) { 13 | // project.plugins.apply(BasePlugin) 14 | DockerPluginExtension extension = project.getExtensions().create(EXTENSION_NAME, DockerPluginExtension.class, project); 15 | // ProviderFactory providers = project.getProviders(); 16 | // extension.getDockerHost().convention(providers.systemProperty("docker.host") 17 | // .orElse(providers.environmentVariable("DOCKER_HOST")) 18 | // .orElse(new DockerEnv().getDockerHost())); 19 | // extension.getCertPath().convention(providers.systemProperty("docker.cert.path") 20 | // .orElse(providers.environmentVariable("DOCKER_CERT_PATH")) 21 | // .getOrNull()); 22 | // extension.getProxy().convention(Proxy.NO_PROXY); 23 | project.getTasks().withType(GenericDockerTask.class).configureEach(task -> { 24 | task.getDockerHost().convention(extension.getDockerHost()); 25 | task.getCertPath().convention(extension.getCertPath()); 26 | task.getProxy().convention(extension.getProxy()); 27 | task.getAuthConfig().convention(extension.getAuthConfig()); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerCopyFromContainerTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import org.gradle.api.model.ObjectFactory; 5 | import org.gradle.api.provider.Property; 6 | import org.gradle.api.tasks.Input; 7 | import org.gradle.api.tasks.Internal; 8 | import org.gradle.api.tasks.TaskAction; 9 | 10 | import javax.inject.Inject; 11 | import java.io.InputStream; 12 | 13 | public class DockerCopyFromContainerTask extends GenericDockerTask { 14 | 15 | private final Property container; 16 | 17 | @Input 18 | public Property getContainer() { 19 | return container; 20 | } 21 | 22 | private final Property sourcePath; 23 | 24 | @Input 25 | public Property getSourcePath() { 26 | return sourcePath; 27 | } 28 | 29 | private EngineResponseContent content; 30 | 31 | @Internal 32 | public EngineResponseContent getContent() { 33 | return content; 34 | } 35 | 36 | @Inject 37 | public DockerCopyFromContainerTask(ObjectFactory objectFactory) { 38 | super(objectFactory); 39 | setDescription("Copy files/folders from a container to your host."); 40 | 41 | container = objectFactory.property(String.class); 42 | sourcePath = objectFactory.property(String.class); 43 | } 44 | 45 | @TaskAction 46 | public EngineResponseContent copyFromContainer() { 47 | getLogger().info("docker cp from container"); 48 | return content = getDockerClient().getArchive(getContainer().get(), getSourcePath().get()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerPushTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.authentication.AuthConfig 4 | import de.gesellix.docker.client.DockerClient 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import spock.lang.Specification 7 | import spock.lang.Unroll 8 | 9 | import java.time.Duration 10 | import java.time.temporal.ChronoUnit 11 | 12 | class DockerPushTaskSpec extends Specification { 13 | 14 | def project 15 | def task 16 | def dockerClient = Mock(DockerClient) 17 | 18 | def setup() { 19 | project = ProjectBuilder.builder().build() 20 | task = project.tasks.register('dockerPush', DockerPushTask).get() 21 | task.dockerClient = dockerClient 22 | task.pushTimeout = Duration.of(1, ChronoUnit.SECONDS) 23 | } 24 | 25 | @Unroll 26 | def "delegates to dockerClient with registry=#registry"() { 27 | given: 28 | def authDetails = new AuthConfig("username": "gesellix", 29 | "password": "-yet-another-password-", 30 | "email": "tobias@gesellix.de", 31 | "serveraddress": "https://index.docker.io/v1/") 32 | task.repositoryName = "repositoryName" 33 | task.registry = registry 34 | task.authConfig = authDetails 35 | // task.authConfigEncoded = "--auth.base64--" 36 | 37 | when: 38 | task.push() 39 | 40 | then: 41 | 1 * dockerClient.encodeAuthConfig(authDetails) >> "--auth.base64--" 42 | 43 | then: 44 | 1 * dockerClient.push(_, _, "repositoryName", "--auth.base64--", registry) 45 | 46 | where: 47 | registry << [null, "registry.docker.io"] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerNetworkRmTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import org.gradle.api.model.ObjectFactory; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.Optional; 7 | import org.gradle.api.tasks.TaskAction; 8 | 9 | import javax.inject.Inject; 10 | 11 | public class DockerNetworkRmTask extends GenericDockerTask { 12 | 13 | private final Property networkName; 14 | 15 | @Input 16 | public Property getNetworkName() { 17 | return networkName; 18 | } 19 | 20 | private final Property ignoreError; 21 | 22 | @Input 23 | @Optional 24 | public Property getIgnoreError() { 25 | return ignoreError; 26 | } 27 | 28 | @Inject 29 | public DockerNetworkRmTask(ObjectFactory objectFactory) { 30 | super(objectFactory); 31 | setDescription("Remove a network"); 32 | 33 | networkName = objectFactory.property(String.class); 34 | ignoreError = objectFactory.property(Boolean.class); 35 | ignoreError.convention(false); 36 | } 37 | 38 | @TaskAction 39 | public void rmNetwork() { 40 | getLogger().info("docker network rm"); 41 | 42 | try { 43 | getDockerClient().rmNetwork(getNetworkName().get()); 44 | } 45 | catch (Exception e) { 46 | if (!ignoreError.get()) { 47 | throw new RuntimeException(e); 48 | } 49 | else { 50 | if (getLogger().isInfoEnabled()) { 51 | getLogger().warn("docker network rm " + getNetworkName().get() + " failed", e); 52 | } 53 | else { 54 | getLogger().warn("docker network rm " + getNetworkName().get() + " failed"); 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/publish-test-results.yml: -------------------------------------------------------------------------------- 1 | name: Publish Test results 2 | 3 | # WARNING: 4 | # workflow_run provides read-write repo token and access to secrets. 5 | # Do *not* merge changes to this file without the proper review. 6 | # We should only be running trusted code here. 7 | # See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ 8 | # Docs: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run 9 | on: 10 | workflow_run: 11 | workflows: 12 | - CI 13 | - Publish 14 | - Release 15 | types: 16 | - completed 17 | permissions: {} 18 | 19 | jobs: 20 | # Job based on 21 | # - https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ 22 | # - https://github.com/marketplace/actions/publish-test-results#support-fork-repositories-and-dependabot-branches 23 | publish-test-results: 24 | runs-on: ubuntu-latest 25 | if: github.event.workflow_run.conclusion != 'skipped' 26 | 27 | permissions: 28 | checks: write 29 | # needed unless run with comment_mode: off 30 | pull-requests: write 31 | # only needed for private repository 32 | #contents: read 33 | # only needed for private repository 34 | #issues: read 35 | # required by download step to access artifacts API 36 | actions: read 37 | 38 | steps: 39 | - name: Download and Extract Artifacts 40 | uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 41 | with: 42 | run_id: ${{ github.event.workflow_run.id }} 43 | path: artifacts 44 | - name: Publish Test Results 45 | uses: EnricoMi/publish-unit-test-result-action@v2 46 | with: 47 | commit: ${{ github.event.workflow_run.head_sha }} 48 | event_file: artifacts/event-file/event.json 49 | event_name: ${{ github.event.workflow_run.event }} 50 | files: "artifacts/**/build/test-results/test/TEST-*.xml" 51 | ... 52 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches-ignore: 7 | - main 8 | jobs: 9 | event-file: 10 | # https://github.com/marketplace/actions/publish-test-results#support-fork-repositories-and-dependabot-branches 11 | name: "Event File" 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Upload 15 | uses: actions/upload-artifact@v6 16 | with: 17 | name: event-file 18 | path: ${{ github.event_path }} 19 | ci-build: 20 | strategy: 21 | matrix: 22 | os: 23 | - ubuntu-latest 24 | - windows-latest 25 | - macos-15-intel 26 | java: 27 | - '21' 28 | runs-on: ${{ matrix.os }} 29 | timeout-minutes: 20 30 | steps: 31 | - uses: actions/checkout@v6 32 | with: 33 | fetch-depth: 1 34 | - name: Set up JDK 35 | uses: actions/setup-java@v5.1.0 36 | with: 37 | distribution: 'zulu' 38 | java-version: ${{ matrix.java }} 39 | - name: Setup Gradle 40 | uses: gradle/actions/setup-gradle@v5 41 | - name: Install Docker on macOS 42 | if: matrix.os == 'macos-15-intel' 43 | uses: douglascamata/setup-docker-macos-action@v1.0.2 44 | - name: Login to Docker Hub 45 | uses: docker/login-action@v3 46 | with: 47 | username: ${{ secrets.DOCKERHUB_USERNAME }} 48 | password: ${{ secrets.DOCKERHUB_TOKEN }} 49 | # - name: Debug 50 | # run: ./gradlew checkDockerAvailability --info --stacktrace 51 | - name: clean build 52 | run: ./gradlew clean build --no-daemon --info --stacktrace 53 | - name: Upload Test Results 54 | # see publish-test-results.yml for workflow that publishes test results without security issues for forks 55 | # https://github.com/marketplace/actions/publish-test-results#support-fork-repositories-and-dependabot-branches 56 | if: always() 57 | uses: actions/upload-artifact@v6 58 | with: 59 | name: Test Results (Java ${{ matrix.java }} on ${{ matrix.os }}) 60 | path: '**/build/test-results/test/TEST-*.xml' 61 | ... 62 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerWaitTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.ContainerWaitResponse; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.provider.Property; 7 | import org.gradle.api.tasks.Input; 8 | import org.gradle.api.tasks.Internal; 9 | import org.gradle.api.tasks.Optional; 10 | import org.gradle.api.tasks.TaskAction; 11 | 12 | import javax.inject.Inject; 13 | 14 | public class DockerWaitTask extends GenericDockerTask { 15 | 16 | private final Property containerId; 17 | 18 | @Input 19 | public Property getContainerId() { 20 | return containerId; 21 | } 22 | 23 | private final Property ignoreError; 24 | 25 | @Input 26 | @Optional 27 | public Property getIgnoreError() { 28 | return ignoreError; 29 | } 30 | 31 | private EngineResponseContent result; 32 | 33 | @Internal 34 | public EngineResponseContent getResult() { 35 | return result; 36 | } 37 | 38 | @Inject 39 | public DockerWaitTask(ObjectFactory objectFactory) { 40 | super(objectFactory); 41 | setDescription("Block until a container stops, then print its exit code."); 42 | 43 | containerId = objectFactory.property(String.class); 44 | ignoreError = objectFactory.property(Boolean.class); 45 | ignoreError.convention(false); 46 | } 47 | 48 | @TaskAction 49 | public EngineResponseContent awaitStop() { 50 | getLogger().info("docker wait"); 51 | 52 | try { 53 | result = getDockerClient().wait(getContainerId().get()); 54 | } 55 | catch (Exception e) { 56 | if (!ignoreError.get()) { 57 | throw new RuntimeException(e); 58 | } 59 | else { 60 | if (getLogger().isInfoEnabled()) { 61 | getLogger().warn("docker container wait " + getContainerId().get() + " failed", e); 62 | } 63 | else { 64 | getLogger().warn("docker container wait " + getContainerId().get() + " failed"); 65 | } 66 | } 67 | } 68 | return result; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerExecTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.remote.api.ExecConfig; 4 | import de.gesellix.docker.remote.api.ExecStartConfig; 5 | import org.gradle.api.model.ObjectFactory; 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.gradle.api.tasks.TaskAction; 11 | 12 | import javax.inject.Inject; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | public class DockerExecTask extends GenericDockerTask { 17 | 18 | private final Property containerId; 19 | 20 | @Input 21 | public Property getContainerId() { 22 | return containerId; 23 | } 24 | 25 | private final ListProperty cmds; 26 | 27 | @Input 28 | @Optional 29 | public ListProperty getCmds() { 30 | return cmds; 31 | } 32 | 33 | private final Property cmd; 34 | 35 | @Input 36 | @Optional 37 | public Property getCmd() { 38 | return cmd; 39 | } 40 | 41 | @Inject 42 | public DockerExecTask(ObjectFactory objectFactory) { 43 | super(objectFactory); 44 | setDescription("Run a command in a running container"); 45 | 46 | containerId = objectFactory.property(String.class); 47 | cmds = objectFactory.listProperty(String.class); 48 | cmd = objectFactory.property(String.class); 49 | } 50 | 51 | @TaskAction 52 | public void exec() { 53 | getLogger().info("docker exec"); 54 | 55 | List commandline = (!cmds.get().isEmpty()) ? cmds.get() : Arrays.asList("sh", "-c", cmd.getOrNull()); 56 | ExecConfig execCreateConfig = new ExecConfig(); 57 | execCreateConfig.setAttachStdin(false); 58 | execCreateConfig.setAttachStdout(true); 59 | execCreateConfig.setAttachStderr(true); 60 | execCreateConfig.setTty(false); 61 | execCreateConfig.setCmd(commandline); 62 | getLogger().debug("exec cmd: '" + execCreateConfig.getCmd() + "'"); 63 | String execId = getDockerClient().createExec(containerId.get(), execCreateConfig).getContent().getId(); 64 | 65 | ExecStartConfig execStartConfig = new ExecStartConfig(false, false, null); 66 | getDockerClient().startExec(execId, execStartConfig, null, null); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish 3 | on: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | event-file: 9 | # https://github.com/marketplace/actions/publish-test-results#support-fork-repositories-and-dependabot-branches 10 | name: "Event File" 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Upload 14 | uses: actions/upload-artifact@v6 15 | with: 16 | name: event-file 17 | path: ${{ github.event_path }} 18 | publish: 19 | strategy: 20 | matrix: 21 | os: 22 | - ubuntu-latest 23 | java: 24 | - '21' 25 | runs-on: ${{ matrix.os }} 26 | timeout-minutes: 20 27 | steps: 28 | - uses: actions/checkout@v6 29 | with: 30 | fetch-depth: 1 31 | - name: Set up JDK 32 | uses: actions/setup-java@v5.1.0 33 | with: 34 | distribution: 'zulu' 35 | java-version: ${{ matrix.java }} 36 | - name: Setup Gradle 37 | uses: gradle/actions/setup-gradle@v5 38 | with: 39 | dependency-graph: generate-and-submit 40 | dependency-graph-continue-on-failure: false 41 | # - name: Install Docker on macOS 42 | # uses: douglascamata/setup-docker-macos-action@v1-alpha 43 | # - name: Login to Docker Hub 44 | # uses: docker/login-action@v3 45 | # with: 46 | # username: ${{ secrets.DOCKERHUB_USERNAME }} 47 | # password: ${{ secrets.DOCKERHUB_TOKEN }} 48 | - name: build publish 49 | run: ./gradlew clean build publishToGitHubPackages --info --stacktrace 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_SIGNING_KEY }} 53 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_SIGNING_PASSWORD }} 54 | - name: Upload Test Results 55 | # see publish-test-results.yml for workflow that publishes test results without security issues for forks 56 | # https://github.com/marketplace/actions/publish-test-results#support-fork-repositories-and-dependabot-branches 57 | if: always() 58 | uses: actions/upload-artifact@v6 59 | with: 60 | name: Test Results (Java ${{ matrix.java }} on ${{ matrix.os }}) 61 | path: '**/build/test-results/test/TEST-*.xml' 62 | ... 63 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerExecTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.ExecConfig 6 | import de.gesellix.docker.remote.api.ExecStartConfig 7 | import de.gesellix.docker.remote.api.IdResponse 8 | import org.gradle.testfixtures.ProjectBuilder 9 | import spock.lang.Specification 10 | 11 | class DockerExecTaskSpec extends Specification { 12 | 13 | def project 14 | def task 15 | def dockerClient = Mock(DockerClient) 16 | 17 | def setup() { 18 | project = ProjectBuilder.builder().build() 19 | task = project.tasks.register('dockerExec', DockerExecTask).get() 20 | task.dockerClient = dockerClient 21 | } 22 | 23 | def "delegates plain exec command via 'sh -c' to dockerClient and saves result"() { 24 | given: 25 | def containerId = 'foo' 26 | task.containerId = containerId 27 | 28 | def commandLine = 'echo "foo" > /bar.txt && cat /bar.txt' 29 | task.cmd = commandLine 30 | 31 | def execConfig = new ExecConfig().tap { 32 | attachStdin = false 33 | attachStdout = true 34 | attachStderr = true 35 | tty = false 36 | cmd = ["sh", "-c", commandLine] 37 | } 38 | 39 | when: 40 | task.exec() 41 | 42 | then: 43 | 1 * dockerClient.createExec(containerId, execConfig) >> new EngineResponseContent(new IdResponse("exec-id")) 44 | 1 * dockerClient.startExec("exec-id", new ExecStartConfig(false, false, null), null, null) 45 | } 46 | 47 | def "delegates exec commands to dockerClient and saves result"() { 48 | given: 49 | def containerId = 'foo' 50 | task.containerId = containerId 51 | 52 | def commands = ['sh', '-c', 'echo "foo" > /baz.txt && cat /baz.txt'] 53 | task.cmds = commands 54 | 55 | def execConfig = new ExecConfig().tap { 56 | attachStdin = false 57 | attachStdout = true 58 | attachStderr = true 59 | tty = false 60 | cmd = commands 61 | } 62 | 63 | when: 64 | task.exec() 65 | 66 | then: 67 | 1 * dockerClient.createExec(containerId, execConfig) >> new EngineResponseContent(new IdResponse("exec-id")) 68 | 1 * dockerClient.startExec("exec-id", new ExecStartConfig(false, false, null), null, null) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/DockerPluginExtension.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker; 2 | 3 | import de.gesellix.docker.authentication.AuthConfig; 4 | import org.gradle.api.Project; 5 | 6 | import java.net.Proxy; 7 | 8 | /** 9 | * Provides project extension class for configuring default values for all Docker tasks. 10 | */ 11 | public class DockerPluginExtension { 12 | 13 | private final Project project; 14 | 15 | /** 16 | * Docker host url. Will default to the system property {@code docker.host} if that is provided, 17 | * otherwise it will try to use the environment variable {@code DOCKER_HOST}. Failing that it will be 18 | * null. 19 | */ 20 | private String dockerHost; 21 | 22 | private String certPath; 23 | 24 | private Proxy proxy; 25 | private AuthConfig authConfig; 26 | 27 | public DockerPluginExtension(Project project) { 28 | this.project = project; 29 | dockerHost = System.getProperty("docker.host", System.getenv("DOCKER_HOST")); 30 | certPath = System.getProperty("docker.cert.path", System.getenv("DOCKER_CERT_PATH")); 31 | } 32 | 33 | /** 34 | * Allows for the setting of the path where certificates can be found. 35 | * 36 | * @param path Any path object that can be resolved via {@code project.file ( )} 37 | */ 38 | public void setCertPath(String path) { 39 | this.certPath = path; 40 | } 41 | 42 | /** 43 | * Returns the certificate path as an absolute path to the certificate. 44 | * If {@code certPath} was not configured it will attempt to check for system property 45 | * {@code docker.cert.path} first and then for the {@code DOCKER_CERT_PATH} environment variable. 46 | * 47 | * @return Absolute path as a string or null. 48 | */ 49 | public String getCertPath() { 50 | if (certPath != null && !certPath.isEmpty()) { 51 | return project.file(certPath).getAbsolutePath(); 52 | } 53 | return null; 54 | } 55 | 56 | public String getDockerHost() { 57 | return dockerHost; 58 | } 59 | 60 | public void setDockerHost(String dockerHost) { 61 | this.dockerHost = dockerHost; 62 | } 63 | 64 | public Proxy getProxy() { 65 | return proxy; 66 | } 67 | 68 | public void setProxy(Proxy proxy) { 69 | this.proxy = proxy; 70 | } 71 | 72 | public AuthConfig getAuthConfig() { 73 | return authConfig; 74 | } 75 | 76 | public void setAuthConfig(AuthConfig authConfig) { 77 | this.authConfig = authConfig; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/github/actions/workflow/status/gesellix/gradle-docker-plugin/cd.yml?branch=main&style=for-the-badge)](https://github.com/gesellix/gradle-docker-plugin/actions) 2 | [![Maven Central](https://img.shields.io/maven-central/v/de.gesellix/gradle-docker-plugin.svg?style=for-the-badge&maxAge=86400)](https://search.maven.org/search?q=g:de.gesellix%20AND%20a:gradle-docker-plugin) 3 | [![API Coverage](https://img.shields.io/static/v1?label=Gradle%20Plugin%20Portal&message=latest%20version&color=blue&style=for-the-badge)](https://plugins.gradle.org/plugin/de.gesellix.docker) 4 | 5 | # Gradle-Docker-Plugin 6 | 7 | [![Gradle logo](https://github.com/gesellix/gradle-docker-plugin/raw/main/img/gradle-logo.png)](https://gradle.org/) 8 | [![Docker logo](https://github.com/gesellix/gradle-docker-plugin/raw/main/img/docker-logo.png)](https://www.docker.com/) 9 | 10 | Yet another Gradle plugin making it easy for your build scripts to talk to a Docker daemon. 11 | Each task delegates to the [Docker-Client](https://github.com/gesellix/docker-client), which connects 12 | to the Docker remote API via HTTP. 13 | 14 | For basic usage please have a look at the tests or the [example project](https://github.com/gesellix/gradle-docker-plugin-example). 15 | 16 | ## Publishing/Release Workflow 17 | 18 | See RELEASE.md 19 | 20 | ## License 21 | 22 | MIT License 23 | 24 | Copyright 2015-2021 [Tobias Gesellchen](https://www.gesellix.net/) ([@gesellix](https://twitter.com/gesellix)) 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining a copy 27 | of this software and associated documentation files (the "Software"), to deal 28 | in the Software without restriction, including without limitation the rights 29 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 30 | copies of the Software, and to permit persons to whom the Software is 31 | furnished to do so, subject to the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be included in all 34 | copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 37 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 38 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 39 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 40 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 41 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 42 | SOFTWARE. 43 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | groovy3 = "3.0.25" 3 | groovy3Versionrange = "[3,4)" 4 | groovy4 = "4.0.28" 5 | groovy4Versionrange = "[4,)" 6 | junitJupiter = "5.11.4" 7 | junitPlatform = "1.11.4" 8 | kotlin = "2.1.0" 9 | kotlinVersionrange = "[1.6,3)" 10 | logback = "1.3.16" 11 | logbackVersionrange = "[1.2,2)" 12 | moshi = "1.15.2" 13 | moshiVersionrange = "[1.12.0,2)" 14 | okhttp = "5.3.2" 15 | okhttpVersionrange = "[4,5)" 16 | okio = "3.16.2" 17 | okioVersionrange = "[3,4)" 18 | slf4j = "2.0.17" 19 | slf4jVersionrange = "[1.7,3)" 20 | 21 | [libraries] 22 | groovy3 = { module = "org.codehaus.groovy:groovy", version.ref = "groovy3" } 23 | groovy3json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy3" } 24 | groovy4 = { module = "org.apache.groovy:groovy", version.ref = "groovy4" } 25 | groovy4json = { module = "org.apache.groovy:groovy-json", version.ref = "groovy4" } 26 | kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } 27 | kotlinCommon = { module = "org.jetbrains.kotlin:kotlin-stdlib-common", version.ref = "kotlin" } 28 | kotlinJdk7 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" } 29 | kotlinJdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } 30 | kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } 31 | kotlinScriptingJvm = { module = "org.jetbrains.kotlin:kotlin-scripting-jvm", version.ref = "kotlin" } 32 | kotlinStdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } 33 | kotlinTest = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 34 | logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } 35 | moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } 36 | moshiKotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } 37 | okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } 38 | okhttpMockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" } 39 | okio = { module = "com.squareup.okio:okio", version.ref = "okio" } 40 | okioJvm = { module = "com.squareup.okio:okio-jvm", version.ref = "okio" } 41 | slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } 42 | 43 | [bundles] 44 | groovy3 = ["groovy3", "groovy3json"] 45 | groovy4 = ["groovy4", "groovy4json"] 46 | kotlin = ["kotlin", "kotlinCommon", "kotlinJdk7", "kotlinJdk8", "kotlinReflect", "kotlinScriptingJvm", "kotlinStdlib", "kotlinTest"] 47 | moshi = ["moshi", "moshiKotlin"] 48 | okhttp = ["okhttp", "okhttpMockwebserver"] 49 | okio = ["okio", "okioJvm"] 50 | 51 | [plugins] 52 | kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 53 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/GenericDockerTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.authentication.AuthConfig 4 | import de.gesellix.docker.client.DockerClient 5 | import de.gesellix.docker.client.DockerClientImpl 6 | import de.gesellix.docker.engine.DockerEnv 7 | import org.gradle.testfixtures.ProjectBuilder 8 | import spock.lang.Specification 9 | 10 | class GenericDockerTaskSpec extends Specification { 11 | 12 | def task 13 | 14 | def setup() { 15 | def project = ProjectBuilder.builder().build() 16 | task = project.tasks.register('dockerTask', TestTask).get() 17 | } 18 | 19 | def "creates dockerClient only once"() { 20 | def clientMock = Mock(DockerClient) 21 | given: 22 | task.dockerClient = clientMock 23 | 24 | when: 25 | def dockerClient = task.dockerClient 26 | 27 | then: 28 | dockerClient == clientMock 29 | } 30 | 31 | def "delegates to dockerClient with default dockerHost"() { 32 | when: 33 | DockerClientImpl dockerClient = task.dockerClient 34 | 35 | then: 36 | dockerClient.env.dockerHost in [ 37 | // well-known default 38 | DockerEnv.getDefaultDockerHost(), 39 | // context-aware default 40 | new DockerEnv().dockerHost, 41 | // 'DockerEnv.getDefaultDockerHost()' should respect the docker context 42 | "npipe:////./pipe/dockerDesktopLinuxEngine", 43 | // for GitHub, where integration tests use a Colima based Docker engine 44 | "unix:///Users/runner/.colima/default/docker.sock", 45 | // pattern for context-aware default on unix 46 | // "unix://${System.getProperty("user.home")}/.docker/run/docker.sock".toString() 47 | ] 48 | } 49 | 50 | def "delegates to dockerClient with configured dockerHost"() { 51 | when: 52 | task.dockerHost.set("http://example.org:4243") 53 | def dockerClient = task.dockerClient 54 | 55 | then: 56 | dockerClient.env.dockerHost == "http://example.org:4243" 57 | } 58 | 59 | def "delegates to dockerClient with configured certPath"() { 60 | when: 61 | task.certPath.set("/path/to/certs") 62 | def dockerClient = task.dockerClient 63 | 64 | then: 65 | dockerClient.env.certPath.endsWith "/path/to/certs".replaceAll('/', "\\${File.separator}") 66 | } 67 | 68 | def "getAuthConfig with plain AuthConfig"() { 69 | when: 70 | task.authConfig = new AuthConfig(identitytoken: "foo") 71 | 72 | then: 73 | task.getEncodedAuthConfig() == "eyJpZGVudGl0eXRva2VuIjoiZm9vIn0=" 74 | } 75 | 76 | def "getAuthConfig without AuthConfig"() { 77 | when: 78 | task.authConfig = null 79 | 80 | then: 81 | task.getEncodedAuthConfig() == '' 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/GenericDockerTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.authentication.AuthConfig; 4 | import de.gesellix.docker.client.DockerClient; 5 | import de.gesellix.docker.client.DockerClientImpl; 6 | import de.gesellix.docker.engine.DockerEnv; 7 | import org.gradle.api.DefaultTask; 8 | import org.gradle.api.model.ObjectFactory; 9 | import org.gradle.api.provider.Property; 10 | import org.gradle.api.tasks.Input; 11 | import org.gradle.api.tasks.Internal; 12 | import org.gradle.api.tasks.Optional; 13 | 14 | import javax.inject.Inject; 15 | import java.net.Proxy; 16 | 17 | import static java.net.Proxy.NO_PROXY; 18 | 19 | public class GenericDockerTask extends DefaultTask { 20 | 21 | Property dockerHost; 22 | 23 | @Input 24 | @Optional 25 | public Property getDockerHost() { 26 | return dockerHost; 27 | } 28 | 29 | Property certPath; 30 | 31 | @Input 32 | @Optional 33 | public Property getCertPath() { 34 | return certPath; 35 | } 36 | 37 | Property proxy; 38 | 39 | @Input 40 | @Optional 41 | public Property getProxy() { 42 | return proxy; 43 | } 44 | 45 | Property authConfig; 46 | 47 | @Input 48 | @Optional 49 | public Property getAuthConfig() { 50 | return authConfig; 51 | } 52 | 53 | @Internal 54 | public String getEncodedAuthConfig() { 55 | return authConfig.map((AuthConfig a) -> getDockerClient().encodeAuthConfig(a)).getOrElse(""); 56 | } 57 | 58 | private DockerClient dockerClient; 59 | 60 | void setDockerClient(DockerClient dockerClient) { 61 | this.dockerClient = dockerClient; 62 | } 63 | 64 | @Inject 65 | public GenericDockerTask(ObjectFactory objectFactory) { 66 | dockerHost = objectFactory.property(String.class); 67 | certPath = objectFactory.property(String.class); 68 | proxy = objectFactory.property(Proxy.class); 69 | authConfig = objectFactory.property(AuthConfig.class); 70 | setGroup("Docker"); 71 | } 72 | 73 | @Internal 74 | public DockerClient getDockerClient() { 75 | if (dockerClient == null) { 76 | if (dockerHost.isPresent() || certPath.isPresent()) { 77 | DockerEnv dockerEnv = new DockerEnv(); 78 | if (dockerHost.isPresent()) { 79 | dockerEnv.setDockerHost(dockerHost.get()); 80 | } 81 | 82 | if (certPath.isPresent()) { 83 | dockerEnv.setCertPath(getProject().file(certPath.get()).getAbsolutePath()); 84 | } 85 | 86 | dockerClient = new DockerClientImpl(dockerEnv, proxy.getOrElse(NO_PROXY)); 87 | } 88 | else { 89 | dockerClient = new DockerClientImpl(); 90 | } 91 | } 92 | 93 | return dockerClient; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | on: 4 | release: 5 | types: 6 | - released 7 | # - published 8 | 9 | jobs: 10 | event-file: 11 | # https://github.com/marketplace/actions/publish-test-results#support-fork-repositories-and-dependabot-branches 12 | name: "Event File" 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Upload 16 | uses: actions/upload-artifact@v6 17 | with: 18 | name: event-file 19 | path: ${{ github.event_path }} 20 | release: 21 | strategy: 22 | matrix: 23 | os: 24 | - ubuntu-latest 25 | java: 26 | - '21' 27 | runs-on: ${{ matrix.os }} 28 | timeout-minutes: 20 29 | steps: 30 | - uses: actions/checkout@v6 31 | with: 32 | fetch-depth: 1 33 | - name: Set up JDK 34 | uses: actions/setup-java@v5.1.0 35 | with: 36 | distribution: 'zulu' 37 | java-version: ${{ matrix.java }} 38 | - name: Setup Gradle 39 | uses: gradle/actions/setup-gradle@v5 40 | # - name: Install Docker on macOS 41 | # uses: douglascamata/setup-docker-macos-action@v1-alpha 42 | # - name: Login to Docker Hub 43 | # uses: docker/login-action@v3 44 | # with: 45 | # username: ${{ secrets.DOCKERHUB_USERNAME }} 46 | # password: ${{ secrets.DOCKERHUB_TOKEN }} 47 | - name: Set artifact version 48 | run: | 49 | echo "RELEASE_VERSION=$(echo '${{ github.event.release.tag_name }}' | sed -e s/^v//)" >> $GITHUB_ENV 50 | - name: build publish 51 | run: ./gradlew -Pgradle.publish.key="${{ secrets.GRADLE_PUBLISH_KEY }}" -Pgradle.publish.secret="${{ secrets.GRADLE_PUBLISH_SECRET }}" clean build publish publishPlugins closeAndReleaseStagingRepositories --info --stacktrace -Pversion="${{ env.RELEASE_VERSION }}" 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_SIGNING_KEY }} 55 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_SIGNING_PASSWORD }} 56 | SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} 57 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 58 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 59 | - name: Upload Test Results 60 | # see publish-test-results.yml for workflow that publishes test results without security issues for forks 61 | # https://github.com/marketplace/actions/publish-test-results#support-fork-repositories-and-dependabot-branches 62 | if: always() 63 | uses: actions/upload-artifact@v6 64 | with: 65 | name: Test Results (Java ${{ matrix.java }} on ${{ matrix.os }}) 66 | path: '**/build/test-results/test/TEST-*.xml' 67 | ... 68 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerNetworkCreateTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import java.util.Objects; 4 | 5 | import de.gesellix.docker.client.EngineResponseContent; 6 | import de.gesellix.docker.remote.api.NetworkCreateRequest; 7 | import de.gesellix.docker.remote.api.NetworkCreateResponse; 8 | 9 | import org.gradle.api.model.ObjectFactory; 10 | import org.gradle.api.provider.Property; 11 | import org.gradle.api.tasks.Input; 12 | import org.gradle.api.tasks.Internal; 13 | import org.gradle.api.tasks.Optional; 14 | import org.gradle.api.tasks.TaskAction; 15 | 16 | import javax.inject.Inject; 17 | 18 | public class DockerNetworkCreateTask extends GenericDockerTask { 19 | 20 | private final Property networkName; 21 | 22 | @Input 23 | @Optional 24 | public Property getNetworkName() { 25 | return networkName; 26 | } 27 | 28 | private final Property networkConfig; 29 | 30 | @Input 31 | @Optional 32 | public Property getNetworkConfig() { 33 | return networkConfig; 34 | } 35 | 36 | private EngineResponseContent response; 37 | 38 | @Internal 39 | public EngineResponseContent getResponse() { 40 | return response; 41 | } 42 | 43 | @Inject 44 | public DockerNetworkCreateTask(ObjectFactory objectFactory) { 45 | super(objectFactory); 46 | setDescription("Create a new network"); 47 | 48 | networkName = objectFactory.property(String.class); 49 | networkConfig = objectFactory.property(NetworkCreateRequest.class); 50 | } 51 | 52 | @TaskAction 53 | public void createNetwork() { 54 | getLogger().info("docker network create"); 55 | 56 | if (networkName.isPresent() && !networkConfig.isPresent()) { 57 | response = getDockerClient().createNetwork(networkName.get()); 58 | return; 59 | } 60 | 61 | if (networkName.isPresent() && networkConfig.isPresent()) { 62 | NetworkCreateRequest networkCreateRequest = networkConfig.get(); 63 | if (networkCreateRequest.getName() == null) { 64 | networkCreateRequest.setName(networkName.get()); 65 | } else { 66 | if (!Objects.equals(networkCreateRequest.getName(), networkName.get())) { 67 | throw new IllegalArgumentException("NetworkName and NetworkConfig are mutually exclusive. Please specify only one of them or keep the network name consistent."); 68 | } 69 | } 70 | response = getDockerClient().createNetwork(networkCreateRequest); 71 | } 72 | 73 | if (!networkName.isPresent() && networkConfig.isPresent()) { 74 | response = getDockerClient().createNetwork(networkConfig.get()); 75 | } 76 | 77 | if (!networkName.isPresent() && !networkConfig.isPresent()) { 78 | throw new IllegalArgumentException("Either NetworkName or NetworkConfig must be specified."); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerCommitTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.remote.api.IdResponse; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.provider.Property; 7 | import org.gradle.api.tasks.Input; 8 | import org.gradle.api.tasks.Optional; 9 | import org.gradle.api.tasks.TaskAction; 10 | 11 | import javax.inject.Inject; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class DockerCommitTask extends GenericDockerTask { 16 | 17 | private final Property containerId; 18 | 19 | @Input 20 | public Property getContainerId() { 21 | return containerId; 22 | } 23 | 24 | private final Property repo; 25 | 26 | @Input 27 | public Property getRepo() { 28 | return repo; 29 | } 30 | 31 | private final Property tag; 32 | 33 | @Optional 34 | @Input 35 | public Property getTag() { 36 | return tag; 37 | } 38 | 39 | private final Property author; 40 | 41 | @Optional 42 | @Input 43 | public Property getAuthor() { 44 | return author; 45 | } 46 | 47 | private final Property comment; 48 | 49 | @Optional 50 | @Input 51 | public Property getComment() { 52 | return comment; 53 | } 54 | 55 | private final Property pauseContainer; 56 | 57 | @Optional 58 | @Input 59 | public Property getPauseContainer() { 60 | return pauseContainer; 61 | } 62 | 63 | private final Property changes; 64 | 65 | @Optional 66 | @Input 67 | public Property getChanges() { 68 | return changes; 69 | } 70 | 71 | @Inject 72 | public DockerCommitTask(ObjectFactory objectFactory) { 73 | super(objectFactory); 74 | setDescription("Commit changes to a container"); 75 | 76 | containerId = objectFactory.property(String.class); 77 | repo = objectFactory.property(String.class); 78 | tag = objectFactory.property(String.class); 79 | author = objectFactory.property(String.class); 80 | comment = objectFactory.property(String.class); 81 | changes = objectFactory.property(String.class); 82 | pauseContainer = objectFactory.property(Boolean.class); 83 | pauseContainer.convention(true); 84 | } 85 | 86 | @TaskAction 87 | public EngineResponseContent commit() { 88 | getLogger().info("docker commit"); 89 | Map map = new HashMap<>(6); 90 | map.put("repo", getRepo().get()); 91 | map.put("tag", getTag().getOrNull()); 92 | map.put("comment", getComment().getOrNull()); 93 | map.put("author", getAuthor().getOrNull()); 94 | map.put("changes", getChanges().getOrNull()); 95 | map.put("pause", getPauseContainer().getOrElse(true)); 96 | return getDockerClient().commit(getContainerId().get(), map); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerLogsTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.remote.api.core.Cancellable; 4 | import de.gesellix.docker.remote.api.core.Frame; 5 | import de.gesellix.docker.remote.api.core.StreamCallback; 6 | import org.gradle.api.model.ObjectFactory; 7 | import org.gradle.api.provider.MapProperty; 8 | import org.gradle.api.provider.Property; 9 | import org.gradle.api.tasks.Input; 10 | import org.gradle.api.tasks.Internal; 11 | import org.gradle.api.tasks.Optional; 12 | import org.gradle.api.tasks.TaskAction; 13 | 14 | import javax.inject.Inject; 15 | import java.time.Duration; 16 | import java.time.temporal.ChronoUnit; 17 | import java.util.HashMap; 18 | import java.util.concurrent.CountDownLatch; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | public class DockerLogsTask extends GenericDockerTask { 22 | 23 | private final Property containerId; 24 | 25 | @Input 26 | public Property getContainerId() { 27 | return containerId; 28 | } 29 | 30 | public Duration logsTimeout = Duration.of(10, ChronoUnit.MINUTES); 31 | 32 | @Internal 33 | public Duration getLogsTimeout() { 34 | return logsTimeout; 35 | } 36 | 37 | private final MapProperty logOptions; 38 | 39 | @Input 40 | @Optional 41 | public MapProperty getLogOptions() { 42 | return logOptions; 43 | } 44 | 45 | @Inject 46 | public DockerLogsTask(ObjectFactory objectFactory) { 47 | super(objectFactory); 48 | 49 | setDescription("Fetch the logs of a container"); 50 | 51 | containerId = objectFactory.property(String.class); 52 | logOptions = objectFactory.mapProperty(String.class, Object.class); 53 | logOptions.convention(new HashMap<>()); 54 | logOptions.put("follow", false); 55 | } 56 | 57 | @TaskAction 58 | public void logs() { 59 | getLogger().info("docker logs {}", containerId.get()); 60 | 61 | CountDownLatch logsFinished = new CountDownLatch(1); 62 | StreamCallback callback = new StreamCallback() { 63 | Cancellable cancellable; 64 | 65 | @Override 66 | public void onStarting(Cancellable cancellable) { 67 | this.cancellable = cancellable; 68 | } 69 | 70 | @Override 71 | public void onNext(Frame frame) { 72 | if (frame != null) { 73 | getLogger().info(frame.toString()); 74 | } 75 | } 76 | 77 | @Override 78 | public void onFailed(Exception e) { 79 | getLogger().error("failed", e); 80 | logsFinished.countDown(); 81 | cancellable.cancel(); 82 | } 83 | 84 | @Override 85 | public void onFinished() { 86 | getLogger().info("finished"); 87 | logsFinished.countDown(); 88 | } 89 | }; 90 | 91 | getDockerClient().logs( 92 | containerId.get(), 93 | logOptions.getOrNull(), 94 | callback, 95 | logsTimeout); 96 | try { 97 | getLogger().debug("Following the logs for " + logsTimeout + "..."); 98 | logsFinished.await(logsTimeout.toMillis(), TimeUnit.MILLISECONDS); 99 | } 100 | catch (InterruptedException ignored) { 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tobias@gesellix.de. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerPullTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.remote.api.CreateImageInfo; 4 | import de.gesellix.docker.remote.api.core.StreamCallback; 5 | import org.gradle.api.GradleException; 6 | import org.gradle.api.model.ObjectFactory; 7 | import org.gradle.api.provider.Property; 8 | import org.gradle.api.tasks.Input; 9 | import org.gradle.api.tasks.Internal; 10 | import org.gradle.api.tasks.Optional; 11 | import org.gradle.api.tasks.TaskAction; 12 | 13 | import javax.inject.Inject; 14 | import java.time.Duration; 15 | import java.time.temporal.ChronoUnit; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.concurrent.CountDownLatch; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | public class DockerPullTask extends GenericDockerTask { 22 | 23 | private final Property imageName; 24 | 25 | @Input 26 | public Property getImageName() { 27 | return imageName; 28 | } 29 | 30 | private final Property imageTag; 31 | 32 | @Input 33 | @Optional 34 | public Property getImageTag() { 35 | return imageTag; 36 | } 37 | 38 | private final Property registry; 39 | 40 | @Input 41 | @Optional 42 | public Property getRegistry() { 43 | return registry; 44 | } 45 | 46 | private String imageId; 47 | 48 | @Internal 49 | public String getImageId() { 50 | return imageId; 51 | } 52 | 53 | public Duration pullTimeout = Duration.of(10, ChronoUnit.MINUTES); 54 | 55 | @Internal 56 | public Duration getPullTimeout() { 57 | return pullTimeout; 58 | } 59 | 60 | @Inject 61 | public DockerPullTask(ObjectFactory objectFactory) { 62 | super(objectFactory); 63 | setDescription("Pull an image or a repository from a Docker registry server"); 64 | 65 | imageName = objectFactory.property(String.class); 66 | imageTag = objectFactory.property(String.class); 67 | registry = objectFactory.property(String.class); 68 | } 69 | 70 | @TaskAction 71 | public String pull() { 72 | getLogger().info("docker pull"); 73 | 74 | String imageName = getImageName() 75 | .map(i -> getRegistry().map(r -> r + "/" + i).getOrElse(i)).get(); 76 | 77 | List infos = new ArrayList<>(); 78 | CountDownLatch pullFinished = new CountDownLatch(1); 79 | 80 | getDockerClient().pull( 81 | new StreamCallback<>() { 82 | @Override 83 | public void onNext(CreateImageInfo element) { 84 | if (element != null) { 85 | getLogger().info(element.toString()); 86 | } 87 | infos.add(element); 88 | } 89 | 90 | @Override 91 | public void onFailed(Exception e) { 92 | pullFinished.countDown(); 93 | } 94 | 95 | @Override 96 | public void onFinished() { 97 | pullFinished.countDown(); 98 | } 99 | }, 100 | pullTimeout, 101 | imageName, 102 | getImageTag().getOrNull(), 103 | getEncodedAuthConfig() 104 | ); 105 | try { 106 | pullFinished.await(pullTimeout.toMillis(), TimeUnit.MILLISECONDS); 107 | } 108 | catch (InterruptedException e) { 109 | throw new GradleException("Pull didn't finish before " + pullTimeout, e); 110 | } 111 | imageId = imageName + getImageTag().map(t -> ":" + t).getOrElse(""); 112 | return imageId; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerDisposeContainerTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.remote.api.ContainerInspectResponse; 4 | import de.gesellix.docker.remote.api.core.ClientException; 5 | import org.gradle.api.model.ObjectFactory; 6 | import org.gradle.api.provider.Property; 7 | import org.gradle.api.tasks.Input; 8 | import org.gradle.api.tasks.Optional; 9 | import org.gradle.api.tasks.TaskAction; 10 | 11 | import javax.inject.Inject; 12 | import java.net.HttpURLConnection; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | public class DockerDisposeContainerTask extends GenericDockerTask { 17 | 18 | private final Property containerId; 19 | 20 | @Input 21 | public Property getContainerId() { 22 | return containerId; 23 | } 24 | 25 | private final Property rmiParentImage; 26 | 27 | @Input 28 | @Optional 29 | public Property getRmiParentImage() { 30 | return rmiParentImage; 31 | } 32 | 33 | private final Property rmiParentImageIgnoreError; 34 | 35 | @Input 36 | @Optional 37 | public Property getRmiParentImageIgnoreError() { 38 | return rmiParentImageIgnoreError; 39 | } 40 | 41 | private final Property removeVolumes; 42 | 43 | @Input 44 | @Optional 45 | public Property getRemoveVolumes() { 46 | return removeVolumes; 47 | } 48 | 49 | @Inject 50 | public DockerDisposeContainerTask(ObjectFactory objectFactory) { 51 | super(objectFactory); 52 | setDescription("Stops and removes a container and optionally its parent image"); 53 | 54 | containerId = objectFactory.property(String.class); 55 | rmiParentImage = objectFactory.property(Boolean.class); 56 | rmiParentImage.convention(false); 57 | rmiParentImageIgnoreError = objectFactory.property(Boolean.class); 58 | rmiParentImageIgnoreError.convention(false); 59 | removeVolumes = objectFactory.property(Boolean.class); 60 | removeVolumes.convention(false); 61 | } 62 | 63 | @TaskAction 64 | public void dispose() { 65 | getLogger().info("docker dispose"); 66 | 67 | String containerId = getContainerId().get(); 68 | ContainerInspectResponse containerDetails; 69 | try { 70 | containerDetails = getDockerClient().inspectContainer(containerId).getContent(); 71 | } catch (ClientException e) { 72 | if (e.getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { 73 | getLogger().info("couldn't dispose container because it doesn't exist"); 74 | return; 75 | } 76 | throw e; 77 | } 78 | 79 | getDockerClient().stop(containerId); 80 | getDockerClient().wait(containerId); 81 | Map query = new HashMap<>(1); 82 | query.put("v", getRemoveVolumes().getOrElse(false) ? 1 : 0); 83 | getDockerClient().rm(containerId, query); 84 | if (getRmiParentImage().getOrElse(false)) { 85 | try { 86 | getDockerClient().rmi(containerDetails.getImage()); 87 | } catch (Exception e) { 88 | if (!rmiParentImageIgnoreError.get()) { 89 | throw new RuntimeException(e); 90 | } else { 91 | if (getLogger().isInfoEnabled()) { 92 | getLogger().warn("docker image rm " + containerDetails.getImage() + " failed", e); 93 | } else { 94 | getLogger().warn("docker image rm " + containerDetails.getImage() + " failed"); 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerCreateTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.remote.api.ContainerCreateRequest 5 | import de.gesellix.docker.remote.api.HostConfig 6 | import de.gesellix.docker.remote.api.PortBinding 7 | import org.gradle.testfixtures.ProjectBuilder 8 | import spock.lang.Specification 9 | 10 | class DockerCreateTaskSpec extends Specification { 11 | 12 | def project 13 | def task 14 | def dockerClient = Mock(DockerClient) 15 | 16 | def setup() { 17 | project = ProjectBuilder.builder().build() 18 | task = project.tasks.register('dockerCreate', DockerCreateTask).get() 19 | task.dockerClient = dockerClient 20 | } 21 | 22 | def "delegates to dockerClient"() { 23 | given: 24 | task.imageName = "anImage" 25 | task.imageTag = "aTag" 26 | task.containerName = "aContainerName" 27 | task.containerConfiguration = new ContainerCreateRequest().tap { 28 | exposedPorts = [ 29 | "8889/tcp": [], 30 | "9300/tcp": [] 31 | ] 32 | hostConfig = new HostConfig().tap { 33 | portBindings = [ 34 | "8889/tcp": [new PortBinding("0.0.0.0", "8889")] 35 | ] 36 | } 37 | } 38 | def containerConfig = new ContainerCreateRequest().tap { 39 | image = "anImage:aTag" 40 | exposedPorts = [ 41 | "8889/tcp": [], 42 | "9300/tcp": [] 43 | ] 44 | hostConfig = new HostConfig().tap { 45 | portBindings = [ 46 | "8889/tcp": [new PortBinding("0.0.0.0", "8889")] 47 | ] 48 | } 49 | } 50 | 51 | when: 52 | task.create() 53 | 54 | then: 55 | 1 * dockerClient.createContainer(containerConfig, "aContainerName", "") 56 | } 57 | 58 | def "parses env-file to containerConfig.Env"() { 59 | URL envfile = getClass().getResource('/env-files/env-test.properties') 60 | 61 | given: 62 | task.imageName = "anImage" 63 | task.containerConfiguration = new ContainerCreateRequest().tap { 64 | hostConfig = new HostConfig().tap { publishAllPorts = true } 65 | } 66 | task.environmentFiles = [new File(envfile.toURI())] 67 | def containerConfig = new ContainerCreateRequest().tap { 68 | env = ['THE_WIND=CAUGHT_IT', 'FOO=BAR Baz'] 69 | image = "anImage" 70 | hostConfig = new HostConfig().tap { publishAllPorts = true } 71 | } 72 | 73 | when: 74 | task.create() 75 | 76 | then: 77 | 1 * dockerClient.createContainer(containerConfig, '', '') 78 | } 79 | 80 | def "maps env and port properties to actual containerConfig"() { 81 | given: 82 | task.imageName = "anImage" 83 | task.imageTag = "aTag" 84 | task.containerName = "aContainerName" 85 | task.env = ["foo=bar"] 86 | task.ports = ["8080:80", "8889:8889"] 87 | def containerConfig = new ContainerCreateRequest().tap { 88 | env = ["foo=bar"] 89 | image = "anImage:aTag" 90 | exposedPorts = [ 91 | "80/tcp" : [:], 92 | "8889/tcp": [:] 93 | ] 94 | hostConfig = new HostConfig().tap { 95 | portBindings = [ 96 | "80/tcp" : [new PortBinding("0.0.0.0", "8080")], 97 | "8889/tcp": [new PortBinding("0.0.0.0", "8889")] 98 | ] 99 | } 100 | } 101 | 102 | when: 103 | task.create() 104 | 105 | then: 106 | 1 * dockerClient.createContainer(containerConfig, "aContainerName", "") 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerPushTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.remote.api.PushImageInfo; 4 | import de.gesellix.docker.remote.api.core.Cancellable; 5 | import de.gesellix.docker.remote.api.core.StreamCallback; 6 | import org.gradle.api.GradleException; 7 | import org.gradle.api.model.ObjectFactory; 8 | import org.gradle.api.provider.Property; 9 | import org.gradle.api.tasks.Input; 10 | import org.gradle.api.tasks.Internal; 11 | import org.gradle.api.tasks.Optional; 12 | import org.gradle.api.tasks.TaskAction; 13 | 14 | import javax.inject.Inject; 15 | import java.time.Duration; 16 | import java.time.temporal.ChronoUnit; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.concurrent.CountDownLatch; 20 | import java.util.concurrent.TimeUnit; 21 | import java.util.stream.Collectors; 22 | 23 | public class DockerPushTask extends GenericDockerTask { 24 | 25 | private final Property repositoryName; 26 | 27 | @Input 28 | public Property getRepositoryName() { 29 | return repositoryName; 30 | } 31 | 32 | private final Property registry; 33 | 34 | @Input 35 | @Optional 36 | public Property getRegistry() { 37 | return registry; 38 | } 39 | 40 | public Duration pushTimeout = Duration.of(10, ChronoUnit.MINUTES); 41 | 42 | @Internal 43 | public Duration getPushTimeout() { 44 | return pushTimeout; 45 | } 46 | 47 | @Inject 48 | public DockerPushTask(ObjectFactory objectFactory) { 49 | super(objectFactory); 50 | setDescription("Push an image or a repository to a Docker registry server"); 51 | 52 | repositoryName = objectFactory.property(String.class); 53 | registry = objectFactory.property(String.class); 54 | } 55 | 56 | @TaskAction 57 | public void push() { 58 | getLogger().info("docker push"); 59 | List infos = new ArrayList<>(); 60 | CountDownLatch pushFinished = new CountDownLatch(1); 61 | StreamCallback callback = new StreamCallback<>() { 62 | 63 | private Cancellable cancellable; 64 | 65 | @Override 66 | public void onStarting(Cancellable cancellable) { 67 | this.cancellable = cancellable; 68 | } 69 | 70 | @Override 71 | public void onNext(PushImageInfo element) { 72 | getLogger().info(element != null ? element.toString() : null); 73 | infos.add(element); 74 | } 75 | 76 | @Override 77 | public void onFailed(Exception e) { 78 | getLogger().error("Push failed", e); 79 | pushFinished.countDown(); 80 | cancellable.cancel(); 81 | } 82 | 83 | @Override 84 | public void onFinished() { 85 | getLogger().info("Push finished"); 86 | pushFinished.countDown(); 87 | } 88 | }; 89 | getDockerClient().push(callback, pushTimeout, getRepositoryName().get(), getEncodedAuthConfig(), getRegistry().getOrNull()); 90 | try { 91 | getLogger().debug("Waiting " + pushTimeout + " for the build to finish..."); 92 | pushFinished.await(pushTimeout.toMillis(), TimeUnit.MILLISECONDS); 93 | 94 | List errors = infos.stream() 95 | .filter(i -> i.getError() != null) 96 | .collect(Collectors.toList()); 97 | if (!errors.isEmpty()) { 98 | throw new GradleException("Push failed: " + errors.stream().findFirst().get()); 99 | } 100 | } 101 | catch (InterruptedException e) { 102 | getLogger().error("Push didn't finish before timeout of " + pushFinished, e); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerRunTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.authentication.AuthConfig 4 | import de.gesellix.docker.client.DockerClient 5 | import de.gesellix.docker.remote.api.ContainerCreateRequest 6 | import de.gesellix.docker.remote.api.HostConfig 7 | import de.gesellix.docker.remote.api.PortBinding 8 | import org.gradle.testfixtures.ProjectBuilder 9 | import spock.lang.Specification 10 | 11 | class DockerRunTaskSpec extends Specification { 12 | 13 | def project 14 | def task 15 | def dockerClient = Mock(DockerClient) 16 | 17 | def setup() { 18 | project = ProjectBuilder.builder().build() 19 | task = project.tasks.register('dockerRun', DockerRunTask).get() 20 | task.dockerClient = dockerClient 21 | } 22 | 23 | def "delegates to dockerClient"() { 24 | given: 25 | task.imageName = "anImage" 26 | task.imageTag = "aTag" 27 | task.containerName = "aContainerName" 28 | task.containerConfiguration = new ContainerCreateRequest().tap { 29 | exposedPorts = [ 30 | "8889/tcp": [], 31 | "9300/tcp": []] 32 | hostConfig = new HostConfig().tap { 33 | portBindings = ["8889/tcp": [new PortBinding("0.0.0.0", "8889")]] 34 | } 35 | } 36 | def containerConfig = new ContainerCreateRequest().tap { 37 | exposedPorts = [ 38 | "8889/tcp": [], 39 | "9300/tcp": []] 40 | hostConfig = new HostConfig().tap { 41 | portBindings = ["8889/tcp": [new PortBinding("0.0.0.0", "8889")]] 42 | } 43 | image = "anImage:aTag" 44 | } 45 | 46 | when: 47 | task.run() 48 | 49 | then: 50 | 1 * dockerClient.run(containerConfig, "aContainerName", "") 51 | } 52 | 53 | def "parses env-file to containerConfig.Env"() { 54 | URL envfile = getClass().getResource('/env-files/env-test.properties') 55 | 56 | given: 57 | task.imageName = "anImage" 58 | task.containerConfiguration = new ContainerCreateRequest().tap { 59 | hostConfig = new HostConfig().tap { publishAllPorts = false } 60 | } 61 | task.environmentFiles = [new File(envfile.toURI())] 62 | def containerConfig = new ContainerCreateRequest().tap { 63 | hostConfig = new HostConfig().tap { publishAllPorts = false } 64 | env = ['THE_WIND=CAUGHT_IT', 'FOO=BAR Baz'] 65 | image = "anImage" 66 | } 67 | 68 | when: 69 | task.run() 70 | 71 | then: 72 | 1 * dockerClient.run(containerConfig, "", "") 73 | } 74 | 75 | def "maps env and port properties to actual containerConfig"() { 76 | given: 77 | task.imageName = "anImage" 78 | task.imageTag = "aTag" 79 | task.containerName = "aContainerName" 80 | task.env = ["foo=bar"] 81 | task.ports = ["8080:80", "8889:8889"] 82 | def containerConfig = new ContainerCreateRequest().tap { 83 | env = ["foo=bar"] 84 | image = "anImage:aTag" 85 | exposedPorts = ["80/tcp" : [:], 86 | "8889/tcp": [:]] 87 | hostConfig = new HostConfig().tap { 88 | portBindings = [ 89 | "80/tcp" : [new PortBinding("0.0.0.0", "8080")], 90 | "8889/tcp": [new PortBinding("0.0.0.0", "8889")] 91 | ] 92 | } 93 | } 94 | 95 | when: 96 | task.run() 97 | 98 | then: 99 | 1 * dockerClient.run(containerConfig, "aContainerName", '') 100 | } 101 | 102 | def "passes auth config to docker client"() { 103 | given: 104 | task.imageName = "anImage" 105 | task.imageTag = "theTag" 106 | task.containerName = "anotherContainerName" 107 | task.authConfig = new AuthConfig(identitytoken: "token") 108 | 109 | when: 110 | task.run() 111 | 112 | then: 113 | 1 * dockerClient.encodeAuthConfig(new AuthConfig(identitytoken: "token")) >> "encoded-auth" 114 | 1 * dockerClient.run(_, "anotherContainerName", "encoded-auth") 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerDisposeContainerTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClient 4 | import de.gesellix.docker.client.EngineResponseContent 5 | import de.gesellix.docker.remote.api.ContainerInspectResponse 6 | import de.gesellix.docker.remote.api.core.ClientException 7 | import org.gradle.testfixtures.ProjectBuilder 8 | import spock.lang.FailsWith 9 | import spock.lang.Specification 10 | 11 | class DockerDisposeContainerTaskSpec extends Specification { 12 | 13 | def project 14 | def task 15 | def dockerClient = Mock(DockerClient) 16 | 17 | def setup() { 18 | project = ProjectBuilder.builder().build() 19 | task = project.tasks.register('dockerDispose', DockerDisposeContainerTask).get() 20 | task.dockerClient = dockerClient 21 | } 22 | 23 | def "delegates to dockerClient (w/o removing the parent image)"() { 24 | given: 25 | task.containerId = "4712" 26 | dockerClient.inspectContainer("4712") >> new EngineResponseContent<>(new ContainerInspectResponse()) 27 | 28 | when: 29 | task.dispose() 30 | 31 | then: 32 | 1 * dockerClient.stop("4712") 33 | then: 34 | 1 * dockerClient.wait("4712") 35 | then: 36 | 1 * dockerClient.rm("4712", [v: 0]) 37 | and: 38 | 0 * dockerClient.rmi(_) 39 | } 40 | 41 | def "delegates to dockerClient (w/ removing the parent image)"() { 42 | given: 43 | task.containerId = "4712" 44 | task.rmiParentImage = true 45 | dockerClient.inspectContainer("4712") >> new EngineResponseContent<>(new ContainerInspectResponse().tap { image = "an-image-id" }) 46 | 47 | when: 48 | task.dispose() 49 | 50 | then: 51 | 1 * dockerClient.stop("4712") 52 | then: 53 | 1 * dockerClient.wait("4712") 54 | then: 55 | 1 * dockerClient.rm("4712", [v: 0]) 56 | then: 57 | 1 * dockerClient.rmi("an-image-id") 58 | } 59 | 60 | @FailsWith(RuntimeException) 61 | def "fails when removing the parent image has errors"() { 62 | given: 63 | task.containerId = "4712" 64 | task.rmiParentImage = true 65 | // default: false 66 | // task.rmiParentImageIgnoreError = false 67 | dockerClient.inspectContainer("4712") >> new EngineResponseContent<>(new ContainerInspectResponse().tap { image = "an-image-id" }) 68 | 69 | when: 70 | task.dispose() 71 | 72 | then: 73 | 1 * dockerClient.stop("4712") 74 | then: 75 | 1 * dockerClient.wait("4712") 76 | then: 77 | 1 * dockerClient.rm("4712", [v: 0]) 78 | then: 79 | 1 * dockerClient.rmi("an-image-id") >> { throw new RuntimeException("expected error") } 80 | } 81 | 82 | def "can ignore errors when removing the parent image"() { 83 | given: 84 | task.containerId = "4712" 85 | task.rmiParentImage = true 86 | task.rmiParentImageIgnoreError = true 87 | dockerClient.inspectContainer("4712") >> new EngineResponseContent<>(new ContainerInspectResponse().tap { image = "an-image-id" }) 88 | 89 | when: 90 | task.dispose() 91 | 92 | then: 93 | 1 * dockerClient.stop("4712") 94 | then: 95 | 1 * dockerClient.wait("4712") 96 | then: 97 | 1 * dockerClient.rm("4712", [v: 0]) 98 | then: 99 | 1 * dockerClient.rmi("an-image-id") >> { throw new RuntimeException("expected error") } 100 | notThrown(Exception) 101 | } 102 | 103 | def "catches ClientException when container is not present"() { 104 | given: 105 | task.containerId = "4711" 106 | 107 | when: 108 | task.dispose() 109 | 110 | then: 111 | 1 * dockerClient.inspectContainer("4711") >> { 112 | throw new ClientException("foo", 404, null) 113 | } 114 | then: 115 | 0 * dockerClient._ 116 | } 117 | 118 | def "allows to removeVolumes"() { 119 | given: 120 | task.containerId = "4712" 121 | task.removeVolumes = true 122 | dockerClient.inspectContainer("4712") >> new EngineResponseContent<>(new ContainerInspectResponse()) 123 | 124 | when: 125 | task.dispose() 126 | 127 | then: 128 | 1 * dockerClient.rm("4712", [v: 1]) 129 | } 130 | 131 | def "does not remove Volumes by default"() { 132 | given: 133 | task.containerId = "4712" 134 | dockerClient.inspectContainer("4712") >> new EngineResponseContent<>(new ContainerInspectResponse()) 135 | 136 | when: 137 | task.dispose() 138 | 139 | then: 140 | task.removeVolumes.get() == false 141 | 1 * dockerClient.rm("4712", [v: 0]) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerBuildTaskFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.client.DockerClientImpl 4 | import de.gesellix.docker.client.LocalDocker 5 | import de.gesellix.gradle.docker.testutil.TestImage 6 | import org.gradle.testkit.runner.GradleRunner 7 | import org.gradle.testkit.runner.TaskOutcome 8 | import spock.lang.Requires 9 | import spock.lang.Specification 10 | import spock.lang.TempDir 11 | 12 | @Requires({ LocalDocker.available() }) 13 | class DockerBuildTaskFunctionalTest extends Specification { 14 | 15 | TestImage testImage 16 | 17 | @TempDir 18 | File testProjectDir 19 | 20 | File buildFile 21 | 22 | // Also requires './gradlew :plugin:pluginUnderTestMetadata' to be run before performing the tests. 23 | def setup() { 24 | testImage = new TestImage(new DockerClientImpl()) 25 | buildFile = new File(testProjectDir, 'build.gradle') 26 | buildFile << """ 27 | plugins { 28 | id 'de.gesellix.docker' 29 | } 30 | """ 31 | } 32 | 33 | def "can perform a build configured via config closure"() { 34 | given: 35 | new DockerClientImpl().tag(testImage.imageWithTag, "test:build-base") 36 | URL dockerfile = getClass().getResource('/docker/Dockerfile') 37 | String baseDir = new File(dockerfile.toURI()).parentFile.absolutePath.replaceAll("\\${File.separator}", "/") 38 | String imageName = "gesellix/test-build:${UUID.randomUUID()}" 39 | 40 | buildFile << """ 41 | task dockerBuild(type: de.gesellix.gradle.docker.tasks.DockerBuildTask) { 42 | buildContextDirectory.set(new File('$baseDir')) 43 | imageName = '$imageName' 44 | doLast { 45 | logger.lifecycle("Resulting image id: \${imageId}") 46 | } 47 | } 48 | """ 49 | 50 | when: 51 | def result = GradleRunner.create() 52 | .withProjectDir(testProjectDir) 53 | .withArguments('dockerBuild', '--info', '--debug', '--stacktrace') 54 | .withPluginClasspath() 55 | .build() 56 | 57 | then: 58 | result.output.contains("Resulting image id: sha256:") 59 | result.task(":dockerBuild").outcome == TaskOutcome.SUCCESS 60 | 61 | cleanup: 62 | new DockerClientImpl().rmi(imageName) 63 | new DockerClientImpl().rmi("test:build-base") 64 | } 65 | 66 | def "can perform a build configured via task property"() { 67 | given: 68 | new DockerClientImpl().tag(testImage.imageWithTag, "test:build-base") 69 | URL dockerfile = getClass().getResource('/docker/Dockerfile') 70 | String baseDir = new File(dockerfile.toURI()).parentFile.absolutePath.replaceAll("\\${File.separator}", "/") 71 | String imageName = "gesellix/test-build:${UUID.randomUUID()}" 72 | 73 | buildFile << """ 74 | task dockerBuild(type: de.gesellix.gradle.docker.tasks.DockerBuildTask) { 75 | imageName = '$imageName' 76 | doFirst { 77 | logger.lifecycle("buildContextDirectory: \${buildContextDirectory}") 78 | } 79 | doLast { 80 | logger.lifecycle("Resulting image id: \${imageId}") 81 | } 82 | } 83 | dockerBuild.buildContextDirectory.set(new File('$baseDir')) 84 | """ 85 | 86 | when: 87 | def result = GradleRunner.create() 88 | .withProjectDir(testProjectDir) 89 | .withArguments('dockerBuild', '--info', '--debug', '--stacktrace') 90 | .withPluginClasspath() 91 | .withDebug(true) 92 | .build() 93 | 94 | then: 95 | result.output.contains("Resulting image id: sha256:") 96 | result.task(":dockerBuild").outcome == TaskOutcome.SUCCESS 97 | 98 | cleanup: 99 | new DockerClientImpl().rmi(imageName) 100 | new DockerClientImpl().rmi("test:build-base") 101 | } 102 | 103 | def "accepts only task configs with at least one of buildContext or buildContextDirectory"() { 104 | given: 105 | buildFile << """ 106 | task dockerBuild(type: de.gesellix.gradle.docker.tasks.DockerBuildTask) { 107 | buildContextDirectory = null 108 | buildContext = null 109 | } 110 | """ 111 | 112 | when: 113 | GradleRunner.create() 114 | .withProjectDir(testProjectDir) 115 | .withArguments('dockerBuild') 116 | .withPluginClasspath() 117 | .build() 118 | 119 | then: 120 | Exception exception = thrown() 121 | exception.message.contains("Execution failed for task ':dockerBuild'.") 122 | } 123 | 124 | def "accepts exactly one of buildContext or buildContextDirectory"() { 125 | URL dockerfile = getClass().getResource('/docker/Dockerfile') 126 | String baseDir = new File(dockerfile.toURI()).parentFile.absolutePath.replaceAll("\\${File.separator}", "/") 127 | 128 | given: 129 | buildFile << """ 130 | task dockerBuild(type: de.gesellix.gradle.docker.tasks.DockerBuildTask) { 131 | buildContextDirectory.set(new File('$baseDir')) 132 | buildContext = new FileInputStream(File.createTempFile("docker", "test")) 133 | } 134 | """ 135 | 136 | when: 137 | GradleRunner.create() 138 | .withProjectDir(testProjectDir) 139 | .withArguments('dockerBuild') 140 | .withPluginClasspath() 141 | .build() 142 | 143 | then: 144 | Exception exception = thrown() 145 | exception.message.contains("Execution failed for task ':dockerBuild'.") 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerRunTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EnvFileParser; 4 | import de.gesellix.docker.remote.api.ContainerCreateRequest; 5 | import de.gesellix.docker.remote.api.HostConfig; 6 | import de.gesellix.docker.remote.api.PortBinding; 7 | import org.gradle.api.model.ObjectFactory; 8 | import org.gradle.api.provider.ListProperty; 9 | import org.gradle.api.provider.Property; 10 | import org.gradle.api.tasks.Input; 11 | import org.gradle.api.tasks.Internal; 12 | import org.gradle.api.tasks.Optional; 13 | import org.gradle.api.tasks.TaskAction; 14 | 15 | import javax.inject.Inject; 16 | import java.io.File; 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | public class DockerRunTask extends GenericDockerTask { 24 | 25 | private final Property imageName; 26 | 27 | @Input 28 | public Property getImageName() { 29 | return imageName; 30 | } 31 | 32 | private final Property imageTag; 33 | 34 | @Input 35 | @Optional 36 | public Property getImageTag() { 37 | return imageTag; 38 | } 39 | 40 | private final Property containerName; 41 | 42 | @Input 43 | @Optional 44 | public Property getContainerName() { 45 | return containerName; 46 | } 47 | 48 | private final ListProperty ports; 49 | 50 | /** 51 | * Accepts a list of port mappings with the following pattern: `hostPort:containerPort`. 52 | * More sophisticated patterns are only supported via plain containerConfig. 53 | */ 54 | @Input 55 | @Optional 56 | public ListProperty getPorts() { 57 | return ports; 58 | } 59 | 60 | private final Property containerConfiguration; 61 | 62 | @Input 63 | @Optional 64 | public Property getContainerConfiguration() { 65 | return containerConfiguration; 66 | } 67 | 68 | private final ListProperty env; 69 | 70 | @Input 71 | @Optional 72 | public ListProperty getEnv() { 73 | return env; 74 | } 75 | 76 | private final ListProperty environmentFiles; 77 | 78 | @Input 79 | @Optional 80 | public ListProperty getEnvironmentFiles() { 81 | return environmentFiles; 82 | } 83 | 84 | private Object result; 85 | 86 | @Internal 87 | public Object getResult() { 88 | return result; 89 | } 90 | 91 | private final EnvFileParser envFileParser = new EnvFileParser(); 92 | 93 | @Inject 94 | public DockerRunTask(ObjectFactory objectFactory) { 95 | super(objectFactory); 96 | setDescription("Run a command in a new container"); 97 | 98 | imageName = objectFactory.property(String.class); 99 | imageTag = objectFactory.property(String.class); 100 | imageTag.convention(""); 101 | containerName = objectFactory.property(String.class); 102 | containerName.convention(""); 103 | ports = objectFactory.listProperty(String.class); 104 | containerConfiguration = objectFactory.property(ContainerCreateRequest.class); 105 | containerConfiguration.convention(new ContainerCreateRequest()); 106 | env = objectFactory.listProperty(String.class); 107 | environmentFiles = objectFactory.listProperty(File.class); 108 | } 109 | 110 | @TaskAction 111 | public void run() { 112 | getLogger().info("docker run"); 113 | 114 | ContainerCreateRequest containerConfig = getActualContainerConfig(); 115 | result = getDockerClient().run( 116 | containerConfig, 117 | getContainerName().getOrElse(""), 118 | getEncodedAuthConfig()); 119 | } 120 | 121 | private String getImageNameWithTag() { 122 | if (getImageTag().isPresent() && !getImageTag().get().isEmpty()) { 123 | return getImageName().get() + ":" + getImageTag().get(); 124 | } 125 | else { 126 | return getImageName().get(); 127 | } 128 | } 129 | 130 | @Internal 131 | public ContainerCreateRequest getActualContainerConfig() { 132 | ContainerCreateRequest containerCreateRequest = getContainerConfiguration().getOrElse(new ContainerCreateRequest()); 133 | if (containerCreateRequest.getHostConfig() == null) { 134 | containerCreateRequest.setHostConfig(new HostConfig()); 135 | } 136 | 137 | containerCreateRequest.setImage(getImageNameWithTag()); 138 | if (!getEnvironmentFiles().get().isEmpty()) { 139 | if (containerCreateRequest.getEnv() == null) { 140 | containerCreateRequest.setEnv(new ArrayList<>()); 141 | } 142 | List env = containerCreateRequest.getEnv(); 143 | getEnvironmentFiles().get().forEach((File file) -> { 144 | List parsedEnv = envFileParser.parse(file); 145 | env.addAll(parsedEnv); 146 | }); 147 | } 148 | if (!getEnv().get().isEmpty()) { 149 | if (containerCreateRequest.getEnv() == null) { 150 | containerCreateRequest.setEnv(new ArrayList<>()); 151 | } 152 | List env = containerCreateRequest.getEnv(); 153 | env.addAll(getEnv().get()); 154 | } 155 | 156 | if (!getPorts().get().isEmpty()) { 157 | if (containerCreateRequest.getExposedPorts() == null) { 158 | containerCreateRequest.setExposedPorts(new HashMap<>()); 159 | } 160 | final Map exposedPorts = containerCreateRequest.getExposedPorts(); 161 | if (containerCreateRequest.getHostConfig().getPortBindings() == null) { 162 | containerCreateRequest.getHostConfig().setPortBindings(new HashMap<>()); 163 | } 164 | final Map> portBindings = containerCreateRequest.getHostConfig().getPortBindings(); 165 | getPorts().get().forEach((String portMapping) -> { 166 | // format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort 167 | final String[] splittedPortMapping = portMapping.split(":"); 168 | if (splittedPortMapping.length != 2) { 169 | throw new UnsupportedOperationException("please use the plain `containerConfig.ExposedPorts and containerConfig.HostConfig.PortBindings` properties"); 170 | } 171 | String hostPort = splittedPortMapping[0]; 172 | String containerPort = splittedPortMapping[1] + "/tcp"; 173 | exposedPorts.put(containerPort, new HashMap<>()); 174 | 175 | PortBinding hostBinding = new PortBinding("0.0.0.0", hostPort); 176 | portBindings.put(containerPort, Collections.singletonList(hostBinding)); 177 | }); 178 | } 179 | 180 | getLogger().info("effective container config: " + containerCreateRequest); 181 | return containerCreateRequest; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /plugin/src/main/java/de/gesellix/gradle/docker/tasks/DockerCreateTask.java: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks; 2 | 3 | import de.gesellix.docker.client.EngineResponseContent; 4 | import de.gesellix.docker.client.EnvFileParser; 5 | import de.gesellix.docker.remote.api.ContainerCreateRequest; 6 | import de.gesellix.docker.remote.api.ContainerCreateResponse; 7 | import de.gesellix.docker.remote.api.HostConfig; 8 | import de.gesellix.docker.remote.api.PortBinding; 9 | import org.gradle.api.model.ObjectFactory; 10 | import org.gradle.api.provider.ListProperty; 11 | import org.gradle.api.provider.Property; 12 | import org.gradle.api.tasks.Input; 13 | import org.gradle.api.tasks.Internal; 14 | import org.gradle.api.tasks.Optional; 15 | import org.gradle.api.tasks.TaskAction; 16 | 17 | import javax.inject.Inject; 18 | import java.io.File; 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | public class DockerCreateTask extends GenericDockerTask { 26 | 27 | private final Property imageName; 28 | 29 | @Input 30 | public Property getImageName() { 31 | return imageName; 32 | } 33 | 34 | private final Property imageTag; 35 | 36 | @Input 37 | @Optional 38 | public Property getImageTag() { 39 | return imageTag; 40 | } 41 | 42 | private final Property containerName; 43 | 44 | @Input 45 | @Optional 46 | public Property getContainerName() { 47 | return containerName; 48 | } 49 | 50 | private final ListProperty ports; 51 | 52 | /** 53 | * Accepts a list of port mappings with the following pattern: `hostPort:containerPort`. 54 | * More sophisticated patterns are only supported via plain containerConfig. 55 | */ 56 | @Input 57 | @Optional 58 | public ListProperty getPorts() { 59 | return ports; 60 | } 61 | 62 | private final Property containerConfiguration; 63 | 64 | @Input 65 | @Optional 66 | public Property getContainerConfiguration() { 67 | return containerConfiguration; 68 | } 69 | 70 | private final ListProperty env; 71 | 72 | @Input 73 | @Optional 74 | public ListProperty getEnv() { 75 | return env; 76 | } 77 | 78 | private final ListProperty environmentFiles; 79 | 80 | @Input 81 | @Optional 82 | public ListProperty getEnvironmentFiles() { 83 | return environmentFiles; 84 | } 85 | 86 | private EngineResponseContent result; 87 | 88 | @Internal 89 | public EngineResponseContent getResult() { 90 | return result; 91 | } 92 | 93 | private final EnvFileParser envFileParser = new EnvFileParser(); 94 | 95 | @Inject 96 | public DockerCreateTask(ObjectFactory objectFactory) { 97 | super(objectFactory); 98 | setDescription("Create a new container"); 99 | 100 | imageName = objectFactory.property(String.class); 101 | imageTag = objectFactory.property(String.class); 102 | imageTag.convention(""); 103 | containerName = objectFactory.property(String.class); 104 | containerName.convention(""); 105 | ports = objectFactory.listProperty(String.class); 106 | containerConfiguration = objectFactory.property(ContainerCreateRequest.class); 107 | containerConfiguration.convention(new ContainerCreateRequest()); 108 | env = objectFactory.listProperty(String.class); 109 | environmentFiles = objectFactory.listProperty(File.class); 110 | } 111 | 112 | @TaskAction 113 | public EngineResponseContent create() { 114 | getLogger().info("docker create"); 115 | 116 | ContainerCreateRequest containerConfig = getActualContainerConfig(); 117 | result = getDockerClient().createContainer(containerConfig, getContainerName().getOrElse(""), getEncodedAuthConfig()); 118 | return result; 119 | } 120 | 121 | private String getImageNameWithTag() { 122 | if (getImageTag().isPresent() && !getImageTag().get().isEmpty()) { 123 | return getImageName().get() + ":" + getImageTag().get(); 124 | } 125 | else { 126 | return getImageName().get(); 127 | } 128 | } 129 | 130 | @Internal 131 | public ContainerCreateRequest getActualContainerConfig() { 132 | ContainerCreateRequest containerCreateRequest = getContainerConfiguration().getOrElse(new ContainerCreateRequest()); 133 | if (containerCreateRequest.getHostConfig() == null) { 134 | containerCreateRequest.setHostConfig(new HostConfig()); 135 | } 136 | 137 | containerCreateRequest.setImage(getImageNameWithTag()); 138 | if (!getEnvironmentFiles().get().isEmpty()) { 139 | if (containerCreateRequest.getEnv() == null) { 140 | containerCreateRequest.setEnv(new ArrayList<>()); 141 | } 142 | List env = containerCreateRequest.getEnv(); 143 | getEnvironmentFiles().get().forEach((File file) -> { 144 | List parsedEnv = envFileParser.parse(file); 145 | env.addAll(parsedEnv); 146 | }); 147 | } 148 | if (!getEnv().get().isEmpty()) { 149 | if (containerCreateRequest.getEnv() == null) { 150 | containerCreateRequest.setEnv(new ArrayList<>()); 151 | } 152 | List env = containerCreateRequest.getEnv(); 153 | env.addAll(getEnv().get()); 154 | } 155 | 156 | if (!getPorts().get().isEmpty()) { 157 | if (containerCreateRequest.getExposedPorts() == null) { 158 | containerCreateRequest.setExposedPorts(new HashMap<>()); 159 | } 160 | final Map exposedPorts = containerCreateRequest.getExposedPorts(); 161 | if (containerCreateRequest.getHostConfig().getPortBindings() == null) { 162 | containerCreateRequest.getHostConfig().setPortBindings(new HashMap<>()); 163 | } 164 | final Map> portBindings = containerCreateRequest.getHostConfig().getPortBindings(); 165 | getPorts().get().forEach((String portMapping) -> { 166 | // format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort 167 | final String[] splittedPortMapping = portMapping.split(":"); 168 | if (splittedPortMapping.length != 2) { 169 | throw new UnsupportedOperationException("please use the plain `containerConfig.ExposedPorts and containerConfig.HostConfig.PortBindings` properties"); 170 | } 171 | String hostPort = splittedPortMapping[0]; 172 | String containerPort = splittedPortMapping[1] + "/tcp"; 173 | exposedPorts.put(containerPort, new HashMap<>()); 174 | 175 | PortBinding hostBinding = new PortBinding("0.0.0.0", hostPort); 176 | portBindings.put(containerPort, Collections.singletonList(hostBinding)); 177 | }); 178 | } 179 | 180 | getLogger().info("effective container config: " + containerCreateRequest); 181 | return containerCreateRequest; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import io.freefair.gradle.plugins.maven.central.ValidateMavenPom 2 | import java.text.SimpleDateFormat 3 | import java.util.* 4 | 5 | plugins { 6 | id("groovy") 7 | id("java-gradle-plugin") 8 | id("maven-publish") 9 | id("signing") 10 | id("com.github.ben-manes.versions") 11 | id("org.sonatype.gradle.plugins.scan") 12 | id("com.gradle.plugin-publish") 13 | id("io.freefair.maven-central.validate-poms") 14 | } 15 | 16 | repositories { 17 | // mavenLocal() 18 | // fun findProperty(s: String) = project.findProperty(s) as String? 19 | // listOf( 20 | // "docker-client/*", 21 | // "gesellix/*" 22 | // ).forEach { repo -> 23 | // maven { 24 | // name = "github" 25 | // setUrl("https://maven.pkg.github.com/$repo") 26 | // credentials { 27 | // username = System.getenv("PACKAGE_REGISTRY_USER") ?: findProperty("github.package-registry.username") 28 | // password = System.getenv("PACKAGE_REGISTRY_TOKEN") ?: findProperty("github.package-registry.password") 29 | // } 30 | // } 31 | // } 32 | mavenCentral() 33 | } 34 | 35 | dependencies { 36 | constraints { 37 | listOf( 38 | "org.apache.groovy:groovy", 39 | "org.apache.groovy:groovy-json", 40 | ).forEach { 41 | implementation(it) { 42 | version { 43 | strictly("[4,5)") 44 | } 45 | } 46 | } 47 | testImplementation("org.junit:junit-bom") { 48 | version { 49 | strictly("[5,6)") 50 | prefer("5.13.4") 51 | } 52 | } 53 | } 54 | api(gradleApi()) 55 | 56 | api("de.gesellix:docker-client:2025-11-30T22-30-00-groovy-4") 57 | 58 | testImplementation(localGroovy()) 59 | testImplementation("org.spockframework:spock-core:2.3-groovy-4.0") 60 | testImplementation("cglib:cglib-nodep:3.3.0") 61 | testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4") 62 | 63 | // see https://docs.gradle.org/current/userguide/test_kit.html 64 | testImplementation(gradleTestKit()) 65 | } 66 | 67 | java { 68 | toolchain { 69 | languageVersion.set(JavaLanguageVersion.of(17)) 70 | } 71 | } 72 | 73 | tasks { 74 | withType(Test::class.java) { 75 | useJUnitPlatform() 76 | } 77 | } 78 | 79 | val javadocJar by tasks.registering(Jar::class) { 80 | dependsOn("classes") 81 | archiveClassifier.set("javadoc") 82 | from(tasks.javadoc) 83 | } 84 | 85 | val sourcesJar by tasks.registering(Jar::class) { 86 | dependsOn("classes") 87 | archiveClassifier.set("sources") 88 | from(sourceSets.main.get().allSource) 89 | } 90 | 91 | artifacts { 92 | add("archives", sourcesJar.get()) 93 | add("archives", javadocJar.get()) 94 | } 95 | 96 | ossIndexAudit { 97 | username = System.getenv("SONATYPE_INDEX_USERNAME") ?: findProperty("sonatype.index.username") 98 | password = System.getenv("SONATYPE_INDEX_PASSWORD") ?: findProperty("sonatype.index.password") 99 | } 100 | 101 | fun findProperty(s: String) = project.findProperty(s) as String? 102 | 103 | val localRepositoryName = "LocalPackages" 104 | val gitHubPackagesRepositoryName = "GitHubPackages" 105 | val isSnapshot = project.version == "unspecified" 106 | val artifactVersion = if (!isSnapshot) project.version as String else SimpleDateFormat("yyyy-MM-dd\'T\'HH-mm-ss").format(Date())!! 107 | val publicationName = "gradleDockerPlugin" 108 | publishing { 109 | repositories { 110 | maven { 111 | name = localRepositoryName 112 | url = uri("../local-plugins") 113 | } 114 | maven { 115 | name = gitHubPackagesRepositoryName 116 | url = uri("https://maven.pkg.github.com/${property("github.package-registry.owner")}/${property("github.package-registry.repository")}") 117 | credentials { 118 | username = System.getenv("GITHUB_ACTOR") ?: findProperty("github.package-registry.username") 119 | password = System.getenv("GITHUB_TOKEN") ?: findProperty("github.package-registry.password") 120 | } 121 | } 122 | } 123 | publications { 124 | register(publicationName) { 125 | pom { 126 | name.set("gradle-docker-plugin") 127 | description.set("A Docker plugin for Gradle") 128 | url.set("https://github.com/gesellix/gradle-docker-plugin") 129 | licenses { 130 | license { 131 | name.set("MIT") 132 | url.set("https://opensource.org/licenses/MIT") 133 | } 134 | } 135 | developers { 136 | developer { 137 | id.set("gesellix") 138 | name.set("Tobias Gesellchen") 139 | email.set("tobias@gesellix.de") 140 | } 141 | } 142 | scm { 143 | connection.set("scm:git:github.com/gesellix/gradle-docker-plugin.git") 144 | developerConnection.set("scm:git:ssh://github.com/gesellix/gradle-docker-plugin.git") 145 | url.set("https://github.com/gesellix/gradle-docker-plugin") 146 | } 147 | } 148 | artifactId = "gradle-docker-plugin" 149 | version = artifactVersion 150 | from(components["java"]) 151 | // TODO how do we ensure that these artifacts will always be added 152 | // automatically? 153 | // artifact(sourcesJar.get()) 154 | // artifact(javadocJar.get()) 155 | } 156 | } 157 | } 158 | 159 | signing { 160 | setRequired({ !isSnapshot }) 161 | val signingKey: String? by project 162 | val signingPassword: String? by project 163 | useInMemoryPgpKeys(signingKey, signingPassword) 164 | sign(publishing.publications[publicationName]) 165 | } 166 | 167 | gradlePlugin { 168 | website.set("https://github.com/gesellix/gradle-docker-plugin") 169 | vcsUrl.set("https://github.com/gesellix/gradle-docker-plugin.git") 170 | 171 | plugins { 172 | register(publicationName) { 173 | id = "de.gesellix.docker" 174 | displayName = "Gradle Docker plugin" 175 | description = "A Docker plugin for Gradle" 176 | implementationClass = "de.gesellix.gradle.docker.DockerPlugin" 177 | version = artifactVersion 178 | tags.set(listOf("docker", "remote api", "client")) 179 | } 180 | } 181 | } 182 | 183 | tasks.withType().configureEach { 184 | ignoreFailures = System.getenv()["IGNORE_INVALID_POMS"] == "true" 185 | || name.contains("For${publicationName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}PluginMarkerMaven") 186 | || name.contains("ForPluginMavenPublication") 187 | } 188 | 189 | tasks.register("publishTo${localRepositoryName}") { 190 | group = "publishing" 191 | description = "Publishes all Maven publications to the $localRepositoryName Maven repository." 192 | dependsOn(tasks.withType().matching { 193 | it.repository == publishing.repositories[localRepositoryName] 194 | }) 195 | } 196 | 197 | tasks.register("publishTo${gitHubPackagesRepositoryName}") { 198 | group = "publishing" 199 | description = "Publishes all Maven publications to the $gitHubPackagesRepositoryName Maven repository." 200 | dependsOn(tasks.withType().matching { 201 | it.repository == publishing.repositories[gitHubPackagesRepositoryName] 202 | }) 203 | } 204 | 205 | val isLocalRepo = { repository: MavenArtifactRepository -> 206 | repository == publishing.repositories[localRepositoryName] 207 | } 208 | val isStandardMavenPublication = { repository: MavenArtifactRepository, publication: MavenPublication -> 209 | publication == publishing.publications[publicationName] 210 | && repository.name in listOf("sonatype", localRepositoryName, gitHubPackagesRepositoryName) 211 | } 212 | val isGradlePluginPublish = { repository: MavenArtifactRepository, publication: MavenPublication -> 213 | publication == publishing.publications["pluginMaven"] 214 | && repository.name !in listOf("sonatype", localRepositoryName, gitHubPackagesRepositoryName) 215 | } 216 | 217 | tasks.withType().configureEach { 218 | onlyIf { 219 | isLocalRepo(repository) 220 | || isStandardMavenPublication(repository, publication) 221 | || isGradlePluginPublish(repository, publication) 222 | } 223 | mustRunAfter(tasks.withType()) 224 | } 225 | 226 | //afterEvaluate { 227 | // publishing.publications.forEach { p -> 228 | // if (p is MavenPublication){ 229 | // p.artifacts.forEach {a-> 230 | // println("${p.name} -> ${a.extension}/${a.classifier} -> ${a.file}") 231 | // } 232 | // } 233 | // } 234 | //} 235 | -------------------------------------------------------------------------------- /plugin/src/test/groovy/de/gesellix/gradle/docker/tasks/DockerBuildTaskSpec.groovy: -------------------------------------------------------------------------------- 1 | package de.gesellix.gradle.docker.tasks 2 | 3 | import de.gesellix.docker.authentication.AuthConfig 4 | import de.gesellix.docker.client.DockerClient 5 | import de.gesellix.gradle.docker.worker.BuildcontextArchiver 6 | import org.gradle.testfixtures.ProjectBuilder 7 | import org.gradle.workers.WorkQueue 8 | import org.gradle.workers.WorkerExecutor 9 | import spock.lang.Specification 10 | import spock.lang.Unroll 11 | 12 | import java.time.Duration 13 | import java.time.temporal.ChronoUnit 14 | 15 | class DockerBuildTaskSpec extends Specification { 16 | 17 | def project 18 | def task 19 | def dockerClient = Mock(DockerClient) 20 | 21 | def setup() { 22 | project = ProjectBuilder.builder().build() 23 | task = project.tasks.register('dockerBuild', DockerBuildTask).get() 24 | task.dockerClient = dockerClient 25 | task.buildTimeout = Duration.of(1, ChronoUnit.SECONDS) 26 | } 27 | 28 | def "should archive the buildcontext in a worker thread"() { 29 | URL dockerfile = getClass().getResource('/docker/Dockerfile') 30 | def baseDir = new File(dockerfile.toURI()).parentFile 31 | 32 | given: 33 | def buildTaskDependency = project.task('buildTaskDependency', type: TestTask) 34 | task.dependsOn buildTaskDependency 35 | task.buildContextDirectory = baseDir 36 | task.imageName = "busybox" 37 | def workerExecutor = Mock(WorkerExecutor) 38 | def workQueue = Mock(WorkQueue) 39 | task.workerExecutor = workerExecutor 40 | 41 | when: 42 | task.build() 43 | 44 | then: 45 | project.tasks.findByName("dockerBuild").getDependsOn().contains project.tasks.findByName("buildTaskDependency") 46 | and: 47 | 1 * workerExecutor.noIsolation() >> workQueue 48 | 1 * workQueue.submit(BuildcontextArchiver, _) >> { task.targetFile = new File(dockerfile.toURI()) } 49 | 1 * workerExecutor.await() 50 | and: 51 | 1 * dockerClient.build(*_) 52 | } 53 | 54 | def "delegates to dockerClient with buildContext"() { 55 | def inputStream = new FileInputStream(File.createTempFile("docker", "test")) 56 | 57 | given: 58 | task.buildContext = inputStream 59 | task.imageName = "imageName" 60 | 61 | when: 62 | task.build() 63 | 64 | then: 65 | 1 * dockerClient.build(_, _, 66 | null, "imageName", 67 | null, null, null, true, 68 | null, null, null, 69 | null, inputStream) 70 | 71 | and: 72 | task.outputs.files.isEmpty() 73 | } 74 | 75 | def "delegates to dockerClient with buildContext and buildParams"() { 76 | def inputStream = new FileInputStream(File.createTempFile("docker", "test")) 77 | 78 | given: 79 | task.buildContext = inputStream 80 | task.buildParams = [ 81 | buildargs : [AN_ARGUMENT: "a value"], 82 | dockerfile: './custom.Dockerfile', 83 | nocache : true, 84 | pull : "true", 85 | quiet : true, 86 | rm : false, 87 | ] 88 | task.imageName = "imageName" 89 | 90 | when: 91 | task.build() 92 | 93 | then: 94 | 1 * dockerClient.build(_, _, 95 | "./custom.Dockerfile", 96 | "imageName", 97 | true, 98 | true, 99 | "true", 100 | false, 101 | '{"AN_ARGUMENT":"a value"}', 102 | null, 103 | null, 104 | null, 105 | inputStream) 106 | 107 | and: 108 | task.outputs.files.isEmpty() 109 | } 110 | 111 | def "delegates to dockerClient with buildContext and buildOptions"() { 112 | def inputStream = new FileInputStream(File.createTempFile("docker", "test")) 113 | 114 | given: 115 | task.buildContext = inputStream 116 | task.buildOptions = [EncodedRegistryConfig: "base-64"] 117 | task.imageName = "imageName" 118 | 119 | when: 120 | task.build() 121 | 122 | then: 123 | 1 * dockerClient.build(_, _, 124 | null, "imageName", 125 | null, null, null, true, 126 | null, null, "base-64", 127 | null, inputStream) 128 | 129 | and: 130 | task.outputs.files.isEmpty() 131 | } 132 | 133 | def "does not override rm build param if given"() { 134 | def inputStream = new FileInputStream(File.createTempFile("docker", "test")) 135 | 136 | given: 137 | task.buildContext = inputStream 138 | task.buildParams = [rm: false, dockerfile: './custom.Dockerfile'] 139 | task.imageName = "imageName" 140 | 141 | when: 142 | task.build() 143 | 144 | then: 145 | 1 * dockerClient.build(_, _, 146 | "./custom.Dockerfile", "imageName", 147 | null, null, null, false, 148 | null, null, null, 149 | null, inputStream) 150 | 151 | and: 152 | task.outputs.files.isEmpty() 153 | } 154 | 155 | @Unroll 156 | def "should accept boolean for 'pull' build param"() { 157 | def inputStream = new FileInputStream(File.createTempFile("docker", "test")) 158 | 159 | given: 160 | task.buildContext = inputStream 161 | task.buildParams = [rm: false, pull: pull, dockerfile: './custom.Dockerfile'] 162 | task.imageName = "imageName" 163 | 164 | when: 165 | task.build() 166 | 167 | then: 168 | 1 * dockerClient.build(_, _, 169 | "./custom.Dockerfile", "imageName", 170 | null, null, pull.toString(), false, 171 | null, null, null, 172 | null, inputStream) 173 | 174 | and: 175 | task.outputs.files.isEmpty() 176 | 177 | where: 178 | pull << [true, false] 179 | } 180 | 181 | def "uses auth configs if not overridden via build options"() { 182 | def inputStream = new FileInputStream(File.createTempFile("docker", "test")) 183 | Map authConfigs = ["host.name": new AuthConfig(username: "user-name", password: "a secret")] 184 | 185 | given: 186 | task.authConfigs = authConfigs 187 | dockerClient.encodeAuthConfigs(authConfigs) >> "encoded-auth" 188 | task.buildContext = inputStream 189 | task.imageName = "imageName" 190 | 191 | when: 192 | task.build() 193 | 194 | then: 195 | 1 * dockerClient.build(_, _, 196 | null, "imageName", 197 | null, null, null, true, 198 | null, null, "encoded-auth", 199 | null, inputStream) 200 | 201 | and: 202 | task.outputs.files.isEmpty() 203 | } 204 | 205 | def "delegates to dockerClient with buildContext (with logs)"() { 206 | def inputStream = new FileInputStream(File.createTempFile("docker", "test")) 207 | 208 | given: 209 | task.buildContext = inputStream 210 | task.imageName = "imageName" 211 | task.enableBuildLog = true 212 | 213 | when: 214 | task.build() 215 | 216 | then: 217 | 1 * dockerClient.build(_, _, 218 | null, "imageName", 219 | null, null, null, true, 220 | null, null, null, 221 | null, inputStream) 222 | 223 | and: 224 | task.outputs.files.isEmpty() 225 | } 226 | 227 | def "delegates to dockerClient with buildContext and buildParams (with logs)"() { 228 | def inputStream = new FileInputStream(File.createTempFile("docker", "test")) 229 | 230 | given: 231 | task.buildContext = inputStream 232 | task.buildParams = [rm: true, dockerfile: './custom.Dockerfile'] 233 | task.imageName = "imageName" 234 | task.enableBuildLog = true 235 | 236 | when: 237 | task.build() 238 | 239 | then: 240 | 1 * dockerClient.build(_, _, 241 | "./custom.Dockerfile", "imageName", 242 | null, null, null, true, 243 | null, null, null, 244 | null, inputStream) 245 | 246 | and: 247 | task.outputs.files.isEmpty() 248 | } 249 | 250 | def "normalizedImageName should match [a-z0-9-_.]"() { 251 | expect: 252 | task.getNormalizedImageName() ==~ "[a-z0-9-_.]+" 253 | } 254 | 255 | def parentDir(URL resource) { 256 | new File(resource.toURI()).parentFile 257 | } 258 | 259 | def wrapInClosure(value) { 260 | new Closure(null) { 261 | 262 | @Override 263 | Object call() { 264 | value 265 | } 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /supported-api.md: -------------------------------------------------------------------------------- 1 | # Supported Features 2 | 3 | *feature set based on the [Docker Engine API v1.25](https://docs.docker.com/engine/api/v1.25/)* 4 | 5 | Since the Docker engine api tends to be backwards compatible, 6 | the underlying Docker Client currently supports most other api versions, too. 7 | 8 | Current api coverage: 40/114 endpoints. 9 | 10 | This project tends to support most api endpoints, but only if there's an actual use case. If you're missing a feature, please file 11 | a [new issue](https://github.com/gesellix/gradle-docker-plugin/issues) or a [pull request](https://github.com/gesellix/gradle-docker-plugin/pulls) 12 | and we'll add it as soon as the time allows. This plugin relies on the [Docker Client](https://github.com/gesellix/docker-client) while 13 | there's a [similar Gradle Docker plugin](https://github.com/bmuschko/gradle-docker-plugin) based 14 | on the [Java Docker API Client](https://github.com/docker-java/docker-java) available, too. 15 | 16 | # Management Commands 17 | 18 | ## Checkpoints - Manage checkpoints (0/3) 19 | 20 | * [ ] `docker checkpoints create`: Create a checkpoint from a running container 21 | * [ ] `docker checkpoints ls`: List checkpoints for a container 22 | * [ ] `docker checkpoints rm`: Remove a checkpoint 23 | 24 | ## Container - Manage containers (18/32) 25 | 26 | * [ ] `docker container attach `: Attach to a running container (supports interactive tty) 27 | * [ ] Attach to a running container (websocket) 28 | * [ ] Resize a container TTY 29 | * [x] `docker container commit `: Create a new image from a container's changes 30 | * [x] `docker container cp : `: Get an archive of a filesystem resource in a container 31 | * [x] `docker container cp :`: Extract an archive of files or folders to a directory in a container 32 | * [ ] Retrieve information about files and folders in a container 33 | * [x] `docker container create`: Create a new container 34 | * [ ] `docker container diff `: Inspect changes on a container's filesystem 35 | * [x] `docker container exec `: Run a command in a running container 36 | * [x] Exec Start (supports interactive tty) 37 | * [x] Exec Create 38 | * [ ] Exec Resize 39 | * [ ] Exec Inspect 40 | * [ ] `docker container export `: Export a container's filesystem as a tar archive 41 | * [x] `docker container inspect `: Display detailed information on one or more containers 42 | * [x] `docker container kill `: Kill one or more running containers 43 | * [ ] `docker container logs `: Fetch the logs of a container 44 | * [x] `docker container ps`: List containers (alias for `ls`, `list`) 45 | * [x] `docker container pause `: Pause all processes within one or more containers 46 | * [ ] `docker container port`: List port mappings or a specific mapping for the container 47 | * [ ] `docker container prune`: Remove all stopped containers 48 | * [x] `docker container rename `: Rename a container 49 | * [x] `docker container restart `: Restart one or more containers 50 | * [x] `docker container rm `: Remove one or more containers 51 | * [ ] `docker container run`: Run a command in a new container 52 | * [x] `docker container start `: Start one or more stopped containers 53 | * [ ] `docker container stats `: Display a live stream of container(s) resource usage statistics 54 | * [x] `docker container stop `: Stop one or more running containers 55 | * [ ] `docker container top `: Display the running processes of a container 56 | * [x] `docker container unpause `: Unpause all processes within one or more containers 57 | * [ ] `docker container update [...]`: Update configuration of one or more containers 58 | * [x] `docker container wait `: Block until one or more containers stop, then print their exit codes 59 | 60 | ## Image - Manage images (6/14) 61 | 62 | * [x] `docker image build`: Build an image from a Dockerfile 63 | * [ ] `docker image history `: Show the history of an image 64 | * [ ] `docker image import`: Import the contents from a tarball to create a filesystem image (from stream) 65 | * [ ] `docker image import`: Import the contents from a tarball to create a filesystem image (from url) 66 | * [ ] `docker image inspect `: Display detailed information on one or more images 67 | * [ ] `docker image load`: Load a tarball with a set of images and tags into docker 68 | * [x] `docker image ls`: List Images 69 | * [ ] `docker image prune`: Remove unused images 70 | * [x] `docker image pull`: Pull an image or a repository from a registry 71 | * [x] `docker image push `: Push an image or a repository to a registry 72 | * [x] `docker image rm `: Remove one or more images 73 | * [ ] `docker image save `: Get a tarball containing all images in a repository 74 | * [ ] `docker image save [ ...]`: Get a tarball containing all images. 75 | * [x] `docker image tag `: Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE 76 | 77 | ## Network - Manage networks (5/7) 78 | 79 | * [x] `docker network connect`: Connect a container to a network 80 | * [x] `docker network create`: Create a network 81 | * [x] `docker network disconnect`: Disconnect a container from a network 82 | * [ ] `docker network inspect`: Display detailed information on one or more networks 83 | * [x] `docker network ls`: List networks 84 | * [ ] `docker network prune`: Remove all unused networks 85 | * [x] `docker network rm`: Remove one or more networks 86 | 87 | ## Node - Manage Swarm nodes (0/7) 88 | 89 | * [ ] `docker node demote`: Demote a node from manager in the swarm 90 | * [ ] `docker node inspect`: Inspect a node in the swarm 91 | * [ ] `docker node ls`: List nodes in the swarm 92 | * [ ] `docker node promote`: Promote a node to a manager in the swarm 93 | * [ ] `docker node ps`: List tasks running on a node 94 | * [ ] `docker node rm`: Remove a node from the swarm 95 | * [ ] `docker node update`: Update a node 96 | 97 | ## Plugin - Manage plugins (0/10) 98 | 99 | * [ ] `docker plugin create`: Create a plugin from a rootfs and config `POST /plugins/create` 100 | * [ ] `docker plugin disable`: Disable a plugin `POST /plugins/{name:.*}/disable` 101 | * [ ] `docker plugin enable`: Enable a plugin `POST /plugins/{name:.*}/enable` 102 | * [ ] `docker plugin inspect`: Inspect a plugin `GET /plugins/{name:.*}/json` 103 | * [ ] `docker plugin install`: Install a plugin (equivalent to pull + enable) 104 | * [ ] `docker plugin ls`: List plugins `GET /plugins` 105 | * [ ] Get plugin privileges `GET /plugins/privileges` 106 | * [ ] `docker plugin push`: Push a plugin `POST /plugins/{name:.*}/push` 107 | * [ ] Pull a plugin `POST /plugins/pull` 108 | * [ ] `docker plugin rm`: Remove a plugin `DELETE /plugins/{name:.*}` 109 | * [ ] `docker plugin set`: Change settings for a plugin `POST /plugins/{name:.*}/set` 110 | 111 | ## Secrets - Manage Docker secrets (0/5) 112 | 113 | * [ ] `docker secret create`: Create a secret 114 | * [ ] `docker secret inspect`: Inspect a secret 115 | * [ ] `docker secret ls`: List secrets 116 | * [ ] Update a Secret `POST /secrets/{id}/update` 117 | * [ ] `docker secret rm`: Delete a secret 118 | 119 | ## Service - Manage services (2/8) 120 | 121 | * [x] `docker service create`: Create a service 122 | * [ ] `docker service inspect`: Return information on the service `` 123 | * [ ] `docker service logs`: Get service logs (GET `/services/{id}/logs`) 124 | * [ ] `docker service ls`: List services 125 | * [ ] `docker service ps`: List the tasks of a service 126 | * [x] `docker service rm`: Remove a service 127 | * [ ] `docker service scale`: Scale one or multiple services 128 | * [ ] `docker service update`: Update a service 129 | 130 | ## Stack - Manage Docker stacks (0/5) 131 | 132 | * [ ] `docker stack deploy`: Deploy a new stack or update an existing stack 133 | * [ ] `docker stack ls`: List stacks 134 | * [ ] `docker stack ps`: List the tasks in the stack 135 | * [ ] `docker stack rm`: Remove the stack 136 | * [ ] `docker stack services`: List the services in the stack 137 | 138 | ## Swarm - Manage Swarm (3/8) 139 | 140 | * [x] `docker swarm init`: Initialize a Swarm 141 | * [x] `docker swarm join`: Join a Swarm as a node and/or manager 142 | * [ ] `docker swarm join-token`: Manage join tokens 143 | * [x] `docker swarm leave`: Leave a Swarm 144 | * [ ] `docker swarm inspect`: Inspect the Swarm 145 | * [ ] `docker swarm unlock`: Unlock swarm 146 | * [ ] `docker swarm unlock-key`: Manage the unlock key 147 | * [ ] `docker swarm update`: Update the Swarm 148 | 149 | ## System - Manage Docker (1/4) 150 | 151 | * [x] `docker system info`: Display system-wide information 152 | * [ ] `docker system df`: Show docker disk usage 153 | * [ ] `docker system events`: Monitor Docker's events 154 | * [ ] `docker system prune`: Remove unused data 155 | 156 | ## Volume - Manage volumes (3/5) 157 | 158 | * [x] `docker volume create`: Create a volume 159 | * [ ] `docker volume inspect`: Display detailed information on one or more volumes 160 | * [x] `docker volume ls`: List volumes from all volume drivers 161 | * [ ] `docker volume prune`: Remove all unused volumes 162 | * [x] `docker volume rm`: Remove one or more volumes 163 | 164 | # Other Commands 165 | 166 | ## Misc (2/4) 167 | 168 | * [ ] `docker search `: Search the Docker Hub for images 169 | * [x] `docker version`: Show the docker version information 170 | * [ ] Check auth configuration 171 | * [x] Ping the docker server 172 | 173 | ## Tasks (0/2) 174 | 175 | * [ ] List all tasks 176 | * [ ] Get details on a task 177 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | --------------------------------------------------------------------------------