Skip to content

feat: scenario suite change (#121) #78

feat: scenario suite change (#121)

feat: scenario suite change (#121) #78

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:
- test
- staging
- prod
default: 'test'
dry_run:
description: 'Dry run mode'
type: boolean
default: false
concurrency:
group: orchestrate-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
actions: read
jobs:
setup:
name: Setup
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
run_build_base: ${{ steps.setup.outputs.run_build_base }}
run_build_app: ${{ steps.setup.outputs.run_build_app }}
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_base: ${{ steps.setup.outputs.base_build_base }}
base_build_app: ${{ steps.setup.outputs.base_build_app }}
base_deploy_app: ${{ steps.setup.outputs.base_deploy_app }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup CLI
uses: stablekernel/cascade/.github/actions/setup-cli@v0.2.0-rc.56
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: v0.2.0-rc.56
- name: Run Setup
id: setup
env:
ENVIRONMENT: ${{ github.event.inputs.environment || 'test' }}
run: |
cascade orchestrate setup \
--environment "$ENVIRONMENT" \
--config .github/manifest.yaml \
--gha-output
validate:
name: Validate (validate)
needs: [setup]
uses: ./.github/workflows/validate.yaml
with:
environment: ${{ github.event.inputs.environment || 'test' }}
sha: ${{ needs.setup.outputs.head_sha }}
validate-retry-1:
name: Validate (validate) - Retry 1
needs: [setup, validate]
if: needs.validate.result == 'failure'
uses: ./.github/workflows/validate.yaml
with:
environment: ${{ github.event.inputs.environment || 'test' }}
sha: ${{ needs.setup.outputs.head_sha }}
build-base:
name: Build (base)
needs: [setup, validate]
if: |
needs.setup.outputs.run_build_base == 'true' &&
needs.validate.result == 'success'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/build-base.yaml
with:
environment: ${{ github.event.inputs.environment || 'test' }}
sha: ${{ needs.setup.outputs.head_sha }}
secrets: inherit
build-base-retry-1:
name: Build (base) - Retry 1
needs: [setup, build-base]
if: needs.build-base.result == 'failure'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/build-base.yaml
with:
environment: ${{ github.event.inputs.environment || 'test' }}
sha: ${{ needs.setup.outputs.head_sha }}
secrets: inherit
build-base-retry-2:
name: Build (base) - Retry 2
needs: [setup, build-base-retry-1]
if: needs.build-base-retry-1.result == 'failure'
permissions:
contents: read
id-token: write
uses: ./.github/workflows/build-base.yaml
with:
environment: ${{ github.event.inputs.environment || 'test' }}
sha: ${{ needs.setup.outputs.head_sha }}
secrets: inherit
build-app:
name: Build (app)
needs: [setup, validate, build-base]
if: |
needs.setup.outputs.run_build_app == 'true' &&
needs.validate.result == 'success' &&
needs.build-base.result == 'success'
uses: ./.github/workflows/build-app.yaml
with:
environment: ${{ github.event.inputs.environment || 'test' }}
sha: ${{ needs.setup.outputs.head_sha }}
deploy-app:
name: Deploy (app)
needs: [setup, validate]
if: |
needs.setup.outputs.run_deploy_app == 'true' &&
needs.validate.result == 'success'
uses: ./.github/workflows/deploy-app.yaml
with:
environment: ${{ github.event.inputs.environment || 'test' }}
sha: ${{ needs.setup.outputs.head_sha }}
finalize:
name: Finalize
needs: [setup, validate, validate-retry-1, build-base, build-base-retry-1, build-base-retry-2, build-app, deploy-app]
if: always() && needs.setup.result == 'success'
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
app_artifact_id: ${{ needs.build-app.outputs.artifact_id }}
base_artifact_id: ${{ needs.build-base.outputs.artifact_id }}
steps:
- uses: actions/checkout@v6
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 "| Validate (validate) | ${{ needs.validate.result }} | abort |" >> "$GITHUB_STEP_SUMMARY"
echo "| Build (base) | ${{ needs.build-base.result }} | abort |" >> "$GITHUB_STEP_SUMMARY"
echo "| Build (app) | ${{ needs.build-app.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 [[ -n "${{ needs.build-app.outputs.artifact_id }}" ]]; then
if [[ "$HAS_OUTPUTS" == "false" ]]; then
echo "| Output | Value |" >> "$GITHUB_STEP_SUMMARY"
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
HAS_OUTPUTS=true
fi
echo "| app_artifact_id | ${{ needs.build-app.outputs.artifact_id }} |" >> "$GITHUB_STEP_SUMMARY"
fi
if [[ -n "${{ needs.build-base.outputs.artifact_id }}" ]]; then
if [[ "$HAS_OUTPUTS" == "false" ]]; then
echo "| Output | Value |" >> "$GITHUB_STEP_SUMMARY"
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
HAS_OUTPUTS=true
fi
echo "| base_artifact_id | ${{ needs.build-base.outputs.artifact_id }} |" >> "$GITHUB_STEP_SUMMARY"
fi
if [[ "$HAS_OUTPUTS" == "false" ]]; then
echo "_No outputs produced_" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Setup CLI
uses: stablekernel/cascade/.github/actions/setup-cli@v0.2.0-rc.56
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: v0.2.0-rc.56
- name: Generate Changelog
id: changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_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 || 'test' }}
sha: ${{ needs.setup.outputs.head_sha }}
changelog: ${{ steps.changelog.outputs.changelog }}
previous_tag: ${{ needs.setup.outputs.previous_tag }}
token: ${{ secrets.GITHUB_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 || 'test' }}
APP_RESULT: ${{ needs.deploy-app.result }}
BUILD_ARTIFACT_BASE: ${{ needs.build-base.outputs.artifact_id }}
BUILD_ARTIFACT_APP: ${{ needs.build-app.outputs.artifact_id }}
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/}"
# One or more callbacks declared auto_commits: true; re-read HEAD
# so state records the post-callback commit, not the triggering SHA.
HEAD_SHA="$(git rev-parse HEAD)"
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 [[ -n "$BUILD_ARTIFACT_BASE" ]]; then
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.builds.base.sha = \"$HEAD_SHA\"" "$MANIFEST_FILE"
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.builds.base.artifact_id = \"$BUILD_ARTIFACT_BASE\"" "$MANIFEST_FILE"
fi
if [[ -n "$BUILD_ARTIFACT_APP" ]]; then
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.builds.app.sha = \"$HEAD_SHA\"" "$MANIFEST_FILE"
yq eval -i ".$MANIFEST_KEY.state.$ENVIRONMENT.builds.app.artifact_id = \"$BUILD_ARTIFACT_APP\"" "$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; do
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"
exit 0
fi
echo "Push attempt $attempt rejected (likely concurrent run); retrying..." >&2
sleep $((RANDOM % 5 + 2))
done
echo "::error::Failed to push state after 5 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; do
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
CONTENT_B64=$(base64 -w0 "$MANIFEST_FILE" 2>/dev/null || base64 "$MANIFEST_FILE" | tr -d '\n')
CURRENT_SHA=$(gh api "repos/${{ github.repository }}/contents/$MANIFEST_FILE?ref=$BRANCH" --jq '.sha' 2>/dev/null || true)
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")
if [[ -n "$CURRENT_SHA" ]]; then
API_ARGS+=(-f "sha=$CURRENT_SHA")
fi
if gh api "${API_ARGS[@]}" >/dev/null; then
echo "Pushed state via API on attempt $attempt"
exit 0
fi
echo "State write attempt $attempt failed (likely concurrent run); retrying..." >&2
sleep $((RANDOM % 5 + 2))
done
echo "::error::Failed to write state via API after 5 attempts" >&2
exit 1
- name: Check for Failures
if: contains(fromJSON('["failure", "cancelled"]'), needs.validate.result) || contains(fromJSON('["failure", "cancelled"]'), needs.build-base.result) || contains(fromJSON('["failure", "cancelled"]'), needs.build-app.result) || contains(fromJSON('["failure", "cancelled"]'), needs.deploy-app.result)
run: |
echo "One or more critical callbacks failed or were cancelled"
exit 1