diff --git a/.github/workflows/0-local-ci.yml b/.github/workflows/0-local-ci.yml index f57b746..0dd0ef1 100644 --- a/.github/workflows/0-local-ci.yml +++ b/.github/workflows/0-local-ci.yml @@ -20,3 +20,6 @@ jobs: permissions: contents: read # baseline for checkout security-events: write # zizmor SARIF upload to GHAS + + lint-renovate: + uses: $/.github/workflows/lint-renovate.yml diff --git a/.github/workflows/lint-codegen-fabrica.yml b/.github/workflows/lint-codegen-fabrica.yml new file mode 100644 index 0000000..b0416a4 --- /dev/null +++ b/.github/workflows/lint-codegen-fabrica.yml @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: © 2026 OpenCHAMI a Series of LF Projects, LLC +# SPDX-License-Identifier: MIT + +name: lint-codegen-fabrica + +# Reusable workflow that fails when a Fabrica project's committed generated +# code has drifted, by running the caller's `make generate-check` with a +# Fabrica built from source (passed as LOCAL_FABRICA). Fabrica stamps its +# version into generated code and only a source build carries that version; +# a `go run` build reports "dev" and always fails the check. + +on: + workflow_call: + inputs: + go-version: + description: 'Go version to use (e.g. "1.26.7", "stable"). Mutually exclusive with go-version-file.' + required: false + type: string + go-version-file: + description: 'Path to a go.mod or .go-version file containing the Go version. Mutually exclusive with go-version.' + required: false + type: string + fabrica-ref: + description: 'Fabrica tag, branch, or commit SHA to build (default: version in go.mod)' + required: false + type: string + default: '' + +permissions: + contents: read # baseline for checkout + +jobs: + lint-codegen-fabrica: + runs-on: ubuntu-slim + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: ${{ inputs.go-version || (inputs.go-version-file == '' && 'stable' || '') }} + go-version-file: ${{ inputs.go-version-file || '' }} + + - name: Resolve Fabrica ref + id: fabrica-ref + env: + FABRICA_REF: ${{ inputs.fabrica-ref }} + run: | + version="$FABRICA_REF" + if [[ -z "$version" ]]; then + version=$(go mod edit -json | jq -r ' + .Require[]? + | select(.Path == "github.com/openchami/fabrica") + | .Version + ') + if [[ -z "$version" ]]; then + echo "::error::fabrica-ref not set and no github.com/openchami/fabrica version found in go.mod" + exit 1 + fi + fi + # Resolve the tag, branch, commit, or pseudo-version to the commit it names + if ! ref=$(go list -mod=mod -m -json "github.com/openchami/fabrica@${version}" | + jq -er '.Origin.Hash | select(type == "string" and test("^[0-9a-f]{40}$"))'); then + echo "::error::Could not resolve Fabrica ref: ${version}" + exit 1 + fi + echo "Using Fabrica ref: ${ref}" + echo "ref=${ref}" >> "$GITHUB_OUTPUT" + - name: Checkout Fabrica + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: openchami/fabrica + ref: ${{ steps.fabrica-ref.outputs.ref }} + path: fabrica + fetch-depth: 0 # full history + tags so `git describe` stamps the same version as a local build + persist-credentials: false + - name: Build Fabrica + working-directory: fabrica + run: make build + - name: Verify generated code is committed + run: | + make generate-check \ + LOCAL_FABRICA="$GITHUB_WORKSPACE/fabrica" \ + FABRICA_SOURCE_ARG="--fabrica-source $GITHUB_WORKSPACE/fabrica --force" diff --git a/.github/workflows/lint-renovate.yml b/.github/workflows/lint-renovate.yml new file mode 100644 index 0000000..00bbcd8 --- /dev/null +++ b/.github/workflows/lint-renovate.yml @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: © 2026 OpenCHAMI a Series of LF Projects, LLC +# SPDX-License-Identifier: MIT + +name: lint-renovate + +# Reusable workflow that validates the caller's Renovate config with +# renovate-config-validator. With no config-file, the validator checks every +# config file it finds in the repository (renovate.json, .github/renovate.json, +# renovate.json5, .renovaterc, ...). + +on: + workflow_call: + inputs: + renovate-version: + description: 'Renovate npm version that provides renovate-config-validator' + required: false + type: string + default: '44.115.13' + config-file: + description: 'Config file to validate (default: every Renovate config file found in the repo)' + required: false + type: string + default: '' + +permissions: + contents: read # baseline for checkout + +jobs: + lint-renovate: + runs-on: ubuntu-slim + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + - name: Validate Renovate config + env: + RENOVATE_VERSION: ${{ inputs.renovate-version }} + CONFIG_FILE: ${{ inputs.config-file }} + run: | + # --no-global: validate as repository config, not self-hosted config. + npx --yes --package "renovate@${RENOVATE_VERSION}" -- \ + renovate-config-validator --no-global ${CONFIG_FILE:+"$CONFIG_FILE"} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..adad710 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: © 2026 OpenCHAMI a Series of LF Projects, LLC +# SPDX-License-Identifier: MIT + +name: stale + +# Reusable workflow that marks and closes inactive issues and PRs with the +# org-default actions/stale policy. Callers own the schedule (a reusable +# workflow can't schedule itself). + +on: + workflow_call: + inputs: + days-before-stale: + description: 'Days of inactivity before an issue or PR is marked stale' + required: false + type: number + default: 35 + days-before-close: + description: 'Days after being marked stale before an issue or PR is closed' + required: false + type: number + default: 7 + stale-issue-label: + description: 'Label applied to stale issues' + required: false + type: string + default: 'stale' + stale-pr-label: + description: 'Label applied to stale PRs' + required: false + type: string + default: 'stale' + exempt-issue-labels: + description: 'Comma-separated labels that exempt an issue from going stale' + required: false + type: string + default: 'pinned,security,help wanted' + exempt-pr-labels: + description: 'Comma-separated labels that exempt a PR from going stale' + required: false + type: string + default: 'pinned,security' + operations-per-run: + description: 'Maximum GitHub API operations per run' + required: false + type: number + default: 500 + stale-issue-message: + description: 'Comment posted when an issue is marked stale' + required: false + type: string + default: >- + This issue has been automatically marked as stale due to inactivity. + Please add a comment or label to keep it open; otherwise it will close in 7 days. + close-issue-message: + description: 'Comment posted when a stale issue is closed' + required: false + type: string + default: 'Closing due to inactivity. Feel free to reopen with new information.' + stale-pr-message: + description: 'Comment posted when a PR is marked stale' + required: false + type: string + default: 'This PR has been marked stale due to inactivity. Comment or push changes to keep it open.' + close-pr-message: + description: 'Comment posted when a stale PR is closed' + required: false + type: string + default: 'Closing due to inactivity. Please reopen when it’s ready again.' + +permissions: + issues: write # label, comment on, and close stale issues + pull-requests: write # label, comment on, and close stale PRs + +jobs: + stale: + runs-on: ubuntu-slim + steps: + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 + with: + days-before-stale: ${{ inputs.days-before-stale }} + days-before-close: ${{ inputs.days-before-close }} + stale-issue-label: ${{ inputs.stale-issue-label }} + stale-pr-label: ${{ inputs.stale-pr-label }} + exempt-issue-labels: ${{ inputs.exempt-issue-labels }} + exempt-pr-labels: ${{ inputs.exempt-pr-labels }} + operations-per-run: ${{ inputs.operations-per-run }} + remove-stale-when-updated: true + stale-issue-message: ${{ inputs.stale-issue-message }} + close-issue-message: ${{ inputs.close-issue-message }} + stale-pr-message: ${{ inputs.stale-pr-message }} + close-pr-message: ${{ inputs.close-pr-message }} diff --git a/README.md b/README.md index 239ff21..ad2edf7 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Reusable GitHub Actions for CI/CD. - `.github/workflows/trivy-image-scan.yml`: Reusable workflow that scans built container images for CVEs - `.github/workflows/scorecard.yml`: Reusable workflow that runs the OpenSSF Scorecard supply-chain analysis - `.github/workflows/pr-registry-cleanup.yml`: Deletes the GHCR container images a PR published, once it closes +- `.github/workflows/stale.yml`: Reusable workflow that marks and closes inactive issues and PRs (org-default policy) +- `.github/workflows/lint-codegen-fabrica.yml`: Reusable workflow that fails when a Fabrica project's committed generated code is out of date ### Workflow naming @@ -369,6 +371,80 @@ jobs: # tag-prefix: pr- ``` +### stale (Reusable Workflow) +Marks issues and PRs stale after a period of inactivity and closes them if nothing changes, using the org-default `actions/stale` policy (stale after 35 days, closed 7 days later; `pinned`/`security` items exempt). Updating an item removes the stale label. The caller owns the schedule. + +**Usage:** +```yaml +name: Stale +on: + schedule: + - cron: '23 3 * * *' # daily at 03:23 UTC + workflow_dispatch: + +jobs: + stale: + uses: OpenCHAMI/github-actions/.github/workflows/stale.yml@v4.0 + permissions: + issues: write + pull-requests: write + # Optional overrides: + # with: + # days-before-stale: 35 + # days-before-close: 7 + # stale-issue-label: stale + # stale-pr-label: stale + # exempt-issue-labels: pinned,security,help wanted + # exempt-pr-labels: pinned,security + # operations-per-run: 500 + # stale-issue-message: ... + # close-issue-message: ... + # stale-pr-message: ... + # close-pr-message: ... +``` + +### lint-codegen-fabrica (Reusable Workflow) +Fails when a Fabrica project's committed generated code is out of date by running the caller's `make generate-check` with Fabrica built from source (passed as `LOCAL_FABRICA`), at the version pinned in `go.mod` unless `fabrica-ref` (tag, branch, or commit SHA) is set. A source build is required: Fabrica stamps its version into generated code, and a `go run` build reports `dev` instead. + +**Usage:** +```yaml +name: Lint Codegen +on: + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + lint-codegen: + uses: OpenCHAMI/github-actions/.github/workflows/lint-codegen-fabrica.yml@v4.0 + # Optional overrides: + # with: + # go-version: stable # mutually exclusive with go-version-file + # go-version-file: go.mod + # fabrica-ref: v0.4.5 # tag, branch, or SHA; default: version in go.mod +``` + +### lint-renovate (Reusable Workflow) +Validates the caller's Renovate config with `renovate-config-validator` (as repository config). With no `config-file`, it checks every Renovate config file it finds (`renovate.json`, `.github/renovate.json`, `renovate.json5`, `.renovaterc`, ...). + +**Usage:** +```yaml +name: Lint Renovate +on: + pull_request: + paths: + - renovate.json + - .github/workflows/lint-renovate.yml + +jobs: + lint-renovate: + uses: OpenCHAMI/github-actions/.github/workflows/lint-renovate.yml@v4.0 + # Optional overrides: + # with: + # renovate-version: 44.115.13 + # config-file: renovate.json +``` + ## Actions ### gpg-configure-release-keys diff --git a/renovate.json b/renovate.json index 8ec9325..bcbbf00 100644 --- a/renovate.json +++ b/renovate.json @@ -3,5 +3,21 @@ "extends": [ "github>OpenCHAMI/.github//renovate-config/default", "config:recommended" + ], + "customManagers": [ + { + "customType": "regex", + "description": "Default renovate-config-validator version in lint-renovate and its README example", + "managerFilePatterns": [ + "/^\\.github/workflows/lint-renovate\\.yml$/", + "/^README\\.md$/" + ], + "matchStrings": [ + "renovate-version:\\s*\\n(?:\\s+[a-z]+:.*\\n)*?\\s+default: '(?\\d+\\.\\d+\\.\\d+)'", + "#\\s+renovate-version: (?\\d+\\.\\d+\\.\\d+)" + ], + "depNameTemplate": "renovate", + "datasourceTemplate": "npm" + } ] }