diff --git a/Jenkinsfile b/Jenkinsfile index 9e2f714f6..27fb3fc8a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,94 +1,128 @@ -node ('lagoon-images') { - withCredentials([ - string(credentialsId: 'SKIP_IMAGE_PUBLISH', variable: 'SKIP_IMAGE_PUBLISH') - ]) { - try { - env.CI_BUILD_TAG = env.BUILD_TAG.replaceAll('%2f','').replaceAll("[^A-Za-z0-9]+", "").toLowerCase() - env.SAFEBRANCH_NAME = env.BRANCH_NAME.replaceAll('%2f','-').replaceAll("[^A-Za-z0-9]+", "-").toLowerCase() - env.SYNC_MAKE_OUTPUT = 'target' - // make/tests will synchronise (buffer) output by default to avoid interspersed - // lines from multiple jobs run in parallel. However this means that output for - // each make target is not written until the command completes. - // - // See `man -P 'less +/-O' make` for more information about this option. - // - // Uncomment the line below to disable output synchronisation. - // env.SYNC_MAKE_OUTPUT = 'none' - - stage ('env') { - sh "env" - } - - deleteDir() +def skipRemainingStages = false + +pipeline { + agent { label 'lagoon-images' } + environment { + // configure build params + SAFEBRANCH_NAME = env.BRANCH_NAME.replaceAll('%2F','-').replaceAll('[^A-Za-z0-9]+', '-').toLowerCase() + SAFEBRANCH_AND_BUILDNUMBER = (env.SAFEBRANCH_NAME+env.BUILD_NUMBER).replaceAll('%2f','').replaceAll('[^A-Za-z0-9]+', '').toLowerCase(); + CI_BUILD_TAG = 'lagoon'.concat(env.SAFEBRANCH_AND_BUILDNUMBER.drop(env.SAFEBRANCH_AND_BUILDNUMBER.length()-26)); + NPROC = "${sh(script:'getconf _NPROCESSORS_ONLN', returnStdout: true).trim()}" + SKIP_IMAGE_PUBLISH = credentials('SKIP_IMAGE_PUBLISH') + SYNC_MAKE_OUTPUT = 'target' + } - stage ('Checkout') { - def checkout = checkout scm - env.GIT_COMMIT = checkout["GIT_COMMIT"] + stages { + stage ('env') { + steps { + sh 'env' } + } - // in order to have the newest images from upstream (with all the security updates) we clean our local docker cache on tag deployments - // we don't do this all the time to still profit from image layer caching - // but we want this on tag deployments in order to ensure that we publish images always with the newest possible images. - if (env.TAG_NAME || env.SAFEBRANCH_NAME == 'main') { - stage ('clean docker image cache') { - sh script: "make docker-buildx-remove", label: "removing leftover buildx" - sh script: "docker image prune -af", label: "Pruning images" - sh script: "docker buildx prune -af", label: "Pruning builder cache" - } + // in order to have the newest images from upstream (with all the security + // updates) we clean our local docker cache on tag deployments + // we don't do this all the time to still profit from image layer caching + // but we want this on tag deployments in order to ensure that we publish + // images always with the newest possible images. + stage ('clean docker image cache') { + when { + buildingTag() } + steps { + sh script: "make docker-buildx-remove", label: "removing leftover buildx" + sh script: "docker image prune -af", label: "Pruning images" + sh script: "docker buildx prune -af", label: "Pruning builder cache" + } + } - stage ('build images') { + stage ('build images') { + steps { sh script: "docker run --privileged --rm tonistiigi/binfmt --install all", label: "setting binfmt correctly" sh script: "make docker-buildx-configure", label: "Configuring buildx for multi-platform build" - env.SCAN_IMAGES = 'true' sh script: "make docker_pull", label: "Ensuring fresh upstream images" sh script: "make -O${SYNC_MAKE_OUTPUT} -j8 build", label: "Building images" } + } - stage ('show built images') { + stage ('show built images') { + steps { sh 'cat build.txt' sh 'docker image ls | grep ${CI_BUILD_TAG} | sort -u' } + } - stage ('Copy examples down') { - sh script: "git clone https://github.com/uselagoon/lagoon-examples.git tests" - dir ('tests') { - sh script: "git submodule sync && git submodule update --init" - sh script: "mkdir -p ./all-images && cp ../helpers/*docker-compose.yml ./all-images/ && cp ../helpers/TESTING_*_dockercompose.md ./all-images/" - sh script: "sed -i '/image: uselagoon/ s/uselagoon/${CI_BUILD_TAG}/' ./all-images/*-docker-compose.yml" - sh script: "yarn install" - sh script: "docker network inspect amazeeio-network >/dev/null || docker network create amazeeio-network" + stage ('prepare tests and images') { + parallel { + stage ('Copy examples down') { + steps { + sh script: "rm -rf tests || echo 'no tests directory to remove'" + sh script: "git clone https://github.com/uselagoon/lagoon-examples.git tests" + dir ('tests') { + sh script: "git submodule sync && git submodule update --init" + sh script: "mkdir -p ./all-images && cp ../helpers/*docker-compose.yml ./all-images/ && cp ../helpers/TESTING_*_dockercompose.md ./all-images/" + sh script: "sed -i '/image: uselagoon/ s/uselagoon/${CI_BUILD_TAG}/' ./all-images/*-docker-compose.yml" + sh script: "yarn install" + sh script: "docker network inspect amazeeio-network >/dev/null || docker network create amazeeio-network" + } + } + } + stage ('push amd64 branch images to testlagoon/*') { + environment { + PASSWORD = credentials('amazeeiojenkins-dockerhub-password') + } + when { + not { + environment name: 'SKIP_IMAGE_PUBLISH', value: 'true' + } + } + steps { + sh script: 'docker login -u amazeeiojenkins -p $PASSWORD', label: "Docker login" + sh script: "make -O${SYNC_MAKE_OUTPUT} -j8 publish-testlagoon-baseimages BRANCH_NAME=${SAFEBRANCH_NAME} PLATFORM='linux/amd64'", label: "Publishing built amd64 images to testlagoon" + } } } + } - parallel ( - 'build and push images to testlagoon dockerhub': { - stage ('push branch images to testlagoon/*') { - withCredentials([string(credentialsId: 'amazeeiojenkins-dockerhub-password', variable: 'PASSWORD')]) { - try { - if (env.SKIP_IMAGE_PUBLISH != 'true') { - sh script: 'docker login -u amazeeiojenkins -p $PASSWORD', label: "Docker login" - sh script: "make -O${SYNC_MAKE_OUTPUT} -j8 publish-testlagoon-baseimages BRANCH_NAME=${SAFEBRANCH_NAME}", label: "Publishing built images to testlagoon" - if (env.SAFEBRANCH_NAME == 'main') { - sh script: "make -O${SYNC_MAKE_OUTPUT} -j8 build PUBLISH_IMAGES=true REGISTRY_ONE=testlagoon TAG_ONE=${SAFEBRANCH_NAME} REGISTRY_TWO=testlagoon TAG_TWO=latest", label: "Publishing built images to testlagoon main&latest images" - } else if (env.SAFEBRANCH_NAME == 'arm64-images') { - sh script: "make -O${SYNC_MAKE_OUTPUT} -j8 build PUBLISH_IMAGES=true REGISTRY_ONE=testlagoon TAG_ONE=${SAFEBRANCH_NAME} REGISTRY_TWO=testlagoon TAG_TWO=multiarch", label: "Publishing built images to testlagoon arm images" - } else { - sh script: 'echo "No multi-arch images required for this build"', label: "Skipping image publishing" - } - } else { - sh script: 'echo "skipped because of SKIP_IMAGE_PUBLISH env variable"', label: "Skipping image publishing" - } - } catch (e) { - echo "Something went wrong, trying to cleanup" - cleanup() - throw e - } + stage ('test and push images') { + parallel { + stage ('push main branch images to testlagoon/*') { + environment { + PASSWORD = credentials('amazeeiojenkins-dockerhub-password') + } + when { + branch 'main' + not { + environment name: 'SKIP_IMAGE_PUBLISH', value: 'true' } } - }, - 'Run all the tests on the local images': { - stage ('running test suite') { + steps { + retry(3) { + sh script: 'docker login -u amazeeiojenkins -p $PASSWORD', label: "Docker login" + sh script: "timeout 60m make -O${SYNC_MAKE_OUTPUT} -j8 build PUBLISH_IMAGES=true REGISTRY_ONE=testlagoon TAG_ONE=${SAFEBRANCH_NAME} REGISTRY_TWO=testlagoon TAG_TWO=latest PLATFORM='linux/arm64/v8'", label: "Publishing built arm64 images to testlagoon main&latest images" + } + sh script: "make -O${SYNC_MAKE_OUTPUT} -j8 build PUBLISH_IMAGES=true REGISTRY_ONE=testlagoon TAG_ONE=${SAFEBRANCH_NAME} REGISTRY_TWO=testlagoon TAG_TWO=latest PLATFORM='linux/amd64,linux/arm64/v8'", label: "Publishing built digest to testlagoon main&latest images" + } + } + stage ('push arm64-images branch images to testlagoon/*') { + environment { + PASSWORD = credentials('amazeeiojenkins-dockerhub-password') + } + when { + branch 'arm64-images' + not { + environment name: 'SKIP_IMAGE_PUBLISH', value: 'true' + } + } + steps { + retry(3) { + sh script: 'docker login -u amazeeiojenkins -p $PASSWORD', label: "Docker login" + sh script: "timeout 60m make -O${SYNC_MAKE_OUTPUT} -j8 build PUBLISH_IMAGES=true REGISTRY_ONE=testlagoon TAG_ONE=${SAFEBRANCH_NAME} REGISTRY_TWO=testlagoon TAG_TWO=multiarch PLATFORM='linux/arm64/v8'", label: "Publishing built arm64 images to testlagoon multiarch images" + } + sh script: "make -O${SYNC_MAKE_OUTPUT} -j8 build PUBLISH_IMAGES=true REGISTRY_ONE=testlagoon TAG_ONE=${SAFEBRANCH_NAME} REGISTRY_TWO=testlagoon TAG_TWO=multiarch PLATFORM='linux/amd64,linux/arm64/v8'", label: "Publishing built digest to testlagoon multiarch images" + } + } + stage ('running test suite') { + steps { dir ('tests') { sh script: "docker buildx use default", label: "Ensure to use default builder" sh script: "grep -rl uselagoon . | xargs sed -i '/^FROM/ s/uselagoon/${CI_BUILD_TAG}/'" @@ -101,56 +135,61 @@ node ('lagoon-images') { } } } - ) + } + } - stage ('publish experimental image tags to testlagoon') { - if (env.SAFEBRANCH_NAME == 'main' || env.CHANGE_ID && pullRequest.labels.contains("experimental")) { - sh script: "make -O${SYNC_MAKE_OUTPUT} -j8 publish-testlagoon-experimental-baseimages BRANCH_NAME=${SAFEBRANCH_NAME}", label: "Publishing experimental images to testlagoon" - } else { - sh script: 'echo "not a PR or main branch push"', label: "Skipping experimantal image publishing" + stage ('push branch images to uselagoon/*') { + environment { + PASSWORD = credentials('amazeeiojenkins-dockerhub-password') + } + when { + buildingTag() + not { + environment name: 'SKIP_IMAGE_PUBLISH', value: 'true' } } - - if (env.TAG_NAME && env.SKIP_IMAGE_PUBLISH != 'true') { - stage ('push branch images to uselagoon/*') { - withCredentials([string(credentialsId: 'amazeeiojenkins-dockerhub-password', variable: 'PASSWORD')]) { - try { - if (env.SKIP_IMAGE_PUBLISH != 'true') { - sh script: 'docker login -u amazeeiojenkins -p $PASSWORD', label: "Docker login" - sh script: "make -O${SYNC_MAKE_OUTPUT} -j8 build PUBLISH_IMAGES=true REGISTRY_ONE=uselagoon TAG_ONE=${TAG_NAME} REGISTRY_TWO=uselagoon TAG_TWO=latest", label: "Publishing built images to uselagoon" - } else { - sh script: 'echo "skipped because of SKIP_IMAGE_PUBLISH env variable"', label: "Skipping image publishing" - } - } catch (e) { - echo "Something went wrong, trying to cleanup" - cleanup() - throw e - } - } + steps { + retry(3) { + sh script: 'docker login -u amazeeiojenkins -p $PASSWORD', label: "Docker login" + sh script: "timeout 60m make -O${SYNC_MAKE_OUTPUT} -j8 build PUBLISH_IMAGES=true REGISTRY_ONE=uselagoon TAG_ONE=${TAG_NAME} REGISTRY_TWO=uselagoon TAG_TWO=latest", label: "Publishing built images to uselagoon" } } + } - if (env.TAG_NAME || env.SAFEBRANCH_NAME == 'main' || env.SAFEBRANCH_NAME == 'testing-scans' ) { - stage ('scan built images') { - sh script: 'make scan-images', label: "perform scan routines" - sh script: 'find ./scans/*trivy* -type f | xargs tail -n +1', label: "Show Trivy vulnerability scan results" - sh script: 'find ./scans/*grype* -type f | xargs tail -n +1', label: "Show Grype vulnerability scan results" - sh script: 'find ./scans/*syft* -type f | xargs tail -n +1', label: "Show Syft SBOM results" + stage ('scan built images') { + when { + anyOf { + branch 'main' + buildingTag() + } + not { + environment name: 'SKIP_IMAGE_PUBLISH', value: 'true' } } + steps { + sh script: 'make scan-images', label: "perform scan routines" + sh script: 'find ./scans/*trivy* -type f | xargs tail -n +1', label: "Show Trivy vulnerability scan results" + sh script: 'find ./scans/*grype* -type f | xargs tail -n +1', label: "Show Grype vulnerability scan results" + sh script: 'find ./scans/*syft* -type f | xargs tail -n +1', label: "Show Syft SBOM results" + } + } + } - } catch (e) { - currentBuild.result = 'FAILURE' - echo "Something went wrong, trying to cleanup" - throw e - } finally { + post { + always { cleanup() - notifySlack(currentBuild.result) + deleteDir() + } + success { + notifySlack('SUCCESS') + } + failure { + notifySlack('FAILURE') + } + aborted { + notifySlack('ABORTED') } - - cleanup() } - } def cleanup() { diff --git a/Makefile b/Makefile index f9ecb0af6..852b321e2 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,8 @@ BRANCH_NAME := # Only set this to false when ready to push images to dockerhub PUBLISH_IMAGES ?= false +PLATFORM ?= linux/amd64 + # Init the file that is used to hold the image tag cross-reference table $(shell >build.txt) $(shell >scan.txt) @@ -79,7 +81,7 @@ docker_build_local = DOCKER_BUILDKIT=1 docker build $(DOCKER_BUILD_PARAMS) \ docker_buildx_two = docker buildx build $(DOCKER_BUILD_PARAMS) \ --builder ci-local \ - --platform linux/amd64,linux/arm64/v8 \ + --platform $(PLATFORM) \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg LAGOON_VERSION=$(LAGOON_VERSION) \ --build-arg IMAGE_REPO=localhost:5000/testlagoon \ @@ -93,7 +95,7 @@ docker_buildx_two = docker buildx build $(DOCKER_BUILD_PARAMS) \ docker_buildx_three = docker buildx build $(DOCKER_BUILD_PARAMS) \ --builder ci-local \ - --platform linux/amd64,linux/arm64/v8 \ + --platform $(PLATFORM) \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg LAGOON_VERSION=$(LAGOON_VERSION) \ --build-arg IMAGE_REPO=localhost:5000/uselagoon \