/** * 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