feat: scenario source change #61
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |