feat(ql3): scope deployment-family release candidates

This commit is contained in:
whyour
2026-08-16 08:54:51 +08:00
parent d991deb7ee
commit 7130d77a76
27 changed files with 1332 additions and 303 deletions
+37 -6
View File
@@ -462,38 +462,66 @@ jobs:
node_arch: x64
image_arch: amd64
image: control
repository: qinglong3-cluster-control
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime
- runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
image: control
repository: qinglong3-cluster-control
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime
- runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
image: control-ai
repository: qinglong3-cluster-control-ai
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime-ai
- runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
image: control-ai
repository: qinglong3-cluster-control-ai
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime-ai
- runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
image: admin
repository: qinglong3-cluster-admin
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-admin/Dockerfile
target: runtime
- runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
image: admin
repository: qinglong3-cluster-admin
runtime_user: 10001:10001
dockerfile: deploy/containers/ql3-cluster-admin/Dockerfile
target: runtime
- runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
image: worker
repository: qinglong3-worker
runtime_user: 65532:65532
dockerfile: deploy/containers/ql3-worker/Dockerfile
target: runtime
- runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
image: worker
repository: qinglong3-worker
runtime_user: 65532:65532
dockerfile: deploy/containers/ql3-worker/Dockerfile
target: runtime
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
@@ -509,7 +537,7 @@ jobs:
pnpm audit:image-release:ql3
- name: Build the native production image
env:
IMAGE: qinglong3-cluster-${{ matrix.image }}:ci-${{ matrix.image_arch }}
IMAGE: ${{ matrix.repository }}:ci-${{ matrix.image_arch }}
run: >-
docker build
--file ${{ matrix.dockerfile }}
@@ -526,7 +554,7 @@ jobs:
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
version: 'v0.70.0'
image-ref: qinglong3-cluster-${{ matrix.image }}:ci-${{ matrix.image_arch }}
image-ref: ${{ matrix.repository }}:ci-${{ matrix.image_arch }}
scanners: 'vuln'
vuln-type: 'os'
severity: 'HIGH,CRITICAL'
@@ -539,8 +567,8 @@ jobs:
trivyignores: ${{ runner.temp }}/ql3-${{ matrix.image }}-${{ matrix.image_arch }}.trivyignore.yaml
- name: Verify architecture and non-root runtime identity
env:
IMAGE: qinglong3-cluster-${{ matrix.image }}:ci-${{ matrix.image_arch }}
EXPECTED: ${{ matrix.image_arch }} 10001:10001
IMAGE: ${{ matrix.repository }}:ci-${{ matrix.image_arch }}
EXPECTED: ${{ matrix.image_arch }} ${{ matrix.runtime_user }}
run: |
set -euo pipefail
actual="$(docker image inspect --format '{{.Architecture}} {{.Config.User}}' "${IMAGE}")"
@@ -561,11 +589,11 @@ jobs:
--output=${{ runner.temp }}/ql3-cluster-${{ matrix.image }}.cdx.json
- name: Reconcile SBOM with the actual read-only image inventory
env:
IMAGE: qinglong3-cluster-${{ matrix.image }}:ci-${{ matrix.image_arch }}
IMAGE: ${{ matrix.repository }}:ci-${{ matrix.image_arch }}
run: >-
docker run --rm --read-only
--security-opt no-new-privileges
--user 10001:10001
--user ${{ matrix.runtime_user }}
--volume "${{ github.workspace }}:/audit:ro"
--workdir /audit
--entrypoint node
@@ -593,6 +621,9 @@ jobs:
- image: local
dockerfile: deploy/containers/ql3-local-application/Dockerfile
target: runtime
- image: worker
dockerfile: deploy/containers/ql3-worker/Dockerfile
target: runtime
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
+123 -62
View File
@@ -7,6 +7,15 @@ on:
description: Exact QingLong 3 SemVer tag without the v prefix
required: true
type: string
release_scope:
description: Deployment family to publish
required: true
default: all
type: choice
options:
- local
- cluster
- all
permissions:
contents: read
@@ -16,8 +25,56 @@ concurrency:
cancel-in-progress: false
jobs:
release-candidate:
name: Freeze the source-derived release candidate contract
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
outputs:
cluster-evidence-required: ${{ steps.contract.outputs.cluster-evidence-required }}
os-matrix: ${{ steps.contract.outputs.os-matrix }}
publish-matrix: ${{ steps.contract.outputs.publish-matrix }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: '24.18.0'
- name: Derive the exact deployment-family release plan
id: contract
env:
RELEASE_VERSION: ${{ inputs.version }}
RELEASE_SCOPE: ${{ inputs.release_scope }}
run: |
set -euo pipefail
umask 077
report="${RUNNER_TEMP}/ql3-release-candidate-contract.json"
node scripts/ql3-release-candidate-contract.cjs \
--mode=create \
--version="${RELEASE_VERSION}" \
--source-revision="${GITHUB_SHA}" \
--source-ref="${GITHUB_REF}" \
--release-scope="${RELEASE_SCOPE}" \
--output="${report}"
REPORT="${report}" node <<'NODE'
const fs = require('node:fs');
const report = JSON.parse(fs.readFileSync(process.env.REPORT, 'utf8'));
const output = [
`cluster-evidence-required=${report.releasePlan.clusterEvidenceRequired}`,
`os-matrix=${JSON.stringify(report.releasePlan.osMatrix)}`,
`publish-matrix=${JSON.stringify(report.releasePlan.publishMatrix)}`,
];
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${output.join('\n')}\n`);
NODE
worker-management-release-evidence:
name: Audit private Worker management release evidence
needs: release-candidate
if: needs.release-candidate.outputs.cluster-evidence-required == 'true'
runs-on: [self-hosted, linux, ql3-release-evidence-ephemeral]
environment: ql3-production-release-evidence
timeout-minutes: 10
@@ -62,6 +119,8 @@ jobs:
cluster-dr-release-evidence:
name: Audit private CloudNativePG disaster-recovery evidence
needs: release-candidate
if: needs.release-candidate.outputs.cluster-evidence-required == 'true'
runs-on: [self-hosted, linux, ql3-release-evidence-ephemeral]
environment: ql3-production-release-evidence
timeout-minutes: 10
@@ -106,6 +165,7 @@ jobs:
os-vulnerability:
name: Scan ${{ matrix.image }} OS packages on ${{ matrix.image_arch }}
needs: release-candidate
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
permissions:
@@ -113,55 +173,7 @@ jobs:
strategy:
fail-fast: false
matrix:
include:
- image: control
runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime
- image: control
runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime
- image: control-ai
runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime-ai
- image: control-ai
runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
dockerfile: deploy/containers/ql3-cluster-control/Dockerfile
target: runtime-ai
- image: admin
runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
dockerfile: deploy/containers/ql3-cluster-admin/Dockerfile
target: runtime
- image: admin
runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
dockerfile: deploy/containers/ql3-cluster-admin/Dockerfile
target: runtime
- image: local
runner: ubuntu-24.04
node_arch: x64
image_arch: amd64
dockerfile: deploy/containers/ql3-local-application/Dockerfile
target: runtime
- image: local
runner: ubuntu-24.04-arm
node_arch: arm64
image_arch: arm64
dockerfile: deploy/containers/ql3-local-application/Dockerfile
target: runtime
include: ${{ fromJSON(needs.release-candidate.outputs.os-matrix) }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
@@ -244,9 +256,21 @@ jobs:
publish:
name: Publish immutable multi-architecture ${{ matrix.image }} image
needs:
- release-candidate
- worker-management-release-evidence
- cluster-dr-release-evidence
- os-vulnerability
if: >-
always() &&
needs.release-candidate.result == 'success' &&
needs.os-vulnerability.result == 'success' &&
(
needs.release-candidate.outputs.cluster-evidence-required != 'true' ||
(
needs.worker-management-release-evidence.result == 'success' &&
needs.cluster-dr-release-evidence.result == 'success'
)
)
runs-on: ubuntu-24.04
permissions:
contents: read
@@ -257,19 +281,7 @@ jobs:
strategy:
fail-fast: false
matrix:
include:
- image: control
repository: qinglong3-cluster-control
runtime_root: deploy/containers/ql3-cluster-control/runtime-dependencies
- image: control-ai
repository: qinglong3-cluster-control-ai
runtime_root: deploy/containers/ql3-cluster-control/runtime-dependencies
- image: admin
repository: qinglong3-cluster-admin
runtime_root: deploy/containers/ql3-cluster-admin/runtime-dependencies
- image: local
repository: qinglong3-local-application
runtime_root: deploy/containers/ql3-local-application/runtime-dependencies
include: ${{ fromJSON(needs.release-candidate.outputs.publish-matrix) }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
@@ -279,6 +291,30 @@ jobs:
with:
node-version: '24.18.0'
- name: Recreate and audit the source-derived release candidate contract
env:
RELEASE_VERSION: ${{ inputs.version }}
RELEASE_SCOPE: ${{ inputs.release_scope }}
run: |
set -euo pipefail
umask 077
contract="${RUNNER_TEMP}/${{ matrix.repository }}-release-candidate-contract.json"
node scripts/ql3-release-candidate-contract.cjs \
--mode=create \
--version="${RELEASE_VERSION}" \
--source-revision="${GITHUB_SHA}" \
--source-ref="${GITHUB_REF}" \
--release-scope="${RELEASE_SCOPE}" \
--output="${contract}"
audit="${RUNNER_TEMP}/${{ matrix.repository }}-release-candidate-audit.json"
node scripts/ql3-release-candidate-contract.cjs \
--mode=audit \
--version="${RELEASE_VERSION}" \
--source-revision="${GITHUB_SHA}" \
--source-ref="${GITHUB_REF}" \
--release-scope="${RELEASE_SCOPE}" \
--report="${contract}" > "${audit}"
- name: Resolve and validate release identity
id: identity
env:
@@ -416,6 +452,15 @@ jobs:
predicate-path: ${{ runner.temp }}/${{ matrix.repository }}-os-vulnerability.json
push-to-registry: true
- name: Attest the source-derived release candidate contract
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
with:
subject-name: ${{ steps.identity.outputs.image }}
subject-digest: ${{ steps.push.outputs.digest }}
predicate-type: https://qinglong.dev/attestations/release-candidate-contract/v1
predicate-path: ${{ runner.temp }}/${{ matrix.repository }}-release-candidate-contract.json
push-to-registry: true
- name: Verify the published manifest and attestation bindings
env:
IMAGE: ${{ steps.identity.outputs.image }}
@@ -512,6 +557,22 @@ jobs:
--deny-self-hosted-runners \
--bundle-from-oci
- name: Verify the release candidate contract from the OCI registry
env:
GH_TOKEN: ${{ github.token }}
IMAGE: ${{ steps.identity.outputs.image }}
DIGEST: ${{ steps.push.outputs.digest }}
run: |
set -euo pipefail
gh attestation verify "oci://${IMAGE}@${DIGEST}" \
--repo "${GITHUB_REPOSITORY}" \
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/ql3-image-release.yml" \
--source-digest "${GITHUB_SHA}" \
--source-ref "${GITHUB_REF}" \
--predicate-type "https://qinglong.dev/attestations/release-candidate-contract/v1" \
--deny-self-hosted-runners \
--bundle-from-oci
- name: Promote only the verified digest to immutable release tags
env:
IMAGE: ${{ steps.identity.outputs.image }}