Skip to content

Commit

Permalink
Merge pull request #15 from palantir/feature/separate-plugins
Browse files Browse the repository at this point in the history
Separate PalantirDockerPlugin and DockerComposeTemplatePlugin into tw…
  • Loading branch information
markelliot committed Dec 10, 2015
2 parents 3ad0990 + 11fe5a8 commit 530f587
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 259 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ pluginBundle {
id = 'com.palantir.docker'
displayName = 'Palantir Gradle Docker'
}
dockerComposePlugin {
id = 'com.palantir.docker-compose'
displayName = 'Palantir Gradle Docker-Compose'
}
}
}

Expand Down
132 changes: 73 additions & 59 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ Docker Gradle Plugin
====================
[![Build Status](https://travis-ci.org/palantir/gradle-docker.svg?branch=develop)](https://travis-ci.org/palantir/gradle-docker)

Adds basic tasks for building and pushing docker images based on a simple
configuration block that specifies the container name, the Dockerfile, task
dependencies, and any additional file resources required for the Docker build.
This repository provides two Gradle plugins for working with Docker containers:
- The `com.palantir.docker` plugin add basic tasks for building and pushing
docker images based on a simple configuration block that specifies the container
name, the Dockerfile, task dependencies, and any additional file resources
required for the Docker build.
- The `com.palantir.docker-compose` plugin adds a task for populating
placeholders in a docker-compose template file with image versions resolved from
dependencies.

Usage
-----
Docker Plugin
-------------

### Usage
Apply the plugin using standard gradle convention:

plugins {
Expand All @@ -33,34 +40,35 @@ Configuration specifying all parameters:
dockerfile 'Dockerfile'
dependsOn tasks.distTar
files 'file1.txt', 'file2.txt'
dockerComposeTemplate 'my-template.yml'
dockerComposeFile 'my-docker-compose.yml'
}

To build a docker container, run the `docker` task. To push that container to a
docker repository, run the `dockerPush` task.


Generating docker-compose dependencies
--------------------------------------
Managing Docker image dependencies
----------------------------------

The plugin provides mechanisms for managing dependencies between docker images
used in orchestrating `docker-compose` environments. Each project can declare
its dependencies on other docker images and publish an artifact advertising those
dependencies. The plugin uses standard Maven/Ivy machanism for declaring and
resolving dependencies.
The `com.palantir.docker` and `com.palantir.docker-compose` plugins provide
functionality to declare and resolve version-aware dependencies between docker
images. The primary use-case is to generate `docker-compose.yml` files whose
image versions are mutually compatible and up-to-date in cases where multiple
images depend on the existence of the same Dockerized service.

The `generateDockerCompose` task generates a `docker-compose.yml` file from
a user-defined template by replacing each version variable by the concrete version
declared by the transitive dependencies of the docker configuration.
### Specifying and publishing dependencies on Docker images

### Specifying dependencies on Docker images
The plugin adds a `docker` Gradle component and a `docker` Gradle configuration that
can be used to specify and publish dependencies on other Docker containers.
The `generateDockerCompose` task (see below) uses the transitive dependencies
of the `docker` configuration to populate a `docker-compose.yml.template` file
with the image versions specified by this project and all its transitive
dependencies.
The `docker` plugin adds a `docker` Gradle component and a `docker` Gradle
configuration that can be used to specify and publish dependencies on other
Docker containers.

**Example**

plugins {
id 'maven-publish'
id 'com.palantir.docker'
}

...

dependencies {
docker 'foogroup:barmodule:0.1.2'
Expand All @@ -76,16 +84,28 @@ dependencies.
}
}

The above configuration adds a Maven publication that specifies dependencies
on `barmodule` and the `someSubProject` Gradle sub project. The resulting POM
file has two `dependency` entries, one for each dependency.
The above configuration adds a Maven publication that specifies dependencies on
`barmodule` and the `someSubProject` Gradle sub project. The resulting POM file
has two `dependency` entries, one for each dependency. Each project can declare
its dependencies on other docker images and publish an artifact advertising
those dependencies.

### Generating docker-compose.yml files from templates
The `generateDockerCompose` task performs two operations: First, it generates
a mapping `group:name --> version` from the dependencies of the `docker`
configuration (see above). Second, it replaces all occurrences of version
variables of the form `{{group:name}}` in the `docker-compose.yml.template` file
by the resolved versions and writes the resulting file as `docker-compose.yml`.
### Generating docker-compose.yml files from dependencies

The `com.palantir.docker-compose` plugin uses the transitive dependencies of the
`docker` configuration to populate a `docker-compose.yml.template` file with the
image versions specified by this project and all its transitive dependencies.
The plugin uses standard Maven/Ivy machanism for declaring and resolving
dependencies.

The `generateDockerCompose` task generates a `docker-compose.yml` file from a
user-defined template by replacing each version variable by the concrete version
declared by the transitive dependencies of the docker configuration. The task
performs two operations: First, it generates a mapping `group:name --> version`
from the dependencies of the `docker` configuration (see above). Second, it
replaces all occurrences of version variables of the form `{{group:name}}` in
the `docker-compose.yml.template` file by the resolved versions and writes the
resulting file as `docker-compose.yml`.

**Example**

Expand All @@ -96,47 +116,39 @@ Assume a `docker-compose.yml.template` as follows:
otherservice:
image: 'repository/otherservice:{{othergroup:otherservice}}'

The `build.gradle` of this project publishes a docker Maven artifact declaring
a dependency on a docker image published as 'othergroup:otherservice' in version
0.1.2:
`build.gradle` declares a dependency on a docker image published as
'othergroup:otherservice' in version 0.1.2:

plugins {
id 'maven-publish'
id 'com.palantir.docker'
}

docker {
name 'foo'
id 'com.palantir.docker-compose'
}

group 'mygroup'
name 'myservice'
version '2.3.4'

dependencies {
docker 'othergroup:otherservice:0.1.2'
}

publishing {
publications {
dockerPublication(MavenPublication) {
from components.docker
artifactId project.name + "-docker"
}
}
}

The `generateDockerCompose` task creates a `docker-compose.yml` as follows:

myservice:
image: 'repository/myservice:latest'
otherservice:
image: 'repository/otherservice:0.1.2'

The `generateDockerCompose` task fails if the template file contains variables that
cannot get resolved using the provided `docker` dependencies. Version conflicts between
transitive dependencies of the same artifact are handled with the standard Gradle
semantics: each artifact is resolved to the highest declared version.
The `generateDockerCompose` task fails if the template file contains variables
that cannot get resolved using the provided `docker` dependencies. Version
conflicts between transitive dependencies of the same artifact are handled with
the standard Gradle semantics: each artifact is resolved to the highest declared
version.

**Configuring file locations**

The template and generated file locations are customizable through the
`dockerCompose` extension:

dockerCompose {
template 'my-template.yml'
dockerComposeFile 'my-docker-compose.yml'
}

Tasks
-----
Expand All @@ -153,6 +165,8 @@ Tasks





License
-------
This plugin is made available under the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0).
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2015 Palantir Technologies
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* <http://www.apache.org/licenses/LICENSE-2.0>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.palantir.gradle.docker

import com.google.common.base.Preconditions
import org.gradle.api.Project

class DockerComposeExtension {
Project project

private String template = 'docker-compose.yml.template'
private String dockerComposeFile = 'docker-compose.yml'

private File resolvedDockerComposeTemplate = null
private File resolvedDockerComposeFile = null

public DockerComposeExtension(Project project) {
this.project = project
}

public void setTemplate(String dockerComposeTemplate) {
this.template = dockerComposeTemplate
Preconditions.checkArgument(project.file(dockerComposeTemplate).exists(),
"Could not find specified template file: %s", project.file(dockerComposeTemplate))
}

public void setDockerComposeFile(String dockerComposeFile) {
this.dockerComposeFile = dockerComposeFile
}

File getResolvedDockerComposeTemplate() {
return resolvedDockerComposeTemplate
}

File getResolvedDockerComposeFile() {
return resolvedDockerComposeFile
}

public void resolvePathsAndValidate() {
resolvedDockerComposeFile = project.file(dockerComposeFile)
resolvedDockerComposeTemplate = project.file(template)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.palantir.gradle.docker

import com.google.common.base.Preconditions
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.Copy

class DockerComposePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
DockerComposeExtension ext =
project.extensions.create('dockerCompose', DockerComposeExtension, project)
project.configurations.create("docker")

Copy generateDockerCompose = project.tasks.create('generateDockerCompose', Copy, {
description = 'Populates docker-compose.yml.template file with image versions specified by "docker" ' +
'dependencies'
})

project.afterEvaluate {
ext.resolvePathsAndValidate()
if (ext.resolvedDockerComposeTemplate.exists()) {
def dockerDependencies = project.configurations.docker.resolvedConfiguration.resolvedArtifacts
def templateTokens = dockerDependencies.collectEntries {
def version = it.moduleVersion.id
[("{{${version.group}:${version.name}}}"): version.version]
}

generateDockerCompose.with {
from(ext.resolvedDockerComposeTemplate)
into(ext.resolvedDockerComposeFile.parentFile)
rename { fileName ->
fileName.replace(
ext.resolvedDockerComposeTemplate.name, ext.resolvedDockerComposeFile.name)
}
filter { String line -> replaceAll(line, templateTokens, ext) }
}
}
}
}

/** Replaces all occurrences of templatesTokens's keys by their corresponding values in the given line. */
static def replaceAll(String line, Map<String, String> templateTokens, DockerComposeExtension ext) {
templateTokens.each { mapping -> line = line.replace(mapping.key, mapping.value) }
def unmatchedTokens = line.findAll(/\{\{.*\}\}/)
Preconditions.checkState(unmatchedTokens.size() == 0,
"Failed to resolve Docker dependencies declared in %s: %s. Known dependencies: %s",
ext.resolvedDockerComposeTemplate, unmatchedTokens, templateTokens)
return line
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class DockerExtension {
public void setDockerComposeTemplate(String dockerComposeTemplate) {
this.dockerComposeTemplate = dockerComposeTemplate
Preconditions.checkArgument(project.file(dockerComposeTemplate).exists(),
"Could not find specified dockerComposeTemplate file: %s", project.file(dockerComposeTemplate))
"Could not find specified template file: %s", project.file(dockerComposeTemplate))
}

public void setDockerComposeFile(String dockerComposeFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package com.palantir.gradle.docker

import com.google.common.base.Preconditions
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
Expand All @@ -27,6 +26,7 @@ import org.gradle.api.tasks.Exec
import org.gradle.api.tasks.bundling.Zip

class PalantirDockerPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
DockerExtension ext = project.extensions.create('docker', DockerExtension, project)
project.configurations.create("docker")
Expand All @@ -52,12 +52,6 @@ class PalantirDockerPlugin implements Plugin<Project> {

Zip dockerfileZip = project.tasks.create('dockerfileZip', Zip, {
description = "Bundles the configured Dockerfile in a ZIP file"

})

Copy generateDockerCompose = project.tasks.create('generateDockerCompose', Copy, {
description = 'Populates docker-compose.yml.template file with image versions specified by "docker" ' +
'dependencies'
})

PublishArtifact dockerArtifact = new ArchivePublishArtifact(dockerfileZip)
Expand Down Expand Up @@ -96,33 +90,6 @@ class PalantirDockerPlugin implements Plugin<Project> {
dockerfileZip.with {
from(ext.resolvedDockerfile)
}

// Configure docker-compose templating
if (ext.resolvedDockerComposeTemplate.exists()) {
def dockerDependencies = project.configurations.docker.resolvedConfiguration.resolvedArtifacts
def templateTokens = dockerDependencies.collectEntries {
def version = it.moduleVersion.id
[("{{${version.group}:${version.name}}}"): version.version]
}

generateDockerCompose.with {
from(ext.resolvedDockerComposeTemplate)
into(ext.resolvedDockerComposeFile.parentFile)
rename { fileName -> fileName.replace(
ext.resolvedDockerComposeTemplate.name, ext.resolvedDockerComposeFile.name) }
filter { String line -> replaceAll(line, templateTokens, ext) }
}
}
}
}

/** Replaces all occurrences of templatesTokens's keys by their corresponding values in the given line. */
static def replaceAll(String line, Map<String, String> templateTokens, DockerExtension ext) {
templateTokens.each { mapping -> line = line.replace(mapping.key, mapping.value) }
def unmatchedTokens = line.findAll(/\{\{.*\}\}/)
Preconditions.checkState(unmatchedTokens.size() == 0,
"Failed to resolve Docker dependencies mention in %s: %s",
ext.resolvedDockerComposeTemplate, unmatchedTokens)
line
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
implementation-class=com.palantir.gradle.docker.DockerComposePlugin
Loading

0 comments on commit 530f587

Please sign in to comment.