pipeline { agent any environment { IMAGE_NAME = 'test-img' DOCKERHUB_USER = 'ramonvasquezliesa' DOCKERHUB_REPO = 'ramonvasquezliesa/test-img' IMAGE_TAG = 'latest' IMAGE = "${DOCKERHUB_REPO}:${IMAGE_TAG}" CONTAINER_NAME = 'test-img-container' PORTS = '8081:80' VOLUMES = '' ENV_FILE = '' REMOVE_DANGLING_IMAGES = 'true' USE_DOCKERHUB_LOGIN = 'false' FORGEJO_URL = 'https://forgejo.test.dev.it.liesa.com.ar' FORGEJO_REPO = 'ramon.vasquez/test-img.git' // Policy REQUIRE_VERSION_TAG = 'true' // Fail if no matching tag exists remotely } stages { stage('Checkout (with submodules)') { steps { checkout(scmGit( branches: [[name: '*/main']], userRemoteConfigs: [[ url: 'https://forgejo.test.dev.it.liesa.com.ar/ramon.vasquez/pl-1.git', credentialsId: 'hermes' ]], // extensions: [ // [$class: 'SubmoduleOption', // disableSubmodules: false, // recursiveSubmodules: true, // parentCredentials: true, // trackingSubmodules: true // ] // ] )) } } stage('Docker Login (optional)') { when { expression { return env.USE_DOCKERHUB_LOGIN?.toBoolean() } } steps { withCredentials([ usernamePassword( credentialsId: 'dockerhub-credentials', usernameVariable: 'DOCKERHUB_USER', passwordVariable: 'DOCKERHUB_PASS' ) ]) { sh ''' set -eu pipefail echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin ''' } } } // 🔎 Resolve the latest version tag from the Forgejo repo *remotely* (no local clone required) stage('Resolve Latest Version Tag (Forgejo remote)') { steps { sh ''' set -eu pipefail : > .latest_version_tag : > .tag_check_status : > .all_tags : > .matching_tags # Version pattern: vN, vN.N, vN.N.N (N = digits) pattern='^[vV][0-9]+(\\.[0-9]+){0,2}$' # List all tags from remote (strip refs/tags/ and peeled ^{} suffixes), unique, sorted git ls-remote --tags "${FORGEJO_URL}/${FORGEJO_REPO}" \ | awk '{print $2}' \ | sed -E 's@^refs/tags/@@; s/\\^\\{\\}$//' \ | sort -u > .all_tags # Filter only tags that match the version pattern matches="$(grep -E "${pattern}" .all_tags || true)" printf '%s\n' "$matches" > .matching_tags if [ -z "$matches" ]; then echo 'NONE' > .tag_check_status exit 0 fi # Select numerically highest MAJOR.MINOR.PATCH (missing minor/patch => 0) latest_line="$( awk ' function to_num(x){ return (x == "" ? 0 : x) + 0 } { orig=$0 t=$0 gsub(/^[vV]/,"",t) n=split(t, a, ".") maj=to_num(a[1]); min=to_num(a[2]); pat=to_num(a[3]) # zero-padded numeric key → stable lexical sort printf("%09d.%09d.%09d %s\\n", maj, min, pat, orig) } ' .matching_tags | sort -t" " -k1,1 | tail -n1 )" latest_tag="$(printf '%s\\n' "$latest_line" | awk '{ $1=""; sub(/^ /,""); print }')" printf '%s\n' "$latest_tag" > .latest_version_tag echo 'OK' > .tag_check_status ''' script { def status = readFile('.tag_check_status').trim() def latestTag = readFile('.latest_version_tag').trim() def requireTag = (env.REQUIRE_VERSION_TAG ?: 'true').toBoolean() def PATTERN_DISPLAY = '^[vV][0-9]+(\\.[0-9]+){0,2}$' def allTags = sh(script: 'cat .all_tags || true', returnStdout: true).trim() def matches = sh(script: 'cat .matching_tags || true', returnStdout: true).trim() if (status == 'NONE') { if (requireTag) { error('''❌ No tags in the Forgejo repo match the required version pattern ''' + PATTERN_DISPLAY) } else { echo 'â„šī¸ No matching version tags found; will clone default branch and keep IMAGE_TAG as-is.' env.LATEST_TAG = '' } } else { env.LATEST_TAG = latestTag // Optionally sync Docker tag with Git tag (comment out if you prefer not to) env.IMAGE_TAG = latestTag env.IMAGE = "${env.DOCKERHUB_REPO}:${env.IMAGE_TAG}" echo "✅ Latest Forgejo tag selected: ${env.LATEST_TAG}" echo "âžĄī¸ Docker image will use tag: ${env.IMAGE_TAG}" } echo '📌 All remote tags:' echo allTags ? allTags : '' echo '📌 Matching version tags:' echo matches ? matches : '' } } } // âŦ‡ī¸ Clone the Forgejo repo at the *latest version tag* (detached HEAD), or default branch if none stage('Clone Git Repository') { steps { sh """ set -eu pipefail echo "Preparing clone from ${FORGEJO_URL}/${FORGEJO_REPO} ..." rm -rf test-img || true if [ -n "\${LATEST_TAG:-}" ]; then echo "Cloning tag: \${LATEST_TAG}" git clone --branch "\${LATEST_TAG}" --depth 1 "${FORGEJO_URL}/${FORGEJO_REPO}" test-img else echo "Cloning default branch (no version tag selected)" git clone "${FORGEJO_URL}/${FORGEJO_REPO}" test-img fi cd test-img echo "Repository ready at commit: \$(git rev-parse --short HEAD)" """ } } stage('Pull or Build Image') { steps { sh ''' set -eu pipefail echo "Pulling $IMAGE ..." docker pull "$IMAGE" docker image inspect "$IMAGE" >/dev/null echo "Image ready: $IMAGE" ''' } } stage('Deploy (stop old, run new)') { steps { sh label: 'Deploy with bash', script: ''' bash -euo pipefail <<'BASH' # Default possibly-unset Jenkins params to empty strings : "${PORTS:=}" : "${VOLUMES:=}" : "${ENV_FILE:=}" # Stop & remove existing container (if any) if docker ps -a --format '{{.Names}}' | grep -w "$CONTAINER_NAME" >/dev/null 2>&1; then echo "Stopping and removing existing container: $CONTAINER_NAME" docker rm -f "$CONTAINER_NAME" || true fi # Check host-port availability (fail fast if any are busy) if [ -n "$PORTS" ]; then IFS=', ' read -r -a PORT_ARR <<< "$PORTS" BUSY="" for map in "${PORT_ARR[@]}"; do [ -z "$map" ] && continue IFS=':' read -r p1 p2 p3 <<< "$map" if [ -n "$p3" ]; then HP="$p2"; else HP="$p1"; fi HP="${HP%%/*}" # strip /proto [ -z "$HP" ] && continue if command -v ss >/dev/null 2>&1; then if ss -ltnH | awk '{print $4}' | grep -Eq "(:|\\.)${HP}$"; then BUSY="$BUSY $HP" fi elif command -v lsof >/dev/null 2>&1; then if lsof -iTCP:"$HP" -sTCP:LISTEN -P -n >/dev/null 2>&1; then BUSY="$BUSY $HP" fi fi done if [ -n "$BUSY" ]; then echo "ERROR: Host port(s) in use:$BUSY" echo "Hint: Jenkins usually listens on 8081. Change PORTS to something like '8081:80'" exit 1 fi fi # Build runtime args dynamically RUNTIME_ARGS="" if [ -n "$PORTS" ]; then IFS=', ' read -r -a PORT_ARR <<< "$PORTS" for p in "${PORT_ARR[@]}"; do [ -n "$p" ] && RUNTIME_ARGS="$RUNTIME_ARGS -p $p" done fi if [ -n "$VOLUMES" ]; then IFS=', ' read -r -a VOL_ARR <<< "$VOLUMES" for v in "${VOL_ARR[@]}"; do [ -n "$v" ] && RUNTIME_ARGS="$RUNTIME_ARGS -v $v" done fi if [ -n "$ENV_FILE" ]; then RUNTIME_ARGS="$RUNTIME_ARGS --env-file \"$ENV_FILE\"" fi echo "Running container: $CONTAINER_NAME from $IMAGE" set -x # shellcheck disable=SC2086 docker run -d --restart on-failure --name "$CONTAINER_NAME" $RUNTIME_ARGS "$IMAGE" set +x echo "Container status:" docker ps --filter "name=^${CONTAINER_NAME}$" ''' } } } post { always { script { if ((env.REMOVE_DANGLING_IMAGES ?: 'true').toBoolean()) { sh 'docker image prune -f || true' } } sh 'docker logout || true' } } }