Skip to content

Commit 2db67ab

Browse files
committed
Move generated GitHub Actions logic upstream
1 parent 02d92ad commit 2db67ab

32 files changed

Lines changed: 2186 additions & 1550 deletions

lib/github_flow_templates/.github/actions/cpflow-build-docker-image/action.yml renamed to .github/actions/cpflow-build-docker-image/action.yml

File renamed without changes.

lib/github_flow_templates/.github/actions/cpflow-delete-control-plane-app/action.yml renamed to .github/actions/cpflow-delete-control-plane-app/action.yml

File renamed without changes.

lib/github_flow_templates/.github/actions/cpflow-delete-control-plane-app/delete-app.sh renamed to .github/actions/cpflow-delete-control-plane-app/delete-app.sh

File renamed without changes.

lib/github_flow_templates/.github/actions/cpflow-detect-release-phase/action.yml renamed to .github/actions/cpflow-detect-release-phase/action.yml

File renamed without changes.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
name: Setup Control Plane Environment
2+
description: Sets up Ruby, installs the Control Plane CLI and cpflow, and configures a default profile
3+
4+
inputs:
5+
token:
6+
description: Control Plane token
7+
required: true
8+
org:
9+
description: Control Plane organization
10+
required: true
11+
ruby_version:
12+
description: >-
13+
Ruby version used for cpflow. When empty (the default), ruby/setup-ruby auto-detects
14+
from .ruby-version, .tool-versions, or the Gemfile.
15+
required: false
16+
default: ""
17+
working_directory:
18+
description: Directory where ruby/setup-ruby should detect .ruby-version, .tool-versions, or the Gemfile.
19+
required: false
20+
default: "."
21+
# GitHub parses double-brace expression snippets inside action metadata (including
22+
# `description:`) while loading the composite action, and the `vars` context is not
23+
# available in that phase. Keep these descriptions in plain prose - reference repo
24+
# variables by NAME only, never with literal GitHub Actions expression syntax.
25+
cpln_cli_version:
26+
description: >-
27+
@controlplane/cli version. Empty string falls back to the action's pinned default,
28+
so callers can wire this input to the CPLN_CLI_VERSION repository variable
29+
unconditionally.
30+
required: false
31+
default: ""
32+
cpflow_version:
33+
description: >-
34+
cpflow gem version to install from RubyGems. Empty string installs cpflow from
35+
the checked-out control-plane-flow repository, so callers can test a GitHub ref
36+
without publishing a release.
37+
required: false
38+
default: ""
39+
40+
runs:
41+
using: composite
42+
# ruby/setup-ruby tracks Ruby/toolcache updates on a major tag. The privileged
43+
# reusable workflows pin GitHub-owned actions to immutable SHAs.
44+
steps:
45+
- name: Set up Ruby
46+
# ruby/setup-ruby intentionally tracks the v1 tag so Ruby/toolcache updates
47+
# roll out automatically; Dependabot/Renovate can manage this non-GitHub action.
48+
uses: ruby/setup-ruby@v1
49+
with:
50+
ruby-version: ${{ inputs.ruby_version }}
51+
working-directory: ${{ inputs.working_directory }}
52+
53+
- name: Install Control Plane CLI and cpflow gem
54+
shell: bash
55+
env:
56+
CPLN_CLI_VERSION: ${{ inputs.cpln_cli_version }}
57+
CPFLOW_VERSION: ${{ inputs.cpflow_version }}
58+
CPFLOW_SOURCE_DIR: ${{ github.action_path }}/../../..
59+
run: |
60+
set -euo pipefail
61+
62+
# Bump this default when a new Control Plane CLI release should roll out by default.
63+
# Override per-repo by setting the `CPLN_CLI_VERSION` repo variable.
64+
default_cpln_cli_version="3.10.2"
65+
66+
CPLN_CLI_VERSION="${CPLN_CLI_VERSION:-${default_cpln_cli_version}}"
67+
68+
npm_global_prefix="${HOME}/.npm-global"
69+
mkdir -p "${npm_global_prefix}"
70+
echo "${npm_global_prefix}/bin" >> "$GITHUB_PATH"
71+
export PATH="${npm_global_prefix}/bin:${PATH}"
72+
73+
npm install --global --prefix "${npm_global_prefix}" "@controlplane/cli@${CPLN_CLI_VERSION}"
74+
cpln --version
75+
76+
if [[ -n "${CPFLOW_VERSION}" ]]; then
77+
gem install cpflow -v "${CPFLOW_VERSION}" --no-document
78+
else
79+
cpflow_source_dir="$(cd "${CPFLOW_SOURCE_DIR}" && pwd)"
80+
if [[ ! -f "${cpflow_source_dir}/cpflow.gemspec" ]]; then
81+
echo "::error::CPFLOW_SOURCE_DIR (${cpflow_source_dir}) does not contain cpflow.gemspec" >&2
82+
exit 1
83+
fi
84+
85+
cpflow_gem="$(mktemp -t cpflow-XXXXXX.gem)"
86+
trap 'rm -f "${cpflow_gem}"' EXIT
87+
(
88+
cd "${cpflow_source_dir}"
89+
gem build cpflow.gemspec --output "${cpflow_gem}"
90+
)
91+
gem install "${cpflow_gem}" --no-document
92+
fi
93+
94+
cpflow --version
95+
96+
- name: Setup Control Plane profile and registry login
97+
shell: bash
98+
env:
99+
# Pass the token via CPLN_TOKEN so cpln picks it up from the environment
100+
# rather than `--token`, which would leak it into /proc/<pid>/cmdline and ps output.
101+
CPLN_TOKEN: ${{ inputs.token }}
102+
ORG: ${{ inputs.org }}
103+
run: |
104+
set -euo pipefail
105+
106+
if [[ -z "$CPLN_TOKEN" ]]; then
107+
echo "Error: Control Plane token not provided" >&2
108+
exit 1
109+
fi
110+
111+
if [[ -z "$ORG" ]]; then
112+
echo "Error: Control Plane organization not provided" >&2
113+
exit 1
114+
fi
115+
116+
# `cpln profile update` lists `create` as an alias (cpln profile --help) and is
117+
# idempotent: it creates the profile if missing and updates it otherwise. Calling
118+
# update directly avoids parsing the CLI's "already exists" English error text,
119+
# which would silently swallow a real failure if the wording ever changed.
120+
cpln profile update default --org "$ORG"
121+
cpln image docker-login --org "$ORG"
122+
123+
# Keep the token available to later cpflow/cpln steps without passing it
124+
# on the command line. GitHub masks secret values in logs, and the env file
125+
# itself is not echoed.
126+
delim="CPLN_TOKEN_DELIM_$(openssl rand -hex 8)"
127+
{
128+
echo "CPLN_TOKEN<<${delim}"
129+
echo "${CPLN_TOKEN}"
130+
echo "${delim}"
131+
} >> "$GITHUB_ENV"

lib/github_flow_templates/.github/actions/cpflow-validate-config/action.yml renamed to .github/actions/cpflow-validate-config/action.yml

File renamed without changes.

lib/github_flow_templates/.github/actions/cpflow-wait-for-health/action.yml renamed to .github/actions/cpflow-wait-for-health/action.yml

File renamed without changes.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Cleanup Stale Review Apps
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
control_plane_flow_ref:
7+
description: Git ref used to load shared cpflow composite actions.
8+
required: false
9+
type: string
10+
default: main
11+
12+
permissions:
13+
contents: read
14+
15+
concurrency:
16+
# Single global group: only one cleanup sweep at a time. Independent of review-app
17+
# deploy/delete groups (different keys), so cleanup will not block per-PR work.
18+
group: cpflow-cleanup-stale-review-apps
19+
# A cancelled `cpflow cleanup-stale-apps` can leave half-deleted review apps; let
20+
# the in-flight run finish before the next scheduled tick begins.
21+
cancel-in-progress: false
22+
23+
jobs:
24+
cleanup:
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 30
27+
steps:
28+
- name: Checkout control-plane-flow actions
29+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
30+
with:
31+
repository: shakacode/control-plane-flow
32+
ref: ${{ inputs.control_plane_flow_ref }}
33+
path: .cpflow
34+
persist-credentials: false
35+
36+
- name: Validate required secrets and variables
37+
uses: ./.cpflow/.github/actions/cpflow-validate-config
38+
env:
39+
CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
40+
CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
41+
REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
42+
with:
43+
required: |
44+
secret:CPLN_TOKEN_STAGING
45+
variable:CPLN_ORG_STAGING
46+
variable:REVIEW_APP_PREFIX
47+
48+
- name: Setup environment
49+
uses: ./.cpflow/.github/actions/cpflow-setup-environment
50+
with:
51+
token: ${{ secrets.CPLN_TOKEN_STAGING }}
52+
org: ${{ vars.CPLN_ORG_STAGING }}
53+
working_directory: .cpflow
54+
cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
55+
cpflow_version: ${{ vars.CPFLOW_VERSION }}
56+
57+
- name: Checkout caller repository
58+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
59+
with:
60+
persist-credentials: false
61+
62+
- name: Remove stale review apps
63+
env:
64+
REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
65+
CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
66+
shell: bash
67+
run: |
68+
set -euo pipefail
69+
cpflow cleanup-stale-apps -a "${REVIEW_APP_PREFIX}" --org "${CPLN_ORG_STAGING}" --yes
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: Delete Review App
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
control_plane_flow_ref:
7+
description: Git ref used to load shared cpflow composite actions.
8+
required: false
9+
type: string
10+
default: main
11+
12+
permissions:
13+
contents: read
14+
issues: write
15+
pull-requests: write
16+
17+
concurrency:
18+
group: cpflow-delete-review-app-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
19+
# Deletions must not cancel each other mid-flight — a cancelled `cpln` delete can leave
20+
# partial state behind. Let the in-progress deletion finish before the next run starts.
21+
cancel-in-progress: false
22+
23+
env:
24+
APP_NAME: ${{ vars.REVIEW_APP_PREFIX }}-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
25+
CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }}
26+
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
27+
28+
jobs:
29+
delete-review-app:
30+
if: |
31+
(github.event_name == 'issue_comment' &&
32+
github.event.issue.pull_request &&
33+
contains(fromJson('["+review-app-delete","+review-app-delete\n","+review-app-delete\r\n"]'), github.event.comment.body) &&
34+
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
35+
(github.event_name == 'pull_request_target' && github.event.action == 'closed') ||
36+
github.event_name == 'workflow_dispatch'
37+
runs-on: ubuntu-latest
38+
timeout-minutes: 15
39+
40+
steps:
41+
- name: React to delete command
42+
if: github.event_name == 'issue_comment'
43+
continue-on-error: true
44+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
45+
with:
46+
script: |
47+
try {
48+
await github.rest.reactions.createForIssueComment({
49+
owner: context.repo.owner,
50+
repo: context.repo.repo,
51+
comment_id: context.payload.comment.id,
52+
content: "eyes"
53+
});
54+
} catch (error) {
55+
if (error.status === 422) {
56+
core.info("Delete command reaction already exists.");
57+
} else {
58+
throw error;
59+
}
60+
}
61+
62+
- name: Checkout control-plane-flow actions
63+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
64+
with:
65+
repository: shakacode/control-plane-flow
66+
ref: ${{ inputs.control_plane_flow_ref }}
67+
path: .cpflow
68+
persist-credentials: false
69+
70+
- name: Validate required secrets and variables
71+
id: config
72+
uses: ./.cpflow/.github/actions/cpflow-validate-config
73+
env:
74+
CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
75+
CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
76+
REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
77+
with:
78+
required: |
79+
secret:CPLN_TOKEN_STAGING
80+
variable:CPLN_ORG_STAGING
81+
variable:REVIEW_APP_PREFIX
82+
pull_request_friendly: "true"
83+
84+
- name: Setup environment
85+
if: steps.config.outputs.ready == 'true'
86+
uses: ./.cpflow/.github/actions/cpflow-setup-environment
87+
with:
88+
token: ${{ secrets.CPLN_TOKEN_STAGING }}
89+
org: ${{ vars.CPLN_ORG_STAGING }}
90+
working_directory: .cpflow
91+
cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
92+
cpflow_version: ${{ vars.CPFLOW_VERSION }}
93+
94+
- name: Set workflow links
95+
if: steps.config.outputs.ready == 'true'
96+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
97+
with:
98+
script: |
99+
const workflowUrl = `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
100+
core.exportVariable("WORKFLOW_URL", workflowUrl);
101+
core.exportVariable(
102+
"CONSOLE_URL",
103+
`https://console.cpln.io/console/org/${process.env.CPLN_ORG}/-info`
104+
);
105+
106+
- name: Create initial PR comment
107+
if: steps.config.outputs.ready == 'true'
108+
id: create-comment
109+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
110+
with:
111+
script: |
112+
const body = [
113+
"## 🗑️ Deleting review app...",
114+
"",
115+
`_Removing the review app for PR #${process.env.PR_NUMBER}_`
116+
].join("\n");
117+
118+
const comment = await github.rest.issues.createComment({
119+
owner: context.repo.owner,
120+
repo: context.repo.repo,
121+
issue_number: Number(process.env.PR_NUMBER),
122+
body
123+
});
124+
core.setOutput("comment-id", comment.data.id);
125+
126+
- name: Delete review app
127+
if: steps.config.outputs.ready == 'true'
128+
uses: ./.cpflow/.github/actions/cpflow-delete-control-plane-app
129+
with:
130+
app_name: ${{ env.APP_NAME }}
131+
cpln_org: ${{ vars.CPLN_ORG_STAGING }}
132+
review_app_prefix: ${{ vars.REVIEW_APP_PREFIX }}
133+
134+
# Finalizer still runs after delete failures, but only after config validation
135+
# created the initial PR comment and workflow link env vars it updates.
136+
- name: Finalize delete status
137+
if: always() && steps.config.outputs.ready == 'true'
138+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
139+
env:
140+
COMMENT_ID: ${{ steps.create-comment.outputs.comment-id }}
141+
JOB_STATUS: ${{ job.status }}
142+
with:
143+
script: |
144+
const commentId = Number(process.env.COMMENT_ID);
145+
const success = process.env.JOB_STATUS === "success";
146+
const body = success
147+
? [
148+
"## ✅ Review App Deleted",
149+
"",
150+
`_Review app for PR #${process.env.PR_NUMBER} is deleted_`,
151+
"",
152+
`🎮 [Control Plane Console](${process.env.CONSOLE_URL})`,
153+
`📋 [View Workflow Logs](${process.env.WORKFLOW_URL})`
154+
].join("\n")
155+
: [
156+
"## ❌ Failed to Delete Review App",
157+
"",
158+
`_Failed to delete review app for PR #${process.env.PR_NUMBER}_`,
159+
"",
160+
`🎮 [Control Plane Console](${process.env.CONSOLE_URL})`,
161+
`📋 [View Workflow Logs](${process.env.WORKFLOW_URL})`
162+
].join("\n");
163+
164+
if (!Number.isFinite(commentId) || commentId <= 0) {
165+
core.warning("Skipping delete status comment update because no comment id was created.");
166+
return;
167+
}
168+
169+
await github.rest.issues.updateComment({
170+
owner: context.repo.owner,
171+
repo: context.repo.repo,
172+
comment_id: commentId,
173+
body
174+
});

0 commit comments

Comments
 (0)