Skip to content

Commit

Permalink
Support task name for tags (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
devkanro authored and pkoenig10 committed Jan 7, 2019
1 parent 6232830 commit 64853c1
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 47 deletions.
9 changes: 6 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ automatically include outputs of task dependencies in the Docker build context.

**Docker Configuration Parameters**
- `name` the name to use for this container, may include a tag
- `tags` (optional) an argument list of tags to create; any tag in `name` will
- `tags` (deprecated) (optional) an argument list of tags to create; any tag in `name` will
be stripped before applying a specific tag; defaults to the empty set
- `tag` (optional) a tag to create with a specified task name
- `dockerfile` (optional) the dockerfile to use for building the image; defaults to
`project.file('Dockerfile')` and must be a file object
- `files` (optional) an argument list of files to be included in the Docker build context, evaluated per `Project#files`. For example, `files tasks.distTar.outputs` adds the TAR/TGZ file produced by the `distTar` tasks, and `files tasks.distTar.outputs, 'my-file.txt'` adds the archive in addition to file `my-file.txt` from the project root directory. The specified files are collected in a Gradle CopySpec which may be copied `into` the Docker build context directory. The underlying CopySpec may also be used to copy entire directories into the build context. The following example adds the aforementioned archive and text file to the CopySpec, uses the CopySpec to add all files `from` `src/myDir` into the CopySpec, then finally executes the copy `into` the docker build context directory `myDir`
Expand All @@ -53,7 +54,7 @@ docker {
To build a docker container, run the `docker` task. To push that container to a
docker repository, run the `dockerPush` task.

Tag and Push tasks for each tag will be generated for each provided `tags` entry.
Tag and Push tasks for each tag will be generated for each provided `tag` and `tags` entry.

**Examples**

Expand All @@ -80,7 +81,8 @@ Configuration specifying all parameters:
```gradle
docker {
name 'hub.docker.com/username/my-app:version'
tags 'latest'
tags 'latest' // deprecated, use 'tag'
tag 'myRegistry', 'my.registry.com/username/my-app:version'
dockerfile file('Dockerfile')
files tasks.distTar.outputs, 'file1.txt', 'file2.txt'
buildArgs([BUILD_VERSION: 'version'])
Expand Down Expand Up @@ -257,6 +259,7 @@ Tasks
* `dockerTag<tag>`: tag the docker image with `<tag>`
* `dockerPush`: push the specified image to a docker repository
* `dockerPush<tag>`: push the `<tag>` docker image to a docker repository
* `dockerTagsPush`: push all tagged docker images to a docker repository
* `dockerPrepare`: prepare to build a docker image by copying
dependent task outputs, referenced files, and `dockerfile` into a temporary
directory
Expand Down
16 changes: 16 additions & 0 deletions src/main/groovy/com/palantir/gradle/docker/DockerExtension.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ import com.google.common.base.Preconditions
import com.google.common.base.Strings
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.CopySpec
import org.gradle.internal.logging.text.StyledTextOutput
import org.gradle.internal.logging.text.StyledTextOutputFactory

class DockerExtension {
Project project
Expand All @@ -33,6 +36,7 @@ class DockerExtension {
private String dockerComposeFile = 'docker-compose.yml'
private Set<Task> dependencies = ImmutableSet.of()
private Set<String> tags = ImmutableSet.of()
private Map<String, String> namedTags = new HashMap<>()
private Map<String, String> labels = ImmutableMap.of()
private Map<String, String> buildArgs = ImmutableMap.of()
private boolean pull = false
Expand Down Expand Up @@ -89,10 +93,22 @@ class DockerExtension {
return tags
}

@Deprecated
public void tags(String... args) {
this.tags = ImmutableSet.copyOf(args)
}

public Map<String, String> getNamedTags() {
return ImmutableMap.copyOf(namedTags)
}

public void tag(String taskName, String tag) {
if (namedTags.putIfAbsent(taskName, tag) != null) {
StyledTextOutput o = project.services.get(StyledTextOutputFactory.class).create(DockerExtension)
o.withStyle(StyledTextOutput.Style.Error).println("WARNING: Task name '${taskName}' is existed.")
}
}

public Map<String, String> getLabels() {
return labels
}
Expand Down
131 changes: 94 additions & 37 deletions src/main/groovy/com/palantir/gradle/docker/PalantirDockerPlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.Exec
import org.gradle.api.tasks.bundling.Zip
import org.gradle.internal.logging.text.StyledTextOutput
import org.gradle.internal.logging.text.StyledTextOutputFactory

import javax.inject.Inject
import java.util.regex.Pattern
Expand Down Expand Up @@ -74,9 +76,9 @@ class PalantirDockerPlugin implements Plugin<Project> {
})

Task tag = project.tasks.create('dockerTag', {
group = 'Docker'
description = 'Applies all tags to the Docker image.'
dependsOn exec
group = 'Docker'
description = 'Applies all tags to the Docker image.'
dependsOn exec
})

Exec push = project.tasks.create('dockerPush', Exec, {
Expand All @@ -85,6 +87,11 @@ class PalantirDockerPlugin implements Plugin<Project> {
dependsOn tag
})

Task pushAllTags = project.tasks.create('dockerTagsPush', {
group = 'Docker'
description = 'Pushes all tagged Docker images to configured Docker Hub.'
})

Zip dockerfileZip = project.tasks.create('dockerfileZip', Zip, {
group = 'Docker'
description = 'Bundles the configured Dockerfile in a zip file'
Expand Down Expand Up @@ -119,32 +126,51 @@ class PalantirDockerPlugin implements Plugin<Project> {
logging.captureStandardError LogLevel.ERROR
}

Map<String, Object> tags = ext.namedTags.collectEntries { taskName, tagName ->
[generateTagTaskName(taskName), [
tagName: tagName,
tagTask: { -> tagName }
]]
}

if (!ext.tags.isEmpty()) {
ext.tags.each { unresolvedTagName ->
String taskName = generateTagTaskName(unresolvedTagName)

ext.tags.each { tagName ->
String taskTagName = ucfirst(tagName)
Exec subTask = project.tasks.create('dockerTag' + taskTagName, Exec, {
group = 'Docker'
description = "Tags Docker image with tag '${tagName}'"
workingDir dockerDir
commandLine 'docker', 'tag', "${ -> ext.name}", "${ -> computeName(ext.name, tagName)}"
dependsOn exec
})
tag.dependsOn subTask

project.tasks.create('dockerPush' + taskTagName, Exec, {
group = 'Docker'
description = "Pushes the Docker image with tag '${tagName}' to configured Docker Hub"
workingDir dockerDir
commandLine 'docker', 'push', "${ -> computeName(ext.name, tagName)}"
dependsOn subTask
})
if (tags.containsKey(taskName)) {
throw new IllegalArgumentException("Task name '${taskName}' is existed.")
}

tags[taskName] = [
tagName: unresolvedTagName,
tagTask: { -> computeName(ext.name, unresolvedTagName) }
]
}
}

tags.each { taskName, tagConfig ->
Exec tagSubTask = project.tasks.create('dockerTag' + taskName, Exec, {
group = 'Docker'
description = "Tags Docker image with tag '${tagConfig.tagName}'"
workingDir dockerDir
commandLine 'docker', 'tag', "${-> ext.name}", "${-> tagConfig.tagTask()}"
dependsOn exec
})
tag.dependsOn tagSubTask

Exec pushSubTask = project.tasks.create('dockerPush' + taskName, Exec, {
group = 'Docker'
description = "Pushes the Docker image with tag '${tagConfig.tagName}' to configured Docker Hub"
workingDir dockerDir
commandLine 'docker', 'push', "${-> tagConfig.tagTask()}"
dependsOn tagSubTask
})
pushAllTags.dependsOn pushSubTask
}

push.with {
workingDir dockerDir
commandLine 'docker', 'push', "${ -> ext.name}"
commandLine 'docker', 'push', "${-> ext.name}"
}

dockerfileZip.with {
Expand Down Expand Up @@ -176,29 +202,60 @@ class PalantirDockerPlugin implements Plugin<Project> {
if (ext.pull) {
buildCommandLine.add '--pull'
}
buildCommandLine.addAll(['-t', "${ -> ext.name}", '.'])
buildCommandLine.addAll(['-t', "${-> ext.name}", '.'])
return buildCommandLine
}

@Deprecated
private static String computeName(String name, String tag) {
int lastColon = name.lastIndexOf(':')
int lastSlash = name.lastIndexOf('/')
int firstAt = tag.indexOf("@")

int endIndex;
String tagValue
if (firstAt > 0) {
tagValue = tag.substring(firstAt + 1, tag.length())
} else {
tagValue = tag
}

// image_name -> this should remain
// host:port/image_name -> this should remain.
// host:port/image_name:v1 -> v1 should be replaced
if (lastColon > lastSlash) endIndex = lastColon
else endIndex = name.length()
if (tagValue.contains(':') || tagValue.contains('/')) {
// tag with ':' or '/' -> force use the tag value
return tagValue
} else {
// tag without ':' and '/' -> replace the tag part of original name
int lastColon = name.lastIndexOf(':')
int lastSlash = name.lastIndexOf('/')

return name.substring(0, endIndex) + ":" + tag
}
int endIndex;

private static String ucfirst(String str) {
StringBuffer sb = new StringBuffer(str);
sb.replace(0, 1, str.substring(0, 1).toUpperCase());
return sb.toString();
// image_name -> this should remain
// host:port/image_name -> this should remain.
// host:port/image_name:v1 -> v1 should be replaced
if (lastColon > lastSlash) endIndex = lastColon
else endIndex = name.length()

return name.substring(0, endIndex) + ":" + tagValue
}
}

@Deprecated
private static String generateTagTaskName(String name) {
String tagTaskName = name
int firstAt = name.indexOf("@")

if (firstAt > 0) {
// Get substring of task name
tagTaskName = name.substring(0, firstAt)
} else if (firstAt == 0) {
// Task name must not be empty
throw new GradleException("Task name of docker tag '${name}' must not be empty.")
} else if (name.contains(':') || name.contains('/')) {
// Tags which with repo or name must have a task name
throw new GradleException("Docker tag '${name}' must have a task name.")
}

StringBuffer sb = new StringBuffer(tagTaskName)
// Uppercase the first letter of task name
sb.replace(0, 1, tagTaskName.substring(0, 1).toUpperCase());
return sb.toString()
}
}
Loading

0 comments on commit 64853c1

Please sign in to comment.