diff --git a/.github/workflows/PR-GATE-README.md b/.github/workflows/PR-GATE-README.md new file mode 100644 index 0000000000..865b19c773 --- /dev/null +++ b/.github/workflows/PR-GATE-README.md @@ -0,0 +1,150 @@ +# PR Gate Workflow + +This document explains the PR Gate workflow and how to configure branch protection rules. + +## Problem + +ToolHive has multiple quality gate workflows that run conditionally based on which files are changed: +- `run-on-pr.yml` - Runs when Go code changes (excludes `deploy/charts/**`) +- `run-on-pr-charts.yml` - Runs when Helm charts change (only `deploy/charts/**`) + +This creates a problem for branch protection: +- If you require BOTH workflows, no PR can pass (one will never trigger) +- If you require neither, you can't enforce quality gates + +## Solution + +The `pr-gate.yml` workflow acts as a sentinel that: +1. Detects which files changed using `dorny/paths-filter` +2. Conditionally runs the appropriate quality checks +3. Provides a single "All Checks Pass" job that dynamically discovers and validates all workflow runs +4. Uses the GitHub API to query workflow statuses (no static list maintenance required) + +This is a well-established pattern used by many large open-source projects. + +## Configuration + +### Branch Protection Rules + +Configure your branch protection to require only the **"All Checks Pass"** job: + +1. Go to Repository Settings → Branches +2. Add or edit a branch protection rule for `main` +3. Enable "Require status checks to pass before merging" +4. Search for and select: **"All Checks Pass"** +5. Remove any other workflow requirements (the gate handles them all) + +### How It Works + +```mermaid +graph TD + A[PR Created] --> B[changes job] + B -->|code files| C[Go code checks] + B -->|chart files| D[Chart checks] + B -->|both| E[Both sets of checks] + C --> F[all-checks sentinel] + D --> F + E --> F + F -->|all passed/skipped| G[✅ PR can merge] + F -->|any failed| H[❌ PR blocked] +``` + +The `all-checks` job: +- Runs with `if: always()` so it executes unconditionally +- Uses GitHub API to dynamically discover all workflow runs for the PR +- Polls until all workflows complete (max 60 minutes timeout) +- Succeeds if all workflows are either `success` or `skipped` +- Fails if any workflow has status `failure` or `cancelled` +- **No manual maintenance** - automatically detects new/removed workflows + +## Deprecating Old Workflows + +Once the PR gate is enabled and tested, you can consider deprecating: +- `run-on-pr.yml` +- `run-on-pr-charts.yml` + +These workflows are now replaced by `pr-gate.yml`. However, you may want to keep them temporarily for: +- Existing PRs that reference them +- Gradual migration period +- Rollback capability if issues arise + +## Modifying the Gate + +The gate automatically discovers all workflows that run on PRs - no manual maintenance needed! + +To add or remove checks: + +1. **Add a new check**: + - Simply add the workflow with `on: pull_request` trigger + - The gate will automatically detect and wait for it + - No need to update the gate workflow itself + +2. **Remove a check**: + - Remove or disable the workflow + - The gate will stop waiting for it automatically + +3. **Modify path filters** (optional, for conditional workflows): + - You can use the `changes` job outputs in your workflow's `if:` condition + - Or implement your own path filtering in the workflow + - The gate respects workflows that choose not to run (treats as skipped) + +## Testing + +To test the gate workflow: + +1. **Test with code changes**: + ```bash + # Make a small Go file change + echo "// test" >> cmd/thv/main.go + git add cmd/thv/main.go + git commit -m "test: verify PR gate with code change" + git push origin feature-branch + ``` + - Verify: Code checks run, chart checks skip, "All Checks Pass" succeeds + +2. **Test with chart changes**: + ```bash + # Make a chart change + echo "# test" >> deploy/charts/toolhive/README.md + git add deploy/charts/toolhive/README.md + git commit -m "test: verify PR gate with chart change" + git push origin feature-branch + ``` + - Verify: Chart checks run, code checks skip, "All Checks Pass" succeeds + +3. **Test with both**: + ```bash + # Change both code and charts + echo "// test" >> cmd/thv/main.go + echo "# test" >> deploy/charts/toolhive/README.md + git add . + git commit -m "test: verify PR gate with mixed changes" + git push origin feature-branch + ``` + - Verify: ALL checks run, "All Checks Pass" succeeds + +## Troubleshooting + +### Jobs not running as expected + +Check the `changes` job output to see which filters matched: +- Go to Actions → PR Gate workflow → changes job +- Look at the outputs: `code: true/false` and `charts: true/false` + +### All checks skip + +This can happen if: +- The path filters don't match any changed files +- Review the `filters:` configuration in the `changes` job + +### Sentinel reports failure even though all checks passed + +Check the status of each job in the `all-checks` step output: +- Jobs should be `success` or `skipped`, not `failure` or `cancelled` +- If a job shows `cancelled`, check if it has proper `if: !cancelled()` conditions + +## References + +- [GitHub Actions: Using jobs in a workflow](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobs) +- [dorny/paths-filter action](https://github.com/dorny/paths-filter) +- [Branch protection rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 7001d662b7..1743fa57a7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -1,15 +1,36 @@ name: E2E Tests on: - workflow_call: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs permissions: contents: read jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + code: + - '!deploy/charts/**' + build-binary: name: Build ToolHive Binary runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 @@ -42,7 +63,11 @@ jobs: e2e-tests-core: name: E2E Tests Core (${{ matrix.title }}) runs-on: ubuntu-latest - needs: build-binary + needs: [changes, build-binary] + if: | + always() && + needs.build-binary.result == 'success' && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') strategy: fail-fast: false matrix: diff --git a/.github/workflows/license-headers.yml b/.github/workflows/license-headers.yml index 39ee79b385..069a5107f0 100644 --- a/.github/workflows/license-headers.yml +++ b/.github/workflows/license-headers.yml @@ -4,15 +4,36 @@ name: License Headers on: - workflow_call: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs permissions: contents: read jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + code: + - '!deploy/charts/**' + check-license-headers: name: Check License Headers runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 diff --git a/.github/workflows/lint-helm-charts.yml b/.github/workflows/lint-helm-charts.yml index b5f4c95831..15dbed17fd 100644 --- a/.github/workflows/lint-helm-charts.yml +++ b/.github/workflows/lint-helm-charts.yml @@ -4,12 +4,33 @@ permissions: contents: read on: - workflow_call: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + charts: ${{ steps.filter.outputs.charts }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + charts: + - 'deploy/charts/**' + lint-helm-charts: name: Lint Helm Charts runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.charts == 'true') env: GO111MODULE: on steps: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e53708278b..e10af81bd2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,15 +1,36 @@ name: Linting on: - workflow_call: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs permissions: contents: read jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + code: + - '!deploy/charts/**' + lint-go-code: name: Lint Go Code runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 diff --git a/.github/workflows/operator-ci.yml b/.github/workflows/operator-ci.yml index 92a84ae2ae..dba92ef2c4 100644 --- a/.github/workflows/operator-ci.yml +++ b/.github/workflows/operator-ci.yml @@ -1,16 +1,36 @@ name: Operator CI on: - workflow_call: - workflow_dispatch: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs permissions: contents: read jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + code: + - '!deploy/charts/**' + operator-tests: name: Operator Tests runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 @@ -32,6 +52,10 @@ jobs: operator-tests-integration: name: Operator Tests Integration runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 @@ -53,6 +77,10 @@ jobs: build-operator: name: Build Operator runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 @@ -74,6 +102,10 @@ jobs: generate-crds: name: Generate CRDs runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 @@ -107,6 +139,10 @@ jobs: generate-crd-docs: name: Generate CRD Docs runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 @@ -140,6 +176,10 @@ jobs: e2e-tests-operator: name: E2E Tests Operator runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') timeout-minutes: 30 defaults: run: diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml new file mode 100644 index 0000000000..5505472842 --- /dev/null +++ b/.github/workflows/pr-gate.yml @@ -0,0 +1,135 @@ +# SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# PR Gate - Dynamic sentinel workflow that ensures all quality checks pass +# +# This workflow acts as a single entry point for branch protection rules. +# It dynamically discovers and waits for all quality checks to complete, +# then reports their combined status. +# +# How it works: +# - Individual quality check workflows run independently on pull_request events +# - Each workflow has path filtering to skip when files aren't relevant +# - This sentinel discovers all workflows that ran via the GitHub API +# - Reports success if all workflows pass (or skip appropriately) +# +# Adding new checks: Just create a workflow with 'on: pull_request' +# No need to update this file - the sentinel discovers workflows automatically +# +# Configure branch protection to require only the "All Checks Pass" job. + +name: PR Gate + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + # ============================================================================ + # SENTINEL JOB - This is the only job to require in branch protection + # ============================================================================ + # This job dynamically discovers all workflows that ran on this PR and + # validates that they all passed (or skipped appropriately). + # + # Individual workflows run independently with path filtering at the job level. + + all-checks: + name: All Checks Pass + runs-on: ubuntu-latest + # CRITICAL: Always run this job, even if some dependencies were skipped + if: always() + + steps: + - name: Wait for all checks to complete + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const prNumber = context.payload.pull_request?.number; + if (!prNumber) { + core.setFailed('No PR number found'); + return; + } + + const sha = context.payload.pull_request.head.sha; + + console.log(`Checking status of all workflow runs for PR #${prNumber} (SHA: ${sha})`); + console.log(''); + + // Wait for all workflows to complete (max 60 minutes) + const maxWaitTime = 60 * 60 * 1000; // 60 minutes + const pollInterval = 10 * 1000; // 10 seconds + const startTime = Date.now(); + + while (Date.now() - startTime < maxWaitTime) { + // Get all workflow runs for this commit + const { data: workflowRuns } = await github.rest.actions.listWorkflowRunsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + head_sha: sha, + per_page: 100 + }); + + // Filter to runs from this PR (exclude the pr-gate workflow itself) + const prWorkflows = workflowRuns.workflow_runs.filter(run => + run.name !== 'PR Gate' && + run.event === 'pull_request' + ); + + console.log(`Found ${prWorkflows.length} workflow runs for this PR`); + + // Check if all are complete + const pending = prWorkflows.filter(run => + run.status === 'queued' || run.status === 'in_progress' + ); + + if (pending.length === 0) { + console.log('All workflows completed'); + console.log(''); + + // Print status of all workflows + console.log('Workflow Status Summary:'); + console.log('========================'); + prWorkflows.forEach(run => { + const status = run.conclusion || run.status; + const icon = status === 'success' ? '✅' : + status === 'skipped' ? '⏭️' : + status === 'cancelled' ? '🚫' : + '❌'; + console.log(`${icon} ${run.name}: ${status}`); + }); + console.log(''); + + // Check for failures or cancellations + const failed = prWorkflows.filter(run => + run.conclusion === 'failure' || run.conclusion === 'cancelled' + ); + + if (failed.length > 0) { + console.log('❌ The following workflows failed or were cancelled:'); + failed.forEach(run => { + console.log(` - ${run.name}: ${run.conclusion}`); + }); + core.setFailed('One or more quality checks failed'); + return; + } + + console.log('✅ All quality checks passed or were skipped appropriately'); + return; + } + + console.log(`Waiting for ${pending.length} workflows to complete...`); + pending.forEach(run => { + console.log(` - ${run.name}: ${run.status}`); + }); + console.log(''); + + // Wait before next poll + await new Promise(resolve => setTimeout(resolve, pollInterval)); + } + + core.setFailed('Timeout waiting for all workflows to complete'); + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/run-on-pr-charts.yml b/.github/workflows/run-on-pr-charts.yml deleted file mode 100644 index 7c6c522f69..0000000000 --- a/.github/workflows/run-on-pr-charts.yml +++ /dev/null @@ -1,21 +0,0 @@ -# These set of workflows run on every push to the main branch -name: PR Checks Helm Charts -permissions: - contents: read - -on: - workflow_dispatch: - pull_request: - paths: - - deploy/charts/** - -jobs: - spellcheck: - name: Spellcheck - uses: ./.github/workflows/spellcheck.yml - linting: - name: Linting - uses: ./.github/workflows/lint-helm-charts.yml - tests: - name: Tests - uses: ./.github/workflows/test-helm-charts.yml diff --git a/.github/workflows/run-on-pr.yml b/.github/workflows/run-on-pr.yml deleted file mode 100644 index 8f2971d16c..0000000000 --- a/.github/workflows/run-on-pr.yml +++ /dev/null @@ -1,49 +0,0 @@ -# These set of workflows run on every push to the main branch -name: PR Checks - -on: - workflow_dispatch: - pull_request: - paths-ignore: - - deploy/charts/** - -permissions: - contents: read - -jobs: - spellcheck: - name: Spellcheck - uses: ./.github/workflows/spellcheck.yml - license-headers: - name: License Headers - uses: ./.github/workflows/license-headers.yml - linting: - name: Linting - uses: ./.github/workflows/lint.yml - security-scan: - name: Security Scan - permissions: - contents: read - security-events: write - uses: ./.github/workflows/security-scan.yml - tests: - name: Tests - uses: ./.github/workflows/test.yml - secrets: inherit - docs: - name: Docs - uses: ./.github/workflows/verify-docgen.yml - codegen: - name: Codegen - uses: ./.github/workflows/verify-gen.yml - # Tier 2: Expensive integration tests - only run after all fast checks pass - e2e-tests: - name: E2E Tests - needs: [linting, tests, docs, codegen] - uses: ./.github/workflows/e2e-tests.yml - operator-ci: - name: Operator CI - needs: [linting, tests, docs, codegen] - permissions: - contents: read - uses: ./.github/workflows/operator-ci.yml diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index be32b62204..f2799e868e 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -4,12 +4,33 @@ permissions: contents: read on: - workflow_call: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + code: + - '!deploy/charts/**' + codespell: name: Codespell runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - name: Checkout Code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 diff --git a/.github/workflows/test-helm-charts.yml b/.github/workflows/test-helm-charts.yml index 6d48f84acf..5911d0dbb6 100644 --- a/.github/workflows/test-helm-charts.yml +++ b/.github/workflows/test-helm-charts.yml @@ -4,12 +4,33 @@ permissions: contents: read on: - workflow_call: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + charts: ${{ steps.filter.outputs.charts }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + charts: + - 'deploy/charts/**' + test-helm-charts: name: Test Helm Charts runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.charts == 'true') steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e873139a90..324fa3c820 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,15 +1,36 @@ name: Tests on: - workflow_call: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs permissions: contents: read jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + code: + - '!deploy/charts/**' + test-go-code: name: Test Go Code runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 diff --git a/.github/workflows/verify-docgen.yml b/.github/workflows/verify-docgen.yml index a7f68b54ff..244230af54 100644 --- a/.github/workflows/verify-docgen.yml +++ b/.github/workflows/verify-docgen.yml @@ -1,14 +1,35 @@ name: Docgen on: - workflow_call: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + code: + - '!deploy/charts/**' + verify-swagger-docs: name: Verify Swagger Documentation runs-on: ubuntu-latest + needs: changes permissions: contents: read + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 diff --git a/.github/workflows/verify-gen.yml b/.github/workflows/verify-gen.yml index 0bad5702bb..d7d90110be 100644 --- a/.github/workflows/verify-gen.yml +++ b/.github/workflows/verify-gen.yml @@ -1,15 +1,36 @@ name: Codegen on: - workflow_call: + workflow_call: # Keep for run-on-main.yml + workflow_dispatch: # Manual trigger + pull_request: # Run independently on PRs permissions: contents: read jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + code: + - '!deploy/charts/**' + verify-code-generation: name: Verify Code Generation runs-on: ubuntu-latest + needs: changes + if: | + always() && + (needs.changes.result == 'skipped' || needs.changes.outputs.code == 'true') steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6