From 3f2cba1f04506b2eb4f0aed5a897329be8a7350a Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Wed, 15 Oct 2025 17:18:41 -0500 Subject: [PATCH] * Updated workflow * Added GPG public signing key --- .github/workflows/release.yml | 241 ++++++++++++++------------ keys_and_checksums/public-gpg-key.asc | 10 ++ 2 files changed, 142 insertions(+), 109 deletions(-) create mode 100644 keys_and_checksums/public-gpg-key.asc diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d39a483..941aee7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,6 +16,11 @@ on: description: "New version to release (e.g., 0.2.0)" required: true type: string + pr_check_timeout: + description: "Timeout in seconds for PR status checks (default: 1800)" + required: false + type: number + default: 1800 jobs: description: "Comma-separated jobs to run (e.g., build-windows,build-debian,build-arch,build-rhel)" required: true @@ -35,15 +40,23 @@ env: jobs: bump-version: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write outputs: pr_number: ${{ steps.create-pr.outputs.pr_number }} steps: - name: Validate version format run: | VERSION="${{ github.event.inputs.new_version }}" - if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + # Full semver regex supporting: + # - Basic: 1.2.3 + # - Prerelease: 1.2.3-beta, 1.2.3-rc.1, 1.2.3-alpha.1.2 + # - Build metadata: 1.2.3+build, 1.2.3+20130313144700 + # - Combined: 1.2.3-beta.1+build.123 + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?(\+[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$ ]]; then echo "::error::Invalid version format: $VERSION" - echo "::error::Expected format: MAJOR.MINOR.PATCH (e.g., 1.2.3 or 1.2.3-beta)" + echo "::error::Expected semantic version format (e.g., 1.2.3, 1.2.3-beta.1, 1.2.3+build.123)" exit 1 fi echo "Version format is valid: $VERSION" @@ -52,7 +65,6 @@ jobs: uses: actions/checkout@v4 with: ref: develop - fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Configure git @@ -68,12 +80,42 @@ jobs: - name: Install Poetry uses: snok/install-poetry@v1 + - name: Check for existing PR or branch + id: check-existing + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + VERSION="${{ github.event.inputs.new_version }}" + BRANCH_NAME="release/v${VERSION}" + + # Check if branch already exists + if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then + echo "::warning::Branch $BRANCH_NAME already exists" + + # Check if there's an open PR for this branch + EXISTING_PR=$(gh pr list --base develop --head "$BRANCH_NAME" --state open --json number --jq '.[0].number' || echo "") + + if [ -n "$EXISTING_PR" ]; then + echo "::error::PR #$EXISTING_PR already exists for version $VERSION" + echo "::error::Please close or merge the existing PR before creating a new release" + exit 1 + fi + + echo "::error::Branch $BRANCH_NAME exists but no open PR found" + echo "::error::Please delete the branch or use a different version number" + exit 1 + fi + + echo "✓ No existing branch or PR found for version $VERSION" + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT + - name: Create release branch and bump version id: bump run: | set -e VERSION="${{ github.event.inputs.new_version }}" - BRANCH_NAME="release/v${VERSION}" + BRANCH_NAME="${{ steps.check-existing.outputs.branch_name }}" # Create and checkout release branch git checkout -b "$BRANCH_NAME" @@ -85,8 +127,11 @@ jobs: git add pyproject.toml git commit -m "Bump version to ${VERSION}" - # Push the branch - git push origin "$BRANCH_NAME" + # Push the branch with error handling + if ! git push origin "$BRANCH_NAME"; then + echo "::error::Failed to push branch $BRANCH_NAME" + exit 1 + fi echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT @@ -109,7 +154,10 @@ jobs: **Auto-generated by release workflow** Once status checks pass, this PR will be automatically merged." \ - --repo ${{ github.repository }}) + --repo ${{ github.repository }} || { + echo "::error::Failed to create PR" + exit 1 + }) # Extract PR number from URL PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$') @@ -117,12 +165,18 @@ jobs: echo "Created PR #$PR_NUMBER: $PR_URL" # Enable auto-merge (squash) - gh pr merge "$PR_NUMBER" --auto --squash --repo ${{ github.repository }} + if ! gh pr merge "$PR_NUMBER" --auto --squash --repo ${{ github.repository }}; then + echo "::error::Failed to enable auto-merge for PR #$PR_NUMBER" + exit 1 + fi echo "Auto-merge enabled for PR #$PR_NUMBER" wait-for-version-pr: needs: [bump-version] runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read steps: - name: Checkout repository uses: actions/checkout@v4 @@ -135,9 +189,10 @@ jobs: PR_NUMBER="${{ needs.bump-version.outputs.pr_number }}" echo "Monitoring PR #$PR_NUMBER for status checks..." - MAX_WAIT=1800 # 30 minutes max wait + MAX_WAIT=${{ github.event.inputs.pr_check_timeout || 1800 }} SLEEP_INTERVAL=30 ELAPSED=0 + echo "Max wait time: ${MAX_WAIT}s" while [ $ELAPSED -lt $MAX_WAIT ]; do # Get PR status @@ -182,6 +237,8 @@ jobs: merge-develop-to-main: needs: [wait-for-version-pr] runs-on: ubuntu-latest + permissions: + contents: write outputs: merge_commit_sha: ${{ steps.merge.outputs.merge_commit_sha }} previous_main_sha: ${{ steps.merge.outputs.previous_main_sha }} @@ -227,14 +284,19 @@ jobs: exit 0 fi - # Fast-forward merge develop into main + # Merge develop into main (create merge commit for revert capability) git checkout main - git merge origin/develop --ff-only + git merge origin/develop --no-ff -m "Merge develop into main for release" - echo "Successfully fast-forwarded main to develop" + echo "Successfully merged develop into main" git log origin/main..HEAD --oneline - git push origin main + # Push with error handling + if ! git push origin main; then + echo "::error::Failed to push merge commit to main" + exit 1 + fi + echo "Successfully pushed merge commit to main" # Store the new merge commit SHA MERGE_COMMIT_SHA=$(git rev-parse HEAD) @@ -244,44 +306,65 @@ jobs: verify-main-status-checks: needs: [merge-develop-to-main] runs-on: ubuntu-latest + permissions: + contents: write + actions: read steps: - name: Checkout main branch uses: actions/checkout@v4 with: ref: main - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install Poetry - uses: snok/install-poetry@v1 - - - name: Install dependencies + - name: Verify commit status via GitHub API + id: check-status + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - poetry install + set -e + MERGE_COMMIT="${{ needs.merge-develop-to-main.outputs.merge_commit_sha }}" + echo "Checking status of commit: $MERGE_COMMIT" - - name: Run unit tests - Linux - id: test-linux - run: | - poetry run pytest tests/ -v + # Wait a moment for status checks to be registered + sleep 5 - - name: Run unit tests - Windows (via Act or skip) - id: test-windows - continue-on-error: true - run: | - echo "Windows tests would run here in a matrix job" - echo "Skipping for now as this is a Linux runner" + # Get commit status using GitHub API + MAX_WAIT=300 # 5 minutes max wait for status checks to appear + SLEEP_INTERVAL=10 + ELAPSED=0 - - name: Build verification - id: build-check - run: | - echo "Build checks passed" + while [ $ELAPSED -lt $MAX_WAIT ]; do + # Get combined status for the commit + STATUS_RESPONSE=$(gh api "repos/${{ github.repository }}/commits/${MERGE_COMMIT}/status" --jq '{state: .state, statuses: .statuses | length, total_count: .total_count}' || echo '{"state":"pending","statuses":0,"total_count":0}') - - name: Handle test failures with rollback - if: failure() + STATE=$(echo "$STATUS_RESPONSE" | jq -r '.state') + TOTAL_COUNT=$(echo "$STATUS_RESPONSE" | jq -r '.total_count') + + echo "Commit status: state=$STATE, checks=$TOTAL_COUNT (${ELAPSED}s elapsed)" + + if [ "$STATE" = "success" ]; then + echo "✓ All status checks passed on main branch" + exit 0 + elif [ "$STATE" = "failure" ] || [ "$STATE" = "error" ]; then + echo "::error::Status checks failed on commit $MERGE_COMMIT" + gh api "repos/${{ github.repository }}/commits/${MERGE_COMMIT}/status" --jq '.statuses[] | select(.state == "failure" or .state == "error") | "- " + .context + ": " + .state' + exit 1 + fi + + sleep $SLEEP_INTERVAL + ELAPSED=$((ELAPSED + SLEEP_INTERVAL)) + done + + # If we get here, treat as success if no checks were registered + if [ "$TOTAL_COUNT" -eq 0 ]; then + echo "⚠ No status checks found for commit, proceeding..." + exit 0 + fi + + echo "::warning::Status checks still pending after ${MAX_WAIT}s, proceeding with caution..." + exit 0 + + - name: Handle status check failures with rollback + if: failure() && steps.check-status.outcome == 'failure' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -303,78 +386,18 @@ jobs: git checkout main git revert "$MERGE_COMMIT" --no-edit -m 1 - # Push the revert commit - git push origin main + # Push the revert commit with error handling + if ! git push origin main; then + echo "::error::Failed to push revert commit" + exit 1 + fi echo "::error::Reverted merge commit $MERGE_COMMIT on main branch" echo "::error::Workflow failed due to status check failures" exit 1 - run-unit-tests-linux: - needs: [verify-main-status-checks] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: main - - - name: Display build information - run: | - echo "Event: ${{ github.event_name }}" - echo "Jobs to run: ${{ github.event.inputs.jobs || 'build-windows,build-debian,build-arch,build-rhel' }}" - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install Poetry - uses: snok/install-poetry@v1 - - - name: Install dependencies - run: | - poetry install - - - name: Run tests - run: | - poetry run pytest tests/ -v - - run-unit-tests-windows: - needs: [verify-main-status-checks] - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - with: - ref: main - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: latest - virtualenvs-create: true - virtualenvs-in-project: true - - - name: Ensure Poetry is on PATH (Windows) - shell: pwsh - run: | - # Add Poetry user bin to PATH for subsequent steps in this job - $poetryPath = Join-Path $env:USERPROFILE ".local\bin" - Write-Output $poetryPath >> $Env:GITHUB_PATH - - - name: Install dependencies - run: | - poetry install - - - name: Run tests - run: | - poetry run pytest tests/ -v build-windows: - needs: [verify-main-status-checks, run-unit-tests-linux, run-unit-tests-windows] + needs: [verify-main-status-checks] if: ${{ contains(github.event.inputs.jobs, 'build-windows') }} runs-on: windows-latest steps: @@ -420,7 +443,7 @@ jobs: dist/android-file-handler.exe build-debian: - needs: [verify-main-status-checks, run-unit-tests-linux, run-unit-tests-windows] + needs: [verify-main-status-checks] if: ${{ contains(github.event.inputs.jobs, 'build-debian') }} env: DISTRO_TYPE: debian @@ -507,7 +530,7 @@ jobs: pkg_dist_debian/** build-arch: - needs: [verify-main-status-checks, run-unit-tests-linux, run-unit-tests-windows] + needs: [verify-main-status-checks] if: ${{ contains(github.event.inputs.jobs, 'build-arch') }} env: DISTRO_TYPE: arch @@ -587,7 +610,7 @@ jobs: build-rhel: - needs: [verify-main-status-checks, run-unit-tests-linux, run-unit-tests-windows] + needs: [verify-main-status-checks] if: ${{ contains(github.event.inputs.jobs, 'build-rhel') }} permissions: contents: read diff --git a/keys_and_checksums/public-gpg-key.asc b/keys_and_checksums/public-gpg-key.asc new file mode 100644 index 0000000..59cd433 --- /dev/null +++ b/keys_and_checksums/public-gpg-key.asc @@ -0,0 +1,10 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaPAWyxYJKwYBBAHaRw8BAQdAZvH8TI491M3W7PCRrs3Iks4qsIMGFZ71UW4E +Di808m20OmFuZHJvaWQtZmlsZS1oYW5kbGVyIFJlbGVhc2UgQm90IDxqYXNvbi5y +b3NzODQxQGdtYWlsLmNvbT6IkwQTFgoAOxYhBAlZWbUAICc77jajXKVv3PRrBEqG +BQJo8BbLAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEKVv3PRrBEqG +FGIA/3wXwy2esmP0M5kVwyjoXvkxz9icqETxvWj613nVgTP0AQCErfBCae5gce2h +Ruw4g2a1dyvO+020t429qXv1T8XhCA== +=GOiu +-----END PGP PUBLIC KEY BLOCK-----