Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ paths:
# ships support for the job workflow-identity properties.
- 'property "workflow_repository" is not defined in object type'
- 'property "workflow_sha" is not defined in object type'
.github/workflows/pack-build-image.yaml:
ignore:
# Same job.workflow_repository/job.workflow_sha checkout pattern as
# pack-release.yaml above (this workflow's composite sign actions must
# also be loaded from a checkout of this repo, not the caller's) --
# same actionlint gap, see the comment on the pack-release.yaml entry.
- 'property "workflow_repository" is not defined in object type'
- 'property "workflow_sha" is not defined in object type'
56 changes: 56 additions & 0 deletions .github/actions/sign-blob/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: sign-blob
description: Keyless-sign and attest (build provenance + SBOM) a local file.

inputs:
path:
description: "Path to the file to sign."
required: true
sbom:
description: "Whether to generate and attest an SPDX SBOM."
required: false
default: "true"

outputs:
bundle:
description: "Path to the .sigstore.json bundle produced by cosign sign-blob."
value: ${{ steps.sign.outputs.bundle }}

runs:
using: composite
steps:
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

- name: Install syft
if: ${{ inputs.sbom == 'true' }}
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0

- name: Sign blob (keyless)
id: sign
shell: bash
env:
FILE: ${{ inputs.path }}
run: |
set -euo pipefail
bundle="${FILE}.sigstore.json"
cosign sign-blob --yes --bundle "$bundle" "$FILE"
echo "bundle=$bundle" >> "$GITHUB_OUTPUT"

- name: Attest build provenance
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: ${{ inputs.path }}

- name: Generate SBOM
if: ${{ inputs.sbom == 'true' }}
shell: bash
env:
FILE: ${{ inputs.path }}
run: syft "$FILE" -o spdx-json="${FILE}.spdx.json"

- name: Attest SBOM
if: ${{ inputs.sbom == 'true' }}
uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
with:
subject-path: ${{ inputs.path }}
sbom-path: ${{ inputs.path }}.spdx.json
53 changes: 53 additions & 0 deletions .github/actions/sign-oci/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: sign-oci
description: Keyless-sign and attest (build provenance + SBOM) an OCI artifact by digest.

inputs:
image:
description: "Registry reference without digest, e.g. ghcr.io/owner/repo/name."
required: true
digest:
description: "The sha256:... digest of the pushed artifact."
required: true
sbom:
description: "Whether to generate and attest an SPDX SBOM."
required: false
default: "true"

runs:
using: composite
steps:
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

- name: Install syft
if: ${{ inputs.sbom == 'true' }}
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0

- name: Sign image (keyless)
shell: bash
env:
REF: ${{ inputs.image }}@${{ inputs.digest }}
run: cosign sign --yes "$REF"

- name: Attest build provenance
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-name: ${{ inputs.image }}
subject-digest: ${{ inputs.digest }}
push-to-registry: true

- name: Generate SBOM
if: ${{ inputs.sbom == 'true' }}
shell: bash
env:
REF: ${{ inputs.image }}@${{ inputs.digest }}
run: syft "$REF" -o spdx-json=sbom.spdx.json

- name: Attest SBOM
if: ${{ inputs.sbom == 'true' }}
uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
with:
subject-name: ${{ inputs.image }}
subject-digest: ${{ inputs.digest }}
sbom-path: sbom.spdx.json
push-to-registry: true
9 changes: 5 additions & 4 deletions .github/workflows/lint-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name: lint-test
# CI for this repo's own reusable workflows and helper scripts.
#
# Scoped to the workflows introduced/maintained under this effort
# (pack-build-image.yaml, pack-release.yaml, and this file) rather than
# the whole .github/workflows/ directory: the other pre-existing
# workflows here (sync-issue-templates.yaml, sync-project-priority.yaml)
# (pack-build-image.yaml, pack-release.yaml, sign-smoke.yaml, and this
# file) rather than the whole .github/workflows/ directory: the other
# pre-existing workflows here (sync-issue-templates.yaml, sync-project-priority.yaml)
# already have unrelated actionlint findings (a floating, EOL
# actions/checkout@v3 pin and a few shellcheck info-level notes) that
# predate this change and are out of scope for it. Widen the file list
Expand Down Expand Up @@ -33,7 +33,8 @@ jobs:
-color \
.github/workflows/pack-build-image.yaml \
.github/workflows/pack-release.yaml \
.github/workflows/lint-test.yaml
.github/workflows/lint-test.yaml \
.github/workflows/sign-smoke.yaml

pytest:
name: pytest
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/pack-build-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand Down Expand Up @@ -87,6 +89,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push
id: build
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: ${{ inputs.context }}
Expand All @@ -98,3 +101,31 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.GHCR_IMAGE }}:cache
cache-to: ${{ inputs.push && format('type=registry,ref={0}:cache,mode=max', env.GHCR_IMAGE) || '' }}

# Reusable workflows resolve `uses: ./` against the CALLER's workspace,
# not this repo, so the composite sign actions must be loaded from a
# checkout of THIS repo. job.workflow_repository@job.workflow_sha pins
# exactly the nebari-dev/.github commit the caller invoked, keeping the
# action version coherent with the workflow version. (Same pattern as
# pack-release.yaml's _dot-github checkout.)
- name: Check out the reusable workflow's repo (for the sign action)
if: ${{ inputs.push }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ${{ job.workflow_repository }}
ref: ${{ job.workflow_sha }}
path: _dot-github

- name: Sign + attest (GHCR)
if: ${{ inputs.push }}
uses: ./_dot-github/.github/actions/sign-oci
with:
image: ${{ env.GHCR_IMAGE }}
digest: ${{ steps.build.outputs.digest }}

- name: Sign + attest (Quay)
if: ${{ inputs.push }}
uses: ./_dot-github/.github/actions/sign-oci
with:
image: ${{ env.QUAY_IMAGE }}
digest: ${{ steps.build.outputs.digest }}
50 changes: 46 additions & 4 deletions .github/workflows/pack-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ name: pack-release
# workflow deliberately does not add its own ensure-quay-repos step: it
# has no access to that token, and calling it here would be redundant
# with (and could race) the downstream automation that already runs it.
#
# Sync opt-out: callers can set `sync: false` to run the full release path
# (read version, pin tags, package, sign/attest, GitHub Release) WITHOUT
# syncing the chart into nebari-dev/helm-repository -- useful for
# test/non-product callers (e.g. an integration test bed) that must not
# publish to the shared catalog. Defaults to true (unchanged behavior).
# NEBARI_HELM_REPO_TOKEN is only needed when sync is true.

on:
workflow_call:
Expand Down Expand Up @@ -71,19 +78,31 @@ on:
required: false
default: ""
type: string
sync:
description: >-
Sync the packaged chart to nebari-dev/helm-repository. Set false to
skip publishing to the shared catalog (e.g. test/non-product callers)
while still running packaging, signing, attestation, and the GitHub
Release.
required: false
type: boolean
default: true
secrets:
NEBARI_HELM_REPO_TOKEN:
description: >-
Fine-grained PAT with contents + pull-request write on
nebari-dev/helm-repository. Used only by the sync-chart step.
required: true
nebari-dev/helm-repository. Used only by the sync-chart step, so it
is only required when `sync` is true.
required: false

jobs:
release:
name: Release ${{ inputs.chart-name }}
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand Down Expand Up @@ -203,13 +222,33 @@ jobs:
*) echo "flag=" >> "$GITHUB_OUTPUT" ;;
esac

- name: Locate packaged chart
id: tgz
if: steps.exists.outputs.exists == 'false'
run: |
set -euo pipefail
shopt -s nullglob
files=(*.tgz)
if [ "${#files[@]}" -ne 1 ]; then
echo "::error::expected exactly one .tgz, found ${#files[@]}"; exit 1
fi
echo "path=${files[0]}" >> "$GITHUB_OUTPUT"

- name: Sign + attest chart
id: sign
if: steps.exists.outputs.exists == 'false'
uses: ./_dot-github/.github/actions/sign-blob
with:
path: ${{ steps.tgz.outputs.path }}

- name: Create GitHub Release
if: steps.exists.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ steps.chart.outputs.tag }}
PRERELEASE_FLAG: ${{ steps.pre.outputs.flag }}
BUNDLE: ${{ steps.sign.outputs.bundle }}
run: |
set -euo pipefail
shopt -s nullglob
Expand All @@ -224,10 +263,13 @@ jobs:
release_args+=("$PRERELEASE_FLAG")
fi

gh release create "$TAG" "${release_args[@]}" "${tgz_files[0]}"
gh release create "$TAG" "${release_args[@]}" \
"${tgz_files[0]}" \
"$BUNDLE" \
"${tgz_files[0]}.spdx.json"

- name: Sync chart to nebari-dev/helm-repository
if: steps.exists.outputs.exists == 'false'
if: steps.exists.outputs.exists == 'false' && inputs.sync
uses: nebari-dev/helm-repository/.github/actions/sync-chart@5cbd23a45c014bf2fa34b4683d4e5ac70ad34fa4 # main 2026-07-03
with:
token: ${{ secrets.NEBARI_HELM_REPO_TOKEN }}
Expand Down
Loading
Loading