diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000..04f1904 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: © 2026 OpenCHAMI a Series of LF Projects, LLC +# SPDX-License-Identifier: MIT + +# actionlint doesn't recognize GitHub's self-repository `uses: $/...` syntax +# yet. Remove this once it does. +paths: + .github/workflows/0-local-ci.yml: + ignore: + - 'reusable workflow call "\$/' diff --git a/.github/workflows/0-local-ci.yml b/.github/workflows/0-local-ci.yml new file mode 100644 index 0000000..f57b746 --- /dev/null +++ b/.github/workflows/0-local-ci.yml @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: © 2025 OpenCHAMI a Series of LF Projects, LLC +# SPDX-License-Identifier: MIT + +name: 0-local-ci + +# Local to this repo: CI for github-actions itself. Not a reusable workflow; +# do not call it from other repos. See "Workflow naming" in the README. + +on: + pull_request: + push: + branches: [ main ] + +permissions: + contents: read # baseline for checkout + +jobs: + lint: + uses: $/.github/workflows/lint-ci.yml + permissions: + contents: read # baseline for checkout + security-events: write # zizmor SARIF upload to GHAS diff --git a/.github/workflows/build-publish-container-goreleaser.yml b/.github/workflows/build-publish-container-goreleaser.yml index 1882568..b254612 100644 --- a/.github/workflows/build-publish-container-goreleaser.yml +++ b/.github/workflows/build-publish-container-goreleaser.yml @@ -5,40 +5,40 @@ # with multi-arch builds, build provenance attestation, and PR snapshot # support. -name: Build and publish container using goreleaser +name: build-publish-container-goreleaser on: workflow_call: inputs: - build_deps: + build-deps: type: string required: false description: 'Space-separated list of apt packages to install before building (e.g. "gcc-aarch64-linux-gnu libc6-dev-arm64-cross").' - cgo_enabled: + cgo-enabled: type: number required: false default: 0 - go_version: + go-version: type: string required: false - description: 'Go version to use (e.g. "1.26.7", "stable"). Mutually exclusive with go_version_file.' - go_version_file: + description: 'Go version to use (e.g. "1.26.7", "stable"). Mutually exclusive with go-version-file.' + go-version-file: type: string required: false - description: 'Path to a go.mod or .go-version file containing the Go version. Mutually exclusive with go_version.' - release_draft: + description: 'Path to a go.mod or .go-version file containing the Go version. Mutually exclusive with go-version.' + release-draft: type: boolean required: false default: false description: 'Create the release as a draft, overriding release.draft in .goreleaser.yml' - is_pr_build: + is-pr-build: type: boolean required: false default: false - pr_number: + pr-number: type: number required: false default: ${{ github.event.number || 0 }} - registry_subject_name: + registry-subject-name: type: string required: true @@ -49,7 +49,7 @@ permissions: attestations: write # write build provenance attestations jobs: - container_build_publish: + container-build-publish: runs-on: ubuntu-latest steps: - name: Checkout @@ -58,9 +58,9 @@ jobs: fetch-tags: true fetch-depth: 0 - name: Install build dependencies - if: ${{ inputs.build_deps != '' }} + if: ${{ inputs.build-deps != '' }} env: - BUILD_DEPS: ${{ inputs.build_deps }} + BUILD_DEPS: ${{ inputs.build-deps }} run: | sudo apt update # shellcheck disable=SC2086 # intentional word splitting @@ -68,8 +68,8 @@ jobs: - name: Set up Go uses: actions/setup-go@v6.4.0 with: - go-version: ${{ inputs.go_version || (inputs.go_version_file == '' && 'stable' || '') }} - go-version-file: ${{ inputs.go_version_file || '' }} + go-version: ${{ inputs.go-version || (inputs.go-version-file == '' && 'stable' || '') }} + go-version-file: ${{ inputs.go-version-file || '' }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx @@ -107,17 +107,17 @@ jobs: echo "BUILD_HOST=$(hostname)" echo "GO_VERSION=$(go version | awk '{print $3}')" echo "BUILD_USER=$(whoami)" - echo "CGO_ENABLED=${{ inputs.cgo_enabled }}" - echo "IS_PR_BUILD=${{ inputs.is_pr_build }}" + echo "CGO_ENABLED=${{ inputs.cgo-enabled }}" + echo "IS_PR_BUILD=${{ inputs.is-pr-build }}" } >> "${GITHUB_ENV}" - name: Create Tag for PR - if: ${{ inputs.is_pr_build }} + if: ${{ inputs.is-pr-build }} run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" - git tag -f -a pr-${{ inputs.pr_number }} -m "PR Release" + git tag -f -a pr-${{ inputs.pr-number }} -m "PR Release" - name: Generate release notes - if: ${{ !inputs.is_pr_build }} + if: ${{ !inputs.is-pr-build }} env: GITHUB_TOKEN: ${{ github.token }} run: gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" -F tag_name="${{ github.ref_name }}" --jq .body > ../notes.md @@ -127,11 +127,11 @@ jobs: GITHUB_TOKEN: ${{ github.token }} with: version: '~> 2' - args: release --clean ${{ inputs.is_pr_build && '--skip=announce,validate,archive' || '' }}${{ env.SKIP_CONTAINER_PUBLISH == 'true' && (inputs.is_pr_build && ',publish' || '--skip=publish') || '' }} ${{ inputs.release_draft && '--draft' || '' }} ${{ !inputs.is_pr_build && '--release-notes ../notes.md' || '' }} + args: release --clean ${{ inputs.is-pr-build && '--skip=announce,validate,archive' || '' }}${{ env.SKIP_CONTAINER_PUBLISH == 'true' && (inputs.is-pr-build && ',publish' || '--skip=publish') || '' }} ${{ inputs.release-draft && '--draft' || '' }} ${{ !inputs.is-pr-build && '--release-notes ../notes.md' || '' }} id: goreleaser - name: Process goreleaser output if: env.SKIP_CONTAINER_PUBLISH == 'false' - id: process_goreleaser_output + id: process-goreleaser-output run: | node - <<'EOF' const fs = require('fs'); @@ -149,14 +149,14 @@ jobs: echo "digest=$(cat digest.txt)" >> "${GITHUB_OUTPUT}" fi - name: Attest Binaries - if: ${{ (env.SKIP_CONTAINER_PUBLISH == 'false') && (inputs.is_pr_build == false) }} + if: ${{ (env.SKIP_CONTAINER_PUBLISH == 'false') && (inputs.is-pr-build == false) }} uses: actions/attest-build-provenance@v4.1.0 with: subject-path: dist/** - name: generate build provenance - if: ${{ (env.SKIP_CONTAINER_PUBLISH == 'false') && (inputs.is_pr_build == false) }} + if: ${{ (env.SKIP_CONTAINER_PUBLISH == 'false') && (inputs.is-pr-build == false) }} uses: actions/attest-build-provenance@v4.1.0 with: - subject-name: ${{ inputs.registry_subject_name }} - subject-digest: ${{ steps.process_goreleaser_output.outputs.digest }} + subject-name: ${{ inputs.registry-subject-name }} + subject-digest: ${{ steps.process-goreleaser-output.outputs.digest }} push-to-registry: true diff --git a/.github/workflows/build-rpm-quadlet.yml b/.github/workflows/build-rpm-quadlet.yml index 4004cab..e9602ab 100644 --- a/.github/workflows/build-rpm-quadlet.yml +++ b/.github/workflows/build-rpm-quadlet.yml @@ -1,11 +1,10 @@ -# Copyright © 2026 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2026 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT # # Reusable workflow: builds the caller repo's podman quadlet RPM and # uploads it as an unsigned artifact for downstream signing. -name: Build RPM for Podman Quadlet Files -run-name: Create Podman Quadlet RPM for ${{ github.ref }} +name: build-rpm-quadlet on: workflow_call: inputs: @@ -18,19 +17,19 @@ permissions: contents: read # baseline for checkout jobs: - rpmbuild: + rpm-build: runs-on: ubuntu-latest container: image: rockylinux:9 steps: - name: Install build dependencies - run: dnf install -y -q git make rpm-build rpmlint tar gzip + run: dnf install -y -q git make rpm-build rpmlint - name: Mark workspace as a safe git directory run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-tags: true fetch-depth: 0 @@ -39,7 +38,7 @@ jobs: run: make rpm-build - name: Upload RPM - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ inputs.artifact-name-unsigned-rpms }} path: '**/*.rpm' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index c843c63..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC -# SPDX-License-Identifier: MIT - -name: CI - -on: - pull_request: - push: - branches: [ main ] - -permissions: - contents: read # baseline for checkout - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6.0.3 - - name: actionlint - uses: raven-actions/actionlint@v2.1.2 diff --git a/.github/workflows/coverage-go.yml b/.github/workflows/coverage-go.yml deleted file mode 100644 index 517d731..0000000 --- a/.github/workflows/coverage-go.yml +++ /dev/null @@ -1,68 +0,0 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC -# SPDX-License-Identifier: MIT - -name: coverage-go - -# Reusable workflow that produces a Go coverage profile, writes the total -# to the job summary, and uploads the profile to Coveralls. -# -# The caller repo must be enrolled in Coveralls; the upload authenticates -# with the automatically-provided GITHUB_TOKEN. - -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 - coverage-command: - description: 'Command that writes the coverage profile to `coverage-file`' - required: false - type: string - default: 'go test -coverprofile=coverage.out ./...' - coverage-file: - description: 'Path to the coverage profile written by `coverage-command`' - required: false - type: string - default: 'coverage.out' - -permissions: - contents: read # baseline for checkout - -jobs: - coverage-go: - name: coverage-go - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: ${{ inputs.go_version || (inputs.go_version_file == '' && 'stable' || '') }} - go-version-file: ${{ inputs.go_version_file || '' }} - - - name: Run tests with coverage - env: - COVERAGE_COMMAND: ${{ inputs.coverage-command }} - run: eval "$COVERAGE_COMMAND" - - - name: Report total coverage - env: - COVERAGE_FILE: ${{ inputs.coverage-file }} - run: | - total=$(go tool cover -func="$COVERAGE_FILE" | awk '/^total:/ {print $3}') - echo "Total coverage: ${total}" | tee -a "$GITHUB_STEP_SUMMARY" - - - name: Upload coverage to Coveralls - uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8 - with: - file: ${{ inputs.coverage-file }} - format: golang - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 1cf93e0..1543a86 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2025 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT name: dependency-review @@ -41,10 +41,9 @@ permissions: jobs: dependency-review: - name: dependency-review - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 diff --git a/.github/workflows/docker-build-release.yml b/.github/workflows/docker-build-release.yml index 53e1e62..2bfe731 100644 --- a/.github/workflows/docker-build-release.yml +++ b/.github/workflows/docker-build-release.yml @@ -1,8 +1,7 @@ # SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT -name: Build image -run-name: Dockeer image build for ${{ github.event.push.ref }} +name: docker-build-release on: workflow_call: @@ -128,7 +127,7 @@ jobs: registry: ghcr.io - name: Build and push image - id: docker_build + id: docker-build uses: docker/build-push-action@v7.2.0 with: push: true @@ -153,14 +152,14 @@ jobs: ${{ env.CC != '' && format('"CC={0}"', env.CC) || '' }} publish-release: - runs-on: ubuntu-latest + runs-on: ubuntu-slim needs: build-push-images if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') permissions: contents: write # create GitHub release steps: - name: Parse semver string - id: semver_parser + id: semver-parser uses: booxmedialtd/ws-action-parse-semver@v1.4.7 with: input_string: ${{ github.event.ref }} @@ -170,5 +169,5 @@ jobs: # by default this will use the tag push tag as the tag and name # if we want to trigger tagging from the workflow, "tag" and "commit" # need to be set to create a new one - prerelease: ${{ steps.semver_parser.outputs.prerelease != '' }} + prerelease: ${{ steps.semver-parser.outputs.prerelease != '' }} skipIfReleaseExists: true diff --git a/.github/workflows/go-build-release.yml b/.github/workflows/go-build-release.yml index 9cee8fe..03e1d94 100644 --- a/.github/workflows/go-build-release.yml +++ b/.github/workflows/go-build-release.yml @@ -1,8 +1,7 @@ # SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT -name: GoReleaser -run-name: GoReleaser ${{ (inputs.snapshot == 'true' || (inputs.snapshot != 'false' && !startsWith(github.ref, 'refs/tags/v'))) && 'Snapshot' || 'Release' }} +name: go-build-release on: workflow_call: @@ -140,7 +139,7 @@ jobs: run: ${{ inputs.pre-build-commands }} - name: Determine snapshot flags - id: snapshot_flags + id: snapshot-flags run: | if [[ "${{ inputs.snapshot }}" == "true" ]]; then echo "flags=--snapshot" >> "$GITHUB_OUTPUT" @@ -156,18 +155,18 @@ jobs: fi fi - - name: GoReleaser ${{ inputs.goreleaser-args }} ${{ steps.snapshot_flags.outputs.flags }} + - name: GoReleaser ${{ inputs.goreleaser-args }} ${{ steps.snapshot-flags.outputs.flags }} id: goreleaser uses: goreleaser/goreleaser-action@v7.2.2 env: GITHUB_TOKEN: ${{ github.token }} with: version: ${{ inputs.goreleaser-version }} - args: ${{ inputs.goreleaser-args }} ${{ steps.snapshot_flags.outputs.flags }} + args: ${{ inputs.goreleaser-args }} ${{ steps.snapshot-flags.outputs.flags }} - name: Process GoReleaser output - id: process_goreleaser_output - if: ${{ !contains(steps.snapshot_flags.outputs.flags, '--snapshot') }} + id: process-goreleaser-output + if: ${{ !contains(steps.snapshot-flags.outputs.flags, '--snapshot') }} run: | { echo "const fs = require('fs');" @@ -186,9 +185,9 @@ jobs: subject-path: ${{ inputs.attestation-binary-path }} - name: Generate build provenance for container - if: ${{ !contains(steps.snapshot_flags.outputs.flags, '--snapshot') && steps.process_goreleaser_output.outputs.digest != '' && steps.process_goreleaser_output.outputs.digest != 'undefined' }} + if: ${{ !contains(steps.snapshot-flags.outputs.flags, '--snapshot') && steps.process-goreleaser-output.outputs.digest != '' && steps.process-goreleaser-output.outputs.digest != 'undefined' }} uses: actions/attest-build-provenance@v4.1.0 with: subject-name: ${{ inputs.registry-name }} - subject-digest: ${{ steps.process_goreleaser_output.outputs.digest }} + subject-digest: ${{ steps.process-goreleaser-output.outputs.digest }} push-to-registry: true diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 7652753..d11896b 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2025 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT name: govulncheck @@ -29,13 +29,12 @@ permissions: jobs: govulncheck: - name: govulncheck - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 + - uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # v1.1.0 with: go-version-file: ${{ inputs.go-version == '' && 'go.mod' || '' }} go-version-input: ${{ inputs.go-version }} diff --git a/.github/workflows/gpg-sign-artifacts.yml b/.github/workflows/gpg-sign-artifacts.yml index 0ac1222..51defc3 100644 --- a/.github/workflows/gpg-sign-artifacts.yml +++ b/.github/workflows/gpg-sign-artifacts.yml @@ -8,8 +8,7 @@ # Keep this file up to date and maintained as we add other package signing # tasks (e.g. .deb, Arch packages). -name: GPG Sign artifacts -run-name: Create signed artifacts for ${{ github.ref }} +name: gpg-sign-artifacts on: workflow_call: inputs: @@ -38,7 +37,7 @@ jobs: - name: Install build dependencies run: | - dnf install -y -q git make rpm-build rpmlint tar gzip + dnf install -y -q git rpmlint - name: Mark workspace as a safe git directory run: git config --global --add safe.directory "$GITHUB_WORKSPACE" diff --git a/.github/workflows/lint-workflows.yml b/.github/workflows/lint-ci.yml similarity index 57% rename from .github/workflows/lint-workflows.yml rename to .github/workflows/lint-ci.yml index 49d23de..39b9653 100644 --- a/.github/workflows/lint-workflows.yml +++ b/.github/workflows/lint-ci.yml @@ -1,7 +1,7 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2025 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT -name: lint-workflows +name: lint-ci # Reusable workflow that lints GitHub Actions workflow files in the caller repo. # Runs actionlint (syntax / shellcheck / action-version sanity) and zizmor @@ -15,24 +15,22 @@ permissions: jobs: actionlint: - name: actionlint - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2 + - uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0 with: flags: -color zizmor: - name: zizmor runs-on: ubuntu-latest permissions: contents: read # baseline for checkout security-events: write # for SARIF upload to GHAS steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + - uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 diff --git a/.github/workflows/lint-go.yml b/.github/workflows/lint-go.yml index b7534dc..960a2df 100644 --- a/.github/workflows/lint-go.yml +++ b/.github/workflows/lint-go.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2025 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT name: lint-go @@ -7,17 +7,17 @@ name: lint-go # and separately verifies go.mod/go.sum are tidy by running the tidy # command and failing on a dirty diff. # -# Pair with test-go for the unit-test half of the Go CI wave. +# Pair with test-unit-go for the unit-test half of the Go CI wave. on: workflow_call: inputs: - go_version: - description: 'Go version to use (e.g. "1.26.7", "stable"). Mutually exclusive with go_version_file.' + 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.' + 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 golangci-lint-version: @@ -31,31 +31,29 @@ permissions: jobs: golangci-lint: - name: golangci-lint - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - 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 || '' }} + go-version: ${{ inputs.go-version || (inputs.go-version-file == '' && 'stable' || '') }} + go-version-file: ${{ inputs.go-version-file || '' }} - uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0 with: version: ${{ inputs.golangci-lint-version }} modules: - name: modules - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - 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 || '' }} + go-version: ${{ inputs.go-version || (inputs.go-version-file == '' && 'stable' || '') }} + go-version-file: ${{ inputs.go-version-file || '' }} - name: Check go.mod and go.sum are tidy run: | go mod tidy diff --git a/.github/workflows/pr-registry-cleanup.yml b/.github/workflows/pr-registry-cleanup.yml index bc3934f..ee7be5c 100644 --- a/.github/workflows/pr-registry-cleanup.yml +++ b/.github/workflows/pr-registry-cleanup.yml @@ -1,11 +1,10 @@ -# Copyright © 2026 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2026 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT # # Reusable workflow: deletes the GHCR container image versions a pull request # published, once that pull request closes. -name: Cleanup PR Container Images -run-name: Cleanup container images for PR ${{ inputs.pr-number }} +name: pr-registry-cleanup on: workflow_call: inputs: @@ -23,7 +22,7 @@ permissions: jobs: cleanup: - runs-on: ubuntu-latest + runs-on: ubuntu-slim timeout-minutes: 10 steps: - name: Resolve PR number diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 50e0c79..7a52ba6 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -1,27 +1,42 @@ -# Copyright © 2026 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2026 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT # # Reusable workflow: publishes the draft GitHub Release for a tag once every # upstream job has finished attaching its artifacts. -name: Publish release -run-name: Publish release for ${{ github.ref }} +name: publish-release permissions: - contents: write + contents: write # publish the draft GitHub Release on: workflow_call: inputs: - make_latest: - description: 'Mark the published release as the latest release. Use "legacy" to defer to GitHub' + make-latest: + description: 'Mark the published release as the latest release: true, false, or legacy (GitHub picks by date and semver)' type: string - default: 'true' + # see this comment for details: + # https://github.com/OpenCHAMI/github-actions/pull/38#discussion_r4213262488 + default: 'legacy' + required: false + draft: + description: 'Leave the release in draft state' + type: boolean + default: false required: false jobs: publish-release: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - name: Publish release - uses: softprops/action-gh-release@v3.0.2 - with: - tag_name: ${{ github.ref_name }} - draft: false - make_latest: ${{ inputs.make_latest }} + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} + DRAFT: ${{ inputs.draft }} + MAKE_LATEST: ${{ inputs.make-latest }} + run: | + release_id=$(gh release view "$TAG" \ + --repo "$GITHUB_REPOSITORY" \ + --json databaseId --jq '.databaseId') + + gh api --method PATCH \ + "repos/$GITHUB_REPOSITORY/releases/$release_id" \ + -F "draft=$DRAFT" \ + -f "make_latest=$MAKE_LATEST" diff --git a/.github/workflows/release-signed-artifacts.yml b/.github/workflows/release-signed-artifacts.yml index 981daf9..b5d80fd 100644 --- a/.github/workflows/release-signed-artifacts.yml +++ b/.github/workflows/release-signed-artifacts.yml @@ -4,8 +4,7 @@ # Reusable workflow: publishes a GitHub Release for a tag, attaching signed # RPMs and public keys, with trust-chain verification instructions in the # release body. -name: Release signed artifacts -run-name: Generate release with signed artifacts for ${{ github.ref }} +name: release-signed-artifacts permissions: contents: write # create GitHub Release and upload artifacts on: @@ -21,32 +20,25 @@ on: type: string default: 'public-keys' required: false - append_body: + append-body: description: 'Append the verification instructions to the existing release body instead of replacing it' type: boolean default: true required: false - release_draft: + release-draft: description: 'Leave the release in draft state' type: boolean default: false required: false - release_name: + release-name: description: 'Release title. When empty, the existing release name is kept' type: string default: '' required: false jobs: artifacts-release: - runs-on: ubuntu-latest - container: - image: rockylinux:9 + runs-on: ubuntu-slim steps: - - name: Install dependencies - run: | - dnf install -y -q git tar gzip zip - - name: Mark workspace as a safe git directory - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Checkout uses: actions/checkout@v6.0.2 with: @@ -66,9 +58,9 @@ jobs: uses: softprops/action-gh-release@v3.0.2 with: tag_name: ${{ github.ref_name }} - name: ${{ inputs.release_name }} - draft: ${{ inputs.release_draft }} - append_body: ${{ inputs.append_body }} + name: ${{ inputs.release-name }} + draft: ${{ inputs.release-draft }} + append_body: ${{ inputs.append-body }} fail_on_unmatched_files: true files: | dist/rpms/**/*.rpm diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml index 33a6712..ab74536 100644 --- a/.github/workflows/reuse.yml +++ b/.github/workflows/reuse.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2025 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT name: reuse @@ -10,7 +10,7 @@ name: reuse on: workflow_call: inputs: - reuse_version: + reuse-version: type: string required: false default: '6.2.0' @@ -21,14 +21,13 @@ permissions: jobs: reuse: - name: reuse - runs-on: ubuntu-latest + runs-on: ubuntu-slim timeout-minutes: 10 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: REUSE compliance check env: - REUSE_VERSION: ${{ inputs.reuse_version }} + REUSE_VERSION: ${{ inputs.reuse-version }} run: pipx run --backend pip --spec "reuse==$REUSE_VERSION" reuse lint --lines diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3599233..8ee8c54 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2025 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT name: scorecard @@ -21,11 +21,10 @@ permissions: jobs: scorecard: - name: scorecard # Scorecard is a container action and needs a full-fat runner. runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -45,7 +44,7 @@ jobs: - name: Upload SARIF to GHAS if: github.event_name != 'pull_request' - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: sarif_file: results.sarif category: scorecard diff --git a/.github/workflows/test-go.yml b/.github/workflows/test-go.yml deleted file mode 100644 index d30dd34..0000000 --- a/.github/workflows/test-go.yml +++ /dev/null @@ -1,46 +0,0 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC -# SPDX-License-Identifier: MIT - -name: test-go - -# Reusable workflow that runs the caller's Go unit tests. Fetches tags so -# tests that assert on version metadata derived from `git describe` behave -# the same as they do locally. - -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 - test-command: - description: 'Command that runs the tests' - required: false - type: string - default: 'go test -race ./...' - -permissions: - contents: read # baseline for checkout - -jobs: - test-go: - name: test-go - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - fetch-tags: true - persist-credentials: false - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: ${{ inputs.go_version || (inputs.go_version_file == '' && 'stable' || '') }} - go-version-file: ${{ inputs.go_version_file || '' }} - - name: Run tests - env: - TEST_COMMAND: ${{ inputs.test-command }} - run: eval "$TEST_COMMAND" diff --git a/.github/workflows/test-unit-go.yml b/.github/workflows/test-unit-go.yml new file mode 100644 index 0000000..f87a6d1 --- /dev/null +++ b/.github/workflows/test-unit-go.yml @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: © 2025 OpenCHAMI a Series of LF Projects, LLC +# SPDX-License-Identifier: MIT + +name: test-unit-go + +# Reusable workflow that runs the caller's Go unit tests, optionally writing a +# coverage profile, reporting the total, and uploading it to Coveralls. Fetches +# tags so tests that assert on version metadata derived from `git describe` +# behave the same as they do locally. +# +# With coverage enabled, the caller repo must be enrolled in Coveralls; the +# upload authenticates with the automatically-provided GITHUB_TOKEN. + +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 + # see comment for details: + # https://github.com/OpenCHAMI/github-actions/pull/38#discussion_r4213283911 + test-args: + description: 'Arguments passed to `go test` (flags and packages), one per line; do not add shell quotes' + required: false + type: string + default: |- + -race + ./... + coverage: + description: 'Write a coverage profile, report the total, and upload it to Coveralls' + required: false + type: boolean + default: false + coverage-file: + description: 'Path of the coverage profile written when coverage is true' + required: false + type: string + default: 'coverage.out' + +permissions: + contents: read # baseline for checkout + +jobs: + test-unit-go: + runs-on: ubuntu-slim + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-tags: true + 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: Run tests + env: + TEST_ARGS: ${{ inputs.test-args }} + COVERAGE: ${{ inputs.coverage }} + COVERAGE_FILE: ${{ inputs.coverage-file }} + run: | + test_args=() + while IFS= read -r arg || [[ -n "$arg" ]]; do + test_args+=("$arg") + done < <(printf '%s' "$TEST_ARGS") + + cover_args=() + if [[ "$COVERAGE" == 'true' ]]; then + cover_args=("-coverprofile=${COVERAGE_FILE}") + fi + go test "${cover_args[@]}" "${test_args[@]}" + + - name: Report total coverage + if: ${{ inputs.coverage }} + env: + COVERAGE_FILE: ${{ inputs.coverage-file }} + run: | + total=$(go tool cover -func="$COVERAGE_FILE" | awk '/^total:/ {print $3}') + echo "Total coverage: ${total}" | tee -a "$GITHUB_STEP_SUMMARY" + + - name: Upload coverage to Coveralls + if: ${{ inputs.coverage }} + uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8 + with: + file: ${{ inputs.coverage-file }} + format: golang + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/trivy-image-scan.yml b/.github/workflows/trivy-image-scan.yml index 0edd45f..dd6a4e6 100644 --- a/.github/workflows/trivy-image-scan.yml +++ b/.github/workflows/trivy-image-scan.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2025 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT name: trivy-image-scan @@ -41,10 +41,9 @@ permissions: jobs: trivy: - name: trivy - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -60,7 +59,7 @@ jobs: - name: Upload SARIF to GHAS if: always() # upload findings even when scan fails the job - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: sarif_file: trivy-results.sarif category: trivy-image-scan diff --git a/.github/workflows/validate-rpm-quadlet.yml b/.github/workflows/validate-rpm-quadlet.yml index 509292e..c5c258d 100644 --- a/.github/workflows/validate-rpm-quadlet.yml +++ b/.github/workflows/validate-rpm-quadlet.yml @@ -1,11 +1,10 @@ -# Copyright © 2026 OpenCHAMI a Series of LF Projects, LLC +# SPDX-FileCopyrightText: © 2026 OpenCHAMI a Series of LF Projects, LLC # SPDX-License-Identifier: MIT # # Reusable workflow: validates a signed quadlet RPM's installed file list # against the set of files the caller expects it to ship. -name: Validate Podman Quadlet RPM -run-name: Validate Podman Quadlet RPM for ${{ github.ref }} +name: validate-rpm-quadlet on: workflow_call: inputs: @@ -23,7 +22,7 @@ permissions: jobs: parse: - runs-on: ubuntu-latest + runs-on: ubuntu-slim outputs: matrix: ${{ steps.parse.outputs.matrix }} steps: @@ -33,31 +32,22 @@ jobs: RPMS: ${{ inputs.rpms }} run: echo "matrix=$(yq -o=json -I=0 '.' <<< "$RPMS")" >> "$GITHUB_OUTPUT" - rpmvalidate: + rpm-validate: needs: parse name: validate (${{ matrix.rpm.name }}) - runs-on: ubuntu-latest + runs-on: ubuntu-slim strategy: fail-fast: false matrix: rpm: ${{ fromJSON(needs.parse.outputs.matrix) }} - container: - image: rockylinux:9 steps: - - - name: Install build dependencies - run: dnf install -y -q git rpmlint tar gzip diffutils jq - - - name: Mark workspace as a safe git directory - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-tags: true fetch-depth: 0 - name: Download signed RPM artifacts - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ${{ inputs.artifact-name-signed-rpms }} path: dist/rpms diff --git a/README.md b/README.md index a7bbe1b..239ff21 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ Reusable GitHub Actions for CI/CD. ## Structure -- `actions/gpg-ephemeral-key`: **Deprecated** - use `actions/gpg-configure-release-keys` instead - `actions/gpg-configure-release-keys`: Generates and certifies a per-run ephemeral GPG key through the repo's release key chain - `actions/gpg-sign-rpm`: RPM signing with ephemeral keys - `actions/gpg-check-key-expiration`: Fails CI if a signing key is expired or expiring soon @@ -22,10 +21,9 @@ Reusable GitHub Actions for CI/CD. - `.github/workflows/validate-rpm-quadlet.yml`: Validates a signed quadlet RPM's installed file list - `.github/workflows/release-signed-artifacts.yml`: Publishes a GitHub Release with signed RPMs and public keys - `.github/workflows/publish-release.yml`: Publishes the draft GitHub Release for a tag -- `.github/workflows/lint-workflows.yml`: Reusable workflow that lints workflow files (actionlint + zizmor) +- `.github/workflows/lint-ci.yml`: Reusable workflow that lints workflow files (actionlint + zizmor) - `.github/workflows/lint-go.yml`: Reusable workflow that runs golangci-lint and checks go.mod/go.sum are tidy -- `.github/workflows/test-go.yml`: Reusable workflow that runs Go unit tests -- `.github/workflows/coverage-go.yml`: Reusable workflow that reports Go coverage and uploads it to Coveralls +- `.github/workflows/test-unit-go.yml`: Reusable workflow that runs Go unit tests, optionally reporting coverage to Coveralls - `.github/workflows/reuse.yml`: Reusable workflow that checks REUSE copyright/licensing compliance - `.github/workflows/govulncheck.yml`: Reusable workflow that scans Go modules for known CVEs - `.github/workflows/dependency-review.yml`: Reusable workflow that gates PRs introducing CVE-flagged deps @@ -33,22 +31,27 @@ Reusable GitHub Actions for CI/CD. - `.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 +### Workflow naming + +`0-local-*` workflows are this repo's own CI, not reusable workflows. The `0` +just sorts them to the top. + ## Versioning & Usage -Use major version tags for stability: +Pin a release tag: ```yaml # For actions -- uses: OpenCHAMI/github-actions/actions/gpg-configure-release-keys@v3.8 -- uses: OpenCHAMI/github-actions/actions/gpg-sign-rpm@v3.8 +- uses: OpenCHAMI/github-actions/actions/gpg-configure-release-keys@v4.0 +- uses: OpenCHAMI/github-actions/actions/gpg-sign-rpm@v4.0 # For reusable workflows jobs: release: - uses: OpenCHAMI/github-actions/.github/workflows/go-build-release.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/go-build-release.yml@v4.0 ``` -Pin a commit SHA internally for maximum supply-chain safety if desired. +Pin a commit SHA instead for maximum supply-chain safety if desired. ## Workflows @@ -56,7 +59,7 @@ Pin a commit SHA internally for maximum supply-chain safety if desired. Standardized GoReleaser workflow for building and releasing Go applications with: - Multi-architecture builds (linux/amd64, linux/arm64) - Flexible pre-build setup steps -- Wraps `goreleaser-action` action with all .gorelease.yaml configurations +- Wraps `goreleaser-action` action with all .goreleaser.yaml configurations - Container image builds and publishing - Binary and container attestation/signing - Snapshot builds on pull requests @@ -76,7 +79,7 @@ on: jobs: goreleaser: name: GoReleaser ${{ startsWith(github.ref, 'refs/tags/v') && 'Release' || 'Snapshot' }} - uses: OpenCHAMI/github-actions/.github/workflows/go-build-release.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/go-build-release.yml@v4.0 with: pre-build-commands: | go install github.com/swaggo/swag/cmd/swag@latest @@ -87,7 +90,7 @@ jobs: See the [workflow](.github/workflows/go-build-release.yml) for additional input parameters. -### lint-workflows (Reusable Workflow) +### lint-ci (Reusable Workflow) Lints the caller repo's GitHub Actions workflow files. - **actionlint** - syntax validation, shellcheck on `run:` steps, deprecated-action checks. @@ -95,7 +98,7 @@ Lints the caller repo's GitHub Actions workflow files. **Usage:** ```yaml -name: Lint Workflows +name: Lint CI on: pull_request: push: @@ -103,11 +106,11 @@ on: jobs: lint: - uses: OpenCHAMI/github-actions/.github/workflows/lint-workflows.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/lint-ci.yml@v4.0 ``` ### lint-go (Reusable Workflow) -Lints the caller's Go module. Runs `golangci-lint`, and separately verifies `go.mod`/`go.sum` are tidy by running the tidy command and failing on a dirty diff. Uses Go `stable` unless the caller sets `go_version` or `go_version_file`. `golangci-lint` tracks `latest` unless pinned. +Lints the caller's Go module. Runs `golangci-lint`, and separately verifies `go.mod`/`go.sum` are tidy by running the tidy command and failing on a dirty diff. Uses Go `stable` unless the caller sets `go-version` or `go-version-file`. `golangci-lint` tracks `latest` unless pinned. **Usage:** ```yaml @@ -119,16 +122,17 @@ on: jobs: lint: - uses: OpenCHAMI/github-actions/.github/workflows/lint-go.yml@v3.9 + uses: OpenCHAMI/github-actions/.github/workflows/lint-go.yml@v4.0 # Optional overrides: # with: # golangci-lint-version: v2.13.2 - # go_version_file: go.mod - # tidy-command: make mod + # go-version-file: go.mod ``` -### test-go (Reusable Workflow) -Runs the caller's Go unit tests. Fetches tags so tests asserting on `git describe` version metadata behave as they do locally. +### test-unit-go (Reusable Workflow) +Runs `go test` with `test-args`, one argument per line without shell quotes (default `-race` and `./...`). Fetches tags so tests asserting on `git describe` version metadata behave as they do locally. + +With `coverage: true`, it also writes a coverage profile, reports the total in the job summary, and uploads the profile to Coveralls using the automatically-provided `GITHUB_TOKEN`. The caller repo must be enrolled in Coveralls. **Usage:** ```yaml @@ -140,31 +144,17 @@ on: jobs: test: - uses: OpenCHAMI/github-actions/.github/workflows/test-go.yml@v3.9 + uses: OpenCHAMI/github-actions/.github/workflows/test-unit-go.yml@v4.0 # Optional overrides: # with: - # go_version_file: go.mod - # test-command: make test -``` - -### coverage-go (Reusable Workflow) -Produces a Go coverage profile, writes the total to the job summary, and uploads the profile to Coveralls using the automatically-provided `GITHUB_TOKEN`. The caller repo must be enrolled in Coveralls. - -**Usage:** -```yaml -name: Coverage -on: - pull_request: - push: - branches: [main] - -jobs: - coverage: - uses: OpenCHAMI/github-actions/.github/workflows/coverage-go.yml@v3.9 - # Optional overrides: - # with: - # go_version_file: go.mod - # coverage-command: make coverage + # go-version: stable # mutually exclusive with go-version-file + # go-version-file: go.mod + # test-args: |- + # -race + # -short + # ./... + # coverage: true + # coverage-file: coverage.out ``` ### reuse (Reusable Workflow) @@ -180,7 +170,10 @@ on: jobs: reuse: - uses: OpenCHAMI/github-actions/.github/workflows/reuse.yml@v3.9 + uses: OpenCHAMI/github-actions/.github/workflows/reuse.yml@v4.0 + # Optional overrides: + # with: + # reuse-version: 6.2.0 ``` ### govulncheck (Reusable Workflow) @@ -198,7 +191,11 @@ on: jobs: govulncheck: - uses: OpenCHAMI/github-actions/.github/workflows/govulncheck.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/govulncheck.yml@v4.0 + # Optional overrides: + # with: + # go-version: 1.26.7 # default: read from go.mod + # go-package: ./cmd/... ``` ### dependency-review (Reusable Workflow) @@ -212,29 +209,35 @@ on: jobs: dependency-review: - uses: OpenCHAMI/github-actions/.github/workflows/dependency-review.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/dependency-review.yml@v4.0 # Optional overrides: # with: # fail-on-severity: moderate # deny-licenses: GPL-3.0,AGPL-3.0 + # allow-licenses: MIT,Apache-2.0 + # comment-summary-in-pr: always # always | on-failure | never ``` ### trivy-image-scan (Reusable Workflow) -Scans an already-pushed container image with Trivy and uploads SARIF findings to GitHub Advanced Security. Designed to chain after `docker-build-release` with a digest-pinned image reference. +Scans an already-pushed container image with Trivy and uploads SARIF findings to GitHub Advanced Security. Designed to chain after `docker-build-release` by scanning an image tag it pushed (`docker-build-release` tags by branch, git tag, or `pr-`). **Usage:** ```yaml jobs: build: - uses: OpenCHAMI/github-actions/.github/workflows/docker-build-release.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/docker-build-release.yml@v4.0 with: registry-name: ghcr.io/openchami/foo scan: needs: build - uses: OpenCHAMI/github-actions/.github/workflows/trivy-image-scan.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/trivy-image-scan.yml@v4.0 with: - image-ref: ghcr.io/openchami/foo:${{ github.sha }} + image-ref: ghcr.io/openchami/foo:${{ github.ref_name }} # on a tag push + # Optional overrides: + # severity: CRITICAL + # ignore-unfixed: true + # exit-code: '0' # report only ``` ### scorecard (Reusable Workflow) @@ -258,24 +261,24 @@ permissions: jobs: scorecard: - uses: OpenCHAMI/github-actions/.github/workflows/scorecard.yml@v3.9 + uses: OpenCHAMI/github-actions/.github/workflows/scorecard.yml@v4.0 ``` ### build-publish-container-goreleaser (Reusable Workflow) -Builds and publishes a container image via GoReleaser, with multi-arch builds, build provenance attestation, and PR snapshot support. Release builds (`is_pr_build: false`) pass GitHub's auto-generated release notes for the pushed tag to GoReleaser via `--release-notes`. +Builds and publishes a container image via GoReleaser, with multi-arch builds, build provenance attestation, and PR snapshot support. Release builds (`is-pr-build: false`) pass GitHub's auto-generated release notes for the pushed tag to GoReleaser via `--release-notes`. -Optional `build_deps` is a space-separated list of apt packages installed before the build, for cases such as CGO cross-compilation that need a toolchain not present on the runner. +Optional `build-deps` is a space-separated list of apt packages installed before the build, for cases such as CGO cross-compilation that need a toolchain not present on the runner. **Usage:** ```yaml jobs: build: - uses: OpenCHAMI/github-actions/.github/workflows/build-publish-container-goreleaser.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/build-publish-container-goreleaser.yml@v4.0 with: - registry_subject_name: ghcr.io/openchami/foo - release_draft: false - cgo_enabled: 1 - build_deps: gcc-aarch64-linux-gnu libc6-dev-arm64-cross + registry-subject-name: ghcr.io/openchami/foo + release-draft: false + cgo-enabled: 1 + build-deps: gcc-aarch64-linux-gnu libc6-dev-arm64-cross ``` ### build-rpm-quadlet (Reusable Workflow) @@ -285,7 +288,10 @@ Builds the caller repo's podman quadlet RPM and uploads it as an unsigned artifa ```yaml jobs: build: - uses: OpenCHAMI/github-actions/.github/workflows/build-rpm-quadlet.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/build-rpm-quadlet.yml@v4.0 + # Optional overrides: + # with: + # artifact-name-unsigned-rpms: rpms-unsigned ``` ### gpg-sign-artifacts (Reusable Workflow) @@ -295,7 +301,7 @@ Signs unsigned RPM artifacts with a per-run ephemeral key certified through the ```yaml jobs: sign: - uses: OpenCHAMI/github-actions/.github/workflows/gpg-sign-artifacts.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/gpg-sign-artifacts.yml@v4.0 secrets: inherit ``` @@ -306,8 +312,9 @@ Validates a signed quadlet RPM's installed file list against the set of files th ```yaml jobs: validate: - uses: OpenCHAMI/github-actions/.github/workflows/validate-rpm-quadlet.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/validate-rpm-quadlet.yml@v4.0 with: + # artifact-name-signed-rpms: rpms-signed # optional rpms: | - name: foo-*.rpm files: @@ -321,20 +328,24 @@ Publishes a GitHub Release for a tag, attaching signed RPMs and public keys, wit ```yaml jobs: release: - uses: OpenCHAMI/github-actions/.github/workflows/release-signed-artifacts.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/release-signed-artifacts.yml@v4.0 with: - release_draft: false + release-draft: false ``` ### publish-release (Reusable Workflow) -Publishes the draft GitHub Release for a tag, for pipelines that set `release_draft: true` upstream so the release only appears once every artifact is attached. +Publishes the draft GitHub Release for a tag, for pipelines that set `release-draft: true` upstream so the release only appears once every artifact is attached. **Usage:** ```yaml jobs: publish: needs: release - uses: OpenCHAMI/github-actions/.github/workflows/publish-release.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/publish-release.yml@v4.0 + # Optional overrides: + # with: + # make-latest: true # true | false | legacy (default) + # draft: true ``` ### pr-registry-cleanup (Reusable Workflow) @@ -349,16 +360,17 @@ on: jobs: cleanup: - uses: OpenCHAMI/github-actions/.github/workflows/pr-registry-cleanup.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/pr-registry-cleanup.yml@v4.0 permissions: packages: write + # Optional overrides: + # with: + # pr-number: 123 + # tag-prefix: pr- ``` ## Actions -### gpg-ephemeral-key (Deprecated - use gpg-configure-release-keys) -Generates a short-lived RSA key and signs it with a repo-scoped subkey. See the [action README](actions/gpg-ephemeral-key/README.md). - ### gpg-configure-release-keys Generates a per-run ephemeral GPG key, certified through the repo's release key chain (master certifies a repo cert key, which certifies the ephemeral key). See the [action README](actions/gpg-configure-release-keys/README.md). @@ -396,12 +408,13 @@ on: types: [opened, synchronize, reopened, edited] workflow_dispatch: inputs: - pr_number: + pr-number: description: 'PR Number to build (optional, for manual PR builds)' required: false type: string -permissions: write-all # Necessary for the generate-build-provenance action with containers +permissions: + contents: read # baseline; jobs that need more request it below jobs: config: @@ -420,33 +433,38 @@ jobs: } >> "$GITHUB_OUTPUT" build: - uses: OpenCHAMI/github-actions/.github/workflows/build-publish-container-goreleaser.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/build-publish-container-goreleaser.yml@v4.0 secrets: inherit + permissions: + contents: write # release creation, uploading assets + packages: write # image push, container provenance + id-token: write # Sigstore signing (attest-build-provenance) + attestations: write # build provenance attestations with: - cgo_enabled: 0 - registry_subject_name: ghcr.io/openchami/metadata-service - is_pr_build: true - pr_number: ${{ inputs.pr_number || github.event.pull_request.number || 0 }} + cgo-enabled: 0 + registry-subject-name: ghcr.io/openchami/metadata-service + is-pr-build: true + pr-number: ${{ inputs.pr-number || github.event.pull_request.number || 0 }} - rpmbuild: + rpm-build: needs: [config, build] - uses: OpenCHAMI/github-actions/.github/workflows/build-rpm-quadlet.yml@v3.8 + uses: OpenCHAMI/github-actions/.github/workflows/build-rpm-quadlet.yml@v4.0 secrets: inherit with: artifact-name-unsigned-rpms: ${{ needs.config.outputs.rpm-unsigned }} - rpmsign: - needs: [config, rpmbuild] - uses: OpenCHAMI/github-actions/.github/workflows/gpg-sign-artifacts.yml@v3.8 + rpm-sign: + needs: [config, rpm-build] + uses: OpenCHAMI/github-actions/.github/workflows/gpg-sign-artifacts.yml@v4.0 secrets: inherit with: artifact-name-unsigned-rpms: ${{ needs.config.outputs.rpm-unsigned }} artifact-name-signed-rpms: ${{ needs.config.outputs.rpm-signed }} artifact-name-public-keys: ${{ needs.config.outputs.keys-public }} - rpmvalidate: - needs: [config, rpmsign] - uses: OpenCHAMI/github-actions/.github/workflows/validate-rpm-quadlet.yml@v3.8 + rpm-validate: + needs: [config, rpm-sign] + uses: OpenCHAMI/github-actions/.github/workflows/validate-rpm-quadlet.yml@v4.0 secrets: inherit with: artifact-name-signed-rpms: ${{ needs.config.outputs.rpm-signed }} @@ -461,7 +479,7 @@ jobs: ## Continuous Integration -- Workflow files are linted via `lint-workflows.yml` (actionlint + zizmor). +- Workflow files are linted via `0-local-ci.yml`, which calls `lint-ci.yml` (actionlint + zizmor). - RPM/quadlet output is validated via `validate-rpm-quadlet.yml`. - TODO: matrix test invoking each action directly. diff --git a/actions/gpg-ephemeral-key/README.md b/actions/gpg-ephemeral-key/README.md deleted file mode 100644 index aa4e146..0000000 --- a/actions/gpg-ephemeral-key/README.md +++ /dev/null @@ -1,109 +0,0 @@ - - -# 🛡️ GPG Ephemeral Key Generator - -> [!WARNING] -> -> Deprecated: use [`gpg-configure-release-keys`](../gpg-configure-release-keys) instead. - -This GitHub composite action generates a new ephemeral GPG key on every build, signs it using a repo‑scoped subkey, and exports the fingerprint and public key. It’s designed for use in CI pipelines where artifacts need secure signing without long‑lived keys in GitHub Actions. - ---- - -## 🔧 How It Works - -- Generates a short‑lived RSA key (default RSA‑3072, expiration 1 day) -- Signs it with your repo‑specific subkey (stored as `GPG_SUBKEY_B64`) -- Returns: - - `ephemeral-fingerprint` → use to sign artifacts - - `ephemeral-public-key` → base64-encoded armored public key - - `gnupg-home` → isolated GNUPGHOME path for downstream steps - ---- - -## 📦 Inputs - -| Name | Required | Description | -|------------------|----------|-------------| -| `subkey-armored` | ✅ | Base64‑encoded ASCII‑armored GPG subkey (secret) used to sign the ephemeral key | -| `name` | ❌ | Name for the ephemeral key (default: Ephemeral Key) | -| `comment` | ❌ | Metadata like build ID, ref, etc. A random suffix is appended | -| `email` | ❌ | Email for ephemeral key (default: ci@build.local) | -| `key-length` | ❌ | RSA key length (default: 3072) | -| `expire-days` | ❌ | Expiration in days (default: 1) | -| `cleanup` | ❌ | If `true`, remove GNUPGHOME after export (default: false) | - ---- - -## 🔑 Outputs - -| Name | Description | -|-------------------------|-------------| -| `ephemeral-fingerprint` | Fingerprint of the generated ephemeral key | -| `ephemeral-public-key` | Base64‑encoded ASCII‑armored public key | -| `gnupg-home` | Path to isolated GNUPGHOME for subsequent actions | - ---- - -## 🛠️ Setup (Per Repo) - -1. Create a GPG subkey tied to this repository, signed by your org's offline master key. -2. Export it (subkey only): - ```bash - gpg --export-secret-subkeys --armor > repo-subkey.asc - base64 < repo-subkey.asc | tr -d '\n' > subkey.b64 - ``` -3. Store as a GitHub Secret in your repo: - `GPG_SUBKEY_B64` = contents of `subkey.b64` - -## ✅ Example: Prove Signing Works - -```yaml -jobs: - test-ephemeral-signing: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Generate ephemeral key - id: gpg - uses: OpenCHAMI/github-actions/actions/gpg-ephemeral-key@v1 - with: - subkey-armored: ${{ secrets.GPG_SUBKEY_B64 }} - comment: "build:${{ github.run_id }}" - key-length: '3072' - expire-days: '1' - cleanup: false - - - name: Sign and verify a test file - run: | - GNUPGHOME="${{ steps.gpg.outputs.gnupg-home }}"; export GNUPGHOME - echo "hello" > test.txt - gpg --batch --yes --local-user "${{ steps.gpg.outputs.ephemeral-fingerprint }}" --detach-sign --output test.txt.sig test.txt - gpg --verify test.txt.sig test.txt - - - name: Show trust chain - run: | - GNUPGHOME="${{ steps.gpg.outputs.gnupg-home }}"; export GNUPGHOME - echo "Ephemeral: ${{ steps.gpg.outputs.ephemeral-fingerprint }}" - gpg --list-keys --with-colons - gpg --list-sigs "${{ steps.gpg.outputs.ephemeral-fingerprint }}" - - - name: Cleanup - if: always() - run: rm -rf "${{ steps.gpg.outputs.gnupg-home }}" -``` - -## 🔐 Security Notes - -- Ephemeral key is short‑lived; expiration limits future signing, not validation of past signatures. -- Subkey is repo‑scoped, easy to rotate/revoke. -- Use the isolated `GNUPGHOME` to avoid polluting runner defaults; set `cleanup: true` when possible. -- Trust chain: `Ephemeral key ← Repo subkey ← Offline master key`. - -## 📝 License - -MIT diff --git a/actions/gpg-ephemeral-key/action.yml b/actions/gpg-ephemeral-key/action.yml deleted file mode 100644 index 3d6ce89..0000000 --- a/actions/gpg-ephemeral-key/action.yml +++ /dev/null @@ -1,176 +0,0 @@ -# SPDX-FileCopyrightText: 2025 OpenCHAMI a Series of LF Projects, LLC -# SPDX-License-Identifier: MIT - -name: '[DEPRECATED] Generate and Sign Ephemeral GPG Key' -author: 'OpenCHAMI' -branding: - icon: 'lock' - color: 'purple' -description: 'Deprecated: use gpg-configure-release-keys instead. Creates an ephemeral GPG key per build and signs it with a repo-scoped subkey' - -inputs: - subkey-armored: - description: 'Base64-encoded ASCII-armored GPG subkey (secret) used to sign the ephemeral key' - required: true - name: - description: 'Name (real) for the ephemeral key' - default: 'Ephemeral Key' - comment: - description: 'Additional comment / metadata (will have a random suffix appended)' - default: '' - email: - description: 'Email for the ephemeral key' - default: 'ci@build.local' - key-length: - description: 'RSA key length' - default: '3072' - expire-days: - description: 'Expiration in days for the ephemeral key' - default: '1' - cleanup: - description: 'If true, remove GNUPGHOME (keys) after export. Set to false if later steps need the key.' - default: 'false' - -outputs: - ephemeral-fingerprint: - description: 'Fingerprint of the generated ephemeral key' - value: ${{ steps.fpr.outputs.fingerprint }} - ephemeral-public-key: - description: 'Base64 of ASCII-armored ephemeral public key' - value: ${{ steps.export.outputs.pubkey }} - gnupg-home: - description: 'Path to isolated GNUPGHOME for subsequent actions' - value: ${{ steps.setup.outputs.gnupghome }} - -runs: - using: "composite" - steps: - - id: deprecation-warning - shell: bash - run: | - echo "::warning::gpg-ephemeral-key is deprecated; migrate to gpg-configure-release-keys." - - - id: setup - shell: bash - run: | - set -euo pipefail - GNUPGHOME="$(mktemp -d)" - chmod 700 "$GNUPGHOME" - echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" - echo "gnupghome=$GNUPGHOME" >> "$GITHUB_OUTPUT" - sudo apt-get update -y - sudo apt-get install -y --no-install-recommends gnupg - - - id: import - shell: bash - run: | - set -euo pipefail - - echo "Importing subkey..." - - # Save subkey - decoded=$(echo "${{ inputs.subkey-armored }}" | base64 -d 2>/dev/null || true) - if [[ -z "$decoded" ]]; then - echo "ERROR: subkey-armored input is invalid or empty!" >&2 - exit 1 - fi - - echo "$decoded" > subkey.asc - - # Import key - gpg --batch --import subkey.asc || { - echo "ERROR: Failed to import subkey" >&2 - exit 1 - } - - # List keys for debug - echo "::group::GPG secret keys" - gpg --list-secret-keys - echo "::endgroup::" - - # Get signer fingerprint - signer_fpr=$(gpg --batch --with-colons --list-secret-keys | awk -F: '/^sec:/ {print $5; exit}') - if [[ -z "$signer_fpr" ]]; then - echo "ERROR: No secret key found after import" >&2 - gpg --list-secret-keys --with-colons - exit 1 - fi - - echo "Using SIGNER_FPR=$signer_fpr" - echo "SIGNER_FPR=$signer_fpr" >> "$GITHUB_ENV" - - shred -u subkey.asc || rm -f subkey.asc - - - - id: generate - shell: bash - run: | - set -euo pipefail - # Sanitize user inputs to avoid config injection - safe_name=$(printf '%s' "${{ inputs.name }}" | tr -cd '[:alnum:] ._@-') - safe_comment=$(printf '%s' "${{ inputs.comment }}" | tr -cd '[:alnum:] ._@:-') - safe_email=$(printf '%s' "${{ inputs.email }}" | tr -cd '[:alnum:]@._-') - marker=$(openssl rand -hex 6 2>/dev/null || date +%s) - safe_comment="$safe_comment build-${GITHUB_RUN_ID:-0}-$marker" - echo "MARKER=$marker" >> "$GITHUB_ENV" - expire_days="${{ inputs.expire-days }}" - key_length="${{ inputs.key-length }}" - - printf "%%no-protection\n" > keygen.conf - printf "Key-Type: RSA\n" >> keygen.conf - printf "Key-Length: %s\n" "$key_length" >> keygen.conf - printf "Name-Real: %s\n" "$safe_name" >> keygen.conf - printf "Name-Comment: %s\n" "$safe_comment" >> keygen.conf - printf "Name-Email: %s\n" "$safe_email" >> keygen.conf - printf "Expire-Date: %sd\n" "$expire_days" >> keygen.conf - printf "Key-Usage: sign\n" >> keygen.conf - printf "%%commit\n" >> keygen.conf - - gpg --batch --gen-key keygen.conf - shred -u keygen.conf || rm -f keygen.conf - - - id: fpr - shell: bash - run: | - set -euo pipefail - echo "Available keys for debug:" - gpg --list-keys - - ephemeral_fpr=$(gpg --with-colons --list-keys | awk -F: -v m="${MARKER:-}" ' - /^fpr:/ { fpr=$10 } - /^uid:/ { - if (index($10, m) > 0) { - print fpr - exit - } - }') - - - if [ -z "$ephemeral_fpr" ]; then - echo "Failed to locate ephemeral key fingerprint" >&2 - exit 1 - fi - echo "fingerprint=$ephemeral_fpr" >> "$GITHUB_OUTPUT" - echo "EPHEMERAL_FPR=$ephemeral_fpr" >> "$GITHUB_ENV" - - - id: sign-ephemeral - shell: bash - run: | - set -euo pipefail - gpg --batch --yes --quick-sign-key --local-user "$SIGNER_FPR" "$EPHEMERAL_FPR" - - - id: export - shell: bash - run: | - set -euo pipefail - gpg --armor --export "$EPHEMERAL_FPR" > pub.asc - b64=$(base64 < pub.asc | tr -d '\n') - echo "pubkey=$b64" >> "$GITHUB_OUTPUT" - shred -u pub.asc || rm -f pub.asc - - - id: optional-cleanup - if: ${{ inputs.cleanup == 'true' }} - shell: bash - run: | - set -euo pipefail - rm -rf "$GNUPGHOME"