diff --git a/.github/actions/dependency-review/action.yml b/.github/actions/dependency-review/action.yml index 6d2ee88..488723c 100644 --- a/.github/actions/dependency-review/action.yml +++ b/.github/actions/dependency-review/action.yml @@ -1,20 +1,44 @@ name: "Dependency Review" -description: "Blocks PRs that introduce dependencies with known vulnerabilities or denied licenses; posts a summary comment on the PR." +description: "Blocks PRs that introduce dependencies with known vulnerabilities or non-permitted licenses; posts a summary comment on the PR." inputs: fail-on-severity: description: "Minimum vulnerability severity to fail: critical|high|moderate|low" default: "high" - deny-licenses: - description: "Comma-separated list of SPDX license identifiers to deny" - default: "GPL-2.0,GPL-3.0,AGPL-3.0" + allow-licenses: + description: "Comma-separated list of SPDX license identifiers that are permitted. Dependencies using any other license will fail the check." + default: "MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Unlicense, CC0-1.0" comment-summary-in-pr: description: "Post a summary comment on the PR: always|on-failure|never" default: "on-failure" + base-ref: + description: "Base commit SHA for non-PR events (e.g. github.event.before on push). Leave empty on pull_request — the action reads PR context automatically." + default: "" + head-ref: + description: "Head commit SHA for non-PR events (e.g. github.sha on push). Leave empty on pull_request." + default: "" runs: using: "composite" steps: + - name: Validate ref inputs + if: ${{ (inputs.base-ref != '' && inputs.head-ref == '') || (inputs.base-ref == '' && inputs.head-ref != '') }} + shell: bash + env: + BASE_REF: ${{ inputs.base-ref }} + HEAD_REF: ${{ inputs.head-ref }} + run: | + echo "::error::dependency-review: base-ref and head-ref must both be set or both be empty. Got base-ref='${BASE_REF}' head-ref='${HEAD_REF}'." + exit 1 - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + if: ${{ inputs.base-ref == '' }} with: fail-on-severity: ${{ inputs.fail-on-severity }} - deny-licenses: ${{ inputs.deny-licenses }} + allow-licenses: ${{ inputs.allow-licenses }} + comment-summary-in-pr: ${{ inputs.comment-summary-in-pr }} + - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + if: ${{ inputs.base-ref != '' }} + with: + base-ref: ${{ inputs.base-ref }} + head-ref: ${{ inputs.head-ref }} + fail-on-severity: ${{ inputs.fail-on-severity }} + allow-licenses: ${{ inputs.allow-licenses }} comment-summary-in-pr: ${{ inputs.comment-summary-in-pr }} diff --git a/.github/actions/scorecard/action.yml b/.github/actions/scorecard/action.yml index b9383e4..4e9f64c 100644 --- a/.github/actions/scorecard/action.yml +++ b/.github/actions/scorecard/action.yml @@ -34,6 +34,26 @@ runs: retention-days: 5 if-no-files-found: ignore + - name: Print scorecard summary + if: always() && hashFiles('scorecard-results.sarif') != '' + shell: bash + run: | + set -euo pipefail + if ! command -v jq &>/dev/null; then + echo "::warning::jq not found — skipping scorecard summary" + exit 0 + fi + echo "=== OpenSSF Scorecard Results ===" + findings=$(jq -r ' + .runs[].results[]? | + " [\(.ruleId | gsub("ID$";""))] \(.message.text | split("\n")[0])" + ' scorecard-results.sarif) + if [[ -z "$findings" ]]; then + echo " All checks passed (no findings in SARIF)" + else + echo "$findings" + fi + - uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 if: always() && hashFiles('scorecard-results.sarif') != '' with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 441671d..4a7b7fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,11 +64,14 @@ jobs: - uses: ./.github/actions/scorecard # Review dependency changes for vulnerabilities and license issues. - # Only meaningful on pull_request — requires PR base/head context. + # On pull_request: compares PR base/head via built-in event context. + # On push to main: compares the pushed range (event.before → sha) so maintainers + # can see the action work end-to-end without opening a PR. + # Skipped on first push to a branch (event.before = zero SHA) — no base to compare. dependency-review: name: Dependency Review runs-on: ubuntu-latest - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main' && github.event.before != '0000000000000000000000000000000000000000') permissions: contents: read pull-requests: write @@ -78,6 +81,9 @@ jobs: persist-credentials: false - uses: ./.github/actions/dependency-review + with: + base-ref: ${{ github.event_name == 'push' && github.event.before || '' }} + head-ref: ${{ github.event_name == 'push' && github.sha || '' }} # Verify Terramate and OpenTofu are installed correctly. # Terramate stack steps are no-ops in this repo (no stacks defined). @@ -95,12 +101,11 @@ jobs: - uses: ./.github/actions/terramate-opentofu-setup # Verify the storage optimizer composite action runs without error. - # Gated to workflow_dispatch: storage-optimizer is only useful when a job does - # heavy disk work on the same runner; running it on a throwaway CI runner wastes minutes. + # Runs on push to main (demonstration) and workflow_dispatch (on-demand). storage-optimizer: name: Storage Optimizer runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main') permissions: contents: read steps: diff --git a/README.md b/README.md index 7ad77d3..4187361 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # sparkgeo/github-actions +[![CI](https://github.com/sparkgeo/github-actions/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/sparkgeo/github-actions/actions/workflows/ci.yml) + Reusable GitHub Actions composite actions and CI workflow for the Sparkgeo organisation. All action references in this repo are pinned to full commit SHAs. See [CONTRIBUTING.md](CONTRIBUTING.md) for authoring standards and how to add new actions. @@ -24,7 +26,7 @@ gh api repos/sparkgeo/github-actions/commits/main --jq '.sha' | GitHub Actionlint | [`github-actionlint`](.github/actions/github-actionlint/action.yml) | Lints workflow and action YAML files using actionlint via reviewdog; posts annotations as GitHub Checks | None | | Zizmor | [`zizmor`](.github/actions/zizmor/action.yml) | Runs zizmor static security analysis against workflow and action YAML files; uploads findings as SARIF to the Security tab | None | | OpenSSF Scorecard | [`scorecard`](.github/actions/scorecard/action.yml) | Runs OpenSSF Scorecard checks; uploads SARIF to the Security tab | `publish_results` (default: `false` — always fails with HTTP 400 if set to `true`; see action description) | -| Dependency Review | [`dependency-review`](.github/actions/dependency-review/action.yml) | Blocks PRs introducing dependencies with known vulnerabilities or denied licenses; posts a summary comment | `fail-on-severity` (default: `high`), `deny-licenses` (default: `GPL-2.0,GPL-3.0,AGPL-3.0`), `comment-summary-in-pr` (default: `on-failure`) | +| Dependency Review | [`dependency-review`](.github/actions/dependency-review/action.yml) | Blocks PRs introducing dependencies with known vulnerabilities or non-permitted licenses; posts a summary comment. Also supports non-PR invocation via `base-ref`/`head-ref`. | `fail-on-severity` (default: `high`), `allow-licenses` (default: `MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Unlicense, CC0-1.0`), `comment-summary-in-pr` (default: `on-failure`), `base-ref` (default: `""`), `head-ref` (default: `""`) | | Storage Optimizer | [`storage-optimizer`](.github/actions/storage-optimizer/action.yml) | Frees disk space on GitHub-hosted runners by removing unused toolchains (JDK, .NET, Swift, Android SDK, etc.) and pruning Docker | None | | Terramate + OpenTofu Setup | [`terramate-opentofu-setup`](.github/actions/terramate-opentofu-setup/action.yml) | Installs Terramate and OpenTofu, validates generated files are up to date, initialises changed stacks, and lists changed stacks | `opentofu_version` (default: `1.10.0`), `terramate_version` (default: `0.14.7`) | @@ -84,13 +86,13 @@ jobs: ### Dependency Review -Only meaningful on `pull_request` events — requires PR base/head context. +Works on `pull_request` events (automatic base/head from PR context) and on push/non-PR events by passing `base-ref`/`head-ref` explicitly. Skip on initial branch push (`event.before` = zero SHA — no base to compare). ```yaml jobs: dependency-review: runs-on: ubuntu-latest - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main' && github.event.before != '0000000000000000000000000000000000000000') permissions: contents: read pull-requests: write @@ -100,9 +102,11 @@ jobs: persist-credentials: false - uses: sparkgeo/github-actions/.github/actions/dependency-review@ with: - fail-on-severity: high # critical | high | moderate | low - deny-licenses: GPL-2.0,AGPL-3.0 # SPDX identifiers - comment-summary-in-pr: always # always | on-failure | never + fail-on-severity: high # critical | high | moderate | low + allow-licenses: MIT, Apache-2.0, BSD-2-Clause # SPDX identifiers; deps with other licenses fail + comment-summary-in-pr: always # always | on-failure | never + base-ref: ${{ github.event_name == 'push' && github.event.before || '' }} # leave empty on pull_request + head-ref: ${{ github.event_name == 'push' && github.sha || '' }} # leave empty on pull_request ``` ### Storage Optimizer