Files
spicy-automation/vars/dockerUtils.groovy
Ryan Wilson 68684df471 Initial commit: Spicy CDK automation framework
Jenkins shared library and CDK constructs for AWS infrastructure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-11-18 22:21:00 -08:00

220 lines
6.7 KiB
Groovy

/**
* Docker utilities for Spicy CDK pipelines
* Pushes images to Nexus repository (Sonatype)
*/
// Default Nexus configuration
def getDefaultConfig() {
return [
registry: 'nexus.kodeniks.com',
repository: 'docker-hosted',
credentialsId: 'kodeniks-nexus-repository'
]
}
def getImageRef(Map args, String tag) {
def config = getDefaultConfig() + args
return "${config.registry}/${config.repository}/${args.imageName}:${tag}"
}
def getImageTag(Map args) {
return args.imageTag ?: gitUtils.getShortSHA()
}
def getImageRefSHA(Map args) {
return getImageRef(args, getImageTag(args))
}
def getImageRefLatest(Map args) {
return getImageRef(args, 'latest')
}
def copyFromImage(Map args) {
sh(
script: """#!/bin/bash +x
set -e
IMG_ID=\$(dd if=/dev/urandom bs=1k count=1 2> /dev/null | LC_CTYPE=C tr -cd "a-z0-9" | cut -c 1-22)
docker create --name \${IMG_ID} ${args.imageID}
docker cp \${IMG_ID}:${args.dockerPath} ${args.jenkinsPath}
docker rm \${IMG_ID}
"""
)
}
def getContainerName(Map args) {
return "${args.imageName}-${gitUtils.getShortSHA()}"
}
/**
* Build a Docker image
* @param args.imageName - Name of the image
* @param args.dockerfile - Path to Dockerfile (default: 'Dockerfile')
* @param args.context - Build context (default: '.')
* @param args.buildArgs - Map of build arguments (optional)
*/
def buildImage(Map args) {
def containerName = getContainerName(args)
def dockerfile = args.dockerfile ?: 'Dockerfile'
def context = args.context ?: '.'
def buildArgsString = ''
if (args.buildArgs) {
buildArgsString = args.buildArgs.collect { k, v -> "--build-arg ${k}=${v}" }.join(' ')
}
sh("docker build -t ${containerName} -f ${dockerfile} ${buildArgsString} ${context}")
return containerName
}
/**
* Tag image for Nexus registry
* @param args.imageName - Name of the image
* @param args.tagLatest - Whether to also tag as 'latest' (default: true on main branch)
*/
def tagImage(Map args) {
def containerName = getContainerName(args)
def tagLatest = args.tagLatest != null ? args.tagLatest : gitUtils.isMain()
def shaImageRef = getImageRefSHA(args)
sh("docker tag ${containerName} ${shaImageRef}")
if (tagLatest) {
// Tag latest from the SHA-tagged image to ensure they point to the same image
sh("docker tag ${shaImageRef} ${getImageRefLatest(args)}")
}
}
/**
* Push image to Nexus registry
* Optionally compares with remote 'latest' to skip push if unchanged
* @param args.imageName - Name of the image
* @param args.credentialsId - Jenkins credentials ID for Nexus (default: 'kodeniks-nexus-repository')
* @param args.registry - Nexus registry URL (default: 'nexus.kodeniks.com')
* @param args.repository - Nexus repository name (default: 'docker-hosted')
* @param args.tagLatest - Whether to also push 'latest' tag (default: true on main branch)
* @param args.compareWithLatest - Skip push if image matches remote 'latest' (default: true)
*/
def pushImage(Map args) {
def config = getDefaultConfig() + args
def tagLatest = args.tagLatest != null ? args.tagLatest : gitUtils.isMain()
def compareWithLatest = args.compareWithLatest != null ? args.compareWithLatest : true
def localImageId = sh(
script: "docker inspect -f '{{.Id}}' ${getImageRefSHA(args)}",
returnStdout: true
).trim()
echo "Local image ID: ${localImageId}"
def remoteImageId = ""
if (compareWithLatest && tagLatest) {
withCredentials([usernamePassword(
credentialsId: config.credentialsId,
usernameVariable: 'NEXUS_USER',
passwordVariable: 'NEXUS_PASS'
)]) {
try {
sh "echo \$NEXUS_PASS | docker login ${config.registry} -u \$NEXUS_USER --password-stdin"
sh "docker pull ${getImageRefLatest(args)}"
remoteImageId = sh(
script: "docker inspect -f '{{.Id}}' ${getImageRefLatest(args)}",
returnStdout: true
).trim()
echo "Remote (latest) image ID: ${remoteImageId}"
} catch (Exception e) {
echo "Could not pull remote 'latest' image (might be the first build): ${e.getMessage()}"
}
}
}
if (remoteImageId && localImageId == remoteImageId) {
echo "No changes detected (new image is identical to remote 'latest'). Skipping push."
return false
}
// Re-tag latest from SHA image right before pushing
// This ensures latest points to the new image even if we pulled the old remote latest for comparison
if (tagLatest) {
sh("docker tag ${getImageRefSHA(args)} ${getImageRefLatest(args)}")
}
withCredentials([usernamePassword(
credentialsId: config.credentialsId,
usernameVariable: 'NEXUS_USER',
passwordVariable: 'NEXUS_PASS'
)]) {
sh "echo \$NEXUS_PASS | docker login ${config.registry} -u \$NEXUS_USER --password-stdin"
sh "docker push ${getImageRefSHA(args)}"
if (tagLatest) {
sh "docker push ${getImageRefLatest(args)}"
}
}
return true
}
/**
* Build and push a Docker image to Nexus
* @param args.imageName - Name of the image (required)
* @param args.dockerfile - Path to Dockerfile (default: 'Dockerfile')
* @param args.context - Build context (default: '.')
* @param args.buildArgs - Map of build arguments (optional)
* @param args.credentialsId - Jenkins credentials ID for Nexus (default: 'kodeniks-nexus-repository')
* @param args.registry - Nexus registry URL (default: 'nexus.kodeniks.com')
* @param args.repository - Nexus repository name (default: 'docker-hosted')
* @param args.tagLatest - Whether to also tag/push 'latest' (default: true on main branch)
* @param args.compareWithLatest - Skip push if image matches remote 'latest' (default: true)
* @return The full image reference (registry/repo/name:tag)
*/
def buildAndPush(Map args) {
buildImage(args)
tagImage(args)
pushImage(args)
return getImageRefSHA(args)
}
/**
* Prune dangling Docker images
* @param args.pruneCache - Whether to also prune build cache (default: false)
*/
def prune(Map args = [:]) {
def pruneCache = args.pruneCache ?: false
echo "Cleaning up dangling Docker images..."
if (pruneCache) {
sh 'docker system prune -f'
} else {
// Only prune dangling images, not build cache
sh 'docker image prune -f'
}
}
/**
* Login to Nexus registry
*/
def login(Map args = [:]) {
def config = getDefaultConfig() + args
withCredentials([usernamePassword(
credentialsId: config.credentialsId,
usernameVariable: 'NEXUS_USER',
passwordVariable: 'NEXUS_PASS'
)]) {
sh "echo \$NEXUS_PASS | docker login ${config.registry} -u \$NEXUS_USER --password-stdin"
}
}
/**
* Pull an image from Nexus
*/
def pullImage(Map args) {
def config = getDefaultConfig() + args
def tag = args.tag ?: 'latest'
def imageRef = getImageRef(args, tag)
login(config)
sh "docker pull ${imageRef}"
return imageRef
}
return this