cpflow 3.0.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 +7 -0
- data/.github/workflows/check_cpln_links.yml +19 -0
- data/.github/workflows/command_docs.yml +24 -0
- data/.github/workflows/rspec-shared.yml +56 -0
- data/.github/workflows/rspec.yml +28 -0
- data/.github/workflows/rubocop.yml +24 -0
- data/.gitignore +18 -0
- data/.overcommit.yml +16 -0
- data/.rubocop.yml +22 -0
- data/.simplecov_spawn.rb +10 -0
- data/CHANGELOG.md +259 -0
- data/CONTRIBUTING.md +73 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +126 -0
- data/LICENSE +21 -0
- data/README.md +546 -0
- data/Rakefile +21 -0
- data/bin/cpflow +6 -0
- data/cpflow +6 -0
- data/cpflow.gemspec +41 -0
- data/docs/assets/grafana-alert.png +0 -0
- data/docs/assets/memcached.png +0 -0
- data/docs/assets/sidekiq-pre-stop-hook.png +0 -0
- data/docs/commands.md +454 -0
- data/docs/dns.md +15 -0
- data/docs/migrating.md +262 -0
- data/docs/postgres.md +436 -0
- data/docs/redis.md +128 -0
- data/docs/secrets-and-env-values.md +42 -0
- data/docs/tips.md +150 -0
- data/docs/troubleshooting.md +6 -0
- data/examples/circleci.yml +104 -0
- data/examples/controlplane.yml +159 -0
- data/lib/command/apply_template.rb +209 -0
- data/lib/command/base.rb +540 -0
- data/lib/command/build_image.rb +49 -0
- data/lib/command/cleanup_images.rb +136 -0
- data/lib/command/cleanup_stale_apps.rb +79 -0
- data/lib/command/config.rb +48 -0
- data/lib/command/copy_image_from_upstream.rb +108 -0
- data/lib/command/delete.rb +149 -0
- data/lib/command/deploy_image.rb +56 -0
- data/lib/command/doctor.rb +47 -0
- data/lib/command/env.rb +22 -0
- data/lib/command/exists.rb +23 -0
- data/lib/command/generate.rb +45 -0
- data/lib/command/info.rb +222 -0
- data/lib/command/latest_image.rb +19 -0
- data/lib/command/logs.rb +49 -0
- data/lib/command/maintenance.rb +42 -0
- data/lib/command/maintenance_off.rb +62 -0
- data/lib/command/maintenance_on.rb +62 -0
- data/lib/command/maintenance_set_page.rb +34 -0
- data/lib/command/no_command.rb +23 -0
- data/lib/command/open.rb +33 -0
- data/lib/command/open_console.rb +26 -0
- data/lib/command/promote_app_from_upstream.rb +38 -0
- data/lib/command/ps.rb +41 -0
- data/lib/command/ps_restart.rb +37 -0
- data/lib/command/ps_start.rb +51 -0
- data/lib/command/ps_stop.rb +82 -0
- data/lib/command/ps_wait.rb +40 -0
- data/lib/command/run.rb +573 -0
- data/lib/command/setup_app.rb +113 -0
- data/lib/command/test.rb +23 -0
- data/lib/command/version.rb +18 -0
- data/lib/constants/exit_code.rb +7 -0
- data/lib/core/config.rb +316 -0
- data/lib/core/controlplane.rb +552 -0
- data/lib/core/controlplane_api.rb +170 -0
- data/lib/core/controlplane_api_direct.rb +112 -0
- data/lib/core/doctor_service.rb +104 -0
- data/lib/core/helpers.rb +26 -0
- data/lib/core/shell.rb +100 -0
- data/lib/core/template_parser.rb +76 -0
- data/lib/cpflow/version.rb +6 -0
- data/lib/cpflow.rb +288 -0
- data/lib/deprecated_commands.json +9 -0
- data/lib/generator_templates/Dockerfile +27 -0
- data/lib/generator_templates/controlplane.yml +62 -0
- data/lib/generator_templates/entrypoint.sh +8 -0
- data/lib/generator_templates/templates/app.yml +21 -0
- data/lib/generator_templates/templates/postgres.yml +176 -0
- data/lib/generator_templates/templates/rails.yml +36 -0
- data/rakelib/create_release.rake +81 -0
- data/script/add_command +37 -0
- data/script/check_command_docs +3 -0
- data/script/check_cpln_links +45 -0
- data/script/rename_command +43 -0
- data/script/update_command_docs +62 -0
- data/templates/app.yml +13 -0
- data/templates/daily-task.yml +32 -0
- data/templates/maintenance.yml +25 -0
- data/templates/memcached.yml +24 -0
- data/templates/postgres.yml +32 -0
- data/templates/rails.yml +27 -0
- data/templates/redis.yml +21 -0
- data/templates/redis2.yml +37 -0
- data/templates/sidekiq.yml +38 -0
- metadata +341 -0
data/lib/cpflow.rb
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
require "dotenv/load"
|
5
|
+
require "cgi"
|
6
|
+
require "json"
|
7
|
+
require "jwt"
|
8
|
+
require "net/http"
|
9
|
+
require "open3"
|
10
|
+
require "pathname"
|
11
|
+
require "tempfile"
|
12
|
+
require "thor"
|
13
|
+
require "yaml"
|
14
|
+
|
15
|
+
require_relative "constants/exit_code"
|
16
|
+
|
17
|
+
# We need to require base before all commands, since the commands inherit from it
|
18
|
+
require_relative "command/base"
|
19
|
+
|
20
|
+
modules = Dir["#{__dir__}/**/*.rb"].reject do |file|
|
21
|
+
file == __FILE__ || file.end_with?("base.rb")
|
22
|
+
end
|
23
|
+
modules.sort.each { require(_1) }
|
24
|
+
|
25
|
+
# NOTE: this snippet combines all subprocesses into a group and kills all on exit to avoid hanging orphans
|
26
|
+
$child_pids = [] # rubocop:disable Style/GlobalVars
|
27
|
+
at_exit do
|
28
|
+
$child_pids.each do |pid| # rubocop:disable Style/GlobalVars
|
29
|
+
Process.kill("TERM", pid)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Fix for https://github.com/erikhuda/thor/issues/398
|
34
|
+
# Copied from https://github.com/rails/thor/issues/398#issuecomment-622988390
|
35
|
+
class Thor
|
36
|
+
module Shell
|
37
|
+
class Basic
|
38
|
+
def print_wrapped(message, options = {})
|
39
|
+
indent = (options[:indent] || 0).to_i
|
40
|
+
if indent.zero?
|
41
|
+
stdout.puts(message)
|
42
|
+
else
|
43
|
+
message.each_line do |message_line|
|
44
|
+
stdout.print(" " * indent)
|
45
|
+
stdout.puts(message_line.chomp)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module Cpflow
|
54
|
+
class Error < StandardError; end
|
55
|
+
|
56
|
+
class Cli < Thor # rubocop:disable Metrics/ClassLength
|
57
|
+
package_name "cpflow"
|
58
|
+
default_task :no_command
|
59
|
+
|
60
|
+
def self.start(*args)
|
61
|
+
ENV["CPLN_SKIP_UPDATE_CHECK"] = "true"
|
62
|
+
|
63
|
+
check_cpln_version
|
64
|
+
check_cpflow_version
|
65
|
+
fix_help_option
|
66
|
+
|
67
|
+
super(*args)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.check_cpln_version # rubocop:disable Metrics/MethodLength
|
71
|
+
return if @checked_cpln_version
|
72
|
+
|
73
|
+
@checked_cpln_version = true
|
74
|
+
|
75
|
+
result = ::Shell.cmd("cpln", "--version", capture_stderr: true)
|
76
|
+
if result[:success]
|
77
|
+
data = JSON.parse(result[:output])
|
78
|
+
|
79
|
+
version = data["npm"]
|
80
|
+
min_version = Cpflow::MIN_CPLN_VERSION
|
81
|
+
if Gem::Version.new(version) < Gem::Version.new(min_version)
|
82
|
+
::Shell.abort("Current 'cpln' version: #{version}. Minimum supported version: #{min_version}. " \
|
83
|
+
"Please update it with 'npm update -g @controlplane/cli'.")
|
84
|
+
end
|
85
|
+
else
|
86
|
+
::Shell.abort("Can't find 'cpln' executable. Please install it with 'npm install -g @controlplane/cli'.")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.check_cpflow_version # rubocop:disable Metrics/MethodLength
|
91
|
+
return if @checked_cpflow_version
|
92
|
+
|
93
|
+
@checked_cpflow_version = true
|
94
|
+
|
95
|
+
result = ::Shell.cmd("gem", "search", "^cpflow$", "--remote", capture_stderr: true)
|
96
|
+
return unless result[:success]
|
97
|
+
|
98
|
+
matches = result[:output].match(/cpflow \((.+)\)/)
|
99
|
+
return unless matches
|
100
|
+
|
101
|
+
version = Cpflow::VERSION
|
102
|
+
latest_version = matches[1]
|
103
|
+
return unless Gem::Version.new(version) < Gem::Version.new(latest_version)
|
104
|
+
|
105
|
+
::Shell.warn("You are not using the latest 'cpflow' version. Please update it with 'gem update cpflow'.")
|
106
|
+
$stderr.puts
|
107
|
+
end
|
108
|
+
|
109
|
+
# This is so that we're able to run `cpflow COMMAND --help` to print the help
|
110
|
+
# (it basically changes it to `cpflow --help COMMAND`, which Thor recognizes)
|
111
|
+
# Based on https://stackoverflow.com/questions/49042591/how-to-add-help-h-flag-to-thor-command
|
112
|
+
def self.fix_help_option
|
113
|
+
help_mappings = Thor::HELP_MAPPINGS + ["help"]
|
114
|
+
matches = help_mappings & ARGV
|
115
|
+
matches.each do |match|
|
116
|
+
ARGV.delete(match)
|
117
|
+
ARGV.unshift(match)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Needed to silence deprecation warning
|
122
|
+
def self.exit_on_failure?
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
# Needed to be able to use "run" as a command
|
127
|
+
def self.is_thor_reserved_word?(word, type) # rubocop:disable Naming/PredicateName
|
128
|
+
return false if word == "run"
|
129
|
+
|
130
|
+
super(word, type)
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.deprecated_commands
|
134
|
+
@deprecated_commands ||= begin
|
135
|
+
deprecated_commands_file_path = "#{__dir__}/deprecated_commands.json"
|
136
|
+
deprecated_commands_data = File.binread(deprecated_commands_file_path)
|
137
|
+
deprecated_commands = JSON.parse(deprecated_commands_data)
|
138
|
+
deprecated_commands.to_h do |old_command_name, new_command_name|
|
139
|
+
file_name = new_command_name.gsub(/[^A-Za-z]/, "_")
|
140
|
+
class_name = file_name.split("_").map(&:capitalize).join
|
141
|
+
|
142
|
+
[old_command_name, Object.const_get("::Command::#{class_name}")]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.all_base_commands
|
148
|
+
::Command::Base.all_commands.merge(deprecated_commands)
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.process_option_params(params)
|
152
|
+
# Ensures that if no value is provided for a non-boolean option (e.g., `cpflow command --option`),
|
153
|
+
# it defaults to an empty string instead of the option name (which is the default Thor behavior)
|
154
|
+
params[:lazy_default] ||= "" if params[:type] != :boolean
|
155
|
+
|
156
|
+
params
|
157
|
+
end
|
158
|
+
|
159
|
+
@commands_with_required_options = []
|
160
|
+
@commands_with_extra_options = []
|
161
|
+
|
162
|
+
::Command::Base.common_options.each do |option|
|
163
|
+
params = process_option_params(option[:params])
|
164
|
+
class_option(option[:name], **params)
|
165
|
+
end
|
166
|
+
|
167
|
+
all_base_commands.each do |command_key, command_class| # rubocop:disable Metrics/BlockLength
|
168
|
+
deprecated = deprecated_commands[command_key]
|
169
|
+
|
170
|
+
name = command_class::NAME
|
171
|
+
name_for_method = deprecated ? command_key : name.tr("-", "_")
|
172
|
+
usage = command_class::USAGE.empty? ? name : command_class::USAGE
|
173
|
+
requires_args = command_class::REQUIRES_ARGS
|
174
|
+
default_args = command_class::DEFAULT_ARGS
|
175
|
+
command_options = command_class::OPTIONS
|
176
|
+
accepts_extra_options = command_class::ACCEPTS_EXTRA_OPTIONS
|
177
|
+
description = command_class::DESCRIPTION
|
178
|
+
long_description = command_class::LONG_DESCRIPTION
|
179
|
+
examples = command_class::EXAMPLES
|
180
|
+
hide = command_class::HIDE || deprecated
|
181
|
+
with_info_header = command_class::WITH_INFO_HEADER
|
182
|
+
validations = command_class::VALIDATIONS
|
183
|
+
|
184
|
+
long_description += "\n#{examples}" if examples.length.positive?
|
185
|
+
|
186
|
+
# `handle_argument_error` does not exist in the context below,
|
187
|
+
# so we store it here to be able to use it
|
188
|
+
raise_args_error = ->(*args) { handle_argument_error(commands[name_for_method], ArgumentError, *args) }
|
189
|
+
|
190
|
+
desc(usage, description, hide: hide)
|
191
|
+
long_desc(long_description)
|
192
|
+
|
193
|
+
command_options.each do |option|
|
194
|
+
params = process_option_params(option[:params])
|
195
|
+
method_option(option[:name], **params)
|
196
|
+
end
|
197
|
+
|
198
|
+
# We'll handle required options manually in `Config`
|
199
|
+
required_options = command_options.select { |option| option[:params][:required] }.map { |option| option[:name] }
|
200
|
+
@commands_with_required_options.push(name_for_method.to_sym) if required_options.any?
|
201
|
+
|
202
|
+
@commands_with_extra_options.push(name_for_method.to_sym) if accepts_extra_options
|
203
|
+
|
204
|
+
define_method(name_for_method) do |*provided_args| # rubocop:disable Metrics/BlockLength, Metrics/MethodLength
|
205
|
+
if deprecated
|
206
|
+
normalized_old_name = ::Helpers.normalize_command_name(command_key)
|
207
|
+
::Shell.warn_deprecated("Command '#{normalized_old_name}' is deprecated, " \
|
208
|
+
"please use '#{name}' instead.")
|
209
|
+
$stderr.puts
|
210
|
+
end
|
211
|
+
|
212
|
+
args = if provided_args.length.positive?
|
213
|
+
provided_args
|
214
|
+
else
|
215
|
+
default_args
|
216
|
+
end
|
217
|
+
|
218
|
+
if (args.empty? && requires_args) || (!args.empty? && !requires_args && !accepts_extra_options)
|
219
|
+
raise_args_error.call(args, nil)
|
220
|
+
end
|
221
|
+
|
222
|
+
begin
|
223
|
+
Cpflow::Cli.validate_options!(options)
|
224
|
+
|
225
|
+
config = Config.new(args, options, required_options)
|
226
|
+
|
227
|
+
Cpflow::Cli.show_info_header(config) if with_info_header
|
228
|
+
|
229
|
+
if validations.any? && ENV.fetch("DISABLE_VALIDATIONS", nil) != "true"
|
230
|
+
doctor = DoctorService.new(config)
|
231
|
+
doctor.run_validations(validations, silent_if_passing: true)
|
232
|
+
end
|
233
|
+
|
234
|
+
command_class.new(config).call
|
235
|
+
rescue RuntimeError => e
|
236
|
+
::Shell.abort(e.message)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
rescue StandardError => e
|
240
|
+
::Shell.abort("Unable to load command: #{e.message}")
|
241
|
+
end
|
242
|
+
|
243
|
+
disable_required_check!(*@commands_with_required_options)
|
244
|
+
check_unknown_options!(except: @commands_with_extra_options)
|
245
|
+
stop_on_unknown_option!
|
246
|
+
|
247
|
+
def self.validate_options!(options) # rubocop:disable Metrics/MethodLength
|
248
|
+
options.each do |name, value|
|
249
|
+
normalized_name = ::Helpers.normalize_option_name(name)
|
250
|
+
raise "No value provided for option #{normalized_name}." if value.to_s.strip.empty?
|
251
|
+
|
252
|
+
option = ::Command::Base.all_options.find { |current_option| current_option[:name].to_s == name }
|
253
|
+
if option[:new_name]
|
254
|
+
normalized_new_name = ::Helpers.normalize_option_name(option[:new_name])
|
255
|
+
::Shell.warn_deprecated("Option #{normalized_name} is deprecated, " \
|
256
|
+
"please use #{normalized_new_name} instead.")
|
257
|
+
$stderr.puts
|
258
|
+
end
|
259
|
+
|
260
|
+
params = option[:params]
|
261
|
+
next unless params[:valid_regex]
|
262
|
+
|
263
|
+
raise "Invalid value provided for option #{normalized_name}." unless value.match?(params[:valid_regex])
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.show_info_header(config) # rubocop:disable Metrics/MethodLength
|
268
|
+
return if @showed_info_header
|
269
|
+
|
270
|
+
rows = {}
|
271
|
+
rows["ORG"] = config.org || "NOT PROVIDED!"
|
272
|
+
rows["ORG"] += " (comes from CPLN_ORG env var)" if config.org_comes_from_env
|
273
|
+
rows["APP"] = config.app || "NOT PROVIDED!"
|
274
|
+
rows["APP"] += " (comes from CPLN_APP env var)" if config.app_comes_from_env
|
275
|
+
|
276
|
+
rows.each do |key, value|
|
277
|
+
puts "#{key}: #{value}"
|
278
|
+
end
|
279
|
+
|
280
|
+
@showed_info_header = true
|
281
|
+
|
282
|
+
# Add a newline after the info header
|
283
|
+
puts
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
Shell.trap_interrupt unless ENV.fetch("DISABLE_INTERRUPT_TRAP", nil) == "true"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
FROM ruby:3.1.2
|
2
|
+
|
3
|
+
RUN apt-get update
|
4
|
+
|
5
|
+
WORKDIR /app
|
6
|
+
|
7
|
+
# install ruby gems
|
8
|
+
COPY Gemfile* ./
|
9
|
+
|
10
|
+
RUN bundle config set without 'development test' && \
|
11
|
+
bundle config set with 'staging production' && \
|
12
|
+
bundle install --jobs=3 --retry=3
|
13
|
+
|
14
|
+
COPY . ./
|
15
|
+
|
16
|
+
ENV RAILS_ENV=production
|
17
|
+
|
18
|
+
# compiling assets requires any value for ENV of SECRET_KEY_BASE
|
19
|
+
ENV SECRET_KEY_BASE=NOT_USED_NON_BLANK
|
20
|
+
|
21
|
+
RUN rails assets:precompile
|
22
|
+
|
23
|
+
# add entrypoint
|
24
|
+
COPY .controlplane/entrypoint.sh ./
|
25
|
+
ENTRYPOINT ["/app/entrypoint.sh"]
|
26
|
+
|
27
|
+
CMD ["rails", "s"]
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Keys beginning with "cpln_" correspond to your settings in Control Plane.
|
2
|
+
|
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
|
+
allow_org_override_by_env: true
|
6
|
+
allow_app_override_by_env: true
|
7
|
+
|
8
|
+
aliases:
|
9
|
+
common: &common
|
10
|
+
# Organization name for staging (customize to your needs).
|
11
|
+
# Production apps will use a different organization, specified below, for security.
|
12
|
+
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
|
+
default_location: aws-us-east-2
|
17
|
+
|
18
|
+
# Configure the workload name used as a template for one-off scripts, like a Heroku one-off dyno.
|
19
|
+
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
|
+
app_workloads:
|
27
|
+
- 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
|
+
additional_workloads:
|
32
|
+
- postgres
|
33
|
+
|
34
|
+
# Configure the workload name used when maintenance mode is on (defaults to "maintenance")
|
35
|
+
maintenance_workload: maintenance
|
36
|
+
|
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
|
48
|
+
|
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.
|
51
|
+
allow_org_override_by_env: false
|
52
|
+
allow_app_override_by_env: false
|
53
|
+
|
54
|
+
# Use a different organization for production.
|
55
|
+
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:
|
60
|
+
<<: *common
|
61
|
+
# You can specify a different `Dockerfile` relative to the `.controlplane/` directory (defaults to "Dockerfile").
|
62
|
+
dockerfile: ../some_other/Dockerfile
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Template setup of the GVC, roughly corresponding to a Heroku app
|
2
|
+
kind: gvc
|
3
|
+
name: {{APP_NAME}}
|
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
|
+
env:
|
8
|
+
- 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
|
+
value: 'postgres://the_user:the_password@postgres.{{APP_NAME}}.cpln.local:5432/{{APP_NAME}}'
|
13
|
+
- name: RAILS_ENV
|
14
|
+
value: production
|
15
|
+
- name: RAILS_SERVE_STATIC_FILES
|
16
|
+
value: 'true'
|
17
|
+
|
18
|
+
# Part of standard configuration
|
19
|
+
staticPlacement:
|
20
|
+
locationLinks:
|
21
|
+
- {{APP_LOCATION_LINK}}
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# Comes from example at
|
2
|
+
# https://github.com/controlplane-com/examples/blob/main/examples/postgres/manifest.yaml
|
3
|
+
|
4
|
+
kind: volumeset
|
5
|
+
name: postgres-poc-vs
|
6
|
+
description: postgres-poc-vs
|
7
|
+
spec:
|
8
|
+
autoscaling:
|
9
|
+
maxCapacity: 1000
|
10
|
+
minFreePercentage: 1
|
11
|
+
scalingFactor: 1.1
|
12
|
+
fileSystemType: ext4
|
13
|
+
initialCapacity: 10
|
14
|
+
performanceClass: general-purpose-ssd
|
15
|
+
snapshots:
|
16
|
+
createFinalSnapshot: true
|
17
|
+
retentionDuration: 7d
|
18
|
+
|
19
|
+
---
|
20
|
+
kind: secret
|
21
|
+
name: postgres-poc-credentials
|
22
|
+
description: ''
|
23
|
+
type: dictionary
|
24
|
+
data:
|
25
|
+
password: the_password #Replace this with a real password
|
26
|
+
username: the_user #Replace this with a real username
|
27
|
+
|
28
|
+
---
|
29
|
+
kind: secret
|
30
|
+
name: postgres-poc-entrypoint-script
|
31
|
+
type: opaque
|
32
|
+
data:
|
33
|
+
encoding: base64
|
34
|
+
payload: >-
|
35
|
+
IyEvdXNyL2Jpbi9lbnYgYmFzaAoKc291cmNlIC91c3IvbG9jYWwvYmluL2RvY2tlci1lbnRyeXBvaW50LnNoCgppbnN0YWxsX2RlcHMoKSB7CiAgYXB0LWdldCB1cGRhdGUgLXkgPiAvZGV2L251bGwKICBhcHQtZ2V0IGluc3RhbGwgY3VybCAteSA+IC9kZXYvbnVsbAogIGFwdC1nZXQgaW5zdGFsbCB1bnppcCAteSA+IC9kZXYvbnVsbAogIGN1cmwgImh0dHBzOi8vYXdzY2xpLmFtYXpvbmF3cy5jb20vYXdzY2xpLWV4ZS1saW51eC14ODZfNjQuemlwIiAtbyAiYXdzY2xpdjIuemlwIiA+IC9kZXYvbnVsbAogIHVuemlwIGF3c2NsaXYyLnppcCA+IC9kZXYvbnVsbAogIC4vYXdzL2luc3RhbGwgPiAvZGV2L251bGwKfQoKZGJfaGFzX2JlZW5fcmVzdG9yZWQoKSB7CiAgaWYgWyAhIC1mICIkUEdEQVRBL0NQTE5fUkVTVE9SRUQiIF07IHRoZW4KICAgIHJldHVybiAxCiAgZmkKCiAgaWYgISBncmVwIC1xICJcLT4gJDEkIiAiJFBHREFUQS9DUExOX1JFU1RPUkVEIjsgdGhlbgogICAgcmV0dXJuIDEKICBlbHNlCiAgICByZXR1cm4gMAogIGZpCn0KCnJlc3RvcmVfZGIoKSB7Cgl3aGlsZSBbICEgLVMgL3Zhci9ydW4vcG9zdGdyZXNxbC8ucy5QR1NRTC41NDMyIF0KCWRvCiAgICBlY2hvICJXYWl0aW5nIDVzIGZvciBkYiBzb2NrZXQgdG8gYmUgYXZhaWxhYmxlIgogICAgc2xlZXAgNXMKICBkb25lCgoKCWlmICEgZGJfaGFzX2JlZW5fcmVzdG9yZWQgIiQxIjsgdGhlbgoJICBlY2hvICJJdCBhcHBlYXJzIGRiICckMScgaGFzIG5vdCB5ZXQgYmVlbiByZXN0b3JlZCBmcm9tIFMzLiBBdHRlbXB0aW5nIHRvIHJlc3RvcmUgJDEgZnJvbSAkMiIKCSAgaW5zdGFsbF9kZXBzCgkgIGRvY2tlcl9zZXR1cF9kYiAjRW5zdXJlcyAkUE9TVEdSRVNfREIgZXhpc3RzIChkZWZpbmVkIGluIHRoZSBlbnRyeXBvaW50IHNjcmlwdCBmcm9tIHRoZSBwb3N0Z3JlcyBkb2NrZXIgaW1hZ2UpCgkgIGF3cyBzMyBjcCAiJDIiIC0gfCBwZ19yZXN0b3JlIC0tY2xlYW4gLS1uby1hY2wgLS1uby1vd25lciAtZCAiJDEiIC1VICIkUE9TVEdSRVNfVVNFUiIKCSAgZWNobyAiJChkYXRlKTogJDIgLT4gJDEiIHwgY2F0ID4+ICIkUEdEQVRBL0NQTE5fUkVTVE9SRUQiCgllbHNlCgkgIGVjaG8gIkRiICckMScgYWxyZWFkeSBleGlzdHMuIFJlYWR5ISIKICBmaQp9CgpfbWFpbiAiJEAiICYKYmFja2dyb3VuZFByb2Nlc3M9JCEKCmlmIFsgLW4gIiRQT1NUR1JFU19BUkNISVZFX1VSSSIgXTsgdGhlbgogIHJlc3RvcmVfZGIgIiRQT1NUR1JFU19EQiIgIiRQT1NUR1JFU19BUkNISVZFX1VSSSIKZWxzZQogIGVjaG8gIkRlY2xpbmluZyB0byByZXN0b3JlIHRoZSBkYiBiZWNhdXNlIG5vIGFyY2hpdmUgdXJpIHdhcyBwcm92aWRlZCIKZmkKCndhaXQgJGJhY2tncm91bmRQcm9jZXNzCgoK
|
36
|
+
|
37
|
+
#Here is the ASCII-encoded version of the script in the secret above
|
38
|
+
#!/usr/bin/env bash
|
39
|
+
#
|
40
|
+
#source /usr/local/bin/docker-entrypoint.sh
|
41
|
+
#
|
42
|
+
#install_deps() {
|
43
|
+
# apt-get update -y > /dev/null
|
44
|
+
# apt-get install curl -y > /dev/null
|
45
|
+
# apt-get install unzip -y > /dev/null
|
46
|
+
# curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" > /dev/null
|
47
|
+
# unzip awscliv2.zip > /dev/null
|
48
|
+
# ./aws/install > /dev/null
|
49
|
+
#}
|
50
|
+
#
|
51
|
+
#db_has_been_restored() {
|
52
|
+
# if [ ! -f "$PGDATA/CPLN_RESTORED" ]; then
|
53
|
+
# return 1
|
54
|
+
# fi
|
55
|
+
#
|
56
|
+
# if ! grep -q "\-> $1$" "$PGDATA/CPLN_RESTORED"; then
|
57
|
+
# return 1
|
58
|
+
# else
|
59
|
+
# return 0
|
60
|
+
# fi
|
61
|
+
#}
|
62
|
+
#
|
63
|
+
#restore_db() {
|
64
|
+
# while [ ! -S /var/run/postgresql/.s.PGSQL.5432 ]
|
65
|
+
# do
|
66
|
+
# echo "Waiting 5s for db socket to be available"
|
67
|
+
# sleep 5s
|
68
|
+
# done
|
69
|
+
#
|
70
|
+
#
|
71
|
+
# if ! db_has_been_restored "$1"; then
|
72
|
+
# echo "It appears db '$1' has not yet been restored from S3. Attempting to restore $1 from $2"
|
73
|
+
# install_deps
|
74
|
+
# docker_setup_db #Ensures $POSTGRES_DB exists (defined in the entrypoint script from the postgres docker image)
|
75
|
+
# aws s3 cp "$2" - | pg_restore --clean --no-acl --no-owner -d "$1" -U "$POSTGRES_USER"
|
76
|
+
# echo "$(date): $2 -> $1" | cat >> "$PGDATA/CPLN_RESTORED"
|
77
|
+
# else
|
78
|
+
# echo "Db '$1' already exists. Ready!"
|
79
|
+
# fi
|
80
|
+
#}
|
81
|
+
#
|
82
|
+
#_main "$@" &
|
83
|
+
#backgroundProcess=$!
|
84
|
+
#
|
85
|
+
#if [ -n "$POSTGRES_ARCHIVE_URI" ]; then
|
86
|
+
# restore_db "$POSTGRES_DB" "$POSTGRES_ARCHIVE_URI"
|
87
|
+
#else
|
88
|
+
# echo "Declining to restore the db because no archive uri was provided"
|
89
|
+
#fi
|
90
|
+
#
|
91
|
+
#wait $backgroundProcess
|
92
|
+
|
93
|
+
---
|
94
|
+
kind: identity
|
95
|
+
name: postgres-poc-identity
|
96
|
+
description: postgres-poc-identity
|
97
|
+
|
98
|
+
---
|
99
|
+
kind: policy
|
100
|
+
name: postgres-poc-access
|
101
|
+
description: postgres-poc-access
|
102
|
+
bindings:
|
103
|
+
- permissions:
|
104
|
+
- reveal
|
105
|
+
# Uncomment these two
|
106
|
+
# - use
|
107
|
+
# - view
|
108
|
+
principalLinks:
|
109
|
+
- //gvc/{{APP_NAME}}/identity/postgres-poc-identity
|
110
|
+
targetKind: secret
|
111
|
+
targetLinks:
|
112
|
+
- //secret/postgres-poc-credentials
|
113
|
+
- //secret/postgres-poc-entrypoint-script
|
114
|
+
|
115
|
+
---
|
116
|
+
kind: workload
|
117
|
+
name: postgres
|
118
|
+
description: postgres
|
119
|
+
spec:
|
120
|
+
type: stateful
|
121
|
+
containers:
|
122
|
+
- cpu: 1000m
|
123
|
+
memory: 512Mi
|
124
|
+
env:
|
125
|
+
# Uncomment next two envs will cause the db to be restored from the archive uri
|
126
|
+
# - name: POSTGRES_ARCHIVE_URI #Use this var to control the automatic restore behavior. If you leave it out, the db will start empty.
|
127
|
+
# value: s3://YOUR_BUCKET/PATH_TO_ARCHIVE_FILE
|
128
|
+
# - name: POSTGRES_DB #The name of the initial db in case of doing a restore
|
129
|
+
# value: test
|
130
|
+
- name: PGDATA #The location postgres stores the db. This can be anything other than /var/lib/postgresql/data, but it must be inside the mount point for the volume set
|
131
|
+
value: "/var/lib/postgresql/data/pg_data"
|
132
|
+
- name: POSTGRES_PASSWORD #The password for the default user
|
133
|
+
value: cpln://secret/postgres-poc-credentials.password
|
134
|
+
- name: POSTGRES_USER #The name of the default user
|
135
|
+
value: cpln://secret/postgres-poc-credentials.username
|
136
|
+
name: stateful
|
137
|
+
image: postgres:15
|
138
|
+
command: /bin/bash
|
139
|
+
args:
|
140
|
+
- "-c"
|
141
|
+
- "cat /usr/local/bin/cpln-entrypoint.sh >> ./cpln-entrypoint.sh && chmod u+x ./cpln-entrypoint.sh && ./cpln-entrypoint.sh postgres"
|
142
|
+
#command: "cpln-entrypoint.sh"
|
143
|
+
#args:
|
144
|
+
# - "postgres"
|
145
|
+
ports:
|
146
|
+
- number: 5432
|
147
|
+
protocol: tcp
|
148
|
+
volumes:
|
149
|
+
- uri: cpln://volumeset/postgres-poc-vs
|
150
|
+
path: "/var/lib/postgresql/data"
|
151
|
+
# Make the ENV value for the entry script a file
|
152
|
+
- uri: cpln://secret/postgres-poc-entrypoint-script
|
153
|
+
path: "/usr/local/bin/cpln-entrypoint.sh"
|
154
|
+
inheritEnv: false
|
155
|
+
livenessProbe:
|
156
|
+
tcpSocket:
|
157
|
+
port: 5432
|
158
|
+
failureThreshold: 1
|
159
|
+
readinessProbe:
|
160
|
+
tcpSocket:
|
161
|
+
port: 5432
|
162
|
+
failureThreshold: 1
|
163
|
+
identityLink: //identity/postgres-poc-identity
|
164
|
+
defaultOptions:
|
165
|
+
capacityAI: false
|
166
|
+
autoscaling:
|
167
|
+
metric: cpu
|
168
|
+
target: 95
|
169
|
+
maxScale: 1
|
170
|
+
firewallConfig:
|
171
|
+
external:
|
172
|
+
inboundAllowCIDR: []
|
173
|
+
outboundAllowCIDR:
|
174
|
+
- 0.0.0.0/0
|
175
|
+
internal:
|
176
|
+
inboundAllowType: same-gvc
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Template setup of Rails server workload, roughly corresponding to Heroku dyno
|
2
|
+
# type within Procfile.
|
3
|
+
kind: workload
|
4
|
+
name: rails
|
5
|
+
spec:
|
6
|
+
type: standard
|
7
|
+
containers:
|
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
|
+
cpu: 300m
|
12
|
+
env:
|
13
|
+
- name: LOG_LEVEL
|
14
|
+
value: debug
|
15
|
+
# Inherit other ENV values from GVC
|
16
|
+
inheritEnv: true
|
17
|
+
image: {{APP_IMAGE_LINK}}
|
18
|
+
# 512 corresponds to a standard 1x dyno type
|
19
|
+
memory: 512Mi
|
20
|
+
ports:
|
21
|
+
- number: 3000
|
22
|
+
protocol: http
|
23
|
+
defaultOptions:
|
24
|
+
# Start out like this for "test apps"
|
25
|
+
autoscaling:
|
26
|
+
# Max of 1 effectively disables autoscaling, so a like a Heroku dyno count of 1
|
27
|
+
maxScale: 1
|
28
|
+
capacityAI: false
|
29
|
+
firewallConfig:
|
30
|
+
external:
|
31
|
+
# Default to allow public access to Rails server
|
32
|
+
inboundAllowCIDR:
|
33
|
+
- 0.0.0.0/0
|
34
|
+
# Could configure outbound for more security
|
35
|
+
outboundAllowCIDR:
|
36
|
+
- 0.0.0.0/0
|