diff --git a/Jenkinsfile b/Jenkinsfile index fb848f8..c48e181 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,25 +1,57 @@ pipeline { + parameters { + string( + name: 'IMAGE_VERSION', + defaultValue: '', + description: 'Optional override for the Docker image tag (e.g., stable, release, 0.0.2). Leave empty to use the commit hash.' + ) + string( + name: 'GIT_REF', + defaultValue: '', + description: 'Branch or tag to build. Leave empty to use Jenkins-provided branch or default to main.' + ) + booleanParam( + name: 'CLEAN_BUILD', + defaultValue: false, + description: 'Run docker build --pull --no-cache when true.' + ) + } + agent any environment { GIT_URL = 'https://gitea.mindboost.team/mindboost/education-flagger.git' - GIT_BRANCH = 'pipeline/deploy-image' - REGISTRY_SCHEME = 'https' - REGISTRY_AUTHORITY = 'gitea.mindboost.team' IMAGE_NAME = 'mindboost/education-flagger' - REGISTRY_CREDENTIALS_ID = 'REGISTRY_CREDENTIALS_ID' + LOCAL_IMAGE_NAME = 'education_flagger_image' + GIT_CREDENTIALS_ID = 'b5f383be-8c74-40f9-b7e1-3a9c5856df0e' + REGISTRY_CREDENTIALS_ID = '62d300cc-d8c6-437a-8699-c58b9e1edcb0' + REGISTRY_SCHEME = 'https' + REGISTRY_HOST = 'gitea.mindboost.team' } stages { stage('Checkout') { steps { script { + def selectedRef = params?.GIT_REF?.trim() + if (!selectedRef) { + selectedRef = env.CHANGE_BRANCH ?: env.BRANCH_NAME ?: env.GIT_BRANCH + } + if (!selectedRef) { + selectedRef = 'main' + echo "No GIT_REF supplied. Falling back to 'main'." + } + + def normalizedRef = selectedRef.replaceFirst('^origin/', '') + def branchSpec = normalizedRef.startsWith('refs/') ? normalizedRef : "*/${normalizedRef}" + echo "Checking out '${branchSpec}' from ${env.GIT_URL}" + checkout([ $class: 'GitSCM', - branches: [[name: "*/${env.GIT_BRANCH}"]], + branches: [[name: branchSpec]], userRemoteConfigs: [[ url: env.GIT_URL, - credentialsId: 'b5f383be-8c74-40f9-b7e1-3a9c5856df0e' + credentialsId: env.GIT_CREDENTIALS_ID ]] ]) } @@ -39,12 +71,41 @@ pipeline { stage('Determine Version') { steps { script { - def fullHash = sh( - script: 'git rev-parse HEAD', + def imageVersion = '' + + if (params?.IMAGE_VERSION) { + imageVersion = params.IMAGE_VERSION.trim() + echo "Using build parameter IMAGE_VERSION=${imageVersion}" + } + + if (!imageVersion) { + def longSha = sh( + script: 'git rev-parse HEAD', + returnStdout: true + ).trim() + imageVersion = "sha256-${longSha}" + echo "No IMAGE_VERSION provided. Falling back to commit hash: ${imageVersion}" + } + + def sanitized = imageVersion.replaceAll('[^A-Za-z0-9_.-]', '-') + if (sanitized != imageVersion) { + echo "Sanitized version value from '${imageVersion}' to '${sanitized}' for Docker tag compatibility." + } + + env.IMAGE_TAG = sanitized + echo "Resolved image tag: ${env.IMAGE_TAG}" + } + } + } + + stage('Get Commit Hash') { + steps { + script { + env.GIT_COMMIT_SHORT = sh( + script: 'git rev-parse --short HEAD', returnStdout: true ).trim() - env.IMAGE_TAG = "sha256-${fullHash}" - echo "Resolved image tag: ${env.IMAGE_TAG}" + echo "Commit Hash: ${env.GIT_COMMIT_SHORT}" } } } @@ -52,17 +113,20 @@ pipeline { stage('Check Docker Image with the same tag') { steps { script { + def cleanBuild = params?.CLEAN_BUILD == true def imageExists = sh( - script: "docker images -q ${env.IMAGE_NAME}:${env.IMAGE_TAG} || true", + script: "docker images -q ${env.LOCAL_IMAGE_NAME}:${env.IMAGE_TAG} || true", returnStdout: true ).trim() - if (imageExists) { - echo "Docker Image mit Tag ${env.IMAGE_TAG} existiert bereits. Überspringe Build." + if (cleanBuild) { + echo "CLEAN_BUILD=true: ignoring existing local image ${env.LOCAL_IMAGE_NAME}:${env.IMAGE_TAG}." + } else if (imageExists) { + echo "Docker image with tag ${env.IMAGE_TAG} already exists locally. Skipping build." currentBuild.result = 'SUCCESS' return } else { - echo "Kein vorhandenes Docker Image gefunden. Baue neues Image..." + echo 'No existing local Docker image found. Building a new image.' } } } @@ -74,7 +138,9 @@ pipeline { } steps { script { - sh "docker build --rm -t ${env.IMAGE_NAME}:${env.IMAGE_TAG} ." + def cleanBuild = params?.CLEAN_BUILD == true + def buildFlags = cleanBuild ? '--pull --no-cache ' : '' + sh "docker build ${buildFlags}-t ${env.LOCAL_IMAGE_NAME}:${env.IMAGE_TAG} ." } } } @@ -85,26 +151,76 @@ pipeline { } steps { script { - withCredentials([usernamePassword( - credentialsId: env.REGISTRY_CREDENTIALS_ID, - usernameVariable: 'REGISTRY_USER', - passwordVariable: 'REGISTRY_PASS' - )]) { - def registryEndpoint = "${env.REGISTRY_SCHEME}://${env.REGISTRY_AUTHORITY}" - sh "echo '${REGISTRY_PASS}' | docker login ${env.REGISTRY_AUTHORITY} -u '${REGISTRY_USER}' --password-stdin" - sh "docker tag ${env.IMAGE_NAME}:${env.IMAGE_TAG} ${env.REGISTRY_AUTHORITY}/${env.IMAGE_NAME}:${env.IMAGE_TAG}" - sh "docker push ${env.REGISTRY_AUTHORITY}/${env.IMAGE_NAME}:${env.IMAGE_TAG}" - sh "docker logout ${env.REGISTRY_AUTHORITY}" + withCredentials([ + usernamePassword( + credentialsId: env.REGISTRY_CREDENTIALS_ID, + usernameVariable: 'REGISTRY_USER', + passwordVariable: 'REGISTRY_PASS' + ) + ]) { + def registryAuthority = env.REGISTRY_HOST + def registryEndpoint = "${env.REGISTRY_SCHEME}://${registryAuthority}" + def remoteImageTag = "${registryAuthority}/${env.IMAGE_NAME}:${env.IMAGE_TAG}" + + withEnv([ + "REGISTRY_AUTHORITY=${registryAuthority}", + "REGISTRY_ENDPOINT=${registryEndpoint}", + "REMOTE_IMAGE_TAG=${remoteImageTag}" + ]) { + sh ''' + set -eux + if [ -z "${IMAGE_TAG:-}" ]; then + echo "IMAGE_TAG is empty. Did the Determine Version stage run?" >&2 + exit 1 + fi + if [ -z "${REGISTRY_USER:-}" ]; then + echo "REGISTRY_USER is empty. Check Jenkins credentials mapping." >&2 + exit 1 + fi + if [ -z "${REGISTRY_PASS:-}" ]; then + echo "REGISTRY_PASS is empty. Check Jenkins credentials mapping." >&2 + exit 1 + fi + if [ -z "${REGISTRY_AUTHORITY:-}" ]; then + echo "REGISTRY_AUTHORITY is empty. Registry authority not resolved." >&2 + exit 1 + fi + if [ -z "${REGISTRY_ENDPOINT:-}" ]; then + echo "REGISTRY_ENDPOINT is empty. Registry endpoint not resolved." >&2 + exit 1 + fi + if [ -z "${REMOTE_IMAGE_TAG:-}" ]; then + echo "REMOTE_IMAGE_TAG is empty. Derived Docker tag missing." >&2 + exit 1 + fi + + docker --version + docker info + docker image inspect "$LOCAL_IMAGE_NAME:$IMAGE_TAG" >/dev/null + + echo "Logging into Docker registry $REGISTRY_ENDPOINT as $REGISTRY_USER" + echo "$REGISTRY_PASS" | docker login "$REGISTRY_ENDPOINT" --username "$REGISTRY_USER" --password-stdin + docker tag "$LOCAL_IMAGE_NAME:$IMAGE_TAG" "$REMOTE_IMAGE_TAG" + echo "Pushing Docker image $REMOTE_IMAGE_TAG to $REGISTRY_AUTHORITY" + docker push "$REMOTE_IMAGE_TAG" + docker logout "$REGISTRY_ENDPOINT" + ''' + } } } } } stage('Cleanup Docker Images') { + when { + expression { currentBuild.result == null } + } steps { - script { - sh 'set -eux; docker image prune -f; docker builder prune -f' - } + sh ''' + set -eux + docker image prune -f + docker builder prune -f + ''' } } }