diff --git a/Jenkinsfile b/Jenkinsfile index 382f9caaf..e1d118fd3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,138 +1,137 @@ node('rating-tracker-build') { - withEnv([ - 'IMAGE_NAME=marvinruder/rating-tracker', - 'FORCE_COLOR=true', - 'DOCKER_CI_FLAGS=-f docker/Dockerfile-ci --network=host --cache-from=registry.internal.mruder.dev/cache:rating-tracker-wasm' - ]) { - withCredentials([usernamePassword(credentialsId: 'dockerhub', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) { - // Use random job identifier and test port numbers to avoid collisions - def JOB_ID = sh (script: "#!/bin/bash\nprintf \"%04d\" \$((1 + RANDOM % 4096))", returnStdout: true) - def PGPORT = sh (script: "#!/bin/bash\necho -n \$((49151 + 10#$JOB_ID))", returnStdout: true) - def REDISPORT = sh (script: "#!/bin/bash\necho -n \$((53247 + 10#$JOB_ID))", returnStdout: true) - def TESTPORT = sh (script: "#!/bin/bash\necho -n \$((57343 + 10#$JOB_ID))", returnStdout: true) + withEnv([ + 'IMAGE_NAME=marvinruder/rating-tracker', + 'FORCE_COLOR=true', + 'DOCKER_CI_FLAGS=-f docker/Dockerfile-ci --network=host --cache-from=registry.internal.mruder.dev/cache:rating-tracker-wasm' + ]) { + withCredentials([usernamePassword(credentialsId: 'dockerhub', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) { + // Use random job identifier and test port numbers to avoid collisions + def JOB_ID = sh (script: "#!/bin/bash\nprintf \"%04d\" \$((1 + RANDOM % 4096))", returnStdout: true) + def PGPORT = sh (script: "#!/bin/bash\necho -n \$((49151 + 10#$JOB_ID))", returnStdout: true) + def REDISPORT = sh (script: "#!/bin/bash\necho -n \$((53247 + 10#$JOB_ID))", returnStdout: true) + def TESTPORT = sh (script: "#!/bin/bash\necho -n \$((57343 + 10#$JOB_ID))", returnStdout: true) - try { - parallel( - scm: { - stage('Clone repository') { - checkout scm - } - }, - docker_env: { - stage('Start Docker environment') { - // Log in to Docker Hub - sh('echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin') + try { + parallel( + scm: { + stage('Clone repository') { + checkout scm + } + }, + docker_env: { + stage('Start Docker environment') { + // Log in to Docker Hub + sh('echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin') - // Create builder instance and prefetch Docker base images - sh """ - JENKINS_NODE_COOKIE=DONT_KILL_ME /bin/sh -c '(curl -Ls https://raw.githubusercontent.com/$IMAGE_NAME/\$BRANCH_NAME/docker/Dockerfile-prefetch | docker build -) &' - docker builder create --name rating-tracker --driver docker-container --driver-opt network=host --bootstrap || : - JENKINS_NODE_COOKIE=DONT_KILL_ME /bin/sh -c '(curl -Ls https://raw.githubusercontent.com/$IMAGE_NAME/\$BRANCH_NAME/docker/Dockerfile-prefetch-buildx | docker buildx build --builder rating-tracker --network=host --cache-from=registry.internal.mruder.dev/cache:rating-tracker-wasm -) &' - """ - } - } - ) + // Create builder instance and prefetch Docker base images + sh """ + JENKINS_NODE_COOKIE=DONT_KILL_ME /bin/sh -c '(curl -Ls https://raw.githubusercontent.com/$IMAGE_NAME/\$BRANCH_NAME/docker/Dockerfile-prefetch | docker build -) &' + docker builder create --name rating-tracker --driver docker-container --driver-opt network=host --bootstrap || : + JENKINS_NODE_COOKIE=DONT_KILL_ME /bin/sh -c '(curl -Ls https://raw.githubusercontent.com/$IMAGE_NAME/\$BRANCH_NAME/docker/Dockerfile-prefetch-buildx | docker buildx build --builder rating-tracker --network=host --cache-from=registry.internal.mruder.dev/cache:rating-tracker-wasm -) &' + """ + } + } + ) - parallel( - testenv: { - stage('Start test environment') { - // Inject IP and ports into test environment - sh """ - sed -i \"s/postgres-test:5432/127.0.0.1:$PGPORT/ ; s/redis-test:6379/127.0.0.1:$REDISPORT/ ; s/30001/$TESTPORT/\" packages/backend/test/env.ts - PGPORT=$PGPORT REDISPORT=$REDISPORT docker compose -p rating-tracker-test-job$JOB_ID -f packages/backend/test/docker-compose.yml up --force-recreate -V -d - """ - } - }, - wasm: { - stage ('Compile WebAssembly utils') { - // Build the WebAssembly image while using registry cache - sh("docker buildx build --builder rating-tracker $DOCKER_CI_FLAGS --target=wasm --cache-to=registry.internal.mruder.dev/cache:rating-tracker-wasm .") - } - }, - dep: { - stage ('Install dependencies') { - // Change config files for use in CI, copy cache to workspace and install dependencies - sh """ - echo \"enableInlineBuilds: true\" >> .yarnrc.yml - echo \"globalFolder: /root/.cache/yarn\" >> .yarnrc.yml - mkdir -p \$HOME/.cache/yarn \$HOME/.cache/node \$HOME/.cache/prisma ./cache - cp -arln \$HOME/.cache/yarn \$HOME/.cache/node \$HOME/.cache/prisma ./cache || : - docker build $DOCKER_CI_FLAGS --target=yarn . - """ - } - } - ) + parallel( + testenv: { + stage('Start test environment') { + // Inject IP and ports into test environment + sh """ + sed -i \"s/postgres-test:5432/127.0.0.1:$PGPORT/ ; s/redis-test:6379/127.0.0.1:$REDISPORT/ ; s/30001/$TESTPORT/\" packages/backend/test/env.ts + PGPORT=$PGPORT REDISPORT=$REDISPORT docker compose -p rating-tracker-test-job$JOB_ID -f packages/backend/test/docker-compose.yml up --force-recreate -V -d + """ + } + }, + wasm: { + stage ('Compile WebAssembly utils') { + // Build the WebAssembly image while using registry cache + sh("docker buildx build --builder rating-tracker $DOCKER_CI_FLAGS --target=wasm --cache-to=registry.internal.mruder.dev/cache:rating-tracker-wasm .") + } + }, + dep: { + stage ('Install dependencies') { + // Change config files for use in CI, copy cache to workspace and install dependencies + sh """ + echo \"globalFolder: /root/.cache/yarn\" >> .yarnrc.yml + mkdir -p \$HOME/.cache/yarn \$HOME/.cache/node \$HOME/.cache/prisma ./cache + cp -arln \$HOME/.cache/yarn \$HOME/.cache/node \$HOME/.cache/prisma ./cache || : + docker build $DOCKER_CI_FLAGS --target=yarn . + """ + } + } + ) - stage ('Run tests and build bundles') { - docker.build("$IMAGE_NAME:job$JOB_ID-ci", "$DOCKER_CI_FLAGS --force-rm .") + stage ('Run tests and build bundles') { + docker.build("$IMAGE_NAME:job$JOB_ID-ci", "$DOCKER_CI_FLAGS --force-rm .") - // Copy build artifacts and cache files to workspace - sh """ - id=\$(docker create $IMAGE_NAME:job$JOB_ID-ci) - docker cp \$id:/app/. ./app - docker cp \$id:/cache/. ./cache - docker rm -v \$id - """ - } + // Copy build artifacts and cache files to workspace + sh """ + id=\$(docker create $IMAGE_NAME:job$JOB_ID-ci) + docker cp \$id:/app/. ./app + docker cp \$id:/cache/. ./cache + docker rm -v \$id + """ + } - parallel( - codacy: { - stage ('Publish coverage results to Codacy') { - withCredentials([string(credentialsId: 'codacy-project-token-rating-tracker', variable: 'CODACY_PROJECT_TOKEN')]) { - // Publish coverage results by running a container from the test image - sh('docker run --rm -e CODACY_PROJECT_TOKEN=$CODACY_PROJECT_TOKEN ' + "$IMAGE_NAME:job$JOB_ID-ci report --commit-uuid \$(git log -n 1 --pretty=format:'%H'); docker rmi $IMAGE_NAME:job$JOB_ID-ci") - } - } - }, - dockerhub: { - stage ('Assemble and publish Docker Image') { - // Identify image tags - def tags = "" - if (env.TAG_NAME) { - // A version tag is present - def VERSION = sh (script: "echo -n \$TAG_NAME | sed 's/^v//'", returnStdout: true) - def MAJOR = sh (script: "#!/bin/bash\nif [[ \$TAG_NAME =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+\$ ]]; then echo -n \$TAG_NAME | sed -E 's/^v([0-9]+)\\.([0-9]+)\\.([0-9]+)\$/\\1/'; fi", returnStdout: true) - def MINOR = sh (script: "#!/bin/bash\nif [[ \$TAG_NAME =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+\$ ]]; then echo -n \$TAG_NAME | sed -E 's/^v([0-9]+)\\.([0-9]+)\\.([0-9]+)\$/\\1.\\2/'; fi", returnStdout: true) + parallel( + codacy: { + stage ('Publish coverage results to Codacy') { + withCredentials([string(credentialsId: 'codacy-project-token-rating-tracker', variable: 'CODACY_PROJECT_TOKEN')]) { + // Publish coverage results by running a container from the test image + sh('docker run --rm -e CODACY_PROJECT_TOKEN=$CODACY_PROJECT_TOKEN ' + "$IMAGE_NAME:job$JOB_ID-ci report --commit-uuid \$(git log -n 1 --pretty=format:'%H'); docker rmi $IMAGE_NAME:job$JOB_ID-ci") + } + } + }, + dockerhub: { + stage ('Assemble and publish Docker Image') { + // Identify image tags + def tags = "" + if (env.TAG_NAME) { + // A version tag is present + def VERSION = sh (script: "echo -n \$TAG_NAME | sed 's/^v//'", returnStdout: true) + def MAJOR = sh (script: "#!/bin/bash\nif [[ \$TAG_NAME =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+\$ ]]; then echo -n \$TAG_NAME | sed -E 's/^v([0-9]+)\\.([0-9]+)\\.([0-9]+)\$/\\1/'; fi", returnStdout: true) + def MINOR = sh (script: "#!/bin/bash\nif [[ \$TAG_NAME =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+\$ ]]; then echo -n \$TAG_NAME | sed -E 's/^v([0-9]+)\\.([0-9]+)\\.([0-9]+)\$/\\1.\\2/'; fi", returnStdout: true) - // Use the tag explicitly - tags += " -t $IMAGE_NAME:$VERSION" + // Use the tag explicitly + tags += " -t $IMAGE_NAME:$VERSION" - // Check for semver syntax - if (MAJOR) { - // Use the major and minor version as additional tags - tags += " -t $IMAGE_NAME:$MINOR -t $IMAGE_NAME:$MAJOR -t $IMAGE_NAME:latest" - } - } else if (env.BRANCH_NAME == 'main') { - // Images with tag `edge` are built from the main branch - tags += " -t $IMAGE_NAME:edge" + // Check for semver syntax + if (MAJOR) { + // Use the major and minor version as additional tags + tags += " -t $IMAGE_NAME:$MINOR -t $IMAGE_NAME:$MAJOR -t $IMAGE_NAME:latest" + } + } else if (env.BRANCH_NAME == 'main') { + // Images with tag `edge` are built from the main branch + tags += " -t $IMAGE_NAME:edge" - // Prepare update of README.md - sh("mkdir -p \$HOME/.cache/README && cat README.md | sed 's|^\$||g;s|\"/packages/frontend/public/assets|\"https://raw.githubusercontent.com/$IMAGE_NAME/main/packages/frontend/public/assets|g' > \$HOME/.cache/README/job$JOB_ID") - // sh("docker run --rm -t -v /tmp:/tmp -e DOCKER_USER -e DOCKER_PASS chko/docker-pushrm --file /tmp/jenkins-cache/README/job$JOB_ID $IMAGE_NAME") - } else if (!(env.BRANCH_NAME).startsWith('renovate')) { - // Images with tag `snapshot` are built from other branches, except when updating dependencies only - tags += " -t $IMAGE_NAME:SNAPSHOT" - } + // Prepare update of README.md + sh("mkdir -p \$HOME/.cache/README && cat README.md | sed 's|^\$||g;s|\"/packages/frontend/public/assets|\"https://raw.githubusercontent.com/$IMAGE_NAME/main/packages/frontend/public/assets|g' > \$HOME/.cache/README/job$JOB_ID") + // sh("docker run --rm -t -v /tmp:/tmp -e DOCKER_USER -e DOCKER_PASS chko/docker-pushrm --file /tmp/jenkins-cache/README/job$JOB_ID $IMAGE_NAME") + } else if (!(env.BRANCH_NAME).startsWith('renovate')) { + // Images with tag `snapshot` are built from other branches, except when updating dependencies only + tags += " -t $IMAGE_NAME:SNAPSHOT" + } - // If tags are present, build and push the image for both amd64 and arm64 architectures - if (tags.length() > 0) { - sh("docker buildx build --builder rating-tracker -f docker/Dockerfile --force-rm --push --platform=linux/amd64,linux/arm64 --build-arg BUILD_DATE=\$(date -u +'%Y-%m-%dT%H:%M:%SZ') $tags .") - } - } - } - ) - } finally { - stage ('Cleanup') { - // Upload cache to external storage and remove build artifacts - sh """#!/bin/bash - cp -arln ./cache/yarn ./cache/node ./cache/prisma \$HOME/.cache - putcache - PGPORT=$PGPORT REDISPORT=$REDISPORT docker compose -p rating-tracker-test-job$JOB_ID -f packages/backend/test/docker-compose.yml down -t 0 - docker rmi $IMAGE_NAME:job$JOB_ID-wasm $IMAGE_NAME:job$JOB_ID-ci || : - rm -rf app cache - """ - } + // If tags are present, build and push the image for both amd64 and arm64 architectures + if (tags.length() > 0) { + sh("docker buildx build --builder rating-tracker -f docker/Dockerfile --force-rm --push --platform=linux/amd64,linux/arm64 --build-arg BUILD_DATE=\$(date -u +'%Y-%m-%dT%H:%M:%SZ') $tags .") + } } + } + ) + } finally { + stage ('Cleanup') { + // Upload cache to external storage and remove build artifacts + sh """#!/bin/bash + cp -arln ./cache/yarn ./cache/node ./cache/prisma \$HOME/.cache + putcache + PGPORT=$PGPORT REDISPORT=$REDISPORT docker compose -p rating-tracker-test-job$JOB_ID -f packages/backend/test/docker-compose.yml down -t 0 + docker rmi $IMAGE_NAME:job$JOB_ID-wasm $IMAGE_NAME:job$JOB_ID-ci || : + rm -rf app cache + """ } + } } + } } diff --git a/docker/Dockerfile-ci b/docker/Dockerfile-ci index d6e63b62e..3289405e3 100644 --- a/docker/Dockerfile-ci +++ b/docker/Dockerfile-ci @@ -54,7 +54,7 @@ RUN \ --mount=type=bind,source=packages/frontend/package.json,target=packages/frontend/package.json \ --mount=type=bind,source=packages/wasm/package.json,target=packages/wasm/package.json \ corepack enable && \ - DEBUG=* yarn workspaces focus -A --production + yarn workspaces focus -A --production FROM node:21.6.2-alpine as test