diff --git a/.controlplane/controlplane.yml b/.controlplane/controlplane.yml index 74dc1583..0d30b91b 100644 --- a/.controlplane/controlplane.yml +++ b/.controlplane/controlplane.yml @@ -52,9 +52,9 @@ apps: react-webpack-rails-tutorial-staging: <<: *common - # QA Apps are like Heroku review apps, but the use `prefix` so you can run a commmand like + # QA Apps are like Heroku review apps, but they use `prefix` so you can run a command like # this to create a QA app for the tutorial app. - # `cpflow setup gvc postgres redis rails -a qa-react-webpack-rails-tutorial-pr-1234` + # `cpflow setup gvc postgres redis rails -a qa-react-webpack-rails-tutorial-1234` qa-react-webpack-rails-tutorial: <<: *common # Order matters! diff --git a/.controlplane/docs/testing-cpflow-github-actions.md b/.controlplane/docs/testing-cpflow-github-actions.md index ffd37395..ee78e5f4 100644 --- a/.controlplane/docs/testing-cpflow-github-actions.md +++ b/.controlplane/docs/testing-cpflow-github-actions.md @@ -1,258 +1,74 @@ # Testing cpflow GitHub Actions Changes -Use this guide when changing generated `cpflow-*` GitHub Actions, updating the -`cpflow` generator version, or debugging review-app automation. - -## What To Test - -Test the flow in three layers: - -1. Local generated-file checks catch YAML, metadata, and lint problems before a PR. -2. GitHub workflow checks prove GitHub can load the workflow and run CI. -3. A real review-app deploy proves the default-branch trusted actions, GitHub - secrets, Docker build, and Control Plane deploy all work together. - -The third layer matters because the review-app workflow intentionally checks out -trusted workflow sources from the repository default branch before passing -Control Plane secrets to local composite actions. A PR branch can contain fixed -`.github/actions/*` files, but the deploy job still loads those local actions -from `master` until the fix is merged there. - -## How Upstream Code Is Pinned - -Generated `cpflow-*` workflows are thin wrappers around reusable workflows in -`shakacode/control-plane-flow`. GitHub reusable workflows cannot be loaded from -a Ruby gem; GitHub resolves them from a repository ref. Each wrapper therefore -has two upstream pins that must stay in sync: - -```yaml -uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-review-app.yml@ -with: - control_plane_flow_ref: -``` - -`uses: ...@` chooses the reusable workflow file. `control_plane_flow_ref` -chooses the same upstream checkout for shared composite actions and, when -`CPFLOW_VERSION` is unset, the source used to build and install the `cpflow` gem -inside the workflow. - -The stable release path is still gem-driven: - -1. Install or update to a released `cpflow` gem. -2. Run `cpflow generate-github-actions --staging-branch master`. -3. The generated wrappers use `v` as the upstream ref. - -That tag should point to the same source that produced the RubyGems release, so -the workflow code is locked to a release tag rather than a moving branch. Do not -pin production workflows to `main` or a feature branch. For unreleased testing, -pin to an immutable commit SHA, not a branch name, then regenerate or repin to -the release tag after the upstream release is published. - -`CPFLOW_VERSION` is separate from the GitHub workflow ref. If the repository -variable is set, the setup action runs `gem install cpflow -v `. If it -is unset, the setup action builds `cpflow` from the checked-out upstream ref. -For normal releases, either leave `CPFLOW_VERSION` unset while using the matching -`v` upstream tag, or set `CPFLOW_VERSION` to the same released gem -version without the leading `v`. - -This repo is currently pinned to an upstream commit SHA because the reusable -workflow change was tested before a new `cpflow` gem release existed. That is -safer than pinning a branch, but it should be treated as a temporary test pin -until the next upstream release tag is available. - -What is tied to the upstream repo: - -- The downstream workflow wrapper hardcodes `shakacode/control-plane-flow` in - `uses:`. -- The reusable workflow file comes from the ref after `@`. -- The reusable workflow checks out `control-plane-flow` at - `control_plane_flow_ref` to load shared composite actions. -- When `CPFLOW_VERSION` is empty, the setup action builds and installs the - `cpflow` gem from that checked-out repository ref. - -What is tied to RubyGems: - -- A released `cpflow` gem is the normal source used to generate downstream - workflow wrappers. -- The `CPFLOW_VERSION` repository variable is a runtime override that runs - `gem install cpflow -v ` inside workflows. - -For stable downstream automation, prefer the release path: generate from a -released gem and pin wrappers to the matching upstream release tag. For -pre-release validation, pin to a full commit SHA from the upstream PR, never a -moving branch. - -## Testing An Unmerged Upstream PR Downstream - -You can test an upstream `control-plane-flow` PR in this downstream app before -merging upstream, without publishing a gem. Use an immutable commit SHA from the -upstream PR branch: - -1. Push the upstream PR branch and copy its head commit SHA. -2. In a downstream test branch, pin every generated wrapper ref: - - ```sh - bin/pin-cpflow-github-ref - ``` - - The helper accepts release tags and full 40-character commit SHAs by default. - It rejects branch names such as `main` or `feature/foo`; use - `--allow-moving-ref` only for short-lived local experiments that will not be - committed. The resulting diff should replace both pins in each reusable - workflow call: - - ```yaml - uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-review-app.yml@ - with: - control_plane_flow_ref: - ``` - -3. Keep `CPFLOW_VERSION` unset unless you intentionally want to test a released - RubyGems version instead of building `cpflow` from the upstream PR SHA. -4. Run `bin/test-cpflow-github-flow`. -5. Open a downstream PR and trigger a real review app with a comment whose body - is exactly: - - ```text - +review-app-deploy - ``` - -6. Verify the deploy logs show the expected upstream commit SHA, the setup step - prints the expected `cpflow` version/source, and the review app URL returns - HTTP 200. -7. After the upstream PR merges and releases, regenerate or repin downstream to - the release tag instead of leaving the temporary commit SHA forever. - -This tests the real reusable workflow and shared composite actions from the -upstream PR. It avoids merging upstream blind while also avoiding a mutable -branch ref in downstream automation. +Generic reusable-workflow behavior belongs upstream in the +[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/main/docs/ci-automation.md). +Use this repo note only as the canary checklist for +`react-webpack-rails-tutorial`. ## Local Checks -After regenerating the flow, run these checks from the repository root. If -`cpflow` is installed as a gem, use `cpflow` directly: +After regenerating the generated `cpflow-*` wrappers, run: ```sh -bin/conductor-exec cpflow generate-github-actions --staging-branch master -bin/test-cpflow-github-flow +bin/conductor-exec bin/test-cpflow-github-flow ``` -When testing an unreleased upstream `control-plane-flow` checkout, replace -`cpflow` with that checkout's `bin/cpflow`: +When testing an unreleased upstream `control-plane-flow` checkout, pass that +checkout's `bin/cpflow`: ```sh -bin/conductor-exec ruby /path/to/control-plane-flow/bin/cpflow generate-github-actions --staging-branch master -bin/test-cpflow-github-flow ruby /path/to/control-plane-flow/bin/cpflow +bin/conductor-exec bin/test-cpflow-github-flow ruby /path/to/control-plane-flow/bin/cpflow ``` -Why the explicit description check exists: GitHub parses expression-like snippets -inside composite action metadata, including `description:` fields. Literal -examples such as `${{ vars.SOME_VALUE }}` can fail action loading before any -shell step starts. The wrapper runs `cpflow github-flow-readiness`, parses the -generated YAML, checks action input descriptions for literal GitHub expressions, -checks that every generated wrapper keeps `uses:` and `control_plane_flow_ref` -on the same upstream ref across all `cpflow-*` wrappers, checks that any -secret-inheriting reusable workflow passes `control_plane_flow_ref`, and runs -`actionlint -ignore 'SC2129' .github/workflows/cpflow-*.yml`. - -## PR Checks +## Testing An Upstream PR Downstream -Open a normal PR for the generated-file diff and wait for CI. The workflow PR -itself is useful for syntax and CI validation, but it does not fully prove -review-app deployment changes that live under `.github/actions/`. - -For top-level workflow edits, you can manually dispatch the PR branch workflow: +Use an immutable upstream commit SHA, not a branch: ```sh -gh workflow run cpflow-deploy-review-app.yml --ref -f pr_number= +bin/pin-cpflow-github-ref <40-character-control-plane-flow-commit-sha> +bin/conductor-exec bin/test-cpflow-github-flow ruby /path/to/control-plane-flow/bin/cpflow ``` -This loads the workflow file from ``, but the deploy workflow's -`Checkout trusted workflow sources` step still checks out `master` before using -local composite actions with secrets. Treat this as a partial smoke test, not as -proof that PR-branch composite action changes work. - -## Post-Merge Review-App Test +Leave `CPFLOW_VERSION` unset while testing a commit SHA. After the upstream gem +and tag ship, repin wrappers to the release tag, such as `v5.0.1`. -After the workflow PR merges to `master`, test a real review-app deployment: +## Review App Canary -1. Pick a same-repository PR to use as the canary. -2. If the review app does not exist yet, comment exactly `+review-app-deploy` on - that PR. -3. If a previous deploy run failed, rerun the failed deploy run after the - workflow PR is merged. -4. Confirm the deploy run checks out `master` at the merge commit in - `Checkout trusted workflow sources`. -5. Confirm `Setup environment` succeeds and prints the expected `cpflow` version. -6. Confirm `Check if review app exists`, `Build Docker image`, and - `Deploy to Control Plane` all run as expected. -7. Open the review-app URL from the PR comment or deployment status and verify - it returns HTTP 200. +1. Open or reuse a same-repository PR. +2. Comment exactly `+review-app-deploy`. +3. Confirm the deploy job checks out the expected upstream Control Plane Flow + source selected by the generated wrapper's `uses:` ref. +4. Confirm `Setup environment`, `Check if review app exists`, + `Build Docker image`, and `Deploy to Control Plane` all pass. +5. Open the review-app URL from the PR comment and verify it returns HTTP 200. -Use the generated app name from the workflow log: +Comment-triggered workflows run from the repository default branch. If you are +testing edits to a workflow file before merging, manually dispatch the PR branch +workflow: -```text -APP_NAME: ${REVIEW_APP_PREFIX}-${PR_NUMBER} +```sh +gh workflow run cpflow-deploy-review-app.yml --ref -f pr_number= ``` -This is a template from the workflow output, not a literal command to evaluate -unless those environment variables are already set. For this repo, verify the -actual `REVIEW_APP_PREFIX` repository variable before assuming the final app -name. - ## Troubleshooting Signals -### Composite action metadata fails before setup - -Error shape: - -```text -Unrecognized named-value: 'vars' -Failed to load ./.github/actions/cpflow-setup-environment/action.yml -``` - -Cause: GitHub parsed a literal expression inside composite action metadata, -usually an input description. Because trusted local actions come from `master`, -fix and merge the generated action metadata on `master`, then rerun the deploy. - -### Setup succeeds, then `cpflow exists` reports token format - -Error shape: +### Token Format Error ```text ERROR: Unknown API token format. Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable. ``` -Cause: the workflow can read `CPLN_TOKEN_STAGING`, but the value is not a valid -Control Plane service-account token for the installed Control Plane CLI. Rotate -the GitHub secret, then rerun the failed deploy job. - -### PR pushes do not create a new review app +The workflow can read `CPLN_TOKEN_STAGING`, but the secret value is not a valid +Control Plane service-account token. Rotate the GitHub secret and rerun the +deploy. -This is expected. Pushes redeploy only after the review app already exists. -Create the first review app by commenting exactly: +### No Deploy After Push -```text -+review-app-deploy -``` +Pushes redeploy only after the review app already exists. Create the first one +with an exact `+review-app-deploy` PR comment. -## Ways To Make This Easier +### No Visible Workflow Changes -- Extend `bin/pin-cpflow-github-ref` so it can also run - `bin/test-cpflow-github-flow`, open a downstream PR, and print or post the - exact `+review-app-deploy` command needed to start the canary deploy. -- Add CI coverage that runs `bin/test-cpflow-github-flow` on generated workflow - changes, so ref mismatches and action metadata parsing issues are caught - before review. -- Add a no-secret GitHub Actions smoke workflow that loads generated local - composite actions from the PR branch and fails fast on action metadata parsing. -- Extend `bin/test-cpflow-github-flow` as more local cpflow GitHub Actions - checks become worth standardizing. -- Add an early token sanity step after `Setup environment` so invalid - `CPLN_TOKEN_STAGING` and `CPLN_TOKEN_PRODUCTION` values fail with a named - "validate Control Plane token" step instead of surfacing later during - `cpflow exists`. -- Keep a tiny canary PR open for review-app workflow testing so post-merge - deploy verification does not depend on whichever feature PR happens to exist. -- Upstream the metadata-description check to `cpflow github-flow-readiness` so - downstream repos get the guard automatically. +Comment-triggered runs use workflow files from `master`. For PR-branch workflow +edits, use `workflow_dispatch` as shown above or merge first and test with a +real review-app deploy. diff --git a/.controlplane/readme.md b/.controlplane/readme.md index fff76eeb..67b794f6 100644 --- a/.controlplane/readme.md +++ b/.controlplane/readme.md @@ -19,6 +19,92 @@ In a real app, you would likely use persistent, external resources, such as AWS You can see the definition of Postgres and Redis in the `.controlplane/templates` directory. +## GitHub and Control Plane Setup + +This repo uses the generated `cpflow-*` GitHub Actions wrappers. Keep the +generic behavior documented upstream in the +[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/main/docs/ci-automation.md); +this section only lists the values that are specific to this app. + +### Review Apps and Staging + +For review apps, GitHub needs one repository secret: + +| Name | Value | +| --- | --- | +| `CPLN_TOKEN_STAGING` | Service-account token for `shakacode-open-source-examples-staging`. | + +No review-app repository variables are required for the standard path. The +workflow infers `qa-react-webpack-rails-tutorial` and +`shakacode-open-source-examples-staging` from `.controlplane/controlplane.yml`, +because that file has one app with `match_if_app_name_starts_with: true`. +`PRIMARY_WORKLOAD` also stays unset because the public workload is `rails`. + +For staging auto-deploys, also set these repository variables: + +| Name | Value | +| --- | --- | +| `CPLN_ORG_STAGING` | `shakacode-open-source-examples-staging` | +| `STAGING_APP_NAME` | `react-webpack-rails-tutorial-staging` | +| `STAGING_APP_BRANCH` | `master` | + +The matching Control Plane resources are: + +| Resource | Name | +| --- | --- | +| Review app prefix | `qa-react-webpack-rails-tutorial` | +| Review app secret dictionary | `qa-react-webpack-rails-tutorial-secrets` | +| Staging app | `react-webpack-rails-tutorial-staging` | +| Staging app secret dictionary | `react-webpack-rails-tutorial-staging-secrets` | + +### Production Promotion + +Production promotion is part of the default demo flow, but the production token +must be gated by a protected GitHub Environment named `production`: + +| Name | Where | Value | +| --- | --- | --- | +| `CPLN_TOKEN_PRODUCTION` | `production` Environment secret | Production Control Plane service-account token. | +| `CPLN_ORG_PRODUCTION` | `production` Environment variable | `shakacode-open-source-examples-production` | +| `PRODUCTION_APP_NAME` | `production` Environment variable | `react-webpack-rails-tutorial-production` | + +Protect the `production` environment with required reviewers, prevent +self-review, and consider disabling administrator bypass. Do not store +`CPLN_TOKEN_PRODUCTION` as a repository or organization secret. The generated +promotion wrapper does not use `secrets: inherit`; GitHub exposes the production +token only after the environment approval gate passes. + +The matching Control Plane resources are: + +| Resource | Name | +| --- | --- | +| Production app | `react-webpack-rails-tutorial-production` | +| Production app secret dictionary | `react-webpack-rails-tutorial-production-secrets` | + +All review, staging, and production secret dictionaries need these app runtime +secrets: + +- `SECRET_KEY_BASE` +- `RENDERER_PASSWORD` +- `REACT_ON_RAILS_PRO_LICENSE` + +Generate `SECRET_KEY_BASE` with `openssl rand -hex 64` and +`RENDERER_PASSWORD` with `openssl rand -hex 32`. For real production, prefer +managed Postgres and Redis services and update `DATABASE_URL` and `REDIS_URL` +accordingly. + +### Advanced Overrides + +Most repos should leave these unset. They exist so forks and clones can test +against their own Control Plane org, prefix, workload, or toolchain: + +- `CPLN_ORG_STAGING` +- `REVIEW_APP_PREFIX` +- `PRIMARY_WORKLOAD` +- `REVIEW_APP_DEPLOYING_ICON_URL` +- `CPLN_CLI_VERSION` +- `CPFLOW_VERSION` + ## Prerequisites 1. Ensure your [Control Plane](https://shakacode.controlplane.com) account is set up. @@ -388,11 +474,19 @@ The production promotion workflow checks that production has all environment variable names present in staging; it does not compare secret values, workload environment variables, or Control Plane secret references. -The repository variables and secrets must match the app names in -`.controlplane/controlplane.yml`. In particular, `REVIEW_APP_PREFIX` should -include the `-pr` suffix for this app, such as -`qa-react-webpack-rails-tutorial-pr`, so generated review apps are named -`qa-react-webpack-rails-tutorial-pr-1234`. +The GitHub settings and Control Plane resources must match the app names in +`.controlplane/controlplane.yml`. For the standard review-app path, leave +`REVIEW_APP_PREFIX` unset and let the workflow infer +`qa-react-webpack-rails-tutorial`; generated review apps are named +`qa-react-webpack-rails-tutorial-`. +If you have older review apps from the previous +`qa-react-webpack-rails-tutorial-pr-` naming, delete them manually +after this flow lands; cleanup targets the current prefix convention. To +inventory old apps, run: + +```sh +cpln gvc query --org shakacode-open-source-examples-staging -o yaml --prop name~qa-react-webpack-rails-tutorial-pr- +``` This allows teams to: - Preview changes in a production-like environment @@ -405,63 +499,27 @@ each review app getting its own resources as defined in the controlplane.yml configuration. -### Workflow for Developing GitHub Actions for Review Apps - -1. Create a PR with changes to the GitHub Actions workflow -2. Make edits to files such as `.github/actions/cpflow-build-docker-image/action.yml` or `.github/workflows/cpflow-deploy-review-app.yml` -3. Run a script like `ga .github && gc -m fixes && gp` to commit and push changes (ga = git add, gc = git commit, gp = git push) -4. Check the GitHub Actions tab in the PR to see the status of the workflow - -### Keeping Generated cpflow Workflows Updated - -Treat `.github/actions/cpflow-*` and `.github/workflows/cpflow-*` as generated -workflow files with project-specific settings layered on top. When `cpflow` -releases generator fixes or the upstream `control-plane-flow` repo changes the -GitHub Actions flow, update a project by regenerating the flow from the desired -`cpflow` version or branch, reviewing the diff, and keeping any local app names, -repository variables, secrets, and docs aligned with `.controlplane/controlplane.yml`. - -The generated workflows are intentionally thin wrappers around reusable -workflows in `shakacode/control-plane-flow`. GitHub loads reusable workflows from -a repository ref, not from the Ruby gem, so each wrapper has a `uses: ...@` -line plus a matching `control_plane_flow_ref: `. For stable releases, -generate these files from a released `cpflow` gem; the default ref is the -matching upstream release tag, `v`. Avoid `main` or feature-branch -refs in production. Use an immutable commit SHA only when testing unreleased -upstream changes, then move back to the release tag after the upstream release -is published. - -`CPFLOW_VERSION` is a runtime gem-install override. When it is unset, the setup -action builds `cpflow` from the checked-out upstream ref. When it is set, the -setup action installs that RubyGems version instead. For normal release pins, -either leave it unset while using the matching `v` workflow tag, or set -it to the same gem version without the leading `v`. - -To test unreleased upstream workflow changes before merging `control-plane-flow`, -pin a downstream PR to the upstream PR's full commit SHA in both `uses:` and -`control_plane_flow_ref`, leave `CPFLOW_VERSION` unset, and trigger a real review -app deploy. That tests the upstream reusable workflow, shared composite actions, -and source-built `cpflow` gem from the same immutable commit. After the upstream -change is released, regenerate or repin back to the matching release tag. -Use `bin/pin-cpflow-github-ref ` to update the generated wrapper refs -together. The helper accepts release tags and full commit SHAs by default; -`--allow-moving-ref` is only for short-lived local experiments. - -For this app, validate a regenerated flow with: +### Updating Generated cpflow Workflows + +Keep the reusable-workflow mechanics in the upstream +[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/main/docs/ci-automation.md). +For this repo, the update loop is: + +1. Generate from the desired `cpflow` release with `--staging-branch master`. +2. Keep generated refs on a release tag such as `v5.0.1`. Use a full upstream + commit SHA only for short-lived downstream testing of an unreleased upstream + PR, and leave `CPFLOW_VERSION` unset in that case. +3. Keep app names and GitHub settings aligned with `.controlplane/controlplane.yml`. +4. Validate locally: ```bash bin/conductor-exec ruby /path/to/control-plane-flow/bin/cpflow generate-github-actions --staging-branch master -bin/conductor-exec ruby /path/to/control-plane-flow/bin/cpflow github-flow-readiness -actionlint -ignore 'SC2129' .github/workflows/cpflow-*.yml -bin/conductor-exec bundle exec rubocop +bin/conductor-exec bin/test-cpflow-github-flow ruby /path/to/control-plane-flow/bin/cpflow ``` -Then open a normal PR and let GitHub Actions prove the generated review-app, -staging, lint, JS, and RSpec workflows before merging. For review-app workflow -changes, test both the local workflow syntax and a real deployment. GitHub runs -`issue_comment` workflows from the default branch, so a `+review-app-deploy` -comment on the PR does not fully exercise command changes that are only on the -PR branch. For top-level workflow edits, run the PR branch workflow explicitly: +Then open a normal PR, wait for GitHub Actions, and test a real review-app +deploy. Comment-triggered workflows run from `master`; for PR-branch workflow +edits, dispatch the workflow explicitly: ```bash gh workflow run cpflow-deploy-review-app.yml --ref -f pr_number= @@ -470,13 +528,5 @@ gh workflow run cpflow-deploy-review-app.yml --ref -f pr_number=`, but trusted local composite actions still come from the default branch before secrets are used. Treat it as a partial smoke test, then verify a real deploy after the workflow changes land -on `master`. - -After the workflow reports a review-app URL, verify the URL returns HTTP 200. -If a project needs to track generator changes automatically, use a scheduled -maintenance PR or Renovate-style workflow that bumps the `cpflow` version, -regenerates these files, and runs the same validation commands. - -For a fuller checklist, including the gotcha that review-app deploys load local -composite actions from `master` before using Control Plane secrets, see -[Testing cpflow GitHub Actions Changes](docs/testing-cpflow-github-actions.md). +on `master`. See the short +[testing checklist](docs/testing-cpflow-github-actions.md) for the canary steps. diff --git a/.controlplane/shakacode-team.md b/.controlplane/shakacode-team.md index bb864651..da70250a 100644 --- a/.controlplane/shakacode-team.md +++ b/.controlplane/shakacode-team.md @@ -6,10 +6,10 @@ Deployments are handled by Control Plane configuration in this repo and GitHub A ### Review Apps - Add a comment `+review-app-deploy` to any PR to deploy a review app -- The generated app name is `${REVIEW_APP_PREFIX}-${PR_NUMBER}`. Keep - `REVIEW_APP_PREFIX` set to `qa-react-webpack-rails-tutorial-pr` so review - apps use names like `qa-react-webpack-rails-tutorial-pr-1234`, matching the - prefix-backed config in `.controlplane/controlplane.yml`. +- Leave `REVIEW_APP_PREFIX` unset for the standard path. The workflow infers + `qa-react-webpack-rails-tutorial` from `.controlplane/controlplane.yml`, so + generated review apps use names like + `qa-react-webpack-rails-tutorial-1234`. - New pushes to a PR redeploy only after the review app already exists. - Add `+review-app-delete` to delete a review app manually; closing the PR also deletes it automatically. Use `+review-app-help` for the command reference. @@ -30,33 +30,45 @@ Deployments are handled by Control Plane configuration in this repo and GitHub A ### GitHub Repository Settings -Required repository secrets: +Required repository secret for review apps and staging: - `CPLN_TOKEN_STAGING` -- `CPLN_TOKEN_PRODUCTION` -Required repository variables: +Required repository variables for staging deploys: - `CPLN_ORG_STAGING=shakacode-open-source-examples-staging` -- `CPLN_ORG_PRODUCTION=shakacode-open-source-examples-production` - `STAGING_APP_NAME=react-webpack-rails-tutorial-staging` -- `PRODUCTION_APP_NAME=react-webpack-rails-tutorial-production` -- `REVIEW_APP_PREFIX=qa-react-webpack-rails-tutorial-pr` - `STAGING_APP_BRANCH=master` -- `PRIMARY_WORKLOAD=rails` -Optional repository settings: +Review apps infer `CPLN_ORG_STAGING`, `REVIEW_APP_PREFIX`, and +`PRIMARY_WORKLOAD` from `.controlplane/controlplane.yml` and workflow defaults, +so those values do not need to be set just to test review apps. Set them only +when testing a fork or clone against a different Control Plane org, review-app +prefix, or public workload. -- `DOCKER_BUILD_SSH_KEY`: secret for private SSH dependencies during Docker builds. -- `DOCKER_BUILD_EXTRA_ARGS`: newline-delimited Docker build tokens, such as `--build-arg=FOO=bar`. -- `DOCKER_BUILD_SSH_KNOWN_HOSTS`: custom `known_hosts` entries when SSH build hosts are not GitHub.com. -- `CPLN_CLI_VERSION`: pin a specific `@controlplane/cli` version; defaults to the generated action pin. -- `CPFLOW_VERSION`: optional runtime gem-install override. When unset, workflows build - `cpflow` from the checked-out upstream workflow ref. When set, use the RubyGems - version number without a leading `v`. -- `HEALTH_CHECK_ACCEPTED_STATUSES`: production promotion health statuses; defaults to `200 301 302`. -- `HEALTH_CHECK_RETRIES` / `HEALTH_CHECK_INTERVAL`: production health polling controls; defaults to `24` retries and `15` seconds. -- `ROLLBACK_READINESS_RETRIES` / `ROLLBACK_READINESS_INTERVAL`: post-rollback health polling controls; defaults to `24` retries and `15` seconds. +Production promotion uses a protected GitHub Environment named `production`: + +- Environment secret `CPLN_TOKEN_PRODUCTION` +- Environment variable `CPLN_ORG_PRODUCTION=shakacode-open-source-examples-production` +- Environment variable `PRODUCTION_APP_NAME=react-webpack-rails-tutorial-production` + +Protect the `production` environment with required reviewers, enable prevent +self-review, and consider disabling administrator bypass. Do not store +`CPLN_TOKEN_PRODUCTION` as a repository or organization secret. The caller +passes `production_environment: production`; the upstream reusable workflow runs +its production job in that environment, and GitHub injects the production token +only after approval. + +Generated caller workflows pass only the named secrets each upstream workflow +needs. They do not use `secrets: inherit`; `CPLN_TOKEN_PRODUCTION` is supplied +only by the protected `production` Environment after approval. + +Advanced optional settings are documented upstream in the +[`control-plane-flow` CI automation guide](https://github.com/shakacode/control-plane-flow/blob/main/docs/ci-automation.md). + +Current workflow wrappers are pinned to the upstream `control-plane-flow` +release tag `v5.0.1`. Keep release tags as the steady-state configuration; use +a full commit SHA only for short-lived upstream PR testing. If staging moves off `master`, update both `STAGING_APP_BRANCH` and the branch filter in `.github/workflows/cpflow-deploy-staging.yml`. @@ -64,22 +76,10 @@ filter in `.github/workflows/cpflow-deploy-staging.yml`. ### Keeping cpflow Automation Current When the upstream `control-plane-flow` repo changes the generated GitHub Actions -flow, regenerate the `cpflow-*` actions/workflows in this repo from the target -`cpflow` version or branch using `--staging-branch master`, review the diff, and -keep the repository variables above aligned with `.controlplane/controlplane.yml`. Validate with -`cpflow github-flow-readiness`, `actionlint .github/workflows/cpflow-*.yml`, and -the normal CI checks before merging. For review-app workflow changes, remember -that the deploy workflow checks out trusted local actions from `master` before -passing Control Plane secrets; PR-branch composite action changes are not fully -tested until they land on `master` and a real review-app deploy is rerun. - -Generated workflow wrappers point to upstream reusable workflows with -`uses: shakacode/control-plane-flow/...@` and pass the same -`control_plane_flow_ref`. For stable releases, `` should be the -`v` tag produced by the released gem. Do not use `main` or a -feature branch for production automation. A commit SHA is acceptable for -unreleased testing, but regenerate or repin to the release tag once the upstream -release exists. +flow, regenerate from the target `cpflow` version with `--staging-branch master`, +review the diff, and validate with `bin/test-cpflow-github-flow` plus the normal +CI checks. Stable automation should use release tags such as `v5.0.1`, not +`main` or a feature branch. See [readme.md](readme.md) and [Testing cpflow GitHub Actions Changes](docs/testing-cpflow-github-actions.md) diff --git a/.github/cpflow-help.md b/.github/cpflow-help.md index f608d4e5..c87e0200 100644 --- a/.github/cpflow-help.md +++ b/.github/cpflow-help.md @@ -1,74 +1,98 @@ -# Review app help +# Review App Commands -You asked for review app help. These commands are generated by [cpflow](https://github.com/shakacode/control-plane-flow). +These commands are generated by [cpflow](https://github.com/shakacode/control-plane-flow). +For full setup, version-pinning, and troubleshooting details, see the upstream +[CI automation guide](https://github.com/shakacode/control-plane-flow/blob/v5.0.1/docs/ci-automation.md). -## PR commands +## Pull Request Commands -`+review-app-deploy` -- Creates the review app if it does not exist -- Builds the PR commit image -- Deploys the image and comments with the review URL -- Comment body must be exactly `+review-app-deploy`, with no surrounding text or trailing spaces. A single trailing newline from GitHub's comment editor is accepted. Comments like `please +review-app-deploy now` or `+review-app-deploy ` (with a trailing space) silently no-op. +Comment with exactly one command, with no surrounding text or trailing spaces. +A single trailing newline from GitHub's comment editor is accepted. -`+review-app-delete` -- Deletes the review app when the PR is done -- This also runs automatically when the PR closes -- Comment body must be exactly `+review-app-delete`, with no surrounding text or trailing spaces. A single trailing newline from GitHub's comment editor is accepted. Same command-match rule as `+review-app-deploy`. +| Command | What it does | +| --- | --- | +| `+review-app-deploy` | Builds the PR image, creates the review app if needed, deploys, and comments with the review URL. | +| `+review-app-delete` | Deletes the review app. This also runs automatically when the PR closes. | +| `+review-app-help` | Posts this help message on the PR. | -`+review-app-help` -- Posts this message on the PR. -- Comment body must be exactly `+review-app-help`, with no surrounding text or trailing spaces. A single trailing newline from GitHub's comment editor is accepted. Same command-match rule as `+review-app-deploy`. +## Standard Setup -## Workflow behavior +For the normal generated review-app path, GitHub needs one repository secret: -- Review apps are opt-in and created with `+review-app-deploy` -- New commits redeploy existing review apps automatically -- Pushes to the staging branch deploy staging automatically -- Promotion to production is manual via the Actions tab -- A nightly workflow removes stale review apps +| Name | Where | Notes | +| --- | --- | --- | +| `CPLN_TOKEN_STAGING` | Repository secret | Control Plane service-account token for the staging/review org. | -
-Advanced: GitHub Actions secrets and variables +No repository variables are required for the standard review-app path when +`.controlplane/controlplane.yml` has exactly one review app entry with +`match_if_app_name_starts_with: true`. cpflow infers the review-app prefix and +staging org from that config. -### GitHub Actions secrets +Optional overrides exist for forks, clones, and unusual apps: -| Name | Required | Notes | -| --- | --- | --- | -| `CPLN_TOKEN_STAGING` | yes | Service-account token scoped to the staging Control Plane org on controlplane.com. | -| `CPLN_TOKEN_PRODUCTION` | yes (for promote) | Service-account token scoped to the production Control Plane org on controlplane.com. | -| `DOCKER_BUILD_SSH_KEY` | optional | Private SSH key used when Docker builds fetch private deps via `RUN --mount=type=ssh`. | +| Name | Notes | +| --- | --- | +| `CPLN_ORG_STAGING` | Override the staging/review Control Plane org inferred from `controlplane.yml`. | +| `REVIEW_APP_PREFIX` | Override the review-app prefix inferred from `controlplane.yml`. | +| `PRIMARY_WORKLOAD` | Public workload used for review URLs and health checks; defaults to `rails`. | + +## Staging And Production -### GitHub Actions variables +Staging deploys use the same `CPLN_TOKEN_STAGING` secret plus `STAGING_APP_NAME`. -| Name | Required | Notes | +Production promotion is part of the generated flow, but keep it protected: + +| Name | Where | Notes | | --- | --- | --- | -| `CPLN_ORG_STAGING` | yes | Control Plane org on controlplane.com for staging and review apps. | -| `CPLN_ORG_PRODUCTION` | yes (for promote) | Control Plane org on controlplane.com for production. | -| `STAGING_APP_NAME` | yes | App name in `controlplane.yml` used as the staging deploy target. | -| `PRODUCTION_APP_NAME` | yes (for promote) | App name in `controlplane.yml` used as the production deploy target. | -| `REVIEW_APP_PREFIX` | yes | Prefix for per-PR review app names (e.g. `review-app`). | -| `REVIEW_APP_DEPLOYING_ICON_URL` | optional | Custom image URL for the animated deploying icon in review-app PR comments. Set to `none` to use the text fallback icon. | -| `STAGING_APP_BRANCH` | optional | Custom staging branch. Custom branches must also appear in `cpflow-deploy-staging.yml`'s push filter. | -| `PRIMARY_WORKLOAD` | optional | Workload polled for health and rollback (defaults to `rails`). | -| `DOCKER_BUILD_EXTRA_ARGS` | optional | Newline-delimited extra docker build tokens (e.g. `--build-arg=FOO=bar`). | -| `DOCKER_BUILD_SSH_KNOWN_HOSTS` | optional | SSH known_hosts entries when SSH build hosts are not GitHub.com. | -| `HEALTH_CHECK_ACCEPTED_STATUSES` | optional | Space-separated HTTP statuses considered healthy on promote (default `200 301 302`). | -| `CPLN_CLI_VERSION` | optional | Pin a specific `@controlplane/cli` version; falls back to the action default when unset. | -| `CPFLOW_VERSION` | optional | Runtime gem-install override. When unset, cpflow is built from the pinned upstream workflow ref. When set, use the RubyGems version without a leading `v`. | - -
- -
-Advanced: testing changes to generated workflows - -When iterating on the generated workflow YAML on a PR branch, comment-triggered runs (`+review-app-deploy`, `+review-app-delete`, `+review-app-help`) execute the workflow code from the repository's default branch — not your PR branch. To exercise the PR-branch workflow code before merging, dispatch the workflow manually with `gh`: +| `CPLN_TOKEN_PRODUCTION` | `production` GitHub Environment secret | Do not store this as a repository or organization secret. | +| `CPLN_ORG_PRODUCTION` | Prefer `production` Environment variable | Production Control Plane org. | +| `PRODUCTION_APP_NAME` | Prefer `production` Environment variable | Production app name from `controlplane.yml`. | + +Configure the `production` GitHub Environment with required reviewers and +prevent self-review. The generated promotion wrapper passes only the staging +token from repository secrets; GitHub injects `CPLN_TOKEN_PRODUCTION` only after +the environment approval gate passes. + +## Version Locking + +Generated wrappers pin Control Plane Flow once with the reusable workflow +`uses:` ref, for example `@v5.0.1`. For stable releases, this ref should be a +release tag. The upstream reusable workflow automatically +loads its matching shared actions from GitHub's workflow context, so downstream +wrappers should not pass a duplicate Control Plane Flow ref input. If your +generated wrappers still include a `with:` block whose only purpose is to repeat +the same ref, regenerate them with a newer `cpflow`. + +Leave `CPFLOW_VERSION` unset so the workflow builds cpflow from the same +checked-out upstream source. If you set `CPFLOW_VERSION`, it must match the +release tag, for example `CPFLOW_VERSION=5.0.1` with a wrapper pinned to +`uses: ...@v5.0.1`. + +Do not leave downstream apps pinned to a moving branch such as `main`. For a +short-lived test of an unreleased upstream PR, pin to a full 40-character commit +SHA and leave `CPFLOW_VERSION` unset: ```sh -gh workflow run cpflow-deploy-review-app.yml --ref -f pr_number= -gh workflow run cpflow-delete-review-app.yml --ref -f pr_number= -gh workflow run cpflow-help-command.yml --ref -f pr_number= +bin/pin-cpflow-github-ref <40-character-control-plane-flow-commit-sha> +bin/test-cpflow-github-flow ruby /path/to/control-plane-flow/bin/cpflow ``` -`workflow_dispatch` runs use the workflow file from the `--ref` you pass, so this is the supported way to test PR-branch workflow edits before merge. After merge, comment triggers go back to running the default-branch workflow code as usual. - -
+## Advanced Variables + +Most apps do not need these: + +| Name | Notes | +| --- | --- | +| `DOCKER_BUILD_EXTRA_ARGS` | Newline-delimited extra Docker build tokens. | +| `DOCKER_BUILD_SSH_KEY` | Private SSH key for Docker builds that fetch private dependencies. | +| `DOCKER_BUILD_SSH_KNOWN_HOSTS` | SSH known_hosts entries when SSH build hosts are not GitHub.com. | +| `REVIEW_APP_DEPLOYING_ICON_URL` | Cosmetic custom image URL for the animated deploying icon. Set to `none` to use the text fallback icon. | +| `STAGING_APP_BRANCH` | Custom staging branch. The branch must also appear in `cpflow-deploy-staging.yml`'s push filter. | +| `CPLN_CLI_VERSION` | Pin a specific `@controlplane/cli` version; normally leave unset. | + +The PR-open help workflow posts a short command reference whenever the generated +wrapper exists. That is intentional for configured demo repos. Forks or clones +that copy the workflow before configuring Control Plane can remove +`.github/workflows/cpflow-review-app-help.yml` or uncomment and adapt the +wrapper-level `if:` guard shown in that file, for example +`vars.REVIEW_APP_PREFIX != '' || vars.CPLN_ORG_STAGING != ''`. diff --git a/.github/testing-github-actions.md b/.github/testing-github-actions.md index 0366c641..d6b65770 100644 --- a/.github/testing-github-actions.md +++ b/.github/testing-github-actions.md @@ -25,8 +25,9 @@ guide: 4. For comment-triggered review-app commands, test a real `+review-app-deploy` after the trusted default-branch wrapper points at the code under test. 5. When testing unreleased upstream `control-plane-flow` changes downstream, pin - both the reusable workflow `uses:` ref and `control_plane_flow_ref` to the - same upstream commit SHA. + the generated reusable workflow `uses:` refs to the same upstream commit SHA. + Newer upstream workflows load their matching shared actions automatically; do + not add a duplicate ref input to downstream wrappers. Avoid testing production automation against moving branch refs such as `main` or a feature branch. Use release tags for normal operation and full commit SHAs for diff --git a/.github/workflows/cpflow-cleanup-stale-review-apps.yml b/.github/workflows/cpflow-cleanup-stale-review-apps.yml index 167f3f37..e2ca5915 100644 --- a/.github/workflows/cpflow-cleanup-stale-review-apps.yml +++ b/.github/workflows/cpflow-cleanup-stale-review-apps.yml @@ -10,7 +10,8 @@ permissions: jobs: cleanup: - uses: shakacode/control-plane-flow/.github/workflows/cpflow-cleanup-stale-review-apps.yml@3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 - with: - control_plane_flow_ref: 3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 - secrets: inherit + # Cleanup targets the current inferred review-app prefix. If you changed + # naming conventions, manually delete review apps under the old prefix. + uses: shakacode/control-plane-flow/.github/workflows/cpflow-cleanup-stale-review-apps.yml@v5.0.1 + secrets: + CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }} diff --git a/.github/workflows/cpflow-delete-review-app.yml b/.github/workflows/cpflow-delete-review-app.yml index 6c9cbf5e..e0989e81 100644 --- a/.github/workflows/cpflow-delete-review-app.yml +++ b/.github/workflows/cpflow-delete-review-app.yml @@ -19,6 +19,9 @@ permissions: jobs: delete-review-app: + # pull_request_target is intentional: fork PR-close events need access to + # staging secrets to delete review apps and update PR comments. The upstream + # reusable workflow checks out trusted base-branch action code, not fork code. if: | (github.event_name == 'issue_comment' && github.event.issue.pull_request && @@ -26,7 +29,8 @@ jobs: contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || (github.event_name == 'pull_request_target' && github.event.action == 'closed') || github.event_name == 'workflow_dispatch' - uses: shakacode/control-plane-flow/.github/workflows/cpflow-delete-review-app.yml@3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 - with: - control_plane_flow_ref: 3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 - secrets: inherit + # This `if:` mirrors the upstream job guard to avoid a billable workflow_call + # when the event does not match. Keep both conditions in sync. + uses: shakacode/control-plane-flow/.github/workflows/cpflow-delete-review-app.yml@v5.0.1 + secrets: + CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }} diff --git a/.github/workflows/cpflow-deploy-review-app.yml b/.github/workflows/cpflow-deploy-review-app.yml index 0693116d..15800591 100644 --- a/.github/workflows/cpflow-deploy-review-app.yml +++ b/.github/workflows/cpflow-deploy-review-app.yml @@ -30,7 +30,7 @@ jobs: github.event.issue.pull_request && contains(fromJson('["+review-app-deploy","+review-app-deploy\n","+review-app-deploy\r\n"]'), github.event.comment.body) && contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) - uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-review-app.yml@3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 - with: - control_plane_flow_ref: 3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 - secrets: inherit + uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-review-app.yml@v5.0.1 + secrets: + CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }} + DOCKER_BUILD_SSH_KEY: ${{ secrets.DOCKER_BUILD_SSH_KEY }} diff --git a/.github/workflows/cpflow-deploy-staging.yml b/.github/workflows/cpflow-deploy-staging.yml index 32cad245..ba603b18 100644 --- a/.github/workflows/cpflow-deploy-staging.yml +++ b/.github/workflows/cpflow-deploy-staging.yml @@ -8,7 +8,7 @@ on: # deploy branches unless `cpflow generate-github-actions --staging-branch BRANCH` # was used. If STAGING_APP_BRANCH is later changed in repository variables, keep # this list in sync so pushes to that branch actually trigger the workflow. - branches: ["main", "master"] + branches: ["master"] workflow_dispatch: permissions: @@ -16,8 +16,9 @@ permissions: jobs: deploy-staging: - uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-staging.yml@3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 + uses: shakacode/control-plane-flow/.github/workflows/cpflow-deploy-staging.yml@v5.0.1 with: - control_plane_flow_ref: 3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 - staging_app_branch_default: "" - secrets: inherit + staging_app_branch_default: "master" + secrets: + CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }} + DOCKER_BUILD_SSH_KEY: ${{ secrets.DOCKER_BUILD_SSH_KEY }} diff --git a/.github/workflows/cpflow-help-command.yml b/.github/workflows/cpflow-help-command.yml index 2c8c5b9d..e1b19676 100644 --- a/.github/workflows/cpflow-help-command.yml +++ b/.github/workflows/cpflow-help-command.yml @@ -23,4 +23,4 @@ jobs: contains(fromJson('["+review-app-help","+review-app-help\n","+review-app-help\r\n"]'), github.event.comment.body) && contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || github.event_name == 'workflow_dispatch' - uses: shakacode/control-plane-flow/.github/workflows/cpflow-help-command.yml@3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 + uses: shakacode/control-plane-flow/.github/workflows/cpflow-help-command.yml@v5.0.1 diff --git a/.github/workflows/cpflow-promote-staging-to-production.yml b/.github/workflows/cpflow-promote-staging-to-production.yml index 8efeb249..83ad4dcf 100644 --- a/.github/workflows/cpflow-promote-staging-to-production.yml +++ b/.github/workflows/cpflow-promote-staging-to-production.yml @@ -9,12 +9,22 @@ on: type: string permissions: + # The upstream reusable workflow's create-github-release job needs + # contents: write, and callers must grant the union of callee permissions. contents: write jobs: promote-to-production: if: github.event.inputs.confirm_promotion == 'promote' - uses: shakacode/control-plane-flow/.github/workflows/cpflow-promote-staging-to-production.yml@3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 + uses: shakacode/control-plane-flow/.github/workflows/cpflow-promote-staging-to-production.yml@v5.0.1 with: - control_plane_flow_ref: 3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 - secrets: inherit + # Keep CPLN_TOKEN_PRODUCTION as a secret on this protected GitHub + # Environment. The caller passes the environment name, the upstream + # reusable workflow runs its production job in that environment, and + # GitHub exposes environment secrets only after required reviewers approve. + production_environment: production + # Only pass the staging token explicitly. CPLN_TOKEN_PRODUCTION must live on + # the protected production Environment, where GitHub exposes it only after + # the required reviewers approve this job. + secrets: + CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }} diff --git a/.github/workflows/cpflow-review-app-help.yml b/.github/workflows/cpflow-review-app-help.yml index 8a25e25d..15fb171a 100644 --- a/.github/workflows/cpflow-review-app-help.yml +++ b/.github/workflows/cpflow-review-app-help.yml @@ -14,5 +14,8 @@ permissions: jobs: show-help: - if: vars.REVIEW_APP_PREFIX != '' - uses: shakacode/control-plane-flow/.github/workflows/cpflow-review-app-help.yml@3e0e7e1f0a35c15648cc9254b573b058d77ca8c4 + # This is intentionally unconditional: committing this wrapper opts the repo in + # to PR-open help. Remove it, or uncomment and adapt this guard, if forks or + # clones should stay quiet until Control Plane is configured: + # if: vars.REVIEW_APP_PREFIX != '' || vars.CPLN_ORG_STAGING != '' + uses: shakacode/control-plane-flow/.github/workflows/cpflow-review-app-help.yml@v5.0.1 diff --git a/bin/pin-cpflow-github-ref b/bin/pin-cpflow-github-ref index 41f643f3..da3cb4a1 100755 --- a/bin/pin-cpflow-github-ref +++ b/bin/pin-cpflow-github-ref @@ -8,6 +8,9 @@ USAGE = <<~USAGE Use a release tag for normal operation, e.g. v5.0.0. Use a full 40-character commit SHA for temporary unreleased upstream testing. + This only updates generated reusable-workflow `uses:` refs. The called + workflows load their own matching shared actions from that same workflow + commit automatically. Regenerate from the cpflow gem when templates changed. Use --allow-moving-ref only for short-lived local branch/ref experiments. USAGE @@ -32,7 +35,7 @@ end ref = positional.first allow_moving_ref = options.include?("--allow-moving-ref") -unless ref.match?(/\A[0-9A-Za-z._\/-]+\z/) +unless ref.match?(%r{\A[0-9A-Za-z._/-]+\z}) warn "Ref contains unsupported characters: #{ref.inspect}" exit 1 end @@ -44,7 +47,7 @@ unless ref.match?(FULL_COMMIT_SHA) || ref.match?(RELEASE_TAG) || allow_moving_re end root = Pathname.new(__dir__).join("..").expand_path -workflow_paths = Dir[root.join(".github/workflows/cpflow-*.yml")].sort +workflow_paths = Dir[root.join(".github/workflows/cpflow-*.yml")] if workflow_paths.empty? warn "No cpflow workflow wrappers found." @@ -55,8 +58,7 @@ changed = [] workflow_paths.each do |path| text = File.read(path) updated = text - .gsub(%r{(uses:\s+shakacode/control-plane-flow/\.github/workflows/[^@\s]+@)[^\s]+}, "\\1#{ref}") - .gsub(/(\bcontrol_plane_flow_ref:\s*)\S+/, "\\1#{ref}") + .gsub(%r{(uses:\s+shakacode/control-plane-flow/\.github/workflows/[^@\s]+@)[^\s]+}, "\\1#{ref}") next if updated == text diff --git a/bin/test-cpflow-github-flow b/bin/test-cpflow-github-flow index 91c8ed46..e7eafa18 100755 --- a/bin/test-cpflow-github-flow +++ b/bin/test-cpflow-github-flow @@ -10,10 +10,10 @@ if [[ $# -gt 0 ]]; then fi echo "==> cpflow github-flow-readiness" -bin/conductor-exec "${cpflow_cmd[@]}" github-flow-readiness +"${cpflow_cmd[@]}" github-flow-readiness echo "==> parse generated GitHub Actions YAML" -bin/conductor-exec ruby <<'RUBY' +ruby <<'RUBY' require "yaml" Dir[".github/actions/**/action.yml", ".github/workflows/*.yml"].sort.each do |path| @@ -23,7 +23,7 @@ end RUBY echo "==> check composite action input descriptions" -bin/conductor-exec ruby <<'RUBY' +ruby <<'RUBY' require "yaml" bad = [] @@ -39,10 +39,11 @@ puts "no action metadata descriptions contain GitHub expressions" RUBY echo "==> check cpflow reusable workflow refs" -bin/conductor-exec ruby <<'RUBY' +ruby <<'RUBY' require "yaml" CONTROL_PLANE_FLOW_WORKFLOW = %r{\Ashakacode/control-plane-flow/\.github/workflows/[^@\s]+@([^\s]+)\z} +PROMOTE_WORKFLOW = %r{\Ashakacode/control-plane-flow/\.github/workflows/cpflow-promote-staging-to-production\.yml@[^\s]+\z} refs = Hash.new { |hash, key| hash[key] = [] } @@ -55,21 +56,32 @@ Dir[".github/workflows/cpflow-*.yml"].sort.each do |path| input_ref = with["control_plane_flow_ref"] uses_match = job["uses"].to_s.match(CONTROL_PLANE_FLOW_WORKFLOW) - unless uses_match - abort "#{path}:#{job_name} has control_plane_flow_ref but no control-plane-flow reusable workflow" if input_ref + if job["secrets"] == "inherit" + abort "#{path}:#{job_name} uses secrets: inherit; pass only the named secrets the upstream workflow needs" + end + + if input_ref + abort "#{path}:#{job_name} passes obsolete control_plane_flow_ref. Remove it; the upstream reusable workflow checks out its own source from GitHub's job.workflow_* context." + end + unless uses_match next end uses_ref = uses_match[1] refs[uses_ref] << "#{path}:#{job_name}" - if input_ref - refs[input_ref] << "#{path}:#{job_name}" - abort "#{path}:#{job_name} mismatched cpflow refs: #{uses_ref}, #{input_ref}" if uses_ref != input_ref - elsif job.key?("secrets") - abort "#{path}:#{job_name} inherits secrets but is missing control_plane_flow_ref for #{uses_ref}" + if job["uses"].to_s.match?(PROMOTE_WORKFLOW) + if with["production_environment"].to_s.strip.empty? + abort "#{path}:#{job_name} must set production_environment so GitHub can expose production environment secrets after approval" + end + + secrets = job["secrets"].is_a?(Hash) ? job["secrets"] : {} + if secrets.key?("CPLN_TOKEN_PRODUCTION") + abort "#{path}:#{job_name} must not pass CPLN_TOKEN_PRODUCTION as a caller secret; store it on the protected production environment" + end end + end end @@ -86,4 +98,7 @@ end RUBY echo "==> actionlint" -actionlint -ignore "SC2129" .github/workflows/cpflow-*.yml +actionlint \ + -ignore "SC2129" \ + -ignore 'property "workflow_(ref|sha|repository|file_path)" is not defined in object type' \ + .github/workflows/cpflow-*.yml