diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..2c5e2d2 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,158 @@ +import groovy.json.JsonOutput + +String shellQuote(String value) { + return "'" + value.replace("'", "'\"'\"'") + "'" +} + +pipeline { + agent any + + options { + timestamps() + disableConcurrentBuilds() + buildDiscarder(logRotator(numToKeepStr: '30')) + } + + parameters { + string(name: 'SERVICES', defaultValue: 'auth,gateway,external,console,other,live,wallet,order', description: 'Comma or space separated java services to build.') + string(name: 'GIT_REF', defaultValue: 'main', description: 'Git branch, tag, or commit to build.') + string(name: 'IMAGE_TAG', defaultValue: '', description: 'Optional Harbor image tag override. Defaults to --.') + string(name: 'HARBOR_REGISTRY', defaultValue: '10.2.1.3:18082', description: 'Harbor registry address reachable from the Jenkins agent.') + string(name: 'HARBOR_PROJECT', defaultValue: 'likei', description: 'Harbor project name.') + string(name: 'HARBOR_CREDENTIALS_ID', defaultValue: 'likei-harbor-robot', description: 'Jenkins username/password credentials id for Harbor.') + string(name: 'DOCKER_PLATFORM', defaultValue: 'linux/amd64', description: 'Docker build platform.') + booleanParam(name: 'SKIP_MAVEN_BUILD', defaultValue: false, description: 'Reuse already-built jars on the Jenkins agent.') + } + + environment { + DOCKER_BUILDKIT = '1' + COMPOSE_DOCKER_CLI_BUILD = '1' + } + + stages { + stage('Checkout') { + steps { + checkout scm + script { + if (params.GIT_REF?.trim()) { + sh """#!/usr/bin/env bash +set -euo pipefail +git fetch --all --tags --prune +git checkout --force ${shellQuote(params.GIT_REF.trim())} +""" + } + } + sh 'mkdir -p ci-out' + } + } + + stage('Resolve Inputs') { + steps { + script { + def componentMap = [ + auth: 'chatapp3-auth', + gateway: 'chatapp3-gateway', + external: 'chatapp3-external', + console: 'chatapp3-console', + other: 'chatapp3-other', + live: 'chatapp3-live', + wallet: 'chatapp3-wallet', + order: 'chatapp3-order', + ] + def services = params.SERVICES.split(/[\s,]+/).collect { it.trim() }.findAll { it } + if (!services) { + error('SERVICES is empty') + } + def unsupported = services.findAll { !componentMap.containsKey(it) } + if (unsupported) { + error("Unsupported java services: ${unsupported.join(', ')}") + } + def branch = (env.BRANCH_NAME ?: env.GIT_BRANCH ?: 'manual').replaceAll('[^0-9A-Za-z._-]+', '-') + env.RESOLVED_GIT_COMMIT = sh(returnStdout: true, script: 'git rev-parse HEAD').trim() + env.RESOLVED_GIT_COMMIT_SHORT = sh(returnStdout: true, script: 'git rev-parse --short=12 HEAD').trim() + env.RESOLVED_IMAGE_TAG = params.IMAGE_TAG?.trim() ? params.IMAGE_TAG.trim() : "${branch}-${env.RESOLVED_GIT_COMMIT_SHORT}-${env.BUILD_NUMBER}" + env.RESOLVED_SERVICES = services.join(',') + writeFile( + file: 'ci-out/request.json', + text: JsonOutput.prettyPrint(JsonOutput.toJson([ + repo: 'chatapp3-java', + gitRef: params.GIT_REF?.trim() ?: 'main', + commit: env.RESOLVED_GIT_COMMIT, + services: services, + imageTag: env.RESOLVED_IMAGE_TAG, + ])) + '\n', + ) + } + } + } + + stage('Login Harbor') { + steps { + withCredentials([usernamePassword(credentialsId: params.HARBOR_CREDENTIALS_ID, usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASS')]) { + sh '''#!/usr/bin/env bash +set -euo pipefail +printf '%s' "$HARBOR_PASS" | docker login "$HARBOR_REGISTRY" -u "$HARBOR_USER" --password-stdin +''' + } + } + } + + stage('Build And Push') { + steps { + script { + def componentMap = [ + auth: 'chatapp3-auth', + gateway: 'chatapp3-gateway', + external: 'chatapp3-external', + console: 'chatapp3-console', + other: 'chatapp3-other', + live: 'chatapp3-live', + wallet: 'chatapp3-wallet', + order: 'chatapp3-order', + ] + def serviceImages = [:] + def components = [] + env.RESOLVED_SERVICES.split(',').each { serviceName -> + def imageRef = "${params.HARBOR_REGISTRY}/${params.HARBOR_PROJECT}/${serviceName}:${env.RESOLVED_IMAGE_TAG}" + sh """#!/usr/bin/env bash +set -euo pipefail +DOCKER_PLATFORM='${params.DOCKER_PLATFORM}' \\ +SKIP_MAVEN_BUILD='${params.SKIP_MAVEN_BUILD ? '1' : '0'}' \\ +./ci/build-service-image.sh '${serviceName}' '${imageRef}' '${env.RESOLVED_GIT_COMMIT_SHORT}' +docker push '${imageRef}' +""" + def digest = sh(returnStdout: true, script: "docker image inspect '${imageRef}' --format '{{index .RepoDigests 0}}'").trim() + serviceImages[serviceName] = digest + components << componentMap[serviceName] + } + writeFile(file: 'ci-out/service-images.json', text: JsonOutput.prettyPrint(JsonOutput.toJson(serviceImages)) + '\n') + writeFile( + file: 'ci-out/deploy-params.json', + text: JsonOutput.prettyPrint(JsonOutput.toJson([ + components: components, + ref: env.RESOLVED_GIT_COMMIT, + serviceImages: serviceImages, + ])) + '\n', + ) + writeFile( + file: 'ci-out/build-metadata.json', + text: JsonOutput.prettyPrint(JsonOutput.toJson([ + repo: 'chatapp3-java', + gitRef: params.GIT_REF?.trim() ?: 'main', + commit: env.RESOLVED_GIT_COMMIT, + imageTag: env.RESOLVED_IMAGE_TAG, + services: env.RESOLVED_SERVICES.split(','), + serviceImages: serviceImages, + ])) + '\n', + ) + } + } + } + } + + post { + always { + archiveArtifacts artifacts: 'ci-out/*.json', fingerprint: true, allowEmptyArchive: false + } + } +}