Skip to content

CI (main)

CI (main) #5425

Workflow file for this run

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