cpflow 4.1.1 → 5.0.0.rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/commands/update-changelog.md +367 -0
- data/.github/workflows/claude-code-review.yml +44 -0
- data/.github/workflows/claude.yml +55 -0
- data/.gitignore +2 -0
- data/.overcommit.yml +43 -3
- data/.rubocop.yml +3 -3
- data/CHANGELOG.md +39 -3
- data/CONTRIBUTING.md +6 -0
- data/Gemfile +8 -7
- data/Gemfile.lock +93 -73
- data/README.md +53 -22
- data/cpflow.gemspec +5 -5
- data/docs/ai-github-flow-prompt.md +61 -0
- data/docs/ci-automation.md +335 -0
- data/docs/commands.md +70 -5
- data/docs/releasing.md +153 -0
- data/lib/command/ai_github_flow_prompt.rb +47 -0
- data/lib/command/base.rb +14 -0
- data/lib/command/cleanup_images.rb +1 -1
- data/lib/command/cleanup_stale_apps.rb +1 -1
- data/lib/command/copy_image_from_upstream.rb +14 -3
- data/lib/command/exists.rb +13 -2
- data/lib/command/generate.rb +153 -4
- data/lib/command/generate_github_actions.rb +170 -0
- data/lib/command/generator_helpers.rb +31 -0
- data/lib/command/github_flow_readiness.rb +37 -0
- data/lib/command/ps_wait.rb +5 -1
- data/lib/command/run.rb +4 -21
- data/lib/command/terraform/generate.rb +1 -0
- data/lib/command/version.rb +1 -0
- data/lib/constants/exit_code.rb +1 -0
- data/lib/core/config.rb +1 -1
- data/lib/core/controlplane.rb +13 -10
- data/lib/core/controlplane_api_direct.rb +25 -3
- data/lib/core/github_flow_readiness/checks.rb +143 -0
- data/lib/core/github_flow_readiness_service.rb +453 -0
- data/lib/core/repo_introspection.rb +118 -0
- data/lib/core/terraform_config/dsl.rb +1 -1
- data/lib/core/terraform_config/local_variable.rb +1 -1
- data/lib/cpflow/version.rb +1 -1
- data/lib/cpflow.rb +66 -3
- data/lib/generator_templates/Dockerfile +59 -3
- data/lib/generator_templates/controlplane.yml +27 -39
- data/lib/generator_templates/entrypoint.sh +1 -1
- data/lib/generator_templates/release_script.sh +23 -0
- data/lib/generator_templates/templates/app.yml +5 -8
- data/lib/generator_templates/templates/rails.yml +2 -11
- data/lib/generator_templates_sqlite/controlplane.yml +46 -0
- data/lib/generator_templates_sqlite/release_script.sh +25 -0
- data/lib/generator_templates_sqlite/templates/app.yml +15 -0
- data/lib/generator_templates_sqlite/templates/db.yml +6 -0
- data/lib/generator_templates_sqlite/templates/rails.yml +32 -0
- data/lib/generator_templates_sqlite/templates/storage.yml +6 -0
- data/lib/github_flow_templates/.github/actions/cpflow-build-docker-image/action.yml +131 -0
- data/lib/github_flow_templates/.github/actions/cpflow-delete-control-plane-app/action.yml +24 -0
- data/lib/github_flow_templates/.github/actions/cpflow-delete-control-plane-app/delete-app.sh +50 -0
- data/lib/github_flow_templates/.github/actions/cpflow-detect-release-phase/action.yml +62 -0
- data/lib/github_flow_templates/.github/actions/cpflow-setup-environment/action.yml +98 -0
- data/lib/github_flow_templates/.github/actions/cpflow-validate-config/action.yml +85 -0
- data/lib/github_flow_templates/.github/actions/cpflow-wait-for-health/action.yml +92 -0
- data/lib/github_flow_templates/.github/cpflow-help.md +47 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-cleanup-stale-review-apps.yml +56 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +142 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +445 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +140 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-help-command.yml +53 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +490 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-review-app-help.yml +46 -0
- data/rakelib/create_release.rake +662 -37
- data/script/check_command_docs +4 -2
- data/script/check_cpln_links +25 -11
- data/script/precommit/check_command_docs +22 -0
- data/script/precommit/check_cpln_links +21 -0
- data/script/precommit/check_trailing_newlines +68 -0
- data/script/precommit/get_changed_files +49 -0
- data/script/precommit/ruby_autofix +52 -0
- data/script/precommit/ruby_lint +33 -0
- metadata +56 -15
- /data/docs/{migrating.md → migrating-heroku-to-control-plane.md} +0 -0
|
@@ -1,23 +1,79 @@
|
|
|
1
|
-
|
|
1
|
+
ARG RUBY_VERSION=__RUBY_VERSION__
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Node and Ruby base images share the same Debian release so that glibc, libssl, and
|
|
4
|
+
# other system libraries line up between stages.
|
|
5
|
+
FROM docker.io/library/node:22-bookworm-slim AS node
|
|
6
|
+
FROM ruby:$RUBY_VERSION-slim-bookworm
|
|
7
|
+
|
|
8
|
+
# Yarn Classic / pnpm fallbacks for projects that haven't adopted corepack via
|
|
9
|
+
# `packageManager` in package.json. Override at build time (`--build-arg=YARN_CLASSIC_VERSION=...`)
|
|
10
|
+
# rather than editing this file so regenerating the Dockerfile keeps your pin.
|
|
11
|
+
ARG YARN_CLASSIC_VERSION=1.22.22
|
|
12
|
+
ARG PNPM_FALLBACK_VERSION=9.12.3
|
|
4
13
|
|
|
5
14
|
WORKDIR /app
|
|
6
15
|
|
|
16
|
+
# Keep Node.js available both for asset compilation and for SSR runtimes that
|
|
17
|
+
# rely on ExecJS in production. Narrowed to just what the node stage actually
|
|
18
|
+
# ships under /usr/local so we don't drag in unused Debian libs from that image.
|
|
19
|
+
COPY --from=node /usr/local/bin/node /usr/local/bin/node
|
|
20
|
+
COPY --from=node /usr/local/bin/npm /usr/local/bin/npm
|
|
21
|
+
COPY --from=node /usr/local/bin/npx /usr/local/bin/npx
|
|
22
|
+
COPY --from=node /usr/local/bin/corepack /usr/local/bin/corepack
|
|
23
|
+
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
|
|
24
|
+
COPY --from=node /usr/local/include/node /usr/local/include/node
|
|
25
|
+
|
|
26
|
+
# Expose Corepack-managed shims so later build steps can call yarn/pnpm
|
|
27
|
+
# directly during asset precompilation hooks.
|
|
28
|
+
RUN printf '%s\n' '#!/bin/sh' 'exec corepack yarn "$@"' > /usr/bin/yarn && \
|
|
29
|
+
chmod +x /usr/bin/yarn && \
|
|
30
|
+
printf '%s\n' '#!/bin/sh' 'exec corepack pnpm "$@"' > /usr/bin/pnpm && \
|
|
31
|
+
chmod +x /usr/bin/pnpm
|
|
32
|
+
|
|
7
33
|
# install ruby gems
|
|
8
34
|
COPY Gemfile* ./
|
|
9
35
|
|
|
10
36
|
RUN bundle config set without 'development test' && \
|
|
11
|
-
bundle config set with '
|
|
37
|
+
bundle config set with 'production' && \
|
|
12
38
|
bundle install --jobs=3 --retry=3
|
|
13
39
|
|
|
14
40
|
COPY . ./
|
|
15
41
|
|
|
42
|
+
# Install JavaScript dependencies only when the project actually has them.
|
|
43
|
+
# Pin `packageManager` in package.json to take the corepack path and avoid the
|
|
44
|
+
# YARN_CLASSIC_VERSION / PNPM_FALLBACK_VERSION fallbacks below; the fallbacks exist for
|
|
45
|
+
# projects that haven't adopted corepack and should be reviewed periodically as they go stale.
|
|
46
|
+
RUN if [ -f package.json ]; then \
|
|
47
|
+
package_manager="$(node -p "require('./package.json').packageManager || ''")"; \
|
|
48
|
+
if [ -f yarn.lock ]; then \
|
|
49
|
+
if printf '%s' "$package_manager" | grep -q '^yarn@'; then \
|
|
50
|
+
corepack prepare "$package_manager" --activate && \
|
|
51
|
+
(corepack yarn install --immutable || corepack yarn install --frozen-lockfile); \
|
|
52
|
+
else \
|
|
53
|
+
npm install -g "yarn@${YARN_CLASSIC_VERSION}" && \
|
|
54
|
+
(yarn install --immutable || yarn install --frozen-lockfile); \
|
|
55
|
+
fi; \
|
|
56
|
+
elif [ -f pnpm-lock.yaml ]; then \
|
|
57
|
+
if printf '%s' "$package_manager" | grep -q '^pnpm@'; then \
|
|
58
|
+
corepack prepare "$package_manager" --activate && \
|
|
59
|
+
corepack pnpm install --frozen-lockfile; \
|
|
60
|
+
else \
|
|
61
|
+
corepack prepare "pnpm@${PNPM_FALLBACK_VERSION}" --activate && \
|
|
62
|
+
corepack pnpm install --frozen-lockfile; \
|
|
63
|
+
fi; \
|
|
64
|
+
elif [ -f package-lock.json ]; then \
|
|
65
|
+
npm ci; \
|
|
66
|
+
else \
|
|
67
|
+
npm install; \
|
|
68
|
+
fi; \
|
|
69
|
+
fi
|
|
70
|
+
|
|
16
71
|
ENV RAILS_ENV=production
|
|
17
72
|
|
|
18
73
|
# compiling assets requires any value for ENV of SECRET_KEY_BASE
|
|
19
74
|
ENV SECRET_KEY_BASE=NOT_USED_NON_BLANK
|
|
20
75
|
|
|
76
|
+
__ASSET_PRECOMPILE_HOOK_RUN__
|
|
21
77
|
RUN rails assets:precompile
|
|
22
78
|
|
|
23
79
|
# add entrypoint
|
|
@@ -1,62 +1,50 @@
|
|
|
1
1
|
# Keys beginning with "cpln_" correspond to your settings in Control Plane.
|
|
2
|
+
#
|
|
3
|
+
# Generated baseline for a Rails app that uses PostgreSQL in production.
|
|
4
|
+
# Rename the app keys below if you want something other than the repo name.
|
|
2
5
|
|
|
3
|
-
# You can opt out of allowing the use of CPLN_ORG and CPLN_APP env vars
|
|
4
|
-
# to avoid any accidents with the wrong org / app.
|
|
5
6
|
allow_org_override_by_env: true
|
|
6
7
|
allow_app_override_by_env: true
|
|
7
8
|
|
|
8
9
|
aliases:
|
|
9
10
|
common: &common
|
|
10
|
-
# Organization name for staging (customize to your needs).
|
|
11
|
-
# Production apps will use a different organization, specified below, for security.
|
|
12
11
|
cpln_org: my-org-staging
|
|
13
|
-
|
|
14
|
-
# Example apps use only one location. Control Plane offers the ability to use multiple locations.
|
|
15
|
-
# TODO: Allow specification of multiple locations.
|
|
16
12
|
default_location: aws-us-east-2
|
|
13
|
+
setup_app_templates:
|
|
14
|
+
- app
|
|
15
|
+
- postgres
|
|
16
|
+
- rails
|
|
17
17
|
|
|
18
|
-
# Configure the workload name used as a template for one-off scripts, like a Heroku one-off dyno.
|
|
19
18
|
one_off_workload: rails
|
|
20
|
-
|
|
21
|
-
# Workloads that are for the application itself and are using application Docker images.
|
|
22
|
-
# These are updated with the new image when running the `deploy-image` command,
|
|
23
|
-
# and are also used by the `info` and `ps:` commands in order to get all of the defined workloads.
|
|
24
|
-
# On the other hand, if you have a workload for Redis, that would NOT use the application Docker image
|
|
25
|
-
# and not be listed here.
|
|
26
19
|
app_workloads:
|
|
27
20
|
- rails
|
|
28
|
-
|
|
29
|
-
# Additional "service type" workloads, using non-application Docker images.
|
|
30
|
-
# These are only used by the `info` and `ps:` commands in order to get all of the defined workloads.
|
|
31
21
|
additional_workloads:
|
|
32
22
|
- postgres
|
|
33
23
|
|
|
34
|
-
# Configure the workload name used when maintenance mode is on (defaults to "maintenance")
|
|
35
24
|
maintenance_workload: maintenance
|
|
25
|
+
release_script: release_script.sh
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# Use the values from the common section above.
|
|
40
|
-
<<: *common
|
|
41
|
-
my-app-review:
|
|
42
|
-
<<: *common
|
|
43
|
-
# If `match_if_app_name_starts_with` is `true`, then use this config for app names starting with this name,
|
|
44
|
-
# e.g., "my-app-review-pr123", "my-app-review-anything-goes", etc.
|
|
45
|
-
match_if_app_name_starts_with: true
|
|
46
|
-
my-app-production:
|
|
47
|
-
<<: *common
|
|
27
|
+
stale_app_image_deployed_days: 5
|
|
28
|
+
image_retention_days: 7
|
|
48
29
|
|
|
49
|
-
|
|
50
|
-
|
|
30
|
+
production: &production
|
|
31
|
+
<<: *common
|
|
51
32
|
allow_org_override_by_env: false
|
|
52
33
|
allow_app_override_by_env: false
|
|
53
|
-
|
|
54
|
-
# Use a different organization for production.
|
|
55
34
|
cpln_org: my-org-production
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
35
|
+
upstream: __APP_PREFIX__-staging
|
|
36
|
+
|
|
37
|
+
apps:
|
|
38
|
+
__APP_PREFIX__-staging:
|
|
60
39
|
<<: *common
|
|
61
|
-
|
|
62
|
-
|
|
40
|
+
|
|
41
|
+
__APP_PREFIX__-review:
|
|
42
|
+
<<: *common
|
|
43
|
+
match_if_app_name_starts_with: true
|
|
44
|
+
# Uncomment to automatically initialize and tear down review-app databases:
|
|
45
|
+
# hooks:
|
|
46
|
+
# post_creation: bundle exec rails db:prepare
|
|
47
|
+
# pre_deletion: bundle exec rails db:drop
|
|
48
|
+
|
|
49
|
+
__APP_PREFIX__-production:
|
|
50
|
+
<<: *production
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
log() {
|
|
5
|
+
echo "[$(date +%Y-%m-%d:%H:%M:%S)]: $1"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
error_exit() {
|
|
9
|
+
log "$1" 1>&2
|
|
10
|
+
exit 1
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
log "Running release_script.sh per controlplane.yml"
|
|
14
|
+
|
|
15
|
+
if [ -x ./bin/rails ]; then
|
|
16
|
+
log "Run DB migrations"
|
|
17
|
+
SECRET_KEY_BASE="${SECRET_KEY_BASE:-precompile_placeholder}" ./bin/rails db:prepare || \
|
|
18
|
+
error_exit "Failed to run DB migrations"
|
|
19
|
+
else
|
|
20
|
+
error_exit "./bin/rails does not exist or is not executable"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
log "Completed release_script.sh per controlplane.yml"
|
|
@@ -2,20 +2,17 @@
|
|
|
2
2
|
kind: gvc
|
|
3
3
|
name: {{APP_NAME}}
|
|
4
4
|
spec:
|
|
5
|
-
# For using templates for test apps, put ENV values here, stored in git repo.
|
|
6
|
-
# Production apps will have values configured manually after app creation.
|
|
7
5
|
env:
|
|
8
6
|
- name: DATABASE_URL
|
|
9
|
-
# Password does not matter because host postgres.{{APP_NAME}}.cpln.local can only be accessed
|
|
10
|
-
# locally within CPLN GVC, and postgres running on a CPLN workload is something only for a
|
|
11
|
-
# test app that lacks persistence.
|
|
12
7
|
value: 'postgres://the_user:the_password@postgres.{{APP_NAME}}.cpln.local:5432/{{APP_NAME}}'
|
|
13
8
|
- name: RAILS_ENV
|
|
14
9
|
value: production
|
|
10
|
+
- name: RAILS_LOG_TO_STDOUT
|
|
11
|
+
value: "true"
|
|
15
12
|
- name: RAILS_SERVE_STATIC_FILES
|
|
16
|
-
value:
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
value: "true"
|
|
14
|
+
- name: SECRET_KEY_BASE
|
|
15
|
+
value: cpln://secret/{{APP_SECRETS}}.SECRET_KEY_BASE
|
|
19
16
|
staticPlacement:
|
|
20
17
|
locationLinks:
|
|
21
18
|
- {{APP_LOCATION_LINK}}
|
|
@@ -6,31 +6,22 @@ spec:
|
|
|
6
6
|
type: standard
|
|
7
7
|
containers:
|
|
8
8
|
- name: rails
|
|
9
|
-
# 300m is a good starting place for a test app. You can experiment with CPU configuration
|
|
10
|
-
# once your app is running.
|
|
11
9
|
cpu: 300m
|
|
12
|
-
env:
|
|
13
|
-
- name: LOG_LEVEL
|
|
14
|
-
value: debug
|
|
15
|
-
# Inherit other ENV values from GVC
|
|
16
10
|
inheritEnv: true
|
|
17
11
|
image: {{APP_IMAGE_LINK}}
|
|
18
|
-
# 512 corresponds to a standard 1x dyno type
|
|
19
12
|
memory: 512Mi
|
|
20
13
|
ports:
|
|
21
14
|
- number: 3000
|
|
22
15
|
protocol: http
|
|
23
16
|
defaultOptions:
|
|
24
|
-
# Start out like this for "test apps"
|
|
25
17
|
autoscaling:
|
|
26
|
-
|
|
18
|
+
minScale: 1
|
|
27
19
|
maxScale: 1
|
|
28
20
|
capacityAI: false
|
|
21
|
+
timeoutSeconds: 60
|
|
29
22
|
firewallConfig:
|
|
30
23
|
external:
|
|
31
|
-
# Default to allow public access to Rails server
|
|
32
24
|
inboundAllowCIDR:
|
|
33
25
|
- 0.0.0.0/0
|
|
34
|
-
# Could configure outbound for more security
|
|
35
26
|
outboundAllowCIDR:
|
|
36
27
|
- 0.0.0.0/0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Keys beginning with "cpln_" correspond to your settings in Control Plane.
|
|
2
|
+
#
|
|
3
|
+
# Generated baseline for a Rails app that uses SQLite in production. This setup
|
|
4
|
+
# keeps `/app/db` and `/app/storage` on persistent volumes.
|
|
5
|
+
|
|
6
|
+
allow_org_override_by_env: true
|
|
7
|
+
allow_app_override_by_env: true
|
|
8
|
+
|
|
9
|
+
aliases:
|
|
10
|
+
common: &common
|
|
11
|
+
cpln_org: my-org-staging
|
|
12
|
+
default_location: aws-us-east-2
|
|
13
|
+
setup_app_templates:
|
|
14
|
+
- app
|
|
15
|
+
- db
|
|
16
|
+
- storage
|
|
17
|
+
- rails
|
|
18
|
+
|
|
19
|
+
one_off_workload: rails
|
|
20
|
+
app_workloads:
|
|
21
|
+
- rails
|
|
22
|
+
additional_workloads: []
|
|
23
|
+
|
|
24
|
+
maintenance_workload: maintenance
|
|
25
|
+
release_script: release_script.sh
|
|
26
|
+
|
|
27
|
+
stale_app_image_deployed_days: 5
|
|
28
|
+
image_retention_days: 7
|
|
29
|
+
|
|
30
|
+
production: &production
|
|
31
|
+
<<: *common
|
|
32
|
+
allow_org_override_by_env: false
|
|
33
|
+
allow_app_override_by_env: false
|
|
34
|
+
cpln_org: my-org-production
|
|
35
|
+
upstream: __APP_PREFIX__-staging
|
|
36
|
+
|
|
37
|
+
apps:
|
|
38
|
+
__APP_PREFIX__-staging:
|
|
39
|
+
<<: *common
|
|
40
|
+
|
|
41
|
+
__APP_PREFIX__-review:
|
|
42
|
+
<<: *common
|
|
43
|
+
match_if_app_name_starts_with: true
|
|
44
|
+
|
|
45
|
+
__APP_PREFIX__-production:
|
|
46
|
+
<<: *production
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
log() {
|
|
5
|
+
echo "[$(date +%Y-%m-%d:%H:%M:%S)]: $1"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
error_exit() {
|
|
9
|
+
log "$1" 1>&2
|
|
10
|
+
exit 1
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
log "Running release_script.sh per controlplane.yml"
|
|
14
|
+
|
|
15
|
+
mkdir -p db storage
|
|
16
|
+
|
|
17
|
+
if [ -x ./bin/rails ]; then
|
|
18
|
+
log "Run DB migrations"
|
|
19
|
+
SECRET_KEY_BASE="${SECRET_KEY_BASE:-precompile_placeholder}" ./bin/rails db:prepare || \
|
|
20
|
+
error_exit "Failed to run DB migrations"
|
|
21
|
+
else
|
|
22
|
+
error_exit "./bin/rails does not exist or is not executable"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
log "Completed release_script.sh per controlplane.yml"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
kind: gvc
|
|
2
|
+
name: {{APP_NAME}}
|
|
3
|
+
spec:
|
|
4
|
+
env:
|
|
5
|
+
- name: RAILS_ENV
|
|
6
|
+
value: production
|
|
7
|
+
- name: RAILS_LOG_TO_STDOUT
|
|
8
|
+
value: "true"
|
|
9
|
+
- name: RAILS_SERVE_STATIC_FILES
|
|
10
|
+
value: "true"
|
|
11
|
+
- name: SECRET_KEY_BASE
|
|
12
|
+
value: cpln://secret/{{APP_SECRETS}}.SECRET_KEY_BASE
|
|
13
|
+
staticPlacement:
|
|
14
|
+
locationLinks:
|
|
15
|
+
- {{APP_LOCATION_LINK}}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
kind: workload
|
|
2
|
+
name: rails
|
|
3
|
+
spec:
|
|
4
|
+
type: standard
|
|
5
|
+
containers:
|
|
6
|
+
- name: rails
|
|
7
|
+
cpu: 300m
|
|
8
|
+
inheritEnv: true
|
|
9
|
+
image: {{APP_IMAGE_LINK}}
|
|
10
|
+
memory: 512Mi
|
|
11
|
+
ports:
|
|
12
|
+
- number: 3000
|
|
13
|
+
protocol: http
|
|
14
|
+
volumes:
|
|
15
|
+
- path: /app/db
|
|
16
|
+
recoveryPolicy: retain
|
|
17
|
+
uri: cpln://volumeset/app-db
|
|
18
|
+
- path: /app/storage
|
|
19
|
+
recoveryPolicy: retain
|
|
20
|
+
uri: cpln://volumeset/app-storage
|
|
21
|
+
defaultOptions:
|
|
22
|
+
autoscaling:
|
|
23
|
+
minScale: 1
|
|
24
|
+
maxScale: 1
|
|
25
|
+
capacityAI: false
|
|
26
|
+
timeoutSeconds: 60
|
|
27
|
+
firewallConfig:
|
|
28
|
+
external:
|
|
29
|
+
inboundAllowCIDR:
|
|
30
|
+
- 0.0.0.0/0
|
|
31
|
+
outboundAllowCIDR:
|
|
32
|
+
- 0.0.0.0/0
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
name: Build Docker Image
|
|
2
|
+
description: Builds and pushes the app image for a Control Plane workload
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
app_name:
|
|
6
|
+
description: Name of the application
|
|
7
|
+
required: true
|
|
8
|
+
org:
|
|
9
|
+
description: Control Plane organization name
|
|
10
|
+
required: true
|
|
11
|
+
commit:
|
|
12
|
+
description: Commit SHA to tag the image with
|
|
13
|
+
required: true
|
|
14
|
+
pr_number:
|
|
15
|
+
description: Pull request number for status messaging
|
|
16
|
+
required: false
|
|
17
|
+
docker_build_extra_args:
|
|
18
|
+
description: Optional newline-delimited extra docker build tokens. Use key=value forms like --build-arg=FOO=bar.
|
|
19
|
+
required: false
|
|
20
|
+
docker_build_ssh_key:
|
|
21
|
+
description: Optional private SSH key used for Docker builds that fetch private dependencies with RUN --mount=type=ssh
|
|
22
|
+
required: false
|
|
23
|
+
docker_build_ssh_known_hosts:
|
|
24
|
+
description: Optional SSH known_hosts entries used with docker_build_ssh_key. Defaults to pinned GitHub.com host keys.
|
|
25
|
+
required: false
|
|
26
|
+
working_directory:
|
|
27
|
+
description: Directory containing the app .controlplane config and Docker build context
|
|
28
|
+
required: false
|
|
29
|
+
default: "."
|
|
30
|
+
|
|
31
|
+
runs:
|
|
32
|
+
using: composite
|
|
33
|
+
steps:
|
|
34
|
+
# Keep SSH key handling in a dedicated step so DOCKER_BUILD_SSH_KEY is never present
|
|
35
|
+
# in the main build step's environment. ACTIONS_STEP_DEBUG=true dumps env before any
|
|
36
|
+
# command runs, so keeping the key out of env there avoids even admin-triggered exposure.
|
|
37
|
+
- name: Prepare SSH agent for Docker build
|
|
38
|
+
if: ${{ inputs.docker_build_ssh_key != '' }}
|
|
39
|
+
shell: bash
|
|
40
|
+
env:
|
|
41
|
+
# Pass the key via env so the file write is a single printf call rather than a
|
|
42
|
+
# heredoc with a fixed terminator (a heredoc would silently truncate the key if
|
|
43
|
+
# any line of the key value happened to match the terminator). Scope is still
|
|
44
|
+
# this step only — the build step below does not receive DOCKER_BUILD_SSH_KEY.
|
|
45
|
+
DOCKER_BUILD_SSH_KEY: ${{ inputs.docker_build_ssh_key }}
|
|
46
|
+
DOCKER_BUILD_SSH_KNOWN_HOSTS: ${{ inputs.docker_build_ssh_known_hosts }}
|
|
47
|
+
run: |
|
|
48
|
+
set -euo pipefail
|
|
49
|
+
|
|
50
|
+
umask 077
|
|
51
|
+
mkdir -p ~/.ssh
|
|
52
|
+
chmod 700 ~/.ssh
|
|
53
|
+
|
|
54
|
+
if [[ -n "${DOCKER_BUILD_SSH_KNOWN_HOSTS}" ]]; then
|
|
55
|
+
printf '%s\n' "${DOCKER_BUILD_SSH_KNOWN_HOSTS}" > ~/.ssh/known_hosts
|
|
56
|
+
else
|
|
57
|
+
printf '%s\n' \
|
|
58
|
+
'github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl' \
|
|
59
|
+
'github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=' \
|
|
60
|
+
'github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=' \
|
|
61
|
+
> ~/.ssh/known_hosts
|
|
62
|
+
fi
|
|
63
|
+
chmod 600 ~/.ssh/known_hosts
|
|
64
|
+
|
|
65
|
+
printf '%s\n' "${DOCKER_BUILD_SSH_KEY}" > ~/.ssh/cpflow_build_key
|
|
66
|
+
chmod 600 ~/.ssh/cpflow_build_key
|
|
67
|
+
|
|
68
|
+
- name: Build Docker image
|
|
69
|
+
shell: bash
|
|
70
|
+
env:
|
|
71
|
+
APP_NAME: ${{ inputs.app_name }}
|
|
72
|
+
COMMIT_SHA: ${{ inputs.commit }}
|
|
73
|
+
CONTROL_PLANE_ORG: ${{ inputs.org }}
|
|
74
|
+
DOCKER_BUILD_EXTRA_ARGS: ${{ inputs.docker_build_extra_args }}
|
|
75
|
+
PR_NUMBER: ${{ inputs.pr_number }}
|
|
76
|
+
WORKING_DIRECTORY: ${{ inputs.working_directory }}
|
|
77
|
+
run: |
|
|
78
|
+
set -euo pipefail
|
|
79
|
+
|
|
80
|
+
PR_INFO=""
|
|
81
|
+
docker_build_args=()
|
|
82
|
+
ssh_agent_started=false
|
|
83
|
+
build_ssh_prepped=false
|
|
84
|
+
|
|
85
|
+
cleanup_build_ssh() {
|
|
86
|
+
if [[ "${ssh_agent_started}" == "true" ]]; then
|
|
87
|
+
ssh-agent -k >/dev/null || true
|
|
88
|
+
fi
|
|
89
|
+
rm -f "${HOME}/.ssh/cpflow_build_key"
|
|
90
|
+
# Only remove known_hosts if this action's prep step wrote it. On self-hosted
|
|
91
|
+
# or reused runners we must not touch a user-managed file we did not create,
|
|
92
|
+
# so the flag is set inside the same prep-detection branch below.
|
|
93
|
+
if [[ "${build_ssh_prepped}" == "true" ]]; then
|
|
94
|
+
rm -f "${HOME}/.ssh/known_hosts"
|
|
95
|
+
fi
|
|
96
|
+
}
|
|
97
|
+
trap cleanup_build_ssh EXIT
|
|
98
|
+
cd "${WORKING_DIRECTORY}"
|
|
99
|
+
|
|
100
|
+
if [[ -n "${PR_NUMBER}" ]]; then
|
|
101
|
+
PR_INFO=" for PR #${PR_NUMBER}"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
if [[ -n "${DOCKER_BUILD_EXTRA_ARGS}" ]]; then
|
|
105
|
+
while IFS= read -r arg; do
|
|
106
|
+
arg="${arg%$'\r'}"
|
|
107
|
+
[[ -n "${arg}" ]] || continue
|
|
108
|
+
|
|
109
|
+
if [[ "${arg}" =~ [[:space:]] ]]; then
|
|
110
|
+
echo "docker_build_extra_args entries must be single docker-build tokens. " \
|
|
111
|
+
"Use key=value forms like --build-arg=FOO=bar." >&2
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
docker_build_args+=("${arg}")
|
|
116
|
+
done <<< "${DOCKER_BUILD_EXTRA_ARGS}"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
if [[ -f "${HOME}/.ssh/cpflow_build_key" ]]; then
|
|
120
|
+
# Mark prep-step ownership so cleanup_build_ssh only removes known_hosts
|
|
121
|
+
# when this action wrote it (see trap above).
|
|
122
|
+
build_ssh_prepped=true
|
|
123
|
+
eval "$(ssh-agent -s)"
|
|
124
|
+
ssh_agent_started=true
|
|
125
|
+
ssh-add "${HOME}/.ssh/cpflow_build_key"
|
|
126
|
+
docker_build_args+=("--ssh=default")
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
echo "🏗️ Building Docker image${PR_INFO} (commit ${COMMIT_SHA})..."
|
|
130
|
+
cpflow build-image -a "${APP_NAME}" --commit="${COMMIT_SHA}" --org="${CONTROL_PLANE_ORG}" "${docker_build_args[@]}"
|
|
131
|
+
echo "✅ Docker image build successful${PR_INFO} (commit ${COMMIT_SHA})"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Delete Control Plane App
|
|
2
|
+
description: Deletes a Control Plane app and all associated resources
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
app_name:
|
|
6
|
+
description: Name of the application to delete
|
|
7
|
+
required: true
|
|
8
|
+
cpln_org:
|
|
9
|
+
description: Control Plane organization name
|
|
10
|
+
required: true
|
|
11
|
+
review_app_prefix:
|
|
12
|
+
description: Prefix used for review app names
|
|
13
|
+
required: true
|
|
14
|
+
|
|
15
|
+
runs:
|
|
16
|
+
using: composite
|
|
17
|
+
steps:
|
|
18
|
+
- name: Delete application
|
|
19
|
+
shell: bash
|
|
20
|
+
run: ${{ github.action_path }}/delete-app.sh
|
|
21
|
+
env:
|
|
22
|
+
APP_NAME: ${{ inputs.app_name }}
|
|
23
|
+
CPLN_ORG: ${{ inputs.cpln_org }}
|
|
24
|
+
REVIEW_APP_PREFIX: ${{ inputs.review_app_prefix }}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
: "${APP_NAME:?APP_NAME environment variable is required}"
|
|
6
|
+
: "${CPLN_ORG:?CPLN_ORG environment variable is required}"
|
|
7
|
+
: "${REVIEW_APP_PREFIX:?REVIEW_APP_PREFIX environment variable is required}"
|
|
8
|
+
|
|
9
|
+
expected_prefix="${REVIEW_APP_PREFIX}-"
|
|
10
|
+
if [[ "$APP_NAME" != "${expected_prefix}"* ]]; then
|
|
11
|
+
echo "❌ ERROR: refusing to delete an app outside the review app prefix" >&2
|
|
12
|
+
echo "App name: $APP_NAME" >&2
|
|
13
|
+
echo "Expected prefix: ${expected_prefix}" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
echo "🔍 Checking if application exists: $APP_NAME"
|
|
18
|
+
exists_output=""
|
|
19
|
+
set +e
|
|
20
|
+
exists_output="$(cpflow exists -a "$APP_NAME" --org "$CPLN_ORG" 2>&1)"
|
|
21
|
+
exists_status=$?
|
|
22
|
+
set -e
|
|
23
|
+
|
|
24
|
+
case "$exists_status" in
|
|
25
|
+
0)
|
|
26
|
+
;;
|
|
27
|
+
3)
|
|
28
|
+
if [[ -n "$exists_output" ]]; then
|
|
29
|
+
printf '%s\n' "$exists_output"
|
|
30
|
+
fi
|
|
31
|
+
echo "⚠️ Application does not exist: $APP_NAME"
|
|
32
|
+
exit 0
|
|
33
|
+
;;
|
|
34
|
+
*)
|
|
35
|
+
echo "❌ ERROR: failed to determine whether application exists: $APP_NAME" >&2
|
|
36
|
+
if [[ -n "$exists_output" ]]; then
|
|
37
|
+
printf '%s\n' "$exists_output" >&2
|
|
38
|
+
fi
|
|
39
|
+
exit "$exists_status"
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
42
|
+
|
|
43
|
+
if [[ -n "$exists_output" ]]; then
|
|
44
|
+
printf '%s\n' "$exists_output"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo "🗑️ Deleting application: $APP_NAME"
|
|
48
|
+
cpflow delete -a "$APP_NAME" --org "$CPLN_ORG" --yes
|
|
49
|
+
|
|
50
|
+
echo "✅ Successfully deleted application: $APP_NAME"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: Detect release phase support
|
|
2
|
+
description: >-
|
|
3
|
+
Inspects .controlplane/controlplane.yml for an app and emits `flag=--run-release-phase`
|
|
4
|
+
when a `release_script:` is configured. Outputs an empty `flag` otherwise.
|
|
5
|
+
|
|
6
|
+
inputs:
|
|
7
|
+
app_name:
|
|
8
|
+
description: cpflow app name to inspect
|
|
9
|
+
required: true
|
|
10
|
+
working_directory:
|
|
11
|
+
description: Directory containing .controlplane/controlplane.yml
|
|
12
|
+
required: false
|
|
13
|
+
default: "."
|
|
14
|
+
|
|
15
|
+
outputs:
|
|
16
|
+
flag:
|
|
17
|
+
description: Either `--run-release-phase` or empty
|
|
18
|
+
value: ${{ steps.detect.outputs.flag }}
|
|
19
|
+
|
|
20
|
+
runs:
|
|
21
|
+
using: composite
|
|
22
|
+
steps:
|
|
23
|
+
- name: Detect release phase support
|
|
24
|
+
id: detect
|
|
25
|
+
shell: bash
|
|
26
|
+
env:
|
|
27
|
+
APP_NAME: ${{ inputs.app_name }}
|
|
28
|
+
WORKING_DIRECTORY: ${{ inputs.working_directory }}
|
|
29
|
+
run: |
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
cd "${WORKING_DIRECTORY}"
|
|
32
|
+
|
|
33
|
+
release_script="$(ruby - "${APP_NAME}" <<'RUBY'
|
|
34
|
+
require "yaml"
|
|
35
|
+
|
|
36
|
+
app_name = ARGV.fetch(0)
|
|
37
|
+
data = YAML.safe_load(File.read(".controlplane/controlplane.yml"), aliases: true)
|
|
38
|
+
apps = data["apps"] || {}
|
|
39
|
+
app_config = apps[app_name]
|
|
40
|
+
|
|
41
|
+
unless app_config
|
|
42
|
+
app_config = apps.find do |name, config|
|
|
43
|
+
config.is_a?(Hash) &&
|
|
44
|
+
config["match_if_app_name_starts_with"] &&
|
|
45
|
+
app_name.start_with?(name)
|
|
46
|
+
end&.last
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
unless app_config.is_a?(Hash)
|
|
50
|
+
warn "Error: app '#{app_name}' is not defined under `apps:` in `.controlplane/controlplane.yml`."
|
|
51
|
+
exit 1
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts app_config["release_script"].to_s
|
|
55
|
+
RUBY
|
|
56
|
+
)"
|
|
57
|
+
|
|
58
|
+
if [[ -n "${release_script}" ]]; then
|
|
59
|
+
echo "flag=--run-release-phase" >> "$GITHUB_OUTPUT"
|
|
60
|
+
else
|
|
61
|
+
echo "flag=" >> "$GITHUB_OUTPUT"
|
|
62
|
+
fi
|