CI (main)
#5425
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| run-name: "CI (${{ github.event_name == 'push' && '`main`' || (contains(github.event.pull_request.head.ref, 'release-please') && 'Release-Please PR' || format('PR `#{0}`', github.event.pull_request.number)) || '' }})" | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| branches: | |
| - main | |
| concurrency: | |
| group: ${{ github.event_name == 'pull_request' && format('ci-pr-{0}', github.event.pull_request.number) || format('ci-main-{0}', github.run_id) }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.head.ref, 'release-please') }} | |
| jobs: | |
| detect-context: | |
| name: Detect Context | |
| runs-on: ubuntu-slim | |
| outputs: | |
| is_release_please_pr: ${{ steps.context.outputs.is_release_please_pr }} | |
| is_regular_pr: ${{ steps.context.outputs.is_regular_pr }} | |
| is_push_to_main: ${{ steps.context.outputs.is_push_to_main }} | |
| is_fork_pr: ${{ steps.context.outputs.is_fork_pr }} | |
| is_release_please_merge: ${{ steps.context.outputs.is_release_please_merge }} | |
| release_version: ${{ steps.context.outputs.release_version }} | |
| pr_base_sha: ${{ steps.context.outputs.pr_base_sha }} | |
| steps: | |
| - id: context | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| HEAD_REF: ${{ github.head_ref }} | |
| IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }} | |
| IS_PUSH_TO_MAIN: ${{ github.event_name == 'push' && github.ref_name == github.event.repository.default_branch }} | |
| IS_REGULAR_PR: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && !contains(github.event.pull_request.head.ref, 'release-please') }} | |
| IS_RELEASE_PLEASE_PR: ${{ contains(github.event.pull_request.head.ref, 'release-please') }} | |
| HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} | |
| run: | | |
| echo "Determining context..." | |
| echo "is_fork_pr=$IS_FORK_PR" >> "$GITHUB_OUTPUT" | |
| echo "is_push_to_main=$IS_PUSH_TO_MAIN" >> "$GITHUB_OUTPUT" | |
| echo "is_regular_pr=$IS_REGULAR_PR" >> "$GITHUB_OUTPUT" | |
| echo "is_release_please_pr=$IS_RELEASE_PLEASE_PR" >> "$GITHUB_OUTPUT" | |
| # Detect release-please merge: push to main where the merge commit | |
| # message matches release-please's title pattern (proven in release-post-process.yml). | |
| IS_RELEASE_PLEASE_MERGE=false | |
| RELEASE_VERSION="" | |
| if [[ "$IS_PUSH_TO_MAIN" == "true" && "$HEAD_COMMIT_MESSAGE" =~ ^chore(\(.*\))?:\ release\ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then | |
| IS_RELEASE_PLEASE_MERGE=true | |
| RELEASE_VERSION="${BASH_REMATCH[2]}" | |
| fi | |
| echo "is_release_please_merge=$IS_RELEASE_PLEASE_MERGE" >> "$GITHUB_OUTPUT" | |
| echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT" | |
| pr-size-check: | |
| name: PR Size Check | |
| needs: detect-context | |
| if: ${{ github.event_name == 'pull_request'}} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| statuses: write | |
| pull-requests: read | |
| steps: | |
| - name: Validate PR Size | |
| uses: SolaceDev/solace-public-workflows/.github/actions/pr-size-check@main | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| max-lines: 1500 | |
| validate-conventional-commit: | |
| name: "Validate Conventional Commit" | |
| needs: detect-context | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event_name == 'pull_request'}} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate PR Title | |
| uses: amannn/action-semantic-pull-request@e32d7e603df1aa1ba07e981f2a23455dee596825 # v5 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| types: | | |
| feat | |
| fix | |
| docs | |
| style | |
| refactor | |
| perf | |
| test | |
| build | |
| ci | |
| chore | |
| revert | |
| requireScope: false | |
| disallowScopes: | | |
| release | |
| subjectPattern: ^.+$ | |
| subjectPatternError: | | |
| The subject "{subject}" found in the pull request title "{title}" | |
| didn't match the configured pattern. Please ensure that the subject | |
| is not empty. | |
| fossa_scan: | |
| name: FOSSA Scan | |
| needs: detect-context | |
| if: ${{ fromJSON(needs.detect-context.outputs.is_regular_pr) || github.event_name == 'push' }} | |
| uses: SolaceDev/solace-public-workflows/.github/workflows/sca-scan-and-guard.yaml@main | |
| permissions: | |
| contents: read | |
| packages: read | |
| pull-requests: write | |
| id-token: write | |
| actions: read | |
| checks: write | |
| statuses: write | |
| with: | |
| use_vault: false | |
| config_file: ".github/workflow-config.json" | |
| setup_actions: '["setup-uv"]' | |
| custom_setup_script: "uv export --format requirements-txt --no-dev --output-file requirements.txt" | |
| additional_scan_params: | | |
| fossa.branch=${{ github.event_name == 'pull_request' && 'PR' || 'main' }} | |
| fossa.revision=${{ needs.detect-context.outputs.release_version || github.event.pull_request.head.ref || github.sha }} | |
| secrets: | |
| FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }} | |
| test-and-sonarqube: | |
| name: Test and SonarQube | |
| needs: detect-context | |
| uses: ./.github/workflows/test-and-sonarqube.yml | |
| permissions: | |
| contents: read | |
| checks: write | |
| pull-requests: write | |
| with: | |
| git_ref: ${{ github.event.pull_request.head.ref || github.ref_name }} | |
| min-python-version: "3.10" | |
| max-python-version: "3.13" | |
| node-version: "25.5.0" | |
| ui-path: "client/webui/frontend" | |
| run_sonarqube_analysis: ${{ !fromJSON(needs.detect-context.outputs.is_fork_pr)}} | |
| run_quality_gate: ${{ fromJSON(needs.detect-context.outputs.is_regular_pr) }} | |
| secrets: | |
| SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} | |
| SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }} | |
| build-and-push: | |
| name: Build and Push Image | |
| needs: detect-context | |
| uses: ./.github/workflows/build-push-image.yaml | |
| permissions: | |
| contents: write | |
| id-token: write | |
| packages: write | |
| checks: write | |
| pull-requests: write | |
| actions: read | |
| repository-projects: read | |
| with: | |
| ref: ${{ github.event.pull_request.head.ref || github.ref_name }} | |
| push_to_ecr: ${{ fromJSON(needs.detect-context.outputs.is_push_to_main) || fromJSON(needs.detect-context.outputs.is_release_please_pr) }} | |
| run_container_scan: ${{ fromJSON(needs.detect-context.outputs.is_regular_pr) }} | |
| is_release_merge: ${{ fromJSON(needs.detect-context.outputs.is_release_please_merge) }} | |
| secrets: inherit | |
| # ── Release-please PR: release readiness ────────────────────── | |
| release-readiness: | |
| name: Release Readiness | |
| needs: [detect-context, build-and-push, test-and-sonarqube] | |
| if: ${{ fromJSON(needs.detect-context.outputs.is_release_please_pr) }} | |
| uses: ./.github/workflows/release-readiness-check.yaml | |
| with: | |
| commit_sha: ${{ github.event.pull_request.head.sha }} | |
| version: ${{ needs.build-and-push.outputs.version }} | |
| image_tag: ${{ needs.build-and-push.outputs.image_tag }} | |
| check_only: false | |
| check_sonarqube_hotspots: true | |
| secrets: inherit | |
| # ── Release-please PR: RC gate (after release readiness) ────── | |
| rc-gate: | |
| name: RC Gate | |
| needs: [detect-context, build-and-push, release-readiness] | |
| if: >- | |
| needs.detect-context.outputs.is_release_please_pr == 'true' && | |
| needs.release-readiness.result == 'success' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| rc_status: ${{ steps.capture.outputs.status }} | |
| steps: | |
| - name: Dispatch RC workflow and wait | |
| id: run-rc | |
| uses: SolaceDev/solace-public-workflows/workflow-dispatch-and-wait@main | |
| with: | |
| workflow: rc-workflow.yaml | |
| repo: SolaceDev/rc-sam-community | |
| ref: main | |
| token: ${{ secrets.RC_TOKEN }} | |
| wait-for-completion: "true" | |
| wait-for-completion-timeout: "2h" | |
| wait-for-completion-interval: "1m" | |
| inputs: >- | |
| {"sha":"${{ github.event.pull_request.head.sha }}","version":"${{ needs.build-and-push.outputs.version }}","image_tag":"${{ needs.build-and-push.outputs.image_tag }}","environment":"rc"} | |
| - name: Capture RC status | |
| id: capture | |
| if: always() | |
| env: | |
| RC_CONCLUSION: ${{ steps.run-rc.outputs.workflow-conclusion }} | |
| run: | | |
| STATUS=success | |
| if [[ "$RC_CONCLUSION" != "success" ]]; then | |
| STATUS=failure | |
| fi | |
| echo "status=$STATUS" >> "$GITHUB_OUTPUT" | |
| - name: Enforce RC result | |
| if: steps.run-rc.outputs.workflow-conclusion != 'success' | |
| env: | |
| RC_CONCLUSION: ${{ steps.run-rc.outputs.workflow-conclusion }} | |
| run: | | |
| echo "RC workflow conclusion: ${RC_CONCLUSION}" >&2 | |
| exit 1 | |
| # ── Release-please PR: propagate statuses ───────────────────── | |
| propagate-release-statuses: | |
| name: Propagate Release Statuses | |
| needs: [detect-context, release-readiness, rc-gate] | |
| if: always() && needs.detect-context.outputs.is_release_please_pr == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| checks: write | |
| statuses: write | |
| steps: | |
| - name: Propagate statuses to PR SHAs | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| env: | |
| PR_HEAD_SHA: ${{ needs.detect-context.outputs.pr_head_sha }} | |
| PR_BASE_SHA: ${{ needs.detect-context.outputs.pr_base_sha }} | |
| FOSSA_STATUS: ${{ needs.release-readiness.outputs.fossa_status }} | |
| PRISMA_STATUS: ${{ needs.release-readiness.outputs.prisma_status }} | |
| SONAR_STATUS: ${{ needs.release-readiness.outputs.sonarqube_hotspots_status }} | |
| GUARDIAN_STATUS: ${{ needs.release-readiness.outputs.guardian_status }} | |
| RC_STATUS: ${{ needs.rc-gate.outputs.rc_status }} | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const targetUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`; | |
| const checks = [ | |
| ['FOSSA / Release Readiness', process.env.FOSSA_STATUS], | |
| ['Prisma / Release Readiness', process.env.PRISMA_STATUS], | |
| ['SonarQube Hotspots / Release Readiness', process.env.SONAR_STATUS], | |
| ['Guardian / Release Readiness', process.env.GUARDIAN_STATUS], | |
| ['RC / Integration Tests (Community)', process.env.RC_STATUS], | |
| ]; | |
| for (const sha of [process.env.PR_HEAD_SHA, process.env.PR_BASE_SHA].filter(Boolean)) { | |
| for (const [contextName, value] of checks) { | |
| const state = (value === 'success' || value === 'skipped') ? 'success' : 'failure'; | |
| await github.rest.repos.createCommitStatus({ | |
| owner, repo, sha, | |
| state, | |
| context: contextName, | |
| description: `Release readiness: ${value || 'unknown'}`, | |
| target_url: targetUrl, | |
| }); | |
| } | |
| } | |
| # ── Unified gate for branch protection ──────────────────────── | |
| ci-status: | |
| name: CI Status | |
| if: always() | |
| runs-on: ubuntu-latest | |
| needs: | |
| - detect-context | |
| - fossa_scan | |
| - test-and-sonarqube | |
| - build-and-push | |
| - release-readiness | |
| - rc-gate | |
| - propagate-release-statuses | |
| permissions: | |
| contents: read | |
| id-token: write | |
| checks: write | |
| statuses: write | |
| steps: | |
| - name: Evaluate required checks | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| IS_RP_PR: ${{ needs.detect-context.outputs.is_release_please_pr }} | |
| CONTEXT_RESULT: ${{ needs.detect-context.result }} | |
| FOSSA_RESULT: ${{ needs.fossa_scan.result }} | |
| TEST_RESULT: ${{ needs.test-and-sonarqube.result }} | |
| BUILD_RESULT: ${{ needs.build-and-push.result }} | |
| READINESS_RESULT: ${{ needs.release-readiness.result }} | |
| RC_GATE_RESULT: ${{ needs.rc-gate.result }} | |
| PROPAGATION_RESULT: ${{ needs.propagate-release-statuses.result }} | |
| run: | | |
| set -euo pipefail | |
| require_result() { | |
| local name="$1"; local value="$2"; shift 2; local allowed=("$@") | |
| for expected in "${allowed[@]}"; do | |
| if [[ "$value" == "$expected" ]]; then | |
| echo "✅ ${name}: ${value}" | |
| return 0 | |
| fi | |
| done | |
| echo "::error::${name} is '${value}' (expected: ${allowed[*]})" | |
| exit 1 | |
| } | |
| require_result "Detect Context" "${CONTEXT_RESULT}" success | |
| require_result "Test and SonarQube" "${TEST_RESULT}" success | |
| require_result "Build and Push" "${BUILD_RESULT}" success | |
| if [[ "$IS_RP_PR" == "true" ]]; then | |
| require_result "Release Readiness" "${READINESS_RESULT}" success | |
| require_result "RC Gate" "${RC_GATE_RESULT}" success | |
| require_result "Propagate Statuses" "${PROPAGATION_RESULT}" success | |
| else | |
| require_result "FOSSA PR Scan" "${FOSSA_RESULT}" success skipped | |
| fi |