Skip to content

Commit a7a1005

Browse files
committed
Address production promotion review feedback
1 parent b7cdc73 commit a7a1005

6 files changed

Lines changed: 50 additions & 13 deletions

File tree

.controlplane/readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ commands.
105105
gh secret set CPLN_TOKEN_PRODUCTION --repo shakacode/react-webpack-rails-tutorial --env production
106106
gh secret list --repo shakacode/react-webpack-rails-tutorial --env production
107107
gh secret list --repo shakacode/react-webpack-rails-tutorial
108+
gh secret list --org shakacode | grep '^CPLN_TOKEN_PRODUCTION[[:space:]]' || true
108109
```
109110

110111
The matching Control Plane resources are:

.controlplane/shakacode-team.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ commands.
7373
gh secret set CPLN_TOKEN_PRODUCTION --repo shakacode/react-webpack-rails-tutorial --env production
7474
gh secret list --repo shakacode/react-webpack-rails-tutorial --env production
7575
gh secret list --repo shakacode/react-webpack-rails-tutorial
76+
gh secret list --org shakacode | grep '^CPLN_TOKEN_PRODUCTION[[:space:]]' || true
7677
```
7778

7879
Generated reusable-workflow callers pass only the named secrets each upstream

.github/cpflow-help.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ commands.
8989
gh secret set CPLN_TOKEN_PRODUCTION --repo shakacode/react-webpack-rails-tutorial --env production
9090
gh secret list --repo shakacode/react-webpack-rails-tutorial --env production
9191
gh secret list --repo shakacode/react-webpack-rails-tutorial
92+
gh secret list --org shakacode | grep '^CPLN_TOKEN_PRODUCTION[[:space:]]' || true
9293
```
9394

9495
Before the first promotion, bootstrap the production app the same way in the

.github/workflows/cpflow-promote-staging-to-production.yml

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ env:
1616
# Worst-case wall time per attempt is HEALTH_CHECK_INTERVAL plus the curl --max-time below
1717
# (10s), so the defaults give a ~10 minute window (24 × (15 + 10) = 600s) — enough for
1818
# most Rails cold boots (asset precompile + db:migrate + workload readiness).
19-
HEALTH_CHECK_RETRIES: 24
20-
HEALTH_CHECK_INTERVAL: 15
19+
HEALTH_CHECK_RETRIES: ${{ vars.HEALTH_CHECK_RETRIES || '24' }}
20+
HEALTH_CHECK_INTERVAL: ${{ vars.HEALTH_CHECK_INTERVAL || '15' }}
2121
# Space-separated list of HTTP statuses considered healthy. The default accepts 301/302
2222
# because `curl` is invoked without `-L`, so a root `/` that redirects to a login page
2323
# (common for Rails apps that auth-gate `/`) would otherwise be reported as unhealthy
@@ -31,8 +31,8 @@ env:
3131
# expose a dedicated health endpoint (e.g. "200" for a plain /health, or "200 401 403"
3232
# for apps that auth-gate / without redirecting).
3333
HEALTH_CHECK_ACCEPTED_STATUSES: ${{ vars.HEALTH_CHECK_ACCEPTED_STATUSES || '200 301 302' }}
34-
ROLLBACK_READINESS_RETRIES: 24
35-
ROLLBACK_READINESS_INTERVAL: 15
34+
ROLLBACK_READINESS_RETRIES: ${{ vars.ROLLBACK_READINESS_RETRIES || '24' }}
35+
ROLLBACK_READINESS_INTERVAL: ${{ vars.ROLLBACK_READINESS_INTERVAL || '15' }}
3636

3737
concurrency:
3838
# Single global group: only one production promotion may run at a time across the
@@ -129,6 +129,7 @@ jobs:
129129
working_directory: .cpflow
130130
cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
131131
cpflow_version: ${{ vars.CPFLOW_VERSION }}
132+
# The setup action validates CPFLOW_VERSION against this full workflow ref.
132133
control_plane_flow_ref: shakacode/control-plane-flow/.github/workflows/cpflow-promote-staging-to-production.yml@v5.0.4
133134

134135
# Runs after Setup production environment so the pinned Ruby (>= 3.1) is on PATH.
@@ -142,11 +143,12 @@ jobs:
142143
run: |
143144
set -euo pipefail
144145
145-
ruby - "${PRODUCTION_APP_NAME}" "${PRIMARY_WORKLOAD}" >> "$GITHUB_OUTPUT" <<'RUBY'
146+
ruby - "${PRODUCTION_APP_NAME}" "${PRIMARY_WORKLOAD}" "${GITHUB_OUTPUT}" <<'RUBY'
146147
require "yaml"
147148
148149
app = ARGV.fetch(0)
149150
requested_primary = ARGV.fetch(1, "").to_s.strip
151+
output_path = ARGV.fetch(2)
150152
data = YAML.safe_load(File.read(".controlplane/controlplane.yml"), aliases: true)
151153
apps = data["apps"] || {}
152154
app_config = apps[app]
@@ -167,19 +169,21 @@ jobs:
167169
elsif workloads.include?("rails")
168170
"rails"
169171
else
170-
warn "::error::PRIMARY_WORKLOAD is not configured and app '#{app}' has multiple workloads: #{workloads.join(', ')}."
172+
puts "::error::PRIMARY_WORKLOAD is not configured and app '#{app}' has multiple workloads: #{workloads.join(', ')}."
171173
warn " Set the PRIMARY_WORKLOAD repository variable to one of these workloads."
172174
exit 1
173175
end
174176
elsif workloads.include?(requested_primary)
175177
requested_primary
176178
else
177-
warn "::error::PRIMARY_WORKLOAD '#{requested_primary}' is not one of: #{workloads.join(', ')}."
179+
puts "::error::PRIMARY_WORKLOAD '#{requested_primary}' is not one of: #{workloads.join(', ')}."
178180
exit 1
179181
end
180182
181-
puts "names=#{workloads.join(',')}"
182-
puts "primary=#{primary}"
183+
File.open(output_path, "a") do |output|
184+
output.puts "names=#{workloads.join(',')}"
185+
output.puts "primary=#{primary}"
186+
end
183187
RUBY
184188
185189
- name: Detect release phase support
@@ -245,7 +249,7 @@ jobs:
245249
[[ -n "${workload_name}" ]] || continue
246250
247251
workload_json="$(cpln workload get "${workload_name}" --gvc "${PRODUCTION_APP_NAME}" --org "${CPLN_ORG_PRODUCTION}" -o json)"
248-
workload_image="$(echo "${workload_json}" | jq -r '.spec.containers[0].image')"
252+
workload_image="$(echo "${workload_json}" | jq -r '.spec.containers[0].image // empty')"
249253
workload_containers="$(echo "${workload_json}" | jq -c '.spec.containers | map({name, image})')"
250254
workload_version="$(echo "${workload_json}" | jq -r '.version')"
251255
@@ -377,6 +381,7 @@ jobs:
377381
run: |
378382
# Best-effort rollback: try every workload, aggregate failures, exit non-zero at the end
379383
# if any failed. A single cpln hiccup shouldn't leave other workloads mid-promotion.
384+
# Keep -e disabled here so rollback can aggregate failures across workloads.
380385
set -uo pipefail
381386
382387
rollback_failures=0

bin/pin-cpflow-github-ref

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ USAGE
1717
ALLOWED_OPTIONS = ["--allow-moving-ref"].freeze
1818
FULL_COMMIT_SHA = /\A[0-9a-f]{40}\z/i
1919
RELEASE_TAG = /\Av\d+\.\d+\.\d+(?:[-.][0-9A-Za-z][0-9A-Za-z.-]*)?\z/
20+
PRODUCTION_WORKFLOW_REF = "shakacode/control-plane-flow/.github/workflows/" \
21+
"cpflow-promote-staging-to-production.yml"
22+
CPFLOW_CHECKOUT_REF_PATTERN = %r{
23+
(^\s*-\s+name:\s+Checkout\ control-plane-flow\ actions\s*\n
24+
(?:(?!^\s*-\s+name:).)*?
25+
^\s+repository:\s+shakacode/control-plane-flow\s*\n
26+
(?:(?!^\s*-\s+name:).)*?
27+
^\s+ref:\s+)
28+
[^\s]+
29+
}mx
2030

2131
options, positional = ARGV.partition { |arg| arg.start_with?("--") }
2232
unknown_options = options - ALLOWED_OPTIONS
@@ -57,10 +67,12 @@ end
5767
changed = []
5868
workflow_paths.each do |path|
5969
text = File.read(path)
70+
production_setup_ref_pattern =
71+
/(control_plane_flow_ref:\s+#{Regexp.escape(PRODUCTION_WORKFLOW_REF)}@)[^\s]+/
6072
updated = text
6173
.gsub(%r{(uses:\s+shakacode/control-plane-flow/\.github/workflows/[^@\s]+@)[^\s]+}, "\\1#{ref}")
62-
.gsub(%r{(repository:\s+shakacode/control-plane-flow\n\s+ref:\s+)[^\s]+}, "\\1#{ref}")
63-
.gsub(%r{(control_plane_flow_ref:\s+shakacode/control-plane-flow/\.github/workflows/cpflow-promote-staging-to-production\.yml@)[^\s]+}, "\\1#{ref}")
74+
.gsub(production_setup_ref_pattern, "\\1#{ref}")
75+
.gsub(CPFLOW_CHECKOUT_REF_PATTERN, "\\1#{ref}")
6476

6577
next if updated == text
6678

bin/test-cpflow-github-flow

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ require "yaml"
4444
4545
CONTROL_PLANE_FLOW_WORKFLOW = %r{\Ashakacode/control-plane-flow/\.github/workflows/[^@\s]+@([^\s]+)\z}
4646
PROMOTE_WORKFLOW = %r{\Ashakacode/control-plane-flow/\.github/workflows/cpflow-promote-staging-to-production\.yml@[^\s]+\z}
47+
EXPECTED_CPFLOW_CHECKOUT_ACTION = "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd"
48+
EXPECTED_CPFLOW_CHECKOUT_REPOSITORY = "shakacode/control-plane-flow"
4749
4850
refs = Hash.new { |hash, key| hash[key] = [] }
4951
@@ -91,7 +93,22 @@ unless promote_job["environment"].to_s == "production"
9193
end
9294
9395
checkout_step = Array(promote_job["steps"]).find { |step| step["name"] == "Checkout control-plane-flow actions" }
94-
checkout_ref = checkout_step&.fetch("with", {})&.fetch("ref", nil)
96+
97+
unless checkout_step
98+
abort "#{promote_path}:promote-to-production must include a Checkout control-plane-flow actions step"
99+
end
100+
101+
unless checkout_step["uses"] == EXPECTED_CPFLOW_CHECKOUT_ACTION
102+
abort "#{promote_path}:promote-to-production must use #{EXPECTED_CPFLOW_CHECKOUT_ACTION} for the Checkout control-plane-flow actions step"
103+
end
104+
105+
checkout_with = checkout_step.fetch("with", {})
106+
checkout_repository = checkout_with["repository"]
107+
checkout_ref = checkout_with["ref"]
108+
109+
unless checkout_repository == EXPECTED_CPFLOW_CHECKOUT_REPOSITORY
110+
abort "#{promote_path}:promote-to-production must check out #{EXPECTED_CPFLOW_CHECKOUT_REPOSITORY}"
111+
end
95112
96113
if checkout_ref.to_s.strip.empty?
97114
abort "#{promote_path}:promote-to-production must pin the Checkout control-plane-flow actions step"

0 commit comments

Comments
 (0)