cpl 2.0.2 → 2.2.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/.gitignore +1 -0
- data/CHANGELOG.md +39 -1
- data/Gemfile.lock +3 -3
- data/README.md +24 -3
- data/docs/commands.md +37 -8
- data/docs/secrets-and-env-values.md +42 -0
- data/docs/tips.md +1 -40
- data/examples/controlplane.yml +24 -3
- data/lib/command/apply_template.rb +70 -80
- data/lib/command/base.rb +82 -71
- data/lib/command/build_image.rb +2 -2
- 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 +3 -3
- data/lib/command/delete.rb +17 -5
- data/lib/command/deploy_image.rb +6 -21
- data/lib/command/doctor.rb +47 -0
- data/lib/command/latest_image.rb +1 -1
- data/lib/command/no_command.rb +1 -0
- data/lib/command/promote_app_from_upstream.rb +1 -1
- data/lib/command/ps_wait.rb +7 -3
- data/lib/command/run.rb +87 -34
- data/lib/command/setup_app.rb +80 -16
- data/lib/command/test.rb +1 -0
- data/lib/command/version.rb +1 -0
- data/lib/core/config.rb +40 -12
- data/lib/core/controlplane.rb +58 -0
- data/lib/core/controlplane_api.rb +13 -7
- data/lib/core/controlplane_api_direct.rb +1 -1
- data/lib/core/doctor_service.rb +104 -0
- data/lib/core/helpers.rb +10 -0
- data/lib/core/template_parser.rb +76 -0
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +24 -6
- data/templates/app.yml +0 -5
- metadata +6 -4
- data/lib/core/controlplane_api_cli.rb +0 -10
- data/templates/secrets.yml +0 -11
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ValidationError < StandardError; end
|
4
|
+
|
5
|
+
class DoctorService
|
6
|
+
attr_reader :config
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def run_validations(validations, silent_if_passing: false) # rubocop:disable Metrics/MethodLength
|
13
|
+
@any_failed_validation = false
|
14
|
+
|
15
|
+
validations.each do |validation|
|
16
|
+
case validation
|
17
|
+
when "config"
|
18
|
+
validate_config
|
19
|
+
when "templates"
|
20
|
+
validate_templates
|
21
|
+
else
|
22
|
+
raise ValidationError, Shell.color("ERROR: Invalid validation '#{validation}'.", :red)
|
23
|
+
end
|
24
|
+
|
25
|
+
progress.puts("#{Shell.color('[PASS]', :green)} #{validation}") unless silent_if_passing
|
26
|
+
rescue ValidationError => e
|
27
|
+
@any_failed_validation = true
|
28
|
+
|
29
|
+
progress.puts("#{Shell.color('[FAIL]', :red)} #{validation}\n\n#{e.message}\n\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
exit(ExitCode::ERROR_DEFAULT) if @any_failed_validation
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_config
|
36
|
+
check_for_app_names_contained_in_others
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_templates
|
40
|
+
@template_parser = TemplateParser.new(config)
|
41
|
+
filenames = Dir.glob("#{@template_parser.template_dir}/*.yml")
|
42
|
+
templates = @template_parser.parse(filenames)
|
43
|
+
|
44
|
+
check_for_duplicate_templates(templates)
|
45
|
+
warn_deprecated_template_variables
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def check_for_app_names_contained_in_others
|
51
|
+
app_names_contained_in_others = find_app_names_contained_in_others
|
52
|
+
return if app_names_contained_in_others.empty?
|
53
|
+
|
54
|
+
message = "App names contained in others found below. Please ensure that app names are unique."
|
55
|
+
list = app_names_contained_in_others
|
56
|
+
.map { |app_prefix, app_name| " - '#{app_prefix}' is a prefix of '#{app_name}'" }
|
57
|
+
.join("\n")
|
58
|
+
raise ValidationError, "#{Shell.color("ERROR: #{message}", :red)}\n#{list}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_app_names_contained_in_others # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
62
|
+
app_names = config.apps.keys.map(&:to_s).sort
|
63
|
+
app_prefixes = config.apps
|
64
|
+
.select { |_, app_options| app_options[:match_if_app_name_starts_with] }
|
65
|
+
.keys
|
66
|
+
.map(&:to_s)
|
67
|
+
.sort
|
68
|
+
app_prefixes.each_with_object([]) do |app_prefix, app_names_contained_in_others|
|
69
|
+
app_names.each do |app_name|
|
70
|
+
if app_prefix != app_name && app_name.start_with?(app_prefix)
|
71
|
+
app_names_contained_in_others.push([app_prefix, app_name])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_for_duplicate_templates(templates)
|
78
|
+
grouped_templates = templates.group_by { |template| [template["kind"], template["name"]] }
|
79
|
+
duplicate_templates = grouped_templates.select { |_, group| group.size > 1 }
|
80
|
+
return if duplicate_templates.empty?
|
81
|
+
|
82
|
+
message = "Duplicate templates found with the kind/names below. Please ensure that templates are unique."
|
83
|
+
list = duplicate_templates
|
84
|
+
.map { |(kind, name), _| " - kind: #{kind}, name: #{name}" }
|
85
|
+
.join("\n")
|
86
|
+
raise ValidationError, "#{Shell.color("ERROR: #{message}", :red)}\n#{list}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def warn_deprecated_template_variables
|
90
|
+
deprecated_variables = @template_parser.deprecated_variables
|
91
|
+
return if deprecated_variables.empty?
|
92
|
+
|
93
|
+
message = "Please replace these variables in the templates, " \
|
94
|
+
"as support for them will be removed in a future major version bump:"
|
95
|
+
list = deprecated_variables
|
96
|
+
.map { |old_key, new_key| " - #{old_key} -> #{new_key}" }
|
97
|
+
.join("\n")
|
98
|
+
progress.puts("\n#{Shell.color("DEPRECATED: #{message}", :yellow)}\n#{list}\n\n")
|
99
|
+
end
|
100
|
+
|
101
|
+
def progress
|
102
|
+
$stderr
|
103
|
+
end
|
104
|
+
end
|
data/lib/core/helpers.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require "securerandom"
|
4
4
|
|
5
5
|
module Helpers
|
6
|
+
module_function
|
7
|
+
|
6
8
|
def strip_str_and_validate(str)
|
7
9
|
return str if str.nil?
|
8
10
|
|
@@ -13,4 +15,12 @@ module Helpers
|
|
13
15
|
def random_four_digits
|
14
16
|
SecureRandom.random_number(1000..9999)
|
15
17
|
end
|
18
|
+
|
19
|
+
def normalize_command_name(name)
|
20
|
+
name.to_s.tr("_", "-")
|
21
|
+
end
|
22
|
+
|
23
|
+
def normalize_option_name(name)
|
24
|
+
"--#{name.to_s.tr('_', '-')}"
|
25
|
+
end
|
16
26
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TemplateParser
|
4
|
+
attr_reader :config, :deprecated_variables
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
def template_dir
|
11
|
+
"#{config.app_cpln_dir}/templates"
|
12
|
+
end
|
13
|
+
|
14
|
+
def template_filename(name)
|
15
|
+
"#{template_dir}/#{name}.yml"
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse(filenames)
|
19
|
+
@deprecated_variables = {}
|
20
|
+
|
21
|
+
filenames.each_with_object([]) do |filename, templates|
|
22
|
+
yaml_file = File.read(filename)
|
23
|
+
yaml_file = replace_variables(yaml_file)
|
24
|
+
|
25
|
+
template_yamls = yaml_file.split(/^---\s*$/)
|
26
|
+
template_yamls.each do |template_yaml|
|
27
|
+
template = YAML.safe_load(template_yaml)
|
28
|
+
templates.push(template)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def replace_variables(yaml_file) # rubocop:disable Metrics/MethodLength
|
36
|
+
yaml_file = yaml_file
|
37
|
+
.gsub("{{APP_ORG}}", config.org)
|
38
|
+
.gsub("{{APP_NAME}}", config.app)
|
39
|
+
.gsub("{{APP_LOCATION}}", config.location)
|
40
|
+
.gsub("{{APP_LOCATION_LINK}}", config.location_link)
|
41
|
+
.gsub("{{APP_IMAGE}}", cp.latest_image)
|
42
|
+
.gsub("{{APP_IMAGE_LINK}}", config.image_link(cp.latest_image))
|
43
|
+
.gsub("{{APP_IDENTITY}}", config.identity)
|
44
|
+
.gsub("{{APP_IDENTITY_LINK}}", config.identity_link)
|
45
|
+
.gsub("{{APP_SECRETS}}", config.secrets)
|
46
|
+
.gsub("{{APP_SECRETS_POLICY}}", config.secrets_policy)
|
47
|
+
|
48
|
+
find_deprecated_variables(yaml_file)
|
49
|
+
|
50
|
+
# Kept for backwards compatibility
|
51
|
+
yaml_file
|
52
|
+
.gsub("APP_ORG", config.org)
|
53
|
+
.gsub("APP_GVC", config.app)
|
54
|
+
.gsub("APP_LOCATION", config.location)
|
55
|
+
.gsub("APP_IMAGE", cp.latest_image)
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_deprecated_variables(yaml_file)
|
59
|
+
new_variables.each do |old_key, new_key|
|
60
|
+
@deprecated_variables[old_key] = new_key if yaml_file.include?(old_key)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def new_variables
|
65
|
+
{
|
66
|
+
"APP_ORG" => "{{APP_ORG}}",
|
67
|
+
"APP_GVC" => "{{APP_NAME}}",
|
68
|
+
"APP_LOCATION" => "{{APP_LOCATION}}",
|
69
|
+
"APP_IMAGE" => "{{APP_IMAGE}}"
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def cp
|
74
|
+
@cp ||= Controlplane.new(config)
|
75
|
+
end
|
76
|
+
end
|
data/lib/cpl/version.rb
CHANGED
data/lib/cpl.rb
CHANGED
@@ -58,6 +58,8 @@ module Cpl
|
|
58
58
|
default_task :no_command
|
59
59
|
|
60
60
|
def self.start(*args)
|
61
|
+
ENV["CPLN_SKIP_UPDATE_CHECK"] = "true"
|
62
|
+
|
61
63
|
check_cpln_version
|
62
64
|
check_cpl_version
|
63
65
|
fix_help_option
|
@@ -177,6 +179,7 @@ module Cpl
|
|
177
179
|
examples = command_class::EXAMPLES
|
178
180
|
hide = command_class::HIDE || deprecated
|
179
181
|
with_info_header = command_class::WITH_INFO_HEADER
|
182
|
+
validations = command_class::VALIDATIONS
|
180
183
|
|
181
184
|
long_description += "\n#{examples}" if examples.length.positive?
|
182
185
|
|
@@ -198,9 +201,10 @@ module Cpl
|
|
198
201
|
|
199
202
|
@commands_with_extra_options.push(name_for_method.to_sym) if accepts_extra_options
|
200
203
|
|
201
|
-
define_method(name_for_method) do |*provided_args| # rubocop:disable Metrics/MethodLength
|
204
|
+
define_method(name_for_method) do |*provided_args| # rubocop:disable Metrics/BlockLength, Metrics/MethodLength
|
202
205
|
if deprecated
|
203
|
-
::
|
206
|
+
normalized_old_name = ::Helpers.normalize_command_name(command_key)
|
207
|
+
::Shell.warn_deprecated("Command '#{normalized_old_name}' is deprecated, " \
|
204
208
|
"please use '#{name}' instead.")
|
205
209
|
$stderr.puts
|
206
210
|
end
|
@@ -222,6 +226,11 @@ module Cpl
|
|
222
226
|
|
223
227
|
Cpl::Cli.show_info_header(config) if with_info_header
|
224
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
|
+
|
225
234
|
command_class.new(config).call
|
226
235
|
rescue RuntimeError => e
|
227
236
|
::Shell.abort(e.message)
|
@@ -235,14 +244,23 @@ module Cpl
|
|
235
244
|
check_unknown_options!(except: @commands_with_extra_options)
|
236
245
|
stop_on_unknown_option!
|
237
246
|
|
238
|
-
def self.validate_options!(options)
|
247
|
+
def self.validate_options!(options) # rubocop:disable Metrics/MethodLength
|
239
248
|
options.each do |name, value|
|
240
|
-
|
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
|
241
259
|
|
242
|
-
params =
|
260
|
+
params = option[:params]
|
243
261
|
next unless params[:valid_regex]
|
244
262
|
|
245
|
-
raise "Invalid value provided for option
|
263
|
+
raise "Invalid value provided for option #{normalized_name}." unless value.match?(params[:valid_regex])
|
246
264
|
end
|
247
265
|
end
|
248
266
|
|
data/templates/app.yml
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cpl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Gordon
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-06-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: debug
|
@@ -243,6 +243,7 @@ files:
|
|
243
243
|
- docs/migrating.md
|
244
244
|
- docs/postgres.md
|
245
245
|
- docs/redis.md
|
246
|
+
- docs/secrets-and-env-values.md
|
246
247
|
- docs/tips.md
|
247
248
|
- docs/troubleshooting.md
|
248
249
|
- examples/circleci.yml
|
@@ -256,6 +257,7 @@ files:
|
|
256
257
|
- lib/command/copy_image_from_upstream.rb
|
257
258
|
- lib/command/delete.rb
|
258
259
|
- lib/command/deploy_image.rb
|
260
|
+
- lib/command/doctor.rb
|
259
261
|
- lib/command/env.rb
|
260
262
|
- lib/command/exists.rb
|
261
263
|
- lib/command/generate.rb
|
@@ -283,10 +285,11 @@ files:
|
|
283
285
|
- lib/core/config.rb
|
284
286
|
- lib/core/controlplane.rb
|
285
287
|
- lib/core/controlplane_api.rb
|
286
|
-
- lib/core/controlplane_api_cli.rb
|
287
288
|
- lib/core/controlplane_api_direct.rb
|
289
|
+
- lib/core/doctor_service.rb
|
288
290
|
- lib/core/helpers.rb
|
289
291
|
- lib/core/shell.rb
|
292
|
+
- lib/core/template_parser.rb
|
290
293
|
- lib/cpl.rb
|
291
294
|
- lib/cpl/version.rb
|
292
295
|
- lib/deprecated_commands.json
|
@@ -310,7 +313,6 @@ files:
|
|
310
313
|
- templates/rails.yml
|
311
314
|
- templates/redis.yml
|
312
315
|
- templates/redis2.yml
|
313
|
-
- templates/secrets.yml
|
314
316
|
- templates/sidekiq.yml
|
315
317
|
homepage: https://github.com/shakacode/control-plane-flow
|
316
318
|
licenses:
|
@@ -1,10 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class ControlplaneApiCli
|
4
|
-
def call(url, method:)
|
5
|
-
result = Shell.cmd("cpln", "rest", method, url, "-o", "json", capture_stderr: true)
|
6
|
-
raise(result[:output]) unless result[:success]
|
7
|
-
|
8
|
-
JSON.parse(result[:output])
|
9
|
-
end
|
10
|
-
end
|
data/templates/secrets.yml
DELETED