Skip to content

feat: scenario source change #61

feat: scenario source change

feat: scenario source change #61

Workflow file for this run

# AUTO-GENERATED by cascade - DO NOT EDIT MANUALLY
# Regenerate with: cascade generate-workflow --config .github/manifest.yaml
name: Orchestrate CI/CD
on:
push:
branches: [main]
paths:
- 'src/**'
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
type: choice
options:
- staging
- prod
default: 'staging'
dry_run:
description: 'Dry run mode'
type: boolean
default: false
concurrency:
group: orchestrate-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
setup:
name: Setup
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
run_build_build_secret: ${{ steps.setup.outputs.run_build_build_secret }}
run_build_build_perms: ${{ steps.setup.outputs.run_build_build_perms }}
run_build_build_oidc: ${{ steps.setup.outputs.run_build_build_oidc }}
run_build_build_needs: ${{ steps.setup.outputs.run_build_build_needs }}
run_build_build_withheld: ${{ steps.setup.outputs.run_build_build_withheld }}
run_deploy_app: ${{ steps.setup.outputs.run_deploy_app }}
head_sha: ${{ steps.setup.outputs.head_sha }}
version: ${{ steps.setup.outputs.version }}
previous_tag: ${{ steps.setup.outputs.previous_tag }}
changelog_base_sha: ${{ steps.setup.outputs.changelog_base_sha }}
base_build_build_secret: ${{ steps.setup.outputs.base_build_build_secret }}
base_build_build_perms: ${{ steps.setup.outputs.base_build_build_perms }}
base_build_build_oidc: ${{ steps.setup.outputs.base_build_build_oidc }}
base_build_build_needs: ${{ steps.setup.outputs.base_build_build_needs }}
base_build_build_withheld: ${{ steps.setup.outputs.base_build_build_withheld }}
base_deploy_app: ${{ steps.setup.outputs.base_deploy_app }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Setup CLI
uses: stablekernel/cascade/.github/actions/setup-cli@v0.15.0-rc.1
with:
token: ${{ secrets.CASCADE_STATE_TOKEN }}
version: v0.15.0-rc.1
- name: Run Setup
id: setup
env:
ENVIRONMENT: ${{ github.event.inputs.environment || 'staging' }}
run: |
cascade orchestrate setup \
--environment "$ENVIRONMENT" \
--config .github/manifest.yaml \
--gha-output
build-build-secret:
name: Build (build-secret)
needs: [setup]
if: |
needs.setup.outputs.run_build_build_secret == 'true'
uses: ./.github/workflows/callee-secret.yaml
with:
environment: ${{ github.event.inputs.environment || 'staging' }}
sha: ${{ needs.setup.outputs.head_sha }}
secrets:
CALLBACK_SECRET: ${{ secrets.CALLBACK_SHARED_SECRET }}
build-build-perms:
name: Build (build-perms)
needs: [setup]
if: |
needs.setup.outputs.run_build_build_perms == 'true'
permissions:
contents: read
packages: read
uses: ./.github/workflows/callee-perms.yaml
with:
environment: ${{ github.event.inputs.environment || 'staging' }}
sha: ${{ needs.setup.outputs.head_sha }}
build-build-oidc:
name: Build (build-oidc)
needs: [setup]
if: |
needs.setup.outputs.run_build_build_oidc == 'true'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/callee-oidc.yaml
with:
environment: ${{ github.event.inputs.environment || 'staging' }}
sha: ${{ needs.setup.outputs.head_sha }}
build-build-needs:
name: Build (build-needs)
needs: [setup, build-build-perms]
if: |
needs.setup.outputs.run_build_build_needs == 'true' &&
needs.build-build-perms.result == 'success'
uses: ./.github/workflows/callee-needs.yaml
with:
environment: ${{ github.event.inputs.environment || 'staging' }}
sha: ${{ needs.setup.outputs.head_sha }}
build-build-needs-retry-1:
name: Build (build-needs) - Retry 1
needs: [setup, build-build-needs]
if: needs.build-build-needs.result == 'failure'
uses: ./.github/workflows/callee-needs.yaml
with:
environment: ${{ github.event.inputs.environment || 'staging' }}
sha: ${{ needs.setup.outputs.head_sha }}
build-build-needs-retry-2:
name: Build (build-needs) - Retry 2
needs: [setup, build-build-needs-retry-1]
if: needs.build-build-needs-retry-1.result == 'failure'
uses: ./.github/workflows/callee-needs.yaml
with:
environment: ${{ github.event.inputs.environment || 'staging' }}
sha: ${{ needs.setup.outputs.head_sha }}
build-build-withheld:
name: Build (build-withheld)
needs: [setup]
if: |
needs.setup.outputs.run_build_build_withheld == 'true'
uses: ./.github/workflows/callee-withheld.yaml
with:
environment: ${{ github.event.inputs.environment || 'staging' }}
sha: ${{ needs.setup.outputs.head_sha }}
secrets:
CALLBACK_SECRET: ${{ secrets.CALLBACK_WITHHELD_SECRET }}
deploy-app:
name: Deploy (app)
needs: [setup]
if: |
needs.setup.outputs.run_deploy_app == 'true'
uses: ./.github/workflows/deploy-app.yaml
with:
environment: ${{ github.event.inputs.environment || 'staging' }}
sha: ${{ needs.setup.outputs.head_sha }}
finalize:
name: Finalize
needs: [setup, build-build-secret, build-build-perms, build-build-oidc, build-build-needs, build-build-needs-retry-1, build-build-needs-retry-2, build-build-withheld, deploy-app]
if: always() && needs.setup.result == 'success'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Generate Summary
run: |
echo "## Orchestration Complete" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "### Callback Results" >> "$GITHUB_STEP_SUMMARY"
echo "| Callback | Result | On Failure |" >> "$GITHUB_STEP_SUMMARY"
echo "|----------|--------|------------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Build (build-secret) | ${{ needs.build-build-secret.result }} | abort |" >> "$GITHUB_STEP_SUMMARY"
echo "| Build (build-perms) | ${{ needs.build-build-perms.result }} | abort |" >> "$GITHUB_STEP_SUMMARY"
echo "| Build (build-oidc) | ${{ needs.build-build-oidc.result }} | abort |" >> "$GITHUB_STEP_SUMMARY"
echo "| Build (build-needs) | ${{ needs.build-build-needs.result }} | abort |" >> "$GITHUB_STEP_SUMMARY"
echo "| Build (build-withheld) | ${{ needs.build-build-withheld.result }} | abort |" >> "$GITHUB_STEP_SUMMARY"
echo "| Deploy (app) | ${{ needs.deploy-app.result }} | abort |" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "### Outputs" >> "$GITHUB_STEP_SUMMARY"
HAS_OUTPUTS=false
if [[ "$HAS_OUTPUTS" == "false" ]]; then
echo "_No outputs produced_" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Setup CLI
uses: stablekernel/cascade/.github/actions/setup-cli@v0.15.0-rc.1
with:
token: ${{ secrets.CASCADE_STATE_TOKEN }}
version: v0.15.0-rc.1
- name: Generate Changelog
id: changelog
env:
GH_TOKEN: ${{ secrets.CASCADE_STATE_TOKEN }}
run: |
# Use changelog_base_sha which compares this env to next env
# This shows commits in this env NOT yet promoted to next env
RESULT=$(cascade generate-changelog \
--base-sha "${{ needs.setup.outputs.changelog_base_sha }}" \
--head-sha "${{ needs.setup.outputs.head_sha }}" \
--repo "${{ github.repository }}")
echo "changelog<<EOF" >> "$GITHUB_OUTPUT"
echo "$RESULT" | jq -r '.changelog' >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
- name: Manage Release
uses: ./.github/actions/manage-release
with:
repo: ${{ github.repository }}
action: update
tag: ${{ needs.setup.outputs.version }}
create_tag: 'true'
environment: ${{ github.event.inputs.environment || 'staging' }}
sha: ${{ needs.setup.outputs.head_sha }}
changelog: ${{ steps.changelog.outputs.changelog }}
previous_tag: ${{ needs.setup.outputs.previous_tag }}
token: ${{ secrets.CASCADE_STATE_TOKEN }}
- name: Update Manifest
env:
GH_TOKEN: ${{ secrets.CASCADE_STATE_TOKEN }}
HEAD_SHA: ${{ needs.setup.outputs.head_sha }}
VERSION: ${{ needs.setup.outputs.version }}
ENVIRONMENT: ${{ github.event.inputs.environment || 'staging' }}
APP_RESULT: ${{ needs.deploy-app.result }}
run: |
MANIFEST_FILE=".github/manifest.yaml"
MANIFEST_KEY="ci"
if [[ ! -f "$MANIFEST_FILE" ]]; then
echo "No $MANIFEST_FILE found - skipping state update"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
BRANCH="${GITHUB_REF##refs/heads/}"
apply_state_edits() {
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
# Update environment-level state (committed, not deployed)
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.sha = \"$HEAD_SHA\"" "$MANIFEST_FILE"
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.version = \"$VERSION\"" "$MANIFEST_FILE"
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.committed_at = \"$TIMESTAMP\"" "$MANIFEST_FILE"
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.committed_by = \"${{ github.actor }}\"" "$MANIFEST_FILE"
if [[ "$APP_RESULT" == "success" ]]; then
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.deploys.app.sha = \"$HEAD_SHA\"" "$MANIFEST_FILE"
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.deploys.app.deployed_at = \"$TIMESTAMP\"" "$MANIFEST_FILE"
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.deploys.app.deployed_by = \"${{ github.actor }}\"" "$MANIFEST_FILE"
fi
}
if [[ "$GITHUB_SERVER_URL" != "https://github.com" ]]; then
# act/gitea e2e: no GitHub API, and the trunk is neither protected nor
# signature-checked, so push the state commit directly with retries.
for attempt in 1 2 3 4 5 6 7 8 9 10; do
echo "cascade-state-write: attempt=$attempt/10"
git fetch origin "$BRANCH"
git reset --hard "origin/$BRANCH"
apply_state_edits
if git diff --quiet "$MANIFEST_FILE"; then
echo "No state changes"
exit 0
fi
git add "$MANIFEST_FILE"
git commit -m "chore: update state for $ENVIRONMENT [skip ci]"
if git push origin "HEAD:$BRANCH"; then
echo "Pushed state on attempt $attempt"
echo "cascade-state-write: ok attempt=$attempt"
exit 0
fi
echo "Push attempt $attempt rejected (likely concurrent run); retrying..." >&2
backoff=$(( 1 << (attempt - 1) ))
if [ "$backoff" -gt 8 ]; then backoff=8; fi
sleep $(( backoff + RANDOM % 2 ))
done
echo "cascade-state-write: exhausted attempts=10" >&2
echo "::error::Failed to push state after 10 attempts" >&2
exit 1
fi
# Real GitHub: write state through the Contents REST API. API commits are
# signed by GitHub (Verified) and, with a bypass-capable token, update the
# trunk even when a required status check protects it.
for attempt in 1 2 3 4 5 6 7 8 9 10; do
echo "cascade-state-write: attempt=$attempt/10"
git fetch origin "$BRANCH"
git reset --hard "origin/$BRANCH"
BASE_SHA=$(git rev-parse "origin/$BRANCH:$MANIFEST_FILE" 2>/dev/null || true)
apply_state_edits
if git diff --quiet "$MANIFEST_FILE"; then
echo "No state changes"
exit 0
fi
CONTENT_B64=$(base64 -w0 "$MANIFEST_FILE" 2>/dev/null || base64 "$MANIFEST_FILE" | tr -d '\n')
API_ARGS=("repos/${{ github.repository }}/contents/$MANIFEST_FILE" -X PUT
-f "message=chore: update state for $ENVIRONMENT [skip ci]"
-f "content=$CONTENT_B64"
-f "branch=$BRANCH"
-f "author[name]=github-actions[bot]"
-f "author[email]=github-actions[bot]@users.noreply.github.com"
-f "committer[name]=github-actions[bot]"
-f "committer[email]=github-actions[bot]@users.noreply.github.com")
if [[ -n "$BASE_SHA" ]]; then
API_ARGS+=(-f "sha=$BASE_SHA")
fi
if gh api "${API_ARGS[@]}" >/dev/null; then
echo "Pushed state via API on attempt $attempt"
echo "cascade-state-write: ok attempt=$attempt"
exit 0
fi
echo "State write attempt $attempt failed (likely concurrent run); retrying..." >&2
backoff=$(( 1 << (attempt - 1) ))
if [ "$backoff" -gt 8 ]; then backoff=8; fi
sleep $(( backoff + RANDOM % 2 ))
done
echo "cascade-state-write: exhausted attempts=10" >&2
echo "::error::Failed to write state via API after 10 attempts" >&2
exit 1
- name: Check for Failures
if: contains(fromJSON('["failure", "cancelled"]'), needs.build-build-secret.result) || contains(fromJSON('["failure", "cancelled"]'), needs.build-build-perms.result) || contains(fromJSON('["failure", "cancelled"]'), needs.build-build-oidc.result) || contains(fromJSON('["failure", "cancelled"]'), needs.build-build-needs.result) || contains(fromJSON('["failure", "cancelled"]'), needs.build-build-withheld.result) || contains(fromJSON('["failure", "cancelled"]'), needs.deploy-app.result)
run: |
echo "One or more critical callbacks failed or were cancelled"
exit 1