cpflow 4.2.0 → 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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/update-changelog.md +367 -0
  3. data/.github/workflows/claude.yml +5 -0
  4. data/.overcommit.yml +43 -3
  5. data/.rubocop.yml +3 -3
  6. data/CHANGELOG.md +28 -4
  7. data/CONTRIBUTING.md +6 -0
  8. data/Gemfile +8 -7
  9. data/Gemfile.lock +92 -72
  10. data/README.md +43 -15
  11. data/cpflow.gemspec +5 -5
  12. data/docs/ai-github-flow-prompt.md +61 -0
  13. data/docs/ci-automation.md +335 -28
  14. data/docs/commands.md +65 -4
  15. data/docs/releasing.md +153 -0
  16. data/lib/command/ai_github_flow_prompt.rb +47 -0
  17. data/lib/command/base.rb +14 -0
  18. data/lib/command/cleanup_images.rb +1 -1
  19. data/lib/command/cleanup_stale_apps.rb +1 -1
  20. data/lib/command/copy_image_from_upstream.rb +14 -3
  21. data/lib/command/exists.rb +13 -2
  22. data/lib/command/generate.rb +153 -4
  23. data/lib/command/generate_github_actions.rb +170 -0
  24. data/lib/command/generator_helpers.rb +31 -0
  25. data/lib/command/github_flow_readiness.rb +37 -0
  26. data/lib/command/run.rb +1 -1
  27. data/lib/command/terraform/generate.rb +1 -0
  28. data/lib/command/version.rb +1 -0
  29. data/lib/constants/exit_code.rb +1 -0
  30. data/lib/core/controlplane.rb +9 -7
  31. data/lib/core/controlplane_api_direct.rb +3 -3
  32. data/lib/core/github_flow_readiness/checks.rb +143 -0
  33. data/lib/core/github_flow_readiness_service.rb +453 -0
  34. data/lib/core/repo_introspection.rb +118 -0
  35. data/lib/core/terraform_config/dsl.rb +1 -1
  36. data/lib/core/terraform_config/local_variable.rb +1 -1
  37. data/lib/cpflow/version.rb +1 -1
  38. data/lib/cpflow.rb +65 -3
  39. data/lib/generator_templates/Dockerfile +59 -3
  40. data/lib/generator_templates/controlplane.yml +27 -39
  41. data/lib/generator_templates/entrypoint.sh +1 -1
  42. data/lib/generator_templates/release_script.sh +23 -0
  43. data/lib/generator_templates/templates/app.yml +5 -8
  44. data/lib/generator_templates/templates/rails.yml +2 -11
  45. data/lib/generator_templates_sqlite/controlplane.yml +46 -0
  46. data/lib/generator_templates_sqlite/release_script.sh +25 -0
  47. data/lib/generator_templates_sqlite/templates/app.yml +15 -0
  48. data/lib/generator_templates_sqlite/templates/db.yml +6 -0
  49. data/lib/generator_templates_sqlite/templates/rails.yml +32 -0
  50. data/lib/generator_templates_sqlite/templates/storage.yml +6 -0
  51. data/lib/github_flow_templates/.github/actions/cpflow-build-docker-image/action.yml +131 -0
  52. data/lib/github_flow_templates/.github/actions/cpflow-delete-control-plane-app/action.yml +24 -0
  53. data/lib/github_flow_templates/.github/actions/cpflow-delete-control-plane-app/delete-app.sh +50 -0
  54. data/lib/github_flow_templates/.github/actions/cpflow-detect-release-phase/action.yml +62 -0
  55. data/lib/github_flow_templates/.github/actions/cpflow-setup-environment/action.yml +98 -0
  56. data/lib/github_flow_templates/.github/actions/cpflow-validate-config/action.yml +85 -0
  57. data/lib/github_flow_templates/.github/actions/cpflow-wait-for-health/action.yml +92 -0
  58. data/lib/github_flow_templates/.github/cpflow-help.md +47 -0
  59. data/lib/github_flow_templates/.github/workflows/cpflow-cleanup-stale-review-apps.yml +56 -0
  60. data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +142 -0
  61. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +445 -0
  62. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +140 -0
  63. data/lib/github_flow_templates/.github/workflows/cpflow-help-command.yml +53 -0
  64. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +490 -0
  65. data/lib/github_flow_templates/.github/workflows/cpflow-review-app-help.yml +46 -0
  66. data/rakelib/create_release.rake +662 -37
  67. data/script/check_command_docs +4 -2
  68. data/script/check_cpln_links +25 -11
  69. data/script/precommit/check_command_docs +22 -0
  70. data/script/precommit/check_cpln_links +21 -0
  71. data/script/precommit/check_trailing_newlines +68 -0
  72. data/script/precommit/get_changed_files +49 -0
  73. data/script/precommit/ruby_autofix +52 -0
  74. data/script/precommit/ruby_lint +33 -0
  75. metadata +52 -14
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module RepoIntrospection
6
+ DEFAULT_APP_PREFIX = "my-app"
7
+ RUBY_VERSION_DIRECTIVE_PATTERN = /^\s*ruby\s+['"\d]/
8
+ RUBY_VERSION_DIRECTIVE_PREFIX = /^\s*ruby\s+/
9
+
10
+ # Pure string → version-string extractor. Strips a leading `ruby-` prefix and returns
11
+ # the first `MAJOR.MINOR[.PATCH]` found in the source, or nil.
12
+ def self.parse_ruby_version_string(source)
13
+ normalized = source.strip.sub(/\Aruby-/, "")
14
+ normalized[/\d+\.\d+(?:\.\d+)?/]
15
+ end
16
+
17
+ # Returns the first Ruby version string the repo declares, checked in the order Bundler
18
+ # itself uses: `.ruby-version`, then `.tool-versions`, then `Gemfile`. Returns nil when
19
+ # no source declares a version. Both `Command::Generator` and `GithubFlowReadinessService`
20
+ # call into this so a future format change (e.g. `.tool-versions`) only updates here.
21
+ def self.inferred_ruby_version_string(root)
22
+ ruby_version_from_ruby_version_file(root) ||
23
+ ruby_version_from_tool_versions(root) ||
24
+ ruby_version_from_gemfile(root)
25
+ end
26
+
27
+ def self.ruby_version_from_ruby_version_file(root)
28
+ path = File.join(root, ".ruby-version")
29
+ return unless File.file?(path)
30
+
31
+ parse_ruby_version_string(File.read(path))
32
+ end
33
+
34
+ def self.ruby_version_from_tool_versions(root)
35
+ path = File.join(root, ".tool-versions")
36
+ return unless File.file?(path)
37
+
38
+ ruby_line = File.readlines(path, chomp: true).find { |line| line.match?(RUBY_VERSION_DIRECTIVE_PREFIX) }
39
+ return unless ruby_line
40
+
41
+ parse_ruby_version_string(ruby_line.sub(RUBY_VERSION_DIRECTIVE_PREFIX, ""))
42
+ end
43
+
44
+ def self.ruby_version_from_gemfile(root)
45
+ path = File.join(root, "Gemfile")
46
+ return unless File.file?(path)
47
+
48
+ ruby_lines = File.readlines(path, chomp: true).select { |line| line.match?(RUBY_VERSION_DIRECTIVE_PREFIX) }
49
+ ruby_line = ruby_lines.find { |line| line.match?(RUBY_VERSION_DIRECTIVE_PATTERN) }
50
+ warn_dynamic_ruby_directive if ruby_lines.any? && ruby_line.nil?
51
+ return unless ruby_line
52
+
53
+ parse_ruby_version_string(ruby_line.sub(RUBY_VERSION_DIRECTIVE_PREFIX, ""))
54
+ end
55
+
56
+ def self.warn_dynamic_ruby_directive
57
+ return unless ENV["CPFLOW_DEBUG"]
58
+
59
+ warn "cpflow: Gemfile has a dynamic `ruby` directive; falling back to the default Ruby version"
60
+ end
61
+
62
+ # Returns a Control Plane-safe app prefix derived from the basename of `root`:
63
+ # lower-cased, with non-alphanumeric runs collapsed to dashes and stripped from
64
+ # the ends. Falls back to DEFAULT_APP_PREFIX when the result is empty.
65
+ def self.inferred_app_prefix(root)
66
+ sanitized = File.basename(root)
67
+ .downcase
68
+ .gsub(/[^a-z0-9]+/, "-")
69
+ .gsub(/\A-+|-+\z/, "")
70
+
71
+ sanitized.empty? ? DEFAULT_APP_PREFIX : sanitized
72
+ end
73
+
74
+ # Returns true if `config/database.yml` under `root` configures SQLite for production.
75
+ # YAML merge keys such as `<<: *default` are resolved by safe_load, so only the
76
+ # final production hash should be inspected.
77
+ def self.sqlite_database_in_production?(root)
78
+ path = File.join(root, "config/database.yml")
79
+ return false unless File.file?(path)
80
+
81
+ parsed = safe_load_database_yml(File.read(path))
82
+ return false unless parsed.is_a?(Hash)
83
+
84
+ production = parsed["production"]
85
+ return false unless production.is_a?(Hash)
86
+
87
+ url = production["url"]
88
+ return sqlite_database_url?(url) if url.is_a?(String) && !url.strip.empty?
89
+
90
+ sqlite_adapter_in_hash?(production)
91
+ end
92
+
93
+ def self.safe_load_database_yml(raw_contents)
94
+ # ERB conditionals can change YAML structure, so avoid guessing. Output-only
95
+ # ERB is stubbed as a scalar so common Rails defaults like `pool: <%= ... %>`
96
+ # still parse, but control-flow ERB returns unknown. `<%- ... %>` is a
97
+ # whitespace-trimming code tag, not an output tag, so treat it as unknown too.
98
+ # Callers treat unknown as non-SQLite and
99
+ # emit the default Postgres scaffold rather than guessing wrong.
100
+ return nil if raw_contents.match?(/<%(?![=#])/m)
101
+
102
+ stubbed = raw_contents.gsub(/<%=.*?%>/m, "__erb__").gsub(/<%#.*?%>/m, "")
103
+ YAML.safe_load(stubbed, aliases: true, permitted_classes: [Symbol])
104
+ rescue Psych::SyntaxError
105
+ nil
106
+ end
107
+
108
+ def self.sqlite_adapter_in_hash?(config)
109
+ return false unless config.is_a?(Hash)
110
+
111
+ adapter = config["adapter"]
112
+ adapter.is_a?(String) && adapter.strip.start_with?("sqlite3")
113
+ end
114
+
115
+ def self.sqlite_database_url?(url)
116
+ url.strip.downcase.start_with?("sqlite:", "sqlite3:")
117
+ end
118
+ end
@@ -4,7 +4,7 @@ module TerraformConfig
4
4
  module Dsl
5
5
  extend Forwardable
6
6
 
7
- EXPRESSION_PATTERN = /(var|local|cpln_\w+)\./.freeze
7
+ EXPRESSION_PATTERN = /(var|local|cpln_\w+)\./
8
8
 
9
9
  def_delegators :current_context, :put, :output
10
10
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module TerraformConfig
4
4
  class LocalVariable < Base
5
- VARIABLE_NAME_REGEX = /\A[a-zA-Z][a-zA-Z0-9_]*\z/.freeze
5
+ VARIABLE_NAME_REGEX = /\A[a-zA-Z][a-zA-Z0-9_]*\z/
6
6
 
7
7
  attr_reader :variables
8
8
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpflow
4
- VERSION = "4.2.0"
4
+ VERSION = "5.0.0.rc.0"
5
5
  MIN_CPLN_VERSION = "3.1.0"
6
6
  end
data/lib/cpflow.rb CHANGED
@@ -53,9 +53,13 @@ module Cpflow
53
53
  ENV["CPLN_SKIP_UPDATE_CHECK"] = "true"
54
54
  ENV["NODE_NO_WARNINGS"] = "1"
55
55
 
56
- check_cpln_version
57
- check_cpflow_version
58
56
  fix_help_option
57
+ # Thor's `start(args = ARGV.dup, ...)` accepts an explicit argv as the first
58
+ # positional argument. Use that when present so the startup-check decision matches
59
+ # the command Thor is about to dispatch (and so test invocations don't pick up
60
+ # rspec's ARGV by accident).
61
+ argv = args.first.is_a?(Array) ? args.first : ARGV
62
+ run_startup_checks if requires_startup_checks?(argv)
59
63
 
60
64
  super
61
65
  end
@@ -120,13 +124,71 @@ module Cpflow
120
124
  end
121
125
  private_class_method :subcommand?
122
126
 
127
+ def self.run_startup_checks
128
+ check_cpln_version
129
+ check_cpflow_version
130
+ end
131
+ private_class_method :run_startup_checks
132
+
133
+ def self.requires_startup_checks?(argv = ARGV)
134
+ return false if argv.empty?
135
+ return false if help_request?(argv)
136
+ return false if version_flag?(argv)
137
+
138
+ command_class = command_class_for_argv(argv)
139
+ # Default to true when the command name is unrecognized so a typo still gets the
140
+ # version check (Thor's "unknown command" error then surfaces). Pre-PR behavior
141
+ # was always-on; only known commands explicitly opt out.
142
+ command_class ? command_class::REQUIRES_STARTUP_CHECKS : true
143
+ end
144
+ private_class_method :requires_startup_checks?
145
+
146
+ def self.help_request?(argv)
147
+ help_mappings = Thor::HELP_MAPPINGS + ["help"]
148
+ help_mappings.include?(argv.first)
149
+ end
150
+ private_class_method :help_request?
151
+
152
+ def self.version_flag?(argv)
153
+ %w[--version -v].include?(argv.first)
154
+ end
155
+ private_class_method :version_flag?
156
+
157
+ def self.command_class_for_argv(argv)
158
+ first_arg = argv[0]
159
+ return if first_arg.nil?
160
+
161
+ return subcommand_class_for_argv(first_arg, argv[1]) if subcommand_names.include?(first_arg)
162
+
163
+ top_level_command_class_for(first_arg)
164
+ end
165
+ private_class_method :command_class_for_argv
166
+
167
+ def self.subcommand_class_for_argv(subcommand_name, command_name)
168
+ return if command_name.nil?
169
+
170
+ all_base_commands[:"#{subcommand_name}_#{command_name.tr('-', '_')}"] ||
171
+ all_base_commands.values.find do |command_class|
172
+ subcommand_name == command_class::SUBCOMMAND_NAME && command_name == command_class::NAME
173
+ end
174
+ end
175
+ private_class_method :subcommand_class_for_argv
176
+
177
+ def self.top_level_command_class_for(command_name)
178
+ all_base_commands[command_name.tr("-", "_").to_sym] ||
179
+ all_base_commands.values.find do |command_class|
180
+ command_class::SUBCOMMAND_NAME.nil? && command_name == command_class::NAME
181
+ end
182
+ end
183
+ private_class_method :top_level_command_class_for
184
+
123
185
  # Needed to silence deprecation warning
124
186
  def self.exit_on_failure?
125
187
  true
126
188
  end
127
189
 
128
190
  # Needed to be able to use "run" as a command
129
- def self.is_thor_reserved_word?(word, type) # rubocop:disable Naming/PredicateName
191
+ def self.is_thor_reserved_word?(word, type) # rubocop:disable Naming/PredicatePrefix
130
192
  return false if word == "run"
131
193
 
132
194
  super
@@ -1,23 +1,79 @@
1
- FROM ruby:3.1.2
1
+ ARG RUBY_VERSION=__RUBY_VERSION__
2
2
 
3
- RUN apt-get update
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 'staging production' && \
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
- apps:
38
- my-app-staging:
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
- # You can also opt out of allowing the use of CPLN_ORG and CPLN_APP env vars per app.
50
- # It's recommended to leave this off for production, to avoid any accidents.
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
- # Allows running the command `cpflow promote-app-from-upstream -a my-app-production`
57
- # to promote the staging app to production.
58
- upstream: my-app-staging
59
- my-app-other:
35
+ upstream: __APP_PREFIX__-staging
36
+
37
+ apps:
38
+ __APP_PREFIX__-staging:
60
39
  <<: *common
61
- # You can specify a different `Dockerfile` relative to the `.controlplane/` directory (defaults to "Dockerfile").
62
- dockerfile: ../some_other/Dockerfile
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
@@ -4,5 +4,5 @@
4
4
  echo " -- Preparing database"
5
5
  rails db:prepare
6
6
 
7
- echo " -- Finishing entrypoint.sh, executing '$@'"
7
+ echo " -- Finishing entrypoint.sh, executing command"
8
8
  exec "$@"
@@ -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: 'true'
17
-
18
- # Part of standard configuration
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
- # Max of 1 effectively disables autoscaling, so a like a Heroku dyno count of 1
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,6 @@
1
+ kind: volumeset
2
+ name: app-db
3
+ spec:
4
+ fileSystemType: ext4
5
+ initialCapacity: 5
6
+ performanceClass: general-purpose-ssd
@@ -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,6 @@
1
+ kind: volumeset
2
+ name: app-storage
3
+ spec:
4
+ fileSystemType: ext4
5
+ initialCapacity: 10
6
+ performanceClass: general-purpose-ssd