jenkins-pipeline-shared-libraries/vars/cloud.groovy (203 lines of code) (raw):

// Quay.io:BEGIN // /* * Make a quay.io image public if not already */ void makeQuayImagePublic(String namespace, String repository, Map credentials = [ 'token': '', 'usernamePassword': '' ]) { if (!isQuayImagePublic(namespace, repository, credentials) && !setQuayImagePublic(namespace, repository, credentials)) { error "Cannot set image quay.io/${namespace}/${repository} as visible" } } /* * Checks whether a quay image is public */ boolean isQuayImagePublic(String namespace, String repository, Map credentials = [ 'token': '', 'usernamePassword': '' ]) { def output = 'false' util.executeWithCredentialsMap(credentials) { output = sh(returnStdout: true, script: "curl -H 'Authorization: Bearer ${QUAY_TOKEN}' -X GET https://quay.io/api/v1/repository/${namespace}/${repository} | jq '.is_public'").trim() } return output == 'true' } /* * Sets a Quay repository as public * * return false if any problem occurs */ boolean setQuayImagePublic(String namespace, String repository, Map credentials = [ 'token': '', 'usernamePassword': '' ]) { def output = 'false' util.executeWithCredentialsMap(credentials) { output = sh(returnStdout: true, script: "curl -H 'Content-Type: application/json' -H 'Authorization: Bearer ${QUAY_TOKEN}' -X POST --data '{\"visibility\": \"public\"}' https://quay.io/api/v1/repository/${namespace}/${repository}/changevisibility | jq '.success'").trim() } return output == 'true' } /* * Update image description on quay.io * descriptionString = string content that will be the description of the image */ void updateQuayImageDescription(String descriptionString, String namespace, String repository, Map credentials = [ 'token': '', 'usernamePassword': '' ]) { util.executeWithCredentialsMap(credentials) { def json = [ description: descriptionString ] writeJSON(file: "description.json", json: json) archiveArtifacts(artifacts: 'description.json') sh(script: "curl -H 'Content-type: application/json' -H 'Authorization: Bearer ${QUAY_TOKEN}' -X PUT --data-binary '@description.json' https://quay.io/api/v1/repository/${namespace}/${repository}") } } // Quay.io:END // /* * Login to given OpenShift API */ void loginOpenShift(String openShiftAPI, String openShiftCredsId) { withCredentials([usernamePassword(credentialsId: openShiftCredsId, usernameVariable: 'OC_USER', passwordVariable: 'OC_PWD')]) { sh "oc login --username=${OC_USER} --password=${OC_PWD} --server=${openShiftAPI} --insecure-skip-tls-verify" } } /* * Login to current OpenShift registry * * It considers that you are already authenticated to OpenShift */ void loginOpenShiftRegistry(String containerEngine = 'docker', String containerEngineTlsOptions = '') { // username can be anything. See https://docs.openshift.com/container-platform/4.4/registry/accessing-the-registry.html#registry-accessing-directly_accessing-the-registry sh "set +x && ${containerEngine} login -u anything -p \$(oc whoami -t) ${containerEngineTlsOptions} ${getOpenShiftRegistryURL()}" } /* * Retrieve the OpenShift registry URL * * It considers that you are already authenticated to OpenShift */ String getOpenShiftRegistryURL() { return sh(returnStdout: true, script: "oc get routes -n openshift-image-registry | tail -1 | awk '{print \$2}'")?.trim() } /* * Login to a container registry */ void loginContainerRegistry(String registry, String userCredsId, String tokenCredsId, String containerEngine = 'docker', String containerEngineTlsOptions = '') { withCredentials([string(credentialsId: userCredsId, variable: 'REGISTRY_USER')]) { withCredentials([string(credentialsId: tokenCredsId, variable: 'REGISTRY_TOKEN')]) { sh """ echo "${REGISTRY_TOKEN}" | ${containerEngine} login -u "${REGISTRY_USER}" --password-stdin ${containerEngineTlsOptions} ${registry} """.trim() } } } void pullImage(String imageTag, int retries = 3, String containerEngine = 'docker', String containerEngineTlsOptions = '') { retry(retries) { sh "${containerEngine} pull ${containerEngineTlsOptions} ${imageTag}" } } void pushImage(String imageTag, int retries = 3, String containerEngine = 'docker', String containerEngineTlsOptions = '') { retry(retries) { sh "${containerEngine} push ${containerEngineTlsOptions} ${imageTag}" } } void tagImage(String oldImageTag, String newImageTag, String containerEngine = 'docker') { sh "${containerEngine} tag ${oldImageTag} ${newImageTag}" } /* * Get reduced tag, aka X.Y, from the given tag */ String getReducedTag(String originalTag) { try { String[] versionSplit = originalTag.split("\\.") return "${versionSplit[0]}.${versionSplit[1]}" } catch (err) { println "[ERROR] ${originalTag} cannot be reduced to the format X.Y" throw err } } /* * Cleanup all containers and images */ void cleanContainersAndImages(String containerEngine = 'podman') { println '[INFO] Cleaning up running containers and images. Any error here can be ignored' sh(script: "${containerEngine } ps -a -q | tr '\\n' ','", returnStdout: true).trim().split(',').findAll { it != '' }.each { sh "${containerEngine} rm -f ${it} || date" } sh(script: "${containerEngine } images -q | tr '\\n' ','", returnStdout: true).trim().split(',').findAll { it != '' }.each { sh "${containerEngine} rmi -f ${it} || date" } } /* * Start local docker registry * * Accessible on `localhost:${port}`. Default port is 5000. */ String startLocalRegistry(int port = 5000) { cleanLocalRegistry(port) sh "docker run -d -p ${port}:5000 --restart=always --name registry-${port} registry:2" sh 'docker ps' return "localhost:${port}" } /* * Find an open local port. */ int findFreePort() { return Integer.valueOf(sh( script: """ echo \$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()') """, returnStdout: true ).trim()) } /* * Clean local registry */ void cleanLocalRegistry(int port = 5000) { sh "docker rm -f registry-${port} || true" sh 'docker ps' } /* * Squash a docker image * * If `replaceCurrentImage` is disabled, the `-squashed` suffix is added to the returned image name */ String dockerSquashImage(String baseImage, String squashMessage = "${baseImage} squashed", boolean replaceCurrentImage = true) { String squashedPlatformImage = replaceCurrentImage ? "${baseImage}" : "${baseImage}-squashed" // Squash images def nbLayers = Integer.parseInt(sh(returnStdout: true, script: "docker history ${baseImage} | grep buildkit.dockerfile | wc -l").trim()) nbLayers++ // Get the next layer not done by buildkit echo "Got ${nbLayers} layers to squash" // Use message option in docker-squash due to https://github.com/goldmann/docker-squash/issues/220 def dockerSquashShellCmd = "docker-squash -v -m '${squashMessage}' -f ${nbLayers} -t ${squashedPlatformImage} ${baseImage}" sh dockerSquashShellCmd sh "docker push ${squashedPlatformImage}" return squashedPlatformImage } /* * Print some debugging for a specific image */ void dockerDebugImage(String imageTag) { sh 'docker images' sh "docker history ${imageTag}" sh "docker inspect ${imageTag}" } // Multiplatform build:BEGIN // /* * Build an image for multiple platforms and create a manifest to gather under a same name * * You should have run `prepareForDockerMultiplatformBuild` method before executing this method */ void dockerBuildMultiPlatformImages(String buildImageTag, List platforms, boolean squashImages = true, String squashMessage = "Squashed ${buildImageTag}", boolean debug = false, boolean outputToFile = false) { // Build image locally in tgz file List buildPlatformImages = platforms.collect { platform -> String os_arch = platform.replaceAll('/', '-') String platformImage = "${buildImageTag}-${os_arch}" String finalPlatformImage = platformImage // Build dockerBuildPlatformImage(platformImage, platform, outputToFile) if (debug) { dockerDebugImage(platformImage) } if (squashImages) { finalPlatformImage = dockerSquashImage(platformImage, squashMessage) if (debug) { dockerDebugImage(platformImage) } } return finalPlatformImage } dockerCreateManifest(buildImageTag, buildPlatformImages) if (debug) { dockerDebugImage(buildImageTag) } } /* * Build an image for a specific platform * * You should have run `prepareForDockerMultiplatformBuild` method before executing this method */ void dockerBuildPlatformImage(String buildImageTag, String platform, boolean outputToFile = false) { def logFileName = (buildImageTag + '-' + platform + '-build.log') .replaceAll('/','_') .replaceAll(':','_') sh "docker buildx build --push --sbom=false --provenance=false --platform ${platform} -t ${buildImageTag} .${outputToFile ? ' 2> ' + "${WORKSPACE}/${logFileName}" : ''}" sh "docker buildx imagetools inspect ${buildImageTag}" sh "docker pull --platform ${platform} ${buildImageTag}" } /* * Create a multiplatform manifest based on the given images */ void dockerCreateManifest(String buildImageTag, List manifestImages) { sh "docker manifest rm ${buildImageTag} || true" sh "docker manifest create ${buildImageTag} --insecure ${manifestImages.join(' ')}" sh "docker manifest push ${buildImageTag}" } /* * Prepare the node for Docker multiplatform build * * Each element of the `mirrorRegistriesConfig` should contain: * - name: Name of the registry to mirror * - mirrors: List of mirrors for that registry, containing: * - url: mirror url * - insecure: whether the mirror is insecure */ void prepareForDockerMultiplatformBuild(List insecureRegistries = [], List mirrorRegistriesConfig = [], boolean debug = false) { cleanDockerMultiplatformBuild(debug) // For multiplatform build sh 'docker run --rm --privileged --name binfmt docker.io/tonistiigi/binfmt --install all' if (debug) { debugDockerMultiplatformBuild() } String buildkitdtomlConfig = "debug = ${debug}\n" insecureRegistries.each { buildkitdtomlConfig += "${getBuildkitRegistryConfigStr(it, true)}" } mirrorRegistriesConfig.each { mirrorRegistryCfg -> buildkitdtomlConfig += "[registry.\"${ mirrorRegistryCfg.name }\"]\n" buildkitdtomlConfig += "mirrors = [${ mirrorRegistryCfg.mirrors.collect { "\"${it.url }\"" }.join(',')}]\n" mirrorRegistryCfg.mirrors.each { mirror -> buildkitdtomlConfig += "${getBuildkitRegistryConfigStr(mirror.url, mirror.insecure)}" } } writeFile(file: 'buildkitd.toml', text: buildkitdtomlConfig) if (debug) { sh 'cat buildkitd.toml' } sh 'docker buildx create --name mybuilder --driver docker-container --driver-opt network=host --bootstrap --config ${WORKSPACE}/buildkitd.toml' sh 'docker buildx use mybuilder' if (debug) { debugDockerMultiplatformBuild() } } String getBuildkitRegistryConfigStr(String registryURL, boolean insecure) { return """[registry."${registryURL}"] http = ${insecure} """ } /* * Return the mirror registry config for `docker.io` * * This checks for internal registry defined as env `DOCKER_REGISTRY_MIRROR`. * Fallback to `mirror.gcr.io` is none defined. */ Map getDockerIOMirrorRegistryConfig() { return [ name: 'docker.io', mirrors: [ [ url : env.DOCKER_REGISTRY_MIRROR ?: 'mirror.gcr.io', insecure: env.DOCKER_REGISTRY_MIRROR ? true : false, ] ], ] } /* * Helpful commands to debug `docker buildx` preparation */ void debugDockerMultiplatformBuild() { sh 'docker context ls' sh 'docker buildx inspect' sh 'docker buildx ls' } /* * Clean the node from Docker multiplatform configuration */ void cleanDockerMultiplatformBuild(boolean debug = false) { sh 'docker buildx rm mybuilder || true' sh 'docker rm -f binfmt || true' if (debug) { debugDockerMultiplatformBuild() } } // Multiplatform build:END // void skopeoCopyRegistryImages(String oldImageName, String newImageName, int retries = 3) { sh "skopeo copy --retry-times ${retries} --tls-verify=false --all docker://${oldImageName} docker://${newImageName}" }