scripts/test-release-workflow.sh
Rama main · réplica de lectura del repositorio original.
#!/bin/sh
set -eu
script_dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)"
project_dir="$(CDPATH='' cd -- "$script_dir/.." && pwd)"
workflow="$project_dir/.github/workflows/release.yml"
release_notes_config="$project_dir/.github/release.yml"
pr_labels_workflow="$project_dir/.github/workflows/pr-release-labels.yml"
reconcile_workflow="$project_dir/.github/workflows/reconcile-installer-r2.yml"
prepare_job="$(sed -n '/^ prepare:/,/^ publish:/p' "$workflow")"
publish_job="$(sed -n '/^ publish:/,/^ release-pr:/p' "$workflow")"
release_pr_job="$(sed -n '/^ release-pr:/,$p' "$workflow")"
require_in() {
section="$1"
expected="$2"
if ! printf '%s\n' "$section" | grep -Fq "$expected"; then
echo "Release workflow is missing: $expected" >&2
exit 1
fi
}
require_in "$(cat "$workflow")" ' group: vastora-installer-r2'
require_in "$(cat "$workflow")" ' cancel-in-progress: false'
require_in "$prepare_job" ' skip-github-pull-request: true'
require_in "$prepare_job" ' pull-requests: write'
require_in "$prepare_job" ' packages: read'
require_in "$prepare_job" ' if release_sha="$(gh api "/repos/$GITHUB_REPOSITORY/commits/$RELEASE_TAG"'
require_in "$prepare_job" " elif printf '%s' \"\$release_json\" | jq -e '.draft == true and (.assets | length) == 0' >/dev/null; then"
require_in "$prepare_job" ' release_sha="$GITHUB_SHA"'
require_in "$prepare_job" ' release_retry=image'
require_in "$prepare_job" ' -f tag_name="$RELEASE_TAG" \'
require_in "$prepare_job" ' -f target_commitish="$release_sha" >/dev/null'
require_in "$prepare_job" ' center_image_digest: ${{ steps.retry.outputs.center_image_digest }}'
require_in "$prepare_job" " release_retry: \${{ steps.retry.outputs.release_retry || 'false' }}"
require_in "$publish_job" ' artifact-metadata: write'
require_in "$publish_job" ' AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}'
require_in "$publish_job" ' R2_BUCKET_NAME: ${{ vars.R2_BUCKET_NAME }}'
require_in "$publish_job" ' scripts/create-installer-release-manifest.sh'
require_in "$publish_job" ' scripts/github-installer-release-assets.sh'
require_in "$publish_job" ' scripts/reconcile-installer-r2.sh'
require_in "$publish_job" ' platforms: linux/amd64,linux/arm64'
require_in "$publish_job" ' outputs: type=image,name=${{ env.CENTER_IMAGE }},push-by-digest=true,name-canonical=true,push=true'
require_in "$publish_job" ' provenance: mode=max'
require_in "$publish_job" ' cache-to: type=registry,ref=${{ env.CENTER_IMAGE }}:buildcache,mode=max,compression=zstd,compression-level=3,ignore-error=true'
require_in "$publish_job" ' sbom: true'
require_in "$publish_job" ' - name: Smoke-test released Center image'
require_in "$publish_job" ' actual_version="$(docker run --rm --platform linux/amd64 --network none --read-only'
require_in "$publish_job" ' "$CENTER_IMAGE@$IMAGE_DIGEST" version)"'
require_in "$publish_job" ' test "$actual_version" = "$RELEASE_VERSION"'
require_in "$publish_job" ' - name: Create durable installer release manifest'
require_in "$publish_job" ' - name: Upload and verify draft GitHub installer assets'
require_in "$publish_job" ' run: sh .release-tools/scripts/github-installer-release-assets.sh upload --tag "$RELEASE_TAG" --directory dist'
require_in "$publish_job" ' - name: Recover verified installer assets for release retry'
require_in "$publish_job" ' run: sh .release-tools/scripts/publish-installer-r2.sh stage --version "$RELEASE_VERSION" --bucket "$R2_BUCKET_NAME" --endpoint "$R2_ENDPOINT" --source-dir dist'
require_in "$publish_job" ' run: sh .release-tools/scripts/reconcile-installer-r2.sh --bucket "$R2_BUCKET_NAME" --endpoint "$R2_ENDPOINT"'
require_in "$publish_job" ' - name: Generate and publish GitHub release notes'
require_in "$publish_job" ' RELEASE_SHA: ${{ needs.prepare.outputs.release_sha }}'
require_in "$publish_job" ' gh api --method POST "/repos/$GITHUB_REPOSITORY/releases/generate-notes"'
require_in "$publish_job" " -f configuration_file_path='.github/release.yml'"
require_in "$publish_job" ' --title "Petauron Vastora $RELEASE_TAG"'
require_in "$publish_job" ' --notes-file "$notes_file"'
require_in "$publish_job" ' --draft=false'
require_in "$release_pr_job" ' needs: [prepare, publish]'
require_in "$release_pr_job" " if: always() && needs.prepare.result == 'success' && (needs.publish.result == 'success' || needs.publish.result == 'skipped')"
require_in "$release_pr_job" ' skip-github-release: true'
require_in "$release_pr_job" ' run: scripts/validate-release-metadata.sh "$BASE_SHA" .release-pr'
require_in "$release_pr_job" ' path: .release-pr'
require_in "$release_pr_job" ' run: echo "sha=$(git -C .release-pr rev-parse HEAD)" >> "$GITHUB_OUTPUT"'
require_in "$release_pr_job" ' actions: write'
require_in "$release_pr_job" ' persist-credentials: false'
dispatch_step="$(printf '%s\n' "$release_pr_job" | sed -n '/^ - name: Dispatch required checks for the version PR$/,$p')"
require_in "$dispatch_step" " if: steps.release_pr_action.outputs.prs_created == 'true'"
require_in "$dispatch_step" ' RELEASE_BRANCH: ${{ steps.release_pr.outputs.head_branch }}'
require_in "$dispatch_step" ' RELEASE_HEAD_SHA: ${{ steps.release_pr_head.outputs.sha }}'
require_in "$dispatch_step" ' run: sh scripts/ensure-version-pr-checks.sh "$RELEASE_BRANCH" "$RELEASE_HEAD_SHA"'
checks_helper="$(cat "$project_dir/scripts/ensure-version-pr-checks.sh")"
require_in "$checks_helper" 'for workflow in ci.yml codeql.yml; do'
require_in "$checks_helper" 'gh workflow run "$workflow" --repo "$repository" --ref "$branch"'
require_in "$checks_helper" '.head_sha == $sha and .head_branch == $branch'
require_in "$checks_helper" 'if [ -n "$run_id" ]; then'
require_in "$checks_helper" 'Approve workflows to run'
for required_workflow in ci.yml codeql.yml; do
require_in "$(cat "$project_dir/.github/workflows/$required_workflow")" ' workflow_dispatch:'
done
if printf '%s\n' "$release_pr_job" "$checks_helper" | grep -Eq 'checks: write|/check-runs|CHECK_CONCLUSION|/(approve|rerun|cancel)(["[:space:]]|$)'; then
echo 'Release PRs must dispatch real workflows, not manufacture required check results.' >&2
exit 1
fi
metadata_line="$(printf '%s\n' "$release_pr_job" | grep -nF ' - name: Validate release pull request metadata' | head -n 1 | cut -d: -f1)"
dispatch_line="$(printf '%s\n' "$release_pr_job" | grep -nF ' - name: Dispatch required checks for the version PR' | head -n 1 | cut -d: -f1)"
if [ -z "$metadata_line" ] || [ -z "$dispatch_line" ] || [ "$dispatch_line" -le "$metadata_line" ]; then
echo 'Version PR metadata must be validated before dispatching required workflows.' >&2
exit 1
fi
require_fresh_release_step() {
step_name="$1"
step_block="$(printf '%s\n' "$publish_job" | sed -n "/^ - name: $step_name$/,/^ - name:/p")"
require_in "$step_block" " if: needs.prepare.outputs.release_retry == 'false'"
}
for step in \
'Log in to GitHub Container Registry' \
'Build and push Center image' \
'Scan released Center image for x64 vulnerabilities' \
'Scan released Center image for ARM64 vulnerabilities' \
'Smoke-test released Center image' \
'Publish verified Center image tags' \
'Attest Center image'; do
require_fresh_release_step "$step"
done
for step in \
'Set up Docker Buildx' \
'Package release installer' \
'Verify release assets and public image access' \
'Create durable installer release manifest' \
'Upload and verify draft GitHub installer assets'; do
step_block="$(printf '%s\n' "$publish_job" | sed -n "/^ - name: $step$/,/^ - name:/p")"
require_in "$step_block" " if: needs.prepare.outputs.release_retry != 'assets'"
done
retry_assets_step="$(printf '%s\n' "$publish_job" | sed -n '/^ - name: Recover verified installer assets for release retry$/,/^ - name:/p')"
require_in "$retry_assets_step" " if: needs.prepare.outputs.release_retry == 'assets'"
for step in \
'Prune stale Vastora installer objects' \
'Verify immutable installer release after pruning' \
'Verify public installer endpoint after pruning'; do
step_block="$(printf '%s\n' "$publish_job" | sed -n "/^ - name: $step$/,/^ - name:/p")"
require_in "$step_block" "!contains(needs.prepare.outputs.release_version, '-alpha.')"
done
for step in \
'Scan released Center image for x64 vulnerabilities' \
'Scan released Center image for ARM64 vulnerabilities'; do
step_block="$(printf '%s\n' "$publish_job" | sed -n "/^ - name: $step$/,/^ - name:/p")"
require_in "$step_block" "if: needs.prepare.outputs.release_retry == 'false'"
done
if printf '%s\n' "$prepare_job" | grep -Fq 'skip-github-release: true'; then
echo 'Release creation must not update the next release pull request.' >&2
exit 1
fi
if printf '%s\n' "$publish_job" | grep -Fq ' tags:'; then
echo 'Official image tags must be promoted by digest after the applicable Alpha or stable checks.' >&2
exit 1
fi
line_of() {
printf '%s\n' "$publish_job" | grep -nF "$1" | head -n 1 | cut -d: -f1
}
scan_line="$(line_of 'Scan released Center image for ARM64 vulnerabilities')"
smoke_line="$(line_of 'Smoke-test released Center image')"
tags_line="$(line_of 'Publish verified Center image tags')"
manifest_line="$(line_of 'Create durable installer release manifest')"
github_assets_line="$(line_of 'Upload and verify draft GitHub installer assets')"
stage_line="$(line_of 'Stage immutable installer assets in R2')"
activate_line="$(line_of 'Atomically activate installer release in R2')"
verify_line="$(line_of 'Verify immutable and public installer endpoints')"
require_in "$publish_job" ' wait "$immutable_pid" || verification_failed=1'
require_in "$publish_job" ' wait "$public_pid" || verification_failed=1'
require_in "$publish_job" ' exit "$verification_failed"'
prune_line="$(line_of 'Prune stale Vastora installer objects')"
immutable_reverify_line="$(line_of 'Verify immutable installer release after pruning')"
public_reverify_line="$(line_of 'Verify public installer endpoint after pruning')"
publish_line="$(line_of 'Generate and publish GitHub release notes')"
previous=0
for current in "$scan_line" "$smoke_line" "$tags_line" "$manifest_line" "$github_assets_line" "$stage_line" "$activate_line" "$verify_line" "$prune_line" "$immutable_reverify_line" "$public_reverify_line" "$publish_line"; do
if [ -z "$current" ] || [ "$current" -le "$previous" ]; then
echo 'Release workflow must enforce build -> smoke -> tags -> draft assets -> stage -> activate -> verify -> optional housekeeping -> publish.' >&2
exit 1
fi
previous="$current"
done
release_notes_text="$(cat "$release_notes_config")"
require_in "$release_notes_text" ' - title: 安全更新'
require_in "$release_notes_text" ' - security'
require_in "$release_notes_text" ' - title: 新功能'
require_in "$release_notes_text" ' - enhancement'
require_in "$release_notes_text" ' - title: 问题修复'
require_in "$release_notes_text" ' - bug'
require_in "$release_notes_text" ' - title: 维护与其他'
require_in "$release_notes_text" ' - "*"'
require_in "$release_notes_text" ' - "dependabot[bot]"'
require_in "$release_notes_text" ' - "github-actions[bot]"'
pr_labels_text="$(cat "$pr_labels_workflow")"
require_in "$pr_labels_text" ' pull_request_target:'
require_in "$pr_labels_text" ' issues: write'
require_in "$pr_labels_text" ' PR_TITLE: ${{ github.event.pull_request.title }}'
require_in "$pr_labels_text" " release_label='security'"
require_in "$pr_labels_text" " release_label='enhancement'"
require_in "$pr_labels_text" " release_label='bug'"
if printf '%s\n' "$pr_labels_text" | grep -Fq 'actions/checkout'; then
echo 'The pull_request_target labeler must not check out pull request code.' >&2
exit 1
fi
reconcile_text="$(cat "$reconcile_workflow")"
require_in "$reconcile_text" ' - cron: "17 3 * * *"'
require_in "$reconcile_text" ' group: vastora-installer-r2'
require_in "$reconcile_text" ' cancel-in-progress: false'
require_in "$reconcile_text" ' --version-out "$RUNNER_TEMP/vastora-active-version"'
require_in "$reconcile_text" ' - name: Verify active installer endpoints'
echo "Release workflow sequencing test passed"