diff --git a/test/groovy/BuildDockerAndPublishImageStepTests.groovy b/test/groovy/BuildDockerAndPublishImageStepTests.groovy index 0c8d8367f..4aaa885af 100644 --- a/test/groovy/BuildDockerAndPublishImageStepTests.groovy +++ b/test/groovy/BuildDockerAndPublishImageStepTests.groovy @@ -562,4 +562,38 @@ class BuildDockerAndPublishImageStepTests extends BaseTest { // And all mocked/stubbed methods have to be called verifyMocks() } + + @Test + void itBuildsAndDeploysWithPlatformsSpecificOnPrincipalBranch() throws Exception { + def script = loadScript(scriptName) + mockPrincipalBranch() + withMocks{ + script.call(testImageName, [ + dockerfile: 'build.Dockerfile', + imageDir: 'docker/', + platforms: ['linux/amd64', 'linux/arm64'], + automaticSemanticVersioning: true, + gitCredentials: 'git-creds', + registryNamespace: 'jenkins', + ]) + } + final String expectedImageName = 'jenkins/' + testImageName + printCallStack() + // Then we expect a successful build with the code cloned + assertJobStatusSuccess() + // With the common workflow run as expected + assertTrue(assertBaseWorkflow()) + assertTrue(assertMethodCallContainsPattern('node', 'docker')) + // And the environement variables set with the custom configuration values + assertTrue(assertMethodCallContainsPattern('withEnv', 'IMAGE_DIR=docker/')) + assertTrue(assertMethodCallContainsPattern('withEnv', 'IMAGE_DOCKERFILE=build.Dockerfile')) + assertTrue(assertMethodCallContainsPattern('withEnv', 'IMAGE_PLATFORM=linux/amd64')) + assertTrue(assertMethodCallContainsPattern('withEnv', 'IMAGE_PLATFORM=linux/arm64')) + assertTrue(assertMethodCallContainsPattern('withEnv', 'IMAGE_NAME=' + expectedImageName)) + // But no tag and no deploy called (branch or PR) + assertTrue(assertMakeDeploy(expectedImageName)) + assertTrue(assertTagPushed(defaultGitTag)) + // And all mocked/stubbed methods have to be called + verifyMocks() + } } diff --git a/vars/buildDockerAndPublishImage.groovy b/vars/buildDockerAndPublishImage.groovy index 26f6012d3..c7d2154ec 100644 --- a/vars/buildDockerAndPublishImage.groovy +++ b/vars/buildDockerAndPublishImage.groovy @@ -4,12 +4,14 @@ import java.util.Date import java.text.DateFormat def call(String imageShortName, Map userConfig=[:]) { + def parallelStages = [failFast:false] def defaultConfig = [ agentLabels: 'docker || linux-amd64-docker', // String expression for the labels the agent must match automaticSemanticVersioning: false, // Do not automagically increase semantic version by default includeImageNameInTag: false, // Set to true for multiple semversioned images built in parallel, will include the image name in tag to avoid conflict dockerfile: 'Dockerfile', // Obvious default platform: 'linux/amd64', // Intel/AMD 64 Bits, following Docker platform identifiers + platforms: [], // Docker platform identifiers nextVersionCommand: 'jx-release-version', // Commmand line used to retrieve the next version gitCredentials: 'github-app-infra', // Credential ID for tagging and creating release imageDir: '.', // Relative path to the context directory for the Docker build @@ -32,249 +34,340 @@ def call(String imageShortName, Map userConfig=[:]) { final Date now = new Date() DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX") final String buildDate = dateFormat.format(now) - - // Warn about potential Linux/Windows contradictions between platform & agentLabels, and set the Windows config suffix for CST files - String cstConfigSuffix = '' - if (finalConfig.agentLabels.contains('windows') || finalConfig.platform.contains('windows')) { - if (finalConfig.agentLabels.contains('windows') && !finalConfig.platform.contains('windows')) { - echo "WARNING: A 'windows' agent is requested, but the 'platform' is set to '${finalConfig.platform}'." - } - if (!finalConfig.agentLabels.contains('windows') && finalConfig.platform.contains('windows')) { - echo "WARNING: The 'platform' is set to '${finalConfig.platform}', but there isn't any 'windows' agent requested." - } - cstConfigSuffix = '-windows' + String nextVersion = '' //global + boolean flagmultiplatforms = false + if (finalConfig.platforms.size() > 0) { + echo "INFO: Using platforms from the pipeline configuration" + flagmultiplatforms = true + } else { + echo "INFO: Using platform from the pipeline configuration" + finalConfig.platforms = [finalConfig.platform] } - String operatingSystem = finalConfig.platform.split('/')[0] + final InfraConfig infraConfig = new InfraConfig(env) final String defaultRegistryNamespace = infraConfig.getDockerRegistryNamespace() final String registryNamespace = finalConfig.registryNamespace ?: defaultRegistryNamespace - final String imageName = registryNamespace + '/' + imageShortName - echo "INFO: Resolved Container Image Name: ${imageName}" + final String defaultImageName = registryNamespace + '/' + imageShortName + final String mygetTime = now.getTime().toString() - node(finalConfig.agentLabels) { - withEnv([ - "BUILD_DATE=${buildDate}", - "IMAGE_NAME=${imageName}", - "IMAGE_DIR=${finalConfig.imageDir}", - "IMAGE_DOCKERFILE=${finalConfig.dockerfile}", - "IMAGE_PLATFORM=${finalConfig.platform}", - ]) { - infra.withDockerPullCredentials{ - String nextVersion = '' - stage("Prepare ${imageName}") { - checkout scm - if (finalConfig.unstash != '') { - unstash finalConfig.unstash - } + finalConfig.platforms.each {oneplatform -> - // The makefile to use must come from the pipeline to avoid a nasty user trying to exfiltrate data from the build - // Even though we have mitigation through the multibranch job config allowing to build PRs only from the repository contributors - writeFile file: 'Makefile', text: makefileContent - } // stage + echo "DEBUG platform in build '${oneplatform}'." - // Automatic tagging on principal branch is not enabled by default, show potential next version in PR anyway - if (finalConfig.automaticSemanticVersioning) { - stage("Get Next Version of ${imageName}") { - String imageInTag = '-' + imageName.replace('-','').replace(':','').toLowerCase() - if (isUnix()) { - sh 'git fetch --all --tags' // Ensure that all the tags are retrieved (uncoupling from job configuration, wether tags are fetched or not) - if (!finalConfig.includeImageNameInTag) { - nextVersion = sh(script: finalConfig.nextVersionCommand, returnStdout: true).trim() - } else { - echo "Including the image name '${imageName}' in the next version" - // Retrieving the semver part from the last tag including the image name - String currentTagScript = 'git tag --list \"*' + imageInTag + '\" --sort=-v:refname | head -1' - String currentSemVerVersion = sh(script: currentTagScript, returnStdout: true).trim() - echo "Current semver version is '${currentSemVerVersion}'" - // Set a default value if there isn't any tag for the current image yet (https://groovy-lang.org/operators.html#_elvis_operator) - currentSemVerVersion = currentSemVerVersion ?: '0.0.0-' + imageInTag - String nextVersionScript = finalConfig.nextVersionCommand + ' -debug --previous-version=' + currentSemVerVersion - String nextVersionSemVerPart = sh(script: nextVersionScript, returnStdout: true).trim() - echo "Next semver version part is '${nextVersionSemVerPart}'" - nextVersion = nextVersionSemVerPart + imageInTag + // Warn about potential Linux/Windows contradictions between platform & agentLabels, and set the Windows config suffix for CST files + String cstConfigSuffix = '' + if (finalConfig.agentLabels.contains('windows') || oneplatform.contains('windows')) { + if (finalConfig.agentLabels.contains('windows') && !oneplatform.contains('windows')) { + echo "WARNING: A 'windows' agent is requested, but the 'platform' is set to '${oneplatform}'." + } + if (!finalConfig.agentLabels.contains('windows') && oneplatform.contains('windows')) { + echo "WARNING: The 'platform' is set to '${oneplatform}', but there isn't any 'windows' agent requested." + } + cstConfigSuffix = '-windows' + } + String operatingSystem = oneplatform.split('/')[0] + + // in case of multi plafforms, we need to add the platform to the image name to be able to amend the image build + String imageName = defaultImageName + if (flagmultiplatforms) { + imageName = defaultImageName + ':' + oneplatform.split('/')[1].replace('/','-') + } + + echo "INFO: Resolved Container Image Name: ${imageName}" + parallelStages["${imageName}"] = { + node(finalConfig.agentLabels) { + withEnv([ + "BUILD_DATE=${buildDate}", + "IMAGE_NAME=${imageName}", + "IMAGE_DIR=${finalConfig.imageDir}", + "IMAGE_DOCKERFILE=${finalConfig.dockerfile}", + "IMAGE_PLATFORM=${oneplatform}", + ]) { + infra.withDockerPullCredentials{ + nextVersion = '' // reset for each turn + stage("Prepare ${imageName}") { + checkout scm + if (finalConfig.unstash != '') { + unstash finalConfig.unstash } - } else { - powershell 'git fetch --all --tags' // Ensure that all the tags are retrieved (uncoupling from job configuration, wether tags are fetched or not) - if (!finalConfig.includeImageNameInTag) { - nextVersion = powershell(script: finalConfig.nextVersionCommand, returnStdout: true).trim() - } else { - echo "Including the image name '${imageName}' in the next version" - // Retrieving the semver part from the last tag including the image name - String currentTagScript = 'git tag --list \"*' + imageInTag + '\" --sort=-v:refname | head -1' - String currentSemVerVersion = powershell(script: currentTagScript, returnStdout: true).trim() - echo "Current semver version is '${currentSemVerVersion}'" - // Set a default value if there isn't any tag for the current image yet (https://groovy-lang.org/operators.html#_elvis_operator) - currentSemVerVersion = currentSemVerVersion ?: '0.0.0-' + imageInTag - String nextVersionScript = finalConfig.nextVersionCommand + ' -debug --previous-version=' + currentSemVerVersion - String nextVersionSemVerPart = powershell(script: nextVersionScript, returnStdout: true).trim() - echo "Next semver version part is '${nextVersionSemVerPart}'" - nextVersion = nextVersionSemVerPart + imageInTag + + // The makefile to use must come from the pipeline to avoid a nasty user trying to exfiltrate data from the build + // Even though we have mitigation through the multibranch job config allowing to build PRs only from the repository contributors + writeFile file: 'Makefile', text: makefileContent + } // stage + + // Automatic tagging on principal branch is not enabled by default, show potential next version in PR anyway + if (finalConfig.automaticSemanticVersioning) { + stage("Get Next Version of ${defaultImageName}") { + String imageInTag = '-' + defaultImageName.replace('-','').replace(':','').toLowerCase() + if (isUnix()) { + sh 'git fetch --all --tags' // Ensure that all the tags are retrieved (uncoupling from job configuration, wether tags are fetched or not) + if (!finalConfig.includeImageNameInTag) { + nextVersion = sh(script: finalConfig.nextVersionCommand, returnStdout: true).trim() + } else { + echo "Including the image name '${defaultImageName}' in the next version" + // Retrieving the semver part from the last tag including the image name + String currentTagScript = 'git tag --list \"*' + imageInTag + '\" --sort=-v:refname | head -1' + String currentSemVerVersion = sh(script: currentTagScript, returnStdout: true).trim() + echo "Current semver version is '${currentSemVerVersion}'" + // Set a default value if there isn't any tag for the current image yet (https://groovy-lang.org/operators.html#_elvis_operator) + currentSemVerVersion = currentSemVerVersion ?: '0.0.0-' + imageInTag + String nextVersionScript = finalConfig.nextVersionCommand + ' -debug --previous-version=' + currentSemVerVersion + String nextVersionSemVerPart = sh(script: nextVersionScript, returnStdout: true).trim() + echo "Next semver version part is '${nextVersionSemVerPart}'" + nextVersion = nextVersionSemVerPart + imageInTag + } + } else { + powershell 'git fetch --all --tags' // Ensure that all the tags are retrieved (uncoupling from job configuration, wether tags are fetched or not) + if (!finalConfig.includeImageNameInTag) { + nextVersion = powershell(script: finalConfig.nextVersionCommand, returnStdout: true).trim() + } else { + echo "Including the image name '${defaultImageName}' in the next version" + // Retrieving the semver part from the last tag including the image name + String currentTagScript = 'git tag --list \"*' + imageInTag + '\" --sort=-v:refname | head -1' + String currentSemVerVersion = powershell(script: currentTagScript, returnStdout: true).trim() + echo "Current semver version is '${currentSemVerVersion}'" + // Set a default value if there isn't any tag for the current image yet (https://groovy-lang.org/operators.html#_elvis_operator) + currentSemVerVersion = currentSemVerVersion ?: '0.0.0-' + imageInTag + String nextVersionScript = finalConfig.nextVersionCommand + ' -debug --previous-version=' + currentSemVerVersion + String nextVersionSemVerPart = powershell(script: nextVersionScript, returnStdout: true).trim() + echo "Next semver version part is '${nextVersionSemVerPart}'" + nextVersion = nextVersionSemVerPart + imageInTag + } + } + echo "Next Release Version = ${nextVersion}" + } // stage + } // if + + stage("Lint ${imageName}") { + // Define the image name as prefix to support multi images per pipeline + String hadolintReportId = "${imageName.replaceAll(':','-').replaceAll('/','-')}-hadolint-${mygetTime}" + String hadoLintReportFile = "${hadolintReportId}.json" + withEnv(["HADOLINT_REPORT=${env.WORKSPACE}/${hadoLintReportFile}"]) { + try { + if (isUnix()) { + sh 'make lint' + } else { + powershell 'make lint' + } + } finally { + recordIssues( + enabledForFailure: true, + aggregatingResults: false, + tool: hadoLint(id: hadolintReportId, pattern: hadoLintReportFile) + ) + } } - } - echo "Next Release Version = ${nextVersion}" - } // stage - } // if + } // stage - stage("Lint ${imageName}") { - // Define the image name as prefix to support multi images per pipeline - String hadolintReportId = "${imageName.replaceAll(':','-').replaceAll('/','-')}-hadolint-${now.getTime()}" - String hadoLintReportFile = "${hadolintReportId}.json" - withEnv(["HADOLINT_REPORT=${env.WORKSPACE}/${hadoLintReportFile}"]) { - try { + stage("Build ${imageName}") { if (isUnix()) { - sh 'make lint' + sh 'make build' } else { - powershell 'make lint' + powershell 'make build' } - } finally { - recordIssues( - enabledForFailure: true, - aggregatingResults: false, - tool: hadoLint(id: hadolintReportId, pattern: hadoLintReportFile) - ) - } - } - } // stage + } //stage - stage("Build ${imageName}") { - if (isUnix()) { - sh 'make build' - } else { - powershell 'make build' - } - } //stage + // There can be 2 kind of tests: per image and per repository + // Assuming Windows versions of cst configuration files finishing by "-windows" (e.g. "common-cst-windows.yml") + [ + 'Image Test Harness': "${finalConfig.imageDir}/cst${cstConfigSuffix}.yml", + 'Common Test Harness': "${env.WORKSPACE}/common-cst${cstConfigSuffix}.yml" + ].each { testName, testHarness -> + if (fileExists(testHarness)) { + stage("Test ${testName} for ${imageName}") { + withEnv(["TEST_HARNESS=${testHarness}"]) { + if (isUnix()) { + sh 'make test' + } else { + powershell 'make test' + } + } // withEnv + } //stage + } else { + echo "Skipping test ${testName} for ${imageName} as ${testHarness} does not exist" + } // if else + } // each - // There can be 2 kind of tests: per image and per repository - // Assuming Windows versions of cst configuration files finishing by "-windows" (e.g. "common-cst-windows.yml") - [ - 'Image Test Harness': "${finalConfig.imageDir}/cst${cstConfigSuffix}.yml", - 'Common Test Harness': "${env.WORKSPACE}/common-cst${cstConfigSuffix}.yml" - ].each { testName, testHarness -> - if (fileExists(testHarness)) { - stage("Test ${testName} for ${imageName}") { - withEnv(["TEST_HARNESS=${testHarness}"]) { + // Automatic tagging on principal branch is not enabled by default + // not on multiplatforms builds + if (semVerEnabledOnPrimaryBranch && !flagmultiplatforms) { + stage("Semantic Release of ${defaultImageName}") { + echo "Configuring credential.helper" + // The credential.helper will execute everything after the '!', here echoing the username, the password and an empty line to be passed to git as credentials when git needs it. if (isUnix()) { - sh 'make test' + sh 'git config --local credential.helper "!set -u; echo username=\\$GIT_USERNAME && echo password=\\$GIT_PASSWORD && echo"' } else { - powershell 'make test' + // Using 'bat' here instead of 'powershell' to avoid variable interpolation problem with $ + bat 'git config --local credential.helper "!sh.exe -c \'set -u; echo username=$GIT_USERNAME && echo password=$GIT_PASSWORD && echo"\'' } - } // withEnv - } //stage - } else { - echo "Skipping test ${testName} for ${imageName} as ${testHarness} does not exist" - } // if else - } // each - // Automatic tagging on principal branch is not enabled by default - if (semVerEnabledOnPrimaryBranch) { - stage("Semantic Release of ${imageName}") { - echo "Configuring credential.helper" - // The credential.helper will execute everything after the '!', here echoing the username, the password and an empty line to be passed to git as credentials when git needs it. - if (isUnix()) { - sh 'git config --local credential.helper "!set -u; echo username=\\$GIT_USERNAME && echo password=\\$GIT_PASSWORD && echo"' - } else { - // Using 'bat' here instead of 'powershell' to avoid variable interpolation problem with $ - bat 'git config --local credential.helper "!sh.exe -c \'set -u; echo username=$GIT_USERNAME && echo password=$GIT_PASSWORD && echo"\'' - } + withCredentials([ + usernamePassword(credentialsId: "${finalConfig.gitCredentials}", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME') + ]) { + withEnv(["NEXT_VERSION=${nextVersion}"]) { + echo "Tagging and pushing the new version: ${nextVersion}" + if (isUnix()) { + sh ''' + git config user.name "${GIT_USERNAME}" + git config user.email "jenkins-infra@googlegroups.com" - withCredentials([ - usernamePassword(credentialsId: "${finalConfig.gitCredentials}", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME') - ]) { - withEnv(["NEXT_VERSION=${nextVersion}"]) { - echo "Tagging and pushing the new version: ${nextVersion}" - if (isUnix()) { - sh ''' - git config user.name "${GIT_USERNAME}" - git config user.email "jenkins-infra@googlegroups.com" + git tag -a "${NEXT_VERSION}" -m "${IMAGE_NAME}" + git push origin --tags + ''' + } else { + powershell ''' + git config user.email "jenkins-infra@googlegroups.com" + git config user.password $env:GIT_PASSWORD + + git tag -a "$env:NEXT_VERSION" -m "$env:IMAGE_NAME" + git push origin --tags + ''' + } + } // withEnv + } // withCredentials + } // stage + } // if + }// withDockerPullCredentials + infra.withDockerPushCredentials{ + if (env.TAG_NAME || env.BRANCH_IS_PRIMARY) { + stage("Deploy ${imageName}") { + String imageDeployName = imageName + if (env.TAG_NAME) { + // User could specify a tag in the image name. In that case the git tag is appended. Otherwise the docker tag is set to the git tag. + if (imageDeployName.contains(':')) { + imageDeployName += "-${env.TAG_NAME}" + } else { + imageDeployName += ":${env.TAG_NAME}" + } + } + + withEnv(["IMAGE_DEPLOY_NAME=${imageDeployName}"]) { + // Please note that "make deploy" uses the environment variable "IMAGE_DEPLOY_NAME" + if (isUnix()) { + sh 'make deploy' + } else { + powershell 'make deploy' + } + } // withEnv + } //stage + } // if + } // withDockerPushCredentials - git tag -a "${NEXT_VERSION}" -m "${IMAGE_NAME}" - git push origin --tags + + if (env.TAG_NAME && finalConfig.automaticSemanticVersioning) { + stage('GitHub Release') { + withCredentials([ + usernamePassword(credentialsId: "${finalConfig.gitCredentials}", passwordVariable: 'GITHUB_TOKEN', usernameVariable: 'GITHUB_USERNAME') + ]) { + String release = '' + if (isUnix()) { + final String releaseScript = ''' + originUrlWithGit="$(git remote get-url origin)" + originUrl="${originUrlWithGit%.git}" + org="$(echo "${originUrl}" | cut -d'/' -f4)" + repository="$(echo "${originUrl}" | cut -d'/' -f5)" + releasesUrl="/repos/${org}/${repository}/releases" + releaseId="$(gh api "${releasesUrl}" | jq -e -r '[ .[] | select(.draft == true and .name == "next").id] | max | select(. != null)')" + if test "${releaseId}" -gt 0 + then + gh api -X PATCH -F draft=false -F name="${TAG_NAME}" -F tag_name="${TAG_NAME}" "${releasesUrl}/${releaseId}" > /dev/null + fi + echo "${releaseId}" ''' + release = sh(script: releaseScript, returnStdout: true) } else { - powershell ''' - git config user.email "jenkins-infra@googlegroups.com" - git config user.password $env:GIT_PASSWORD - - git tag -a "$env:NEXT_VERSION" -m "$env:IMAGE_NAME" - git push origin --tags + final String releaseScript = ''' + $originUrl = (git remote get-url origin) -replace '\\.git', '' + $org = $originUrl.split('/')[3] + $repository = $originUrl.split('/')[4] + $releasesUrl = "/repos/$org/$repository/releases" + $releaseId = (gh api $releasesUrl | jq -e -r '[ .[] | select(.draft == true and .name == \"next\").id] | max | select(. != null)') + $output = '' + if ($releaseId -gt 0) + { + Invoke-Expression -Command "gh api -X PATCH -F draft=false -F name=$env:TAG_NAME -F tag_name=$env:TAG_NAME $releasesUrl/$releaseId" > $null + $output = $releaseId + } + Write-Output $output ''' + release = powershell(script: releaseScript, returnStdout: true) } - } // withEnv - } // withCredentials - } // stage - } // if - }// withDockerPullCredentials - infra.withDockerPushCredentials{ - if (env.TAG_NAME || env.BRANCH_IS_PRIMARY) { - stage("Deploy ${imageName}") { - String imageDeployName = imageName - if (env.TAG_NAME) { - // User could specify a tag in the image name. In that case the git tag is appended. Otherwise the docker tag is set to the git tag. - if (imageDeployName.contains(':')) { - imageDeployName += "-${env.TAG_NAME}" - } else { - imageDeployName += ":${env.TAG_NAME}" - } - } + if (release == '') { + echo "No next release draft found." + } // if + } // withCredentials + } // stage + } // if + } // withEnv + } // node + } //parallelStages + } // each platform - withEnv(["IMAGE_DEPLOY_NAME=${imageDeployName}"]) { - // Please note that "make deploy" uses the environment variable "IMAGE_DEPLOY_NAME" - if (isUnix()) { - sh 'make deploy' - } else { - powershell 'make deploy' - } - } // withEnv - } //stage - } // if - } // withDockerPushCredentials + parallel(parallelStages) // parallel + //After parallelStages + if (flagmultiplatforms) { + node(finalConfig.agentLabels) { + stage("Multiplatform Semantic Release of ${defaultImageName}") { + checkout scm // should not be necessary on main node + echo "Configuring credential.helper" + // The credential.helper will execute everything after the '!', here echoing the username, the password and an empty line to be passed to git as credentials when git needs it. + if (isUnix()) { + sh 'git config --local credential.helper "!set -u; echo username=\\$GIT_USERNAME && echo password=\\$GIT_PASSWORD && echo"' + } else { + // Using 'bat' here instead of 'powershell' to avoid variable interpolation problem with $ + bat 'git config --local credential.helper "!sh.exe -c \'set -u; echo username=$GIT_USERNAME && echo password=$GIT_PASSWORD && echo"\'' + } - if (env.TAG_NAME && finalConfig.automaticSemanticVersioning) { - stage('GitHub Release') { - withCredentials([ - usernamePassword(credentialsId: "${finalConfig.gitCredentials}", passwordVariable: 'GITHUB_TOKEN', usernameVariable: 'GITHUB_USERNAME') - ]) { - String release = '' + withCredentials([ + usernamePassword(credentialsId: "${finalConfig.gitCredentials}", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME') + ]) { + withEnv(["NEXT_VERSION=${nextVersion}", "IMAGE_NAME=${defaultImageName}"]) { + echo "Tagging and pushing the new version: ${nextVersion}" if (isUnix()) { - final String releaseScript = ''' - originUrlWithGit="$(git remote get-url origin)" - originUrl="${originUrlWithGit%.git}" - org="$(echo "${originUrl}" | cut -d'/' -f4)" - repository="$(echo "${originUrl}" | cut -d'/' -f5)" - releasesUrl="/repos/${org}/${repository}/releases" - releaseId="$(gh api "${releasesUrl}" | jq -e -r '[ .[] | select(.draft == true and .name == "next").id] | max | select(. != null)')" - if test "${releaseId}" -gt 0 - then - gh api -X PATCH -F draft=false -F name="${TAG_NAME}" -F tag_name="${TAG_NAME}" "${releasesUrl}/${releaseId}" > /dev/null - fi - echo "${releaseId}" + sh ''' + git config user.name "${GIT_USERNAME}" + git config user.email "jenkins-infra@googlegroups.com" + + git tag -a "${NEXT_VERSION}" -m "${IMAGE_NAME}" + git push origin --tags ''' - release = sh(script: releaseScript, returnStdout: true) } else { - final String releaseScript = ''' - $originUrl = (git remote get-url origin) -replace '\\.git', '' - $org = $originUrl.split('/')[3] - $repository = $originUrl.split('/')[4] - $releasesUrl = "/repos/$org/$repository/releases" - $releaseId = (gh api $releasesUrl | jq -e -r '[ .[] | select(.draft == true and .name == \"next\").id] | max | select(. != null)') - $output = '' - if ($releaseId -gt 0) - { - Invoke-Expression -Command "gh api -X PATCH -F draft=false -F name=$env:TAG_NAME -F tag_name=$env:TAG_NAME $releasesUrl/$releaseId" > $null - $output = $releaseId - } - Write-Output $output + powershell ''' + git config user.email "jenkins-infra@googlegroups.com" + git config user.password $env:GIT_PASSWORD + + git tag -a "$env:NEXT_VERSION" -m "$env:IMAGE_NAME" + git push origin --tags ''' - release = powershell(script: releaseScript, returnStdout: true) } - if (release == '') { - echo "No next release draft found." - } // if - } // withCredentials - } // stage - } // if - } // withEnv - } // node + } // withEnv + } // withCredentials + } // stage + stage('Multiplatforms Amend') { + String manifestList = '' + finalConfig.platforms.each {eachplatform -> + specificImageName = defaultImageName + ':' + eachplatform.split('/')[1].replace('/','-') + manifestList += "--amend $specificImageName " + } + infra.withDockerPushCredentials { + if (env.TAG_NAME || env.BRANCH_IS_PRIMARY) { + if (env.TAG_NAME) { + dockertag = env.TAG_NAME + } else { + dockertag = 'latest' + } + withEnv(["FULL_IMAGE_NAME=${defaultImageName}:${dockertag}", "MANIFESTLIST=${manifestList}"]) { + sh ''' + docker manifest create "${FULL_IMAGE_NAME}" ${MANIFESTLIST} + ''' + sh 'docker manifest push "${FULL_IMAGE_NAME}"' + } // withEnv + } // amend manifest only for primary branch or tags + } // need docker credential to push + } // stage + } // node + } // if } // call