diff --git a/.github/workflows/cleanup-releases.yml b/.github/workflows/cleanup-releases.yml new file mode 100644 index 00000000..1c1693b1 --- /dev/null +++ b/.github/workflows/cleanup-releases.yml @@ -0,0 +1,123 @@ +name: Cleanup Old Releases + +on: + workflow_call: + inputs: + channel: + description: "Release channel: beta or stable" + required: true + type: string + + workflow_dispatch: + inputs: + channel: + description: "Release channel" + required: true + type: choice + options: + - beta + - stable + +jobs: + cleanup: + name: Cleanup old pre-releases + runs-on: ubuntu-latest + + permissions: + contents: write + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + with: + ref: dev + sparse-checkout: gradle.properties + sparse-checkout-cone-mode: false + + - name: Delete outdated beta and dev releases and tags + run: | + set -euo pipefail + + VERSION=$(grep '^cloudVersion=' gradle.properties | cut -d'=' -f2) + CHANNEL="${{ inputs.channel }}" + REPO="${{ github.repository }}" + + # Parse current version into components + IFS='.' read -r CUR_MAJOR CUR_MINOR CUR_PATCH <<< "$VERSION" + + echo "### Cleanup Summary" >> "$GITHUB_STEP_SUMMARY" + echo "- **Version:** ${VERSION}" >> "$GITHUB_STEP_SUMMARY" + echo "- **Channel:** ${CHANNEL}" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + + DELETED_COUNT=0 + + # Fetch all releases (paginated) + PAGE=1 + ALL_RELEASES="[]" + while true; do + BATCH=$(gh api "repos/${REPO}/releases?per_page=100&page=${PAGE}" 2>/dev/null || echo "[]") + COUNT=$(echo "$BATCH" | jq 'length') + if [[ "$COUNT" -eq 0 ]]; then + break + fi + ALL_RELEASES=$(echo "$ALL_RELEASES $BATCH" | jq -s 'add') + PAGE=$((PAGE + 1)) + done + + echo "Found $(echo "$ALL_RELEASES" | jq 'length') total releases" + + # Filter to only beta and dev pre-releases (tags matching v*-beta.* or v*-dev.*) + PRE_RELEASES=$(echo "$ALL_RELEASES" | jq -c '[.[] | select(.prerelease == true and (.tag_name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+-(beta|dev)\\.[0-9]+$")))]') + echo "Found $(echo "$PRE_RELEASES" | jq 'length') beta/dev pre-releases" + + # Iterate over pre-releases and decide which to delete + while IFS= read -r release; do + TAG=$(echo "$release" | jq -r '.tag_name') + RELEASE_ID=$(echo "$release" | jq -r '.id') + + # Extract version from tag: v2.5.0-beta.3 -> 2.5.0, v2.5.0-dev.7 -> 2.5.0 + RELEASE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)-(beta|dev)\.[0-9]+$/\1/') + IFS='.' read -r REL_MAJOR REL_MINOR REL_PATCH <<< "$RELEASE_VERSION" + + DELETE=false + + if [[ "$CHANNEL" == "beta" ]]; then + # Beta release: delete all beta/dev pre-releases from OLDER versions + if [[ "$REL_MAJOR" -lt "$CUR_MAJOR" ]]; then + DELETE=true + elif [[ "$REL_MAJOR" -eq "$CUR_MAJOR" && "$REL_MINOR" -lt "$CUR_MINOR" ]]; then + DELETE=true + elif [[ "$REL_MAJOR" -eq "$CUR_MAJOR" && "$REL_MINOR" -eq "$CUR_MINOR" && "$REL_PATCH" -lt "$CUR_PATCH" ]]; then + DELETE=true + fi + elif [[ "$CHANNEL" == "stable" ]]; then + # Stable release: delete all beta/dev pre-releases of the SAME version and older + if [[ "$REL_MAJOR" -lt "$CUR_MAJOR" ]]; then + DELETE=true + elif [[ "$REL_MAJOR" -eq "$CUR_MAJOR" && "$REL_MINOR" -lt "$CUR_MINOR" ]]; then + DELETE=true + elif [[ "$REL_MAJOR" -eq "$CUR_MAJOR" && "$REL_MINOR" -eq "$CUR_MINOR" && "$REL_PATCH" -le "$CUR_PATCH" ]]; then + DELETE=true + fi + fi + + if [[ "$DELETE" == "true" ]]; then + echo "Deleting release: ${TAG} (id: ${RELEASE_ID})" + gh api -X DELETE "repos/${REPO}/releases/${RELEASE_ID}" || echo " Warning: failed to delete release ${TAG}" + + # Also delete the associated tag + echo "Deleting tag: ${TAG}" + gh api -X DELETE "repos/${REPO}/git/refs/tags/${TAG}" || echo " Warning: failed to delete tag ${TAG}" + + echo "- Deleted **${TAG}**" >> "$GITHUB_STEP_SUMMARY" + DELETED_COUNT=$((DELETED_COUNT + 1)) + else + echo "Keeping release: ${TAG}" + fi + done < <(echo "$PRE_RELEASES" | jq -c '.[]') + + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "**Total deleted:** ${DELETED_COUNT}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index 3b9fc622..4293d88f 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -4,25 +4,29 @@ on: workflow_dispatch: jobs: - release: - name: Build & Publish Beta + # ────────────────────────────────────────────── + # Job 1: Determine version metadata + # ────────────────────────────────────────────── + version: + name: Determine Version runs-on: ubuntu-latest permissions: - contents: write - packages: write + contents: read - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + outputs: + base: ${{ steps.version.outputs.base }} + build: ${{ steps.version.outputs.build }} + git: ${{ steps.version.outputs.git }} + branch: ${{ steps.version.outputs.branch }} + full: ${{ steps.version.outputs.full }} + display: ${{ steps.version.outputs.display }} steps: - uses: actions/checkout@v4 with: ref: dev - - uses: ./.github/actions/setup - - # --- Determine version --- - name: Determine version id: version run: | @@ -46,22 +50,41 @@ jobs: echo "- **Build:** ${BUILD_NUMBER}" >> "$GITHUB_STEP_SUMMARY" echo "- **Commit:** ${GIT_SHA}" >> "$GITHUB_STEP_SUMMARY" - # --- Replace start script placeholders --- + # ────────────────────────────────────────────── + # Job 2: Build, assemble artifacts, create zip + # ────────────────────────────────────────────── + build: + name: Build & Assemble + runs-on: ubuntu-latest + needs: version + + permissions: + contents: read + packages: read + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + with: + ref: dev + + - uses: ./.github/actions/setup + - name: Prepare start scripts run: | - VERSION="${{ steps.version.outputs.display }}" + VERSION="${{ needs.version.outputs.display }}" sed -i "s/%version%/${VERSION}/g" scripts/start.sh - # --- Build --- - name: Build env: REDICLOUD_CHANNEL: beta - REDICLOUD_BUILD: ${{ steps.version.outputs.build }} - REDICLOUD_GIT: ${{ steps.version.outputs.git }} - REDICLOUD_BRANCH: ${{ steps.version.outputs.branch }} - run: ./gradlew build -x test -PcloudVersion=${{ steps.version.outputs.display }} + REDICLOUD_BUILD: ${{ needs.version.outputs.build }} + REDICLOUD_GIT: ${{ needs.version.outputs.git }} + REDICLOUD_BRANCH: ${{ needs.version.outputs.branch }} + run: ./gradlew build -x test -PcloudVersion=${{ needs.version.outputs.display }} - # --- Assemble export --- - name: Assemble release artifacts run: | mkdir -p export/connectors export/modules export/api-files @@ -73,23 +96,43 @@ jobs: cp modules/*/build/libs/*.jar export/modules/ 2>/dev/null || true rm -f export/modules/*module-handler*.jar - # --- Create zip --- - name: Create zip archive run: | cd export - zip -r "../redicloud-${{ steps.version.outputs.full }}.zip" . + zip -r "../redicloud-${{ needs.version.outputs.full }}.zip" . cd .. - # --- Generate manifest --- + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: release-artifacts + retention-days: 1 + path: | + export/** + redicloud-${{ needs.version.outputs.full }}.zip + + # ────────────────────────────────────────────── + # Job 3: Generate manifest + GPG sign + # ────────────────────────────────────────────── + sign: + name: Manifest & Sign + runs-on: ubuntu-latest + needs: [version, build] + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: release-artifacts + - name: Generate release manifest run: | - VERSION="${{ steps.version.outputs.base }}" - FULL="${{ steps.version.outputs.full }}" - DISPLAY="${{ steps.version.outputs.display }}" + VERSION="${{ needs.version.outputs.base }}" + FULL="${{ needs.version.outputs.full }}" + DISPLAY="${{ needs.version.outputs.display }}" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") ZIP_NAME="redicloud-${FULL}.zip" - # Start JSON cat > manifest.json <> manifest.json done - # Close JSON cat >> manifest.json <<'MANIFEST_TAIL' } } MANIFEST_TAIL - # --- GPG signing --- - name: GPG sign manifest - if: ${{ env.GPG_PRIVATE_KEY != '' }} + if: ${{ secrets.GPG_PRIVATE_KEY != '' }} env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} run: | @@ -147,20 +186,58 @@ jobs: gpg --batch --yes --pinentry-mode loopback \ --detach-sign --armor manifest.json - # --- Publish to GitHub Packages --- + - name: Upload signed manifest + uses: actions/upload-artifact@v4 + with: + name: signed-manifest + retention-days: 1 + path: | + manifest.json + manifest.json.asc + + # ────────────────────────────────────────────── + # Job 4: Publish Maven artifacts + GitHub Release + # ────────────────────────────────────────────── + publish: + name: Publish Release + runs-on: ubuntu-latest + needs: [version, build, sign] + + permissions: + contents: write + packages: write + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + with: + ref: dev + + - uses: ./.github/actions/setup + - name: Publish Maven artifacts - run: ./gradlew publish -PcloudVersion=${{ steps.version.outputs.display }} + run: ./gradlew publish -PcloudVersion=${{ needs.version.outputs.display }} + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: release-artifacts + + - name: Download signed manifest + uses: actions/download-artifact@v4 + with: + name: signed-manifest - # --- Generate release body --- - name: Generate release body id: body run: | - VERSION="${{ steps.version.outputs.display }}" - FULL="${{ steps.version.outputs.full }}" + VERSION="${{ needs.version.outputs.display }}" + FULL="${{ needs.version.outputs.full }}" REPO="${{ github.repository }}" FINGERPRINT="2ABA6BC97D5FE276C1C7FCA47D569523230B5367" - # Find previous tag for changelog range git fetch --tags --quiet PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") if [[ -n "$PREV_TAG" ]]; then @@ -171,7 +248,6 @@ jobs: COMPARE_URL="" fi - # Parse conventional commits into categories FEATURES="" FIXES="" REFACTORS="" @@ -194,7 +270,6 @@ jobs: esac done < <(git log --oneline --no-merges ${RANGE} 2>/dev/null) - # Build changelog section CHANGELOG="" [[ -n "$FEATURES" ]] && CHANGELOG="${CHANGELOG}\n#### Features\n${FEATURES}\n" [[ -n "$FIXES" ]] && CHANGELOG="${CHANGELOG}\n#### Bug Fixes\n${FIXES}\n" @@ -202,7 +277,6 @@ jobs: [[ -n "$CI_BUILD" ]] && CHANGELOG="${CHANGELOG}\n#### CI / Build\n${CI_BUILD}\n" [[ -n "$OTHER" ]] && CHANGELOG="${CHANGELOG}\n#### Other\n${OTHER}\n" - # Compose full body { echo "body< [!WARNING]" @@ -233,17 +307,27 @@ jobs: echo "BODY_EOF" } >> "$GITHUB_OUTPUT" - # --- Create GitHub Pre-Release --- - name: Create GitHub Pre-Release uses: softprops/action-gh-release@v2 with: - tag_name: v${{ steps.version.outputs.display }} - name: ${{ steps.version.outputs.display }} + tag_name: v${{ needs.version.outputs.display }} + name: ${{ needs.version.outputs.display }} body: ${{ steps.body.outputs.body }} prerelease: true files: | - redicloud-${{ steps.version.outputs.full }}.zip + redicloud-${{ needs.version.outputs.full }}.zip manifest.json manifest.json.asc export/connectors/*.jar fail_on_unmatched_files: false + + # ────────────────────────────────────────────── + # Job 5: Cleanup old beta releases + # ────────────────────────────────────────────── + cleanup: + name: Cleanup Old Betas + needs: publish + uses: ./.github/workflows/cleanup-releases.yml + with: + channel: beta + secrets: inherit diff --git a/.github/workflows/release-stable.yml b/.github/workflows/release-stable.yml index 9ae686eb..60c20fe2 100644 --- a/.github/workflows/release-stable.yml +++ b/.github/workflows/release-stable.yml @@ -4,25 +4,27 @@ on: workflow_dispatch: jobs: - release: - name: Build & Publish Stable + # ────────────────────────────────────────────── + # Job 1: Determine version metadata + # ────────────────────────────────────────────── + version: + name: Determine Version runs-on: ubuntu-latest permissions: - contents: write - packages: write + contents: read - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + outputs: + base: ${{ steps.version.outputs.base }} + git: ${{ steps.version.outputs.git }} + branch: ${{ steps.version.outputs.branch }} + full: ${{ steps.version.outputs.full }} steps: - uses: actions/checkout@v4 with: ref: main - - uses: ./.github/actions/setup - - # --- Determine version --- - name: Determine version id: version run: | @@ -40,22 +42,41 @@ jobs: echo "- **Version:** ${FULL_VERSION}" >> "$GITHUB_STEP_SUMMARY" echo "- **Commit:** ${GIT_SHA}" >> "$GITHUB_STEP_SUMMARY" - # --- Replace start script placeholders --- + # ────────────────────────────────────────────── + # Job 2: Build, assemble artifacts, create zip + # ────────────────────────────────────────────── + build: + name: Build & Assemble + runs-on: ubuntu-latest + needs: version + + permissions: + contents: read + packages: read + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + with: + ref: main + + - uses: ./.github/actions/setup + - name: Prepare start scripts run: | - VERSION="${{ steps.version.outputs.base }}" + VERSION="${{ needs.version.outputs.base }}" sed -i "s/%version%/${VERSION}/g" scripts/start.sh - # --- Build --- - name: Build env: REDICLOUD_CHANNEL: stable REDICLOUD_BUILD: "0" - REDICLOUD_GIT: ${{ steps.version.outputs.git }} - REDICLOUD_BRANCH: ${{ steps.version.outputs.branch }} - run: ./gradlew build -x test -PcloudVersion=${{ steps.version.outputs.base }} + REDICLOUD_GIT: ${{ needs.version.outputs.git }} + REDICLOUD_BRANCH: ${{ needs.version.outputs.branch }} + run: ./gradlew build -x test -PcloudVersion=${{ needs.version.outputs.base }} - # --- Assemble export --- - name: Assemble release artifacts run: | mkdir -p export/connectors export/modules export/api-files @@ -67,22 +88,42 @@ jobs: cp modules/*/build/libs/*.jar export/modules/ 2>/dev/null || true rm -f export/modules/*module-handler*.jar - # --- Create zip --- - name: Create zip archive run: | cd export - zip -r "../redicloud-${{ steps.version.outputs.full }}.zip" . + zip -r "../redicloud-${{ needs.version.outputs.full }}.zip" . cd .. - # --- Generate manifest --- + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: release-artifacts + retention-days: 1 + path: | + export/** + redicloud-${{ needs.version.outputs.full }}.zip + + # ────────────────────────────────────────────── + # Job 3: Generate manifest + GPG sign + # ────────────────────────────────────────────── + sign: + name: Manifest & Sign + runs-on: ubuntu-latest + needs: [version, build] + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: release-artifacts + - name: Generate release manifest run: | - VERSION="${{ steps.version.outputs.base }}" - FULL="${{ steps.version.outputs.full }}" + VERSION="${{ needs.version.outputs.base }}" + FULL="${{ needs.version.outputs.full }}" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") ZIP_NAME="redicloud-${FULL}.zip" - # Start JSON cat > manifest.json <> manifest.json done - # Close JSON cat >> manifest.json <<'MANIFEST_TAIL' } } MANIFEST_TAIL - # --- GPG signing --- - name: GPG sign manifest - if: ${{ env.GPG_PRIVATE_KEY != '' }} + if: ${{ secrets.GPG_PRIVATE_KEY != '' }} env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} run: | @@ -140,20 +177,58 @@ jobs: gpg --batch --yes --pinentry-mode loopback \ --detach-sign --armor manifest.json - # --- Publish to GitHub Packages --- + - name: Upload signed manifest + uses: actions/upload-artifact@v4 + with: + name: signed-manifest + retention-days: 1 + path: | + manifest.json + manifest.json.asc + + # ────────────────────────────────────────────── + # Job 4: Publish Maven artifacts + GitHub Release + # ────────────────────────────────────────────── + publish: + name: Publish Release + runs-on: ubuntu-latest + needs: [version, build, sign] + + permissions: + contents: write + packages: write + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + with: + ref: main + + - uses: ./.github/actions/setup + - name: Publish Maven artifacts - run: ./gradlew publish -PcloudVersion=${{ steps.version.outputs.base }} + run: ./gradlew publish -PcloudVersion=${{ needs.version.outputs.base }} + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: release-artifacts + + - name: Download signed manifest + uses: actions/download-artifact@v4 + with: + name: signed-manifest - # --- Generate release body --- - name: Generate release body id: body run: | - VERSION="${{ steps.version.outputs.base }}" - FULL="${{ steps.version.outputs.full }}" + VERSION="${{ needs.version.outputs.base }}" + FULL="${{ needs.version.outputs.full }}" REPO="${{ github.repository }}" FINGERPRINT="2ABA6BC97D5FE276C1C7FCA47D569523230B5367" - # Find previous tag for changelog range git fetch --tags --quiet PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") if [[ -n "$PREV_TAG" ]]; then @@ -164,7 +239,6 @@ jobs: COMPARE_URL="" fi - # Parse conventional commits into categories FEATURES="" FIXES="" REFACTORS="" @@ -187,7 +261,6 @@ jobs: esac done < <(git log --oneline --no-merges ${RANGE} 2>/dev/null) - # Build changelog section CHANGELOG="" [[ -n "$FEATURES" ]] && CHANGELOG="${CHANGELOG}\n#### Features\n${FEATURES}\n" [[ -n "$FIXES" ]] && CHANGELOG="${CHANGELOG}\n#### Bug Fixes\n${FIXES}\n" @@ -195,7 +268,6 @@ jobs: [[ -n "$CI_BUILD" ]] && CHANGELOG="${CHANGELOG}\n#### CI / Build\n${CI_BUILD}\n" [[ -n "$OTHER" ]] && CHANGELOG="${CHANGELOG}\n#### Other\n${OTHER}\n" - # Compose full body { echo "body<> "$GITHUB_OUTPUT" - # --- Create GitHub Release --- - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - tag_name: v${{ steps.version.outputs.base }} - name: ${{ steps.version.outputs.base }} + tag_name: v${{ needs.version.outputs.base }} + name: ${{ needs.version.outputs.base }} body: ${{ steps.body.outputs.body }} prerelease: false files: | - redicloud-${{ steps.version.outputs.full }}.zip + redicloud-${{ needs.version.outputs.full }}.zip manifest.json manifest.json.asc export/connectors/*.jar fail_on_unmatched_files: false + + # ────────────────────────────────────────────── + # Job 5: Cleanup beta releases for this version + # ────────────────────────────────────────────── + cleanup: + name: Cleanup Betas + needs: publish + uses: ./.github/workflows/cleanup-releases.yml + with: + channel: stable + secrets: inherit