name: Maven Release (reusable) on: workflow_call: inputs: compose-repos: description: "Repo docker-compose da aggiornare, separati da virgola (es. Biters/bitwork,Biters/altro). Vuoto = skip." required: false type: string default: "" maven-profiles: description: "Profili Maven da attivare (es. bitwork,ci,git)" required: false type: string default: "" docker-registry: description: "Registry Docker per il pattern sed nei docker-compose" required: false type: string default: "artifacts.biters.it:8443" jobs: release: runs-on: ubuntu-latest container: image: artifacts.biters.it:8443/biters/gitea-ci-maven:21 credentials: username: ${{ secrets.MAVEN_USERNAME }} password: ${{ secrets.MAVEN_PASSWORD }} volumes: - /opt/ci-cache/m2-repository:/root/.m2/repository env: RUNNER_TOOL_CACHE: /toolcache MAVEN_REPO_URL: ${{ vars.MAVEN_REPO_URL }} MAVEN_REPO_RELEASES_URL: ${{ vars.MAVEN_REPO_RELEASES_URL }} MAVEN_REPO_SNAPSHOTS_URL: ${{ vars.MAVEN_REPO_SNAPSHOTS_URL }} MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} GITEA_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }} MVN_PROFILES: ${{ inputs.maven-profiles }} steps: # ── Bootstrap ────────────────────────────────────────────── - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Checkout CI config run: | git clone --depth=1 "https://biters.gitea:${GITEA_TOKEN}@git.biters.it/Biters/ci-workflows.git" .ci-config - name: Configura git run: | git config user.name "CI Release Bot" git config user.email "ci@biters.it" git remote set-url origin "https://biters.gitea:${GITEA_TOKEN}@git.biters.it/${{ github.repository }}.git" - name: Verifica configurazione run: | missing=0 for v in MAVEN_REPO_URL MAVEN_REPO_RELEASES_URL MAVEN_REPO_SNAPSHOTS_URL \ MAVEN_USERNAME MAVEN_PASSWORD GITEA_TOKEN; do eval "val=\${$v}" if [ -z "$val" ]; then echo "MANCANTE: $v"; missing=1; else echo "OK: $v"; fi done [ "$missing" -eq 0 ] || { echo "Imposta le Variables/Secrets mancanti a livello org."; exit 1; } # ── Versione ─────────────────────────────────────────────── - name: Promuovi a release (rimuovi -SNAPSHOT) id: version run: | PROF="" [ -n "$MVN_PROFILES" ] && PROF="-P$MVN_PROFILES" mvn -B -s .ci-config/settings.xml $PROF versions:set \ -DremoveSnapshot=true -DgenerateBackupPoms=false RELEASE_VERSION=$(mvn -q -s .ci-config/settings.xml $PROF help:evaluate \ -Dexpression=project.version -DforceStdout) echo "RELEASE_VERSION=$RELEASE_VERSION" >> "$GITHUB_OUTPUT" ARTIFACT_ID=$(mvn -q -s .ci-config/settings.xml $PROF help:evaluate \ -Dexpression=project.artifactId -DforceStdout) echo "ARTIFACT_ID=$ARTIFACT_ID" >> "$GITHUB_OUTPUT" GROUP_ID=$(mvn -q -s .ci-config/settings.xml $PROF help:evaluate \ -Dexpression=project.groupId -DforceStdout) echo "GROUP_ID=$GROUP_ID" >> "$GITHUB_OUTPUT" PROJECT_NAME=$(mvn -q -s .ci-config/settings.xml $PROF help:evaluate \ -Dexpression=project.artifactId -DforceStdout) echo "PROJECT_NAME=$PROJECT_NAME" >> "$GITHUB_OUTPUT" MAJOR=$(echo "$RELEASE_VERSION" | cut -d. -f1) MINOR=$(echo "$RELEASE_VERSION" | cut -d. -f2) PATCH=$(echo "$RELEASE_VERSION" | cut -d. -f3) NEXT_VERSION="$MAJOR.$MINOR.$((PATCH + 1))-SNAPSHOT" echo "NEXT_VERSION=$NEXT_VERSION" >> "$GITHUB_OUTPUT" echo "── Riepilogo ──" echo " release : $RELEASE_VERSION" echo " artifactId: $ARTIFACT_ID" echo " groupId : $GROUP_ID" echo " next : $NEXT_VERSION" # ── Check Nexus ──────────────────────────────────────────── - name: Verifica che la versione non esista già su Nexus run: | VERSION="${{ steps.version.outputs.RELEASE_VERSION }}" ARTIFACT_ID="${{ steps.version.outputs.ARTIFACT_ID }}" GROUP_PATH=$(echo "${{ steps.version.outputs.GROUP_ID }}" | tr '.' '/') HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ -u "$MAVEN_USERNAME:$MAVEN_PASSWORD" \ "${MAVEN_REPO_RELEASES_URL}/${GROUP_PATH}/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}.pom") if [ "$HTTP_CODE" = "200" ]; then echo "ERRORE: la versione $VERSION esiste già su Nexus!" exit 1 fi echo "OK: versione $VERSION non presente su Nexus (HTTP $HTTP_CODE)" # ── Build & Deploy ───────────────────────────────────────── - name: Build + Deploy artifact + Push immagine Docker run: | PROF="" [ -n "$MVN_PROFILES" ] && PROF="-P$MVN_PROFILES" mvn -B -U $PROF -s .ci-config/settings.xml -DskipTests \ -Djib.to.auth.username="$MAVEN_USERNAME" \ -Djib.to.auth.password="$MAVEN_PASSWORD" \ -Djib.from.auth.username="$MAVEN_USERNAME" \ -Djib.from.auth.password="$MAVEN_PASSWORD" \ clean deploy # ── Tag & Bump ───────────────────────────────────────────── - name: Commit release e tag run: | VERSION="${{ steps.version.outputs.RELEASE_VERSION }}" git add pom.xml git commit -m "release: ${VERSION} [skip ci]" git tag -a "${VERSION}" -m "Release ${VERSION}" - name: Avanza a prossima versione SNAPSHOT run: | PROF="" [ -n "$MVN_PROFILES" ] && PROF="-P$MVN_PROFILES" NEXT="${{ steps.version.outputs.NEXT_VERSION }}" mvn -B -s .ci-config/settings.xml $PROF versions:set \ -DnewVersion="$NEXT" -DgenerateBackupPoms=false git add pom.xml git commit -m "chore: avanza versione a ${NEXT} [skip ci]" - name: Push release + tag + versione successiva run: | VERSION="${{ steps.version.outputs.RELEASE_VERSION }}" git push origin HEAD "${VERSION}" # ── Gitea Release ────────────────────────────────────────── - name: Crea release su Gitea run: | VERSION="${{ steps.version.outputs.RELEASE_VERSION }}" ARTIFACT_ID="${{ steps.version.outputs.ARTIFACT_ID }}" GROUP_PATH=$(echo "${{ steps.version.outputs.GROUP_ID }}" | tr '.' '/') JAR_URL="${MAVEN_REPO_RELEASES_URL}/${GROUP_PATH}/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}.jar" DOCKER_IMAGE="${{ inputs.docker-registry }}/biters/${ARTIFACT_ID}:${VERSION}" BODY="## Artifacts - **JAR**: [${ARTIFACT_ID}-${VERSION}.jar](${JAR_URL}) - **Docker**: \`${DOCKER_IMAGE}\`" curl -s -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ "https://git.biters.it/api/v1/repos/${{ github.repository }}/releases" \ -d "$(jq -n \ --arg tag "$VERSION" \ --arg name "v${VERSION}" \ --arg body "$BODY" \ '{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')" # ── Rebase develop ───────────────────────────────────────── - name: Rebase develop su master run: | git fetch origin develop git checkout develop git rebase origin/${{ github.ref_name }} git push origin develop --force-with-lease # ── Aggiorna docker-compose (opzionale, multi-repo) ──────── - name: Aggiorna docker-compose nei repo compose if: ${{ inputs.compose-repos != '' }} run: | VERSION="${{ steps.version.outputs.RELEASE_VERSION }}" PROJECT_NAME="${{ steps.version.outputs.PROJECT_NAME }}" REGISTRY="${{ inputs.docker-registry }}" ESCAPED_REGISTRY=$(echo "$REGISTRY" | sed 's/\./\\./g') APP_YML="${GITHUB_WORKSPACE}/src/main/resources/application.yml" # ── Estrai variabili da application.yml (una volta sola) ── APP_VARS_REQUIRED="" APP_VARS_WITH_DEFAULT="" APP_VARS_ALL="" if [ -f "$APP_YML" ]; then ALL_PLACEHOLDERS=$(grep -oP '\$\{[^}]+\}' "$APP_YML" | sort -u) for ph in $ALL_PLACEHOLDERS; do var=$(echo "$ph" | sed 's/\${//;s/[:}].*//') echo "$var" | grep -q '\.' && continue if echo "$ph" | grep -q ':'; then APP_VARS_WITH_DEFAULT=$(printf '%s\n%s' "$APP_VARS_WITH_DEFAULT" "$var") else APP_VARS_REQUIRED=$(printf '%s\n%s' "$APP_VARS_REQUIRED" "$var") fi done APP_VARS_REQUIRED=$(echo "$APP_VARS_REQUIRED" | grep -v '^$' | sort -u) APP_VARS_WITH_DEFAULT=$(echo "$APP_VARS_WITH_DEFAULT" | grep -v '^$' | sort -u) APP_VARS_ALL=$(printf '%s\n%s' "$APP_VARS_REQUIRED" "$APP_VARS_WITH_DEFAULT" | grep -v '^$' | sort -u) echo "Variabili application.yml: $(echo "$APP_VARS_REQUIRED" | wc -w) obbligatorie, $(echo "$APP_VARS_WITH_DEFAULT" | wc -w) con default" fi # ── Cicla su ogni repo compose ── IFS=',' read -ra REPO_LIST <<< "${{ inputs.compose-repos }}" for COMPOSE_REPO in "${REPO_LIST[@]}"; do COMPOSE_REPO=$(echo "$COMPOSE_REPO" | xargs) [ -z "$COMPOSE_REPO" ] && continue REPO_DIR="/tmp/compose-$(echo "$COMPOSE_REPO" | tr '/' '-')" echo "" echo "════════════════════════════════════════" echo " Repo: ${COMPOSE_REPO}" echo "════════════════════════════════════════" # ── Clone e aggiorna versione immagine ── git clone "https://biters.gitea:${GITEA_TOKEN}@git.biters.it/${COMPOSE_REPO}.git" "$REPO_DIR" cd "$REPO_DIR" git config user.name "CI Release Bot" git config user.email "ci@biters.it" find . -name 'docker-compose*.yml' -exec \ sed -i "s|\(${ESCAPED_REGISTRY}/biters/${PROJECT_NAME}:\)[^ \"']*|\1${VERSION}|g" {} + # ── Verifica variabili environment ── if [ -n "$APP_VARS_ALL" ]; then MISSING="" for compose_file in docker-compose-*.yml; do [ -f "$compose_file" ] || continue ENV_NAME=${compose_file#docker-compose-} ENV_NAME=${ENV_NAME%.yml} ENV_FILE=".env_${ENV_NAME}" echo " ── ${compose_file} ──" COMMON_VARS=$(yq '.services["common-env-hc"].environment // {} | keys | .[]' \ "$compose_file" 2>/dev/null || true) SVC_VARS=$(yq ".services | to_entries[] | select(.value.image // \"\" | test(\"${PROJECT_NAME}\")) | .value.environment // {} | keys | .[]" \ "$compose_file" 2>/dev/null || true) COMPOSE_VARS=$(printf '%s\n%s' "$COMMON_VARS" "$SVC_VARS" | grep -v '^$' | sort -u) for var in $APP_VARS_ALL; do if ! echo "$COMPOSE_VARS" | grep -qx "$var"; then severity="REQUIRED" echo "$APP_VARS_WITH_DEFAULT" | grep -qx "$var" && severity="HAS_DEFAULT" MISSING="${MISSING}| \`${var}\` | \`${compose_file}\` | ${severity} |\n" fi done if [ -f "$ENV_FILE" ]; then ENV_REFS=$(grep -oP '\$\{\K\w+(?=\})' "$compose_file" | sort -u) for ref in $ENV_REFS; do if ! grep -q "^${ref}=" "$ENV_FILE"; then MISSING="${MISSING}| \`${ref}\` | \`${ENV_FILE}\` | REQUIRED |\n" fi done fi done MISSING_REQUIRED=$(printf '%b' "$MISSING" | grep "REQUIRED" || true) MISSING_DEFAULTS=$(printf '%b' "$MISSING" | grep "HAS_DEFAULT" || true) if [ -n "$MISSING_DEFAULTS" ] && [ -z "$MISSING_REQUIRED" ]; then echo " WARNING: variabili con default mancanti (non bloccante)" elif [ -n "$MISSING_REQUIRED" ]; then echo " WARNING: variabili obbligatorie mancanti" ISSUE_BODY="## Variabili mancanti — ${PROJECT_NAME} v${VERSION} La pipeline di release ha rilevato variabili non censite: | Variabile | File | Severità | |-----------|------|----------| $(printf '%b' "$MISSING") **REQUIRED** = nessun default, il servizio non parte senza. **HAS_DEFAULT** = ha un default in application.yml, verificare se adeguato. ### Azione richiesta Aggiungere le variabili mancanti nei rispettivi file e committare su ${COMPOSE_REPO}." curl -s -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ "https://git.biters.it/api/v1/repos/${COMPOSE_REPO}/issues" \ -d "$(jq -n \ --arg title "[CI] Variabili mancanti: ${PROJECT_NAME} v${VERSION}" \ --arg body "$ISSUE_BODY" \ '{title: $title, body: $body}')" echo " Issue creata su ${COMPOSE_REPO}." else echo " Tutte le variabili censite." fi fi # ── Push se ci sono modifiche ── if git diff --quiet; then echo " Nessuna modifica necessaria" else git add -A git commit -m "chore(${PROJECT_NAME}): aggiorna immagine a ${VERSION}" git push origin HEAD echo " Docker-compose aggiornati con versione ${VERSION}" fi cd "$GITHUB_WORKSPACE" done