Last active
January 4, 2019 12:57
-
-
Save double16/e9588de0f9cc063751cb390809626e5e to your computer and use it in GitHub Desktop.
Creates a JSON file suitable for deployment using AWS CodePipeline + ECS deploy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dependencies { | |
compile "com.bmuschko:gradle-docker-plugin:3.6.0" | |
compile 'org.yaml:snakeyaml:1.17' | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.bmuschko.gradle.docker.DockerRegistryCredentials | |
import com.bmuschko.gradle.docker.tasks.AbstractDockerRemoteApiTask | |
import com.bmuschko.gradle.docker.tasks.RegistryCredentialsAware | |
import groovy.json.JsonOutput | |
import groovy.json.JsonSlurper | |
import groovy.util.logging.Slf4j | |
import org.gradle.api.tasks.InputFile | |
import org.gradle.api.tasks.Nested | |
import org.gradle.api.tasks.Optional | |
import org.gradle.api.tasks.OutputFile | |
/** | |
* Copies Docker images from one registry to another. The input is a JSON file intended for AWS CodeDeploy specifying | |
* which ECS containers to update. | |
*/ | |
@Slf4j | |
class ImageCopyTask extends AbstractDockerRemoteApiTask implements RegistryCredentialsAware { | |
@InputFile | |
File imageDefinitions | |
@OutputFile | |
@Optional | |
File copiedDefinitions | |
/** | |
* The target Docker registry credentials. | |
*/ | |
@Nested | |
@Optional | |
DockerRegistryCredentials registryCredentials | |
ImageCopyTask() { | |
onlyIf { registryCredentials != null } | |
} | |
void runRemoteCommand(dockerClient) { | |
def authConfig = threadContextClassLoader.createAuthConfig(getRegistryCredentials()) | |
URL repoUrl | |
if (registryCredentials.url.contains('://')) { | |
repoUrl = new URL(registryCredentials.url) | |
} else { | |
repoUrl = new URL('https://'+registryCredentials.url) | |
} | |
def images = new JsonSlurper().parse(imageDefinitions) | |
def copiedImages = [] | |
images.each { | |
String definitionName = it['name'] | |
String originalImage = it['imageUri'] | |
def match = originalImage =~ /(?:.*\/)(.+)(?::(.*))/ | |
if (match) { | |
String name = match[0][1] | |
String tag = match[0][2] ?: 'latest' | |
String newImage = (repoUrl.port > 0) ? "${repoUrl.host}:${repoUrl.port}/${name}":"${repoUrl.host}/${name}" | |
log.info "Copying ${originalImage} to ${newImage}:${tag}" | |
log.info "Pulling ${originalImage}" | |
def pullImageCmd = dockerClient.pullImageCmd(originalImage-":${tag}").withTag(tag) | |
pullImageCmd.exec(threadContextClassLoader.createPullImageResultCallback(onNext)).awaitSuccess() | |
def inspectImage = dockerClient.inspectImageCmd(originalImage).exec() | |
log.info "Tagging ${originalImage} as ${newImage}:${tag}" | |
def tagImageCmd = dockerClient.tagImageCmd(inspectImage.id, newImage, tag) | |
tagImageCmd.exec() | |
log.info "Pushing ${newImage}:${tag}" | |
def pushImageCmd = dockerClient.pushImageCmd(newImage) | |
.withTag(tag) | |
.withAuthConfig(authConfig) | |
pushImageCmd.exec(threadContextClassLoader.createPushImageResultCallback(onNext)).awaitSuccess() | |
copiedImages << [name: definitionName, imageUri: "${newImage}:${tag}"] | |
} | |
} | |
if (copiedDefinitions != null) { | |
copiedDefinitions.text = JsonOutput.prettyPrint(JsonOutput.toJson(copiedImages)) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
version: "3" | |
volumes: | |
consul: | |
services: | |
mongodb: | |
image: pdouble16/autopilotpattern-mongodb:3.6.3-r3 | |
api: | |
image: ${PRIVATE_DOCKER_REGISTRY}/api:${APPLICATION_VERSION:-latest} | |
consul: | |
image: pdouble16/autopilotpattern-consul:1.0.6-r1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
version: "3" | |
volumes: | |
consul: | |
services: | |
consul: | |
image: consul:latest | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.json.JsonOutput | |
import org.gradle.api.DefaultTask | |
import org.gradle.api.tasks.CacheableTask | |
import org.gradle.api.tasks.Input | |
import org.gradle.api.tasks.InputFiles | |
import org.gradle.api.tasks.Optional | |
import org.gradle.api.tasks.OutputFile | |
import org.gradle.api.tasks.TaskAction | |
import org.yaml.snakeyaml.Yaml | |
import java.util.regex.Pattern | |
/** | |
* Creates a JSON file suitable for deployment using AWS CodePipeline + ECS deploy. It describes the docker | |
* images pushed and the containers that should be updated. The images are taken from a set of docker files. | |
*/ | |
@CacheableTask | |
class ImageDefinitionsTask extends DefaultTask { | |
protected static final Pattern VARIABLE = ~/\$\{(.*?)}/ | |
/** The destination for the JSON file. */ | |
@OutputFile | |
File output | |
/** The name to resolve variables in docker files. */ | |
@Input | |
Map<String, CharSequence> environment = [:] | |
/** List of services to only include in the result. */ | |
@Input | |
@Optional | |
List<String> only = [] | |
@InputFiles | |
List<File> dockerComposeFiles = [] | |
@TaskAction | |
void createOutput() { | |
Map<String, CharSequence> images = [:] | |
Yaml parser = new Yaml() | |
dockerComposeFiles.each { File file -> | |
Map<String, Object> yaml = Collections.emptyMap() | |
file.withInputStream { yaml = parser.loadAs(it, Map) } | |
yaml['services']?.each { k,v -> | |
if (v.containsKey('image')) { | |
String image = v['image'].toString() | |
image = image.replaceAll(VARIABLE, { m, x -> interpolateSingle(x as String) } ) | |
images[k.toString()] = image | |
} | |
} | |
} | |
if (only) { | |
images = images.findAll { k,v -> only.contains(k) } | |
} | |
List model = [] | |
images.each { k, v -> model << [name: k, imageUri: v.toString()] } | |
output.parentFile.mkdirs() | |
output.text = JsonOutput.prettyPrint(JsonOutput.toJson(model)) | |
} | |
protected String interpolateSingle(String variable) { | |
if (!variable) { | |
return '' | |
} | |
String[] split = variable.split(':-') | |
String replacement = environment[split[0]] | |
if (replacement == null) { | |
replacement = (split.length > 1) ? split[1] : '' | |
} | |
replacement | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.json.JsonSlurper | |
import org.gradle.api.Project | |
import org.gradle.testfixtures.ProjectBuilder | |
import org.junit.Rule | |
import org.junit.rules.TemporaryFolder | |
import spock.lang.Specification | |
import spock.lang.Unroll | |
@Unroll | |
class ImageDefinitionsTaskSpec extends Specification { | |
@Rule | |
TemporaryFolder tmp = new TemporaryFolder() | |
Project project | |
void setup() { | |
project = ProjectBuilder.builder().withProjectDir(tmp.newFolder()).build() | |
} | |
void "single compose file no environment"() { | |
given: | |
File testFile = tmp.newFile() | |
project.tasks.create('imageDef', ImageDefinitionsTask) { | |
output = testFile | |
dockerComposeFiles = [new File('src/test/resources/imagedefinitions-compose1.yml')] | |
} | |
when: | |
project.evaluate() | |
project.tasks.imageDef.createOutput() | |
then: | |
new JsonSlurper().parse(testFile) == [ | |
[ | |
name: 'mongodb', | |
imageUri: 'pdouble16/autopilotpattern-mongodb:3.6.3-r3', | |
], | |
[ | |
name: 'api', | |
imageUri: '/api:latest', | |
], | |
[ | |
name: 'consul', | |
imageUri: 'pdouble16/autopilotpattern-consul:1.0.6-r1', | |
] | |
] | |
} | |
void "single compose file with environment"() { | |
given: | |
File testFile = tmp.newFile() | |
project.tasks.create('imageDef', ImageDefinitionsTask) { | |
output = testFile | |
dockerComposeFiles = [new File('src/test/resources/imagedefinitions-compose1.yml')] | |
environment = [ PRIVATE_DOCKER_REGISTRY: 'gatekeeper:5001', APPLICATION_VERSION: '1.2.3' ] | |
} | |
when: | |
project.evaluate() | |
project.tasks.imageDef.createOutput() | |
then: | |
new JsonSlurper().parse(testFile) == [ | |
[ | |
name: 'mongodb', | |
imageUri: 'pdouble16/autopilotpattern-mongodb:3.6.3-r3', | |
], | |
[ | |
name: 'api', | |
imageUri: 'gatekeeper:5001/api:1.2.3', | |
], | |
[ | |
name: 'consul', | |
imageUri: 'pdouble16/autopilotpattern-consul:1.0.6-r1', | |
] | |
] | |
} | |
void "multiple compose files"() { | |
given: | |
File testFile = tmp.newFile() | |
project.tasks.create('imageDef', ImageDefinitionsTask) { | |
output = testFile | |
dockerComposeFiles = [ | |
new File('src/test/resources/imagedefinitions-compose1.yml'), | |
new File('src/test/resources/imagedefinitions-compose2.yml') | |
] | |
environment = [ PRIVATE_DOCKER_REGISTRY: 'gatekeeper:5001', APPLICATION_VERSION: '1.2.3' ] | |
} | |
when: | |
project.evaluate() | |
project.tasks.imageDef.createOutput() | |
then: | |
new JsonSlurper().parse(testFile) == [ | |
[ | |
name: 'mongodb', | |
imageUri: 'pdouble16/autopilotpattern-mongodb:3.6.3-r3', | |
], | |
[ | |
name: 'api', | |
imageUri: 'gatekeeper:5001/api:1.2.3', | |
], | |
[ | |
name: 'consul', | |
imageUri: 'consul:latest', | |
] | |
] | |
} | |
void "single compose file with environment and subset of services"() { | |
given: | |
File testFile = tmp.newFile() | |
project.tasks.create('imageDef', ImageDefinitionsTask) { | |
output = testFile | |
only << 'api' | |
dockerComposeFiles = [new File('src/test/resources/imagedefinitions-compose1.yml')] | |
environment = [ PRIVATE_DOCKER_REGISTRY: 'gatekeeper:5001', APPLICATION_VERSION: '1.2.3' ] | |
} | |
when: | |
project.evaluate() | |
project.tasks.imageDef.createOutput() | |
then: | |
new JsonSlurper().parse(testFile) == [ | |
[ | |
name: 'api', | |
imageUri: 'gatekeeper:5001/api:1.2.3', | |
] | |
] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment