cpflow 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|