diff --git a/.gitea/workflows/maven-release-on-branch.yml b/.gitea/workflows/maven-release-on-branch.yml new file mode 100644 index 0000000..3f3db38 --- /dev/null +++ b/.gitea/workflows/maven-release-on-branch.yml @@ -0,0 +1,332 @@ +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" + build-docker: + description: "Costruisce e pubblica l'immagine Docker (jib). false per librerie jar-only." + required: false + type: string + default: "true" + +jobs: + release: + runs-on: ubuntu-latest + container: + image: artifacts.biters.it:8443/biters/gitea-ci-maven-native: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 (+ Docker) + run: | + PROF="" + [ -n "$MVN_PROFILES" ] && PROF="-P$MVN_PROFILES" + + if [ "${{ inputs.build-docker }}" = "true" ]; then + 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 + else + mvn -B -U $PROF -s .ci-config/settings.xml -DskipTests clean deploy + fi + + # ── Tag & Bump ───────────────────────────────────────────── + - name: Commit release e tag + run: | + VERSION="${{ steps.version.outputs.RELEASE_VERSION }}" + git add $(find . -name 'pom.xml' -not -path './.ci-config/*') + 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 $(find . -name 'pom.xml' -not -path './.ci-config/*') + 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" + + if [ "${{ inputs.build-docker }}" = "true" ]; then + DOCKER_IMAGE="${{ inputs.docker-registry }}/biters/${ARTIFACT_ID}:${VERSION}" + BODY="## Artifacts + + - **JAR**: [${ARTIFACT_ID}-${VERSION}.jar](${JAR_URL}) + - **Docker**: \`${DOCKER_IMAGE}\`" + else + BODY="## Artifacts + + - **JAR**: [${ARTIFACT_ID}-${VERSION}.jar](${JAR_URL})" + fi + + 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}')" + + # ── 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 ── + echo "${{ inputs.compose-repos }}" | tr ',' '\n' | while IFS= read -r COMPOSE_REPO; 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