cpl 1.4.0 → 2.0.0.rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/command_docs.yml +1 -1
- data/.github/workflows/rspec-shared.yml +56 -0
- data/.github/workflows/rspec.yml +19 -31
- data/.github/workflows/rubocop.yml +2 -10
- data/.gitignore +2 -0
- data/.simplecov_spawn.rb +10 -0
- data/CHANGELOG.md +8 -0
- data/CONTRIBUTING.md +32 -2
- data/Gemfile.lock +34 -29
- data/README.md +34 -25
- data/cpl.gemspec +1 -1
- data/docs/commands.md +54 -54
- data/docs/dns.md +6 -0
- data/docs/migrating.md +10 -10
- data/docs/tips.md +12 -10
- data/examples/circleci.yml +3 -3
- data/examples/controlplane.yml +25 -16
- data/lib/command/apply_template.rb +9 -9
- data/lib/command/base.rb +132 -37
- data/lib/command/build_image.rb +4 -9
- data/lib/command/cleanup_stale_apps.rb +1 -1
- data/lib/command/copy_image_from_upstream.rb +0 -7
- data/lib/command/delete.rb +39 -7
- data/lib/command/deploy_image.rb +18 -3
- data/lib/command/exists.rb +1 -1
- data/lib/command/generate.rb +1 -1
- data/lib/command/info.rb +7 -3
- data/lib/command/logs.rb +22 -2
- data/lib/command/maintenance_off.rb +1 -1
- data/lib/command/maintenance_on.rb +1 -1
- data/lib/command/open.rb +2 -2
- data/lib/command/open_console.rb +2 -2
- data/lib/command/ps.rb +1 -1
- data/lib/command/ps_start.rb +2 -1
- data/lib/command/ps_stop.rb +40 -8
- data/lib/command/ps_wait.rb +3 -2
- data/lib/command/run.rb +430 -69
- data/lib/command/setup_app.rb +4 -1
- data/lib/constants/exit_code.rb +7 -0
- data/lib/core/config.rb +1 -1
- data/lib/core/controlplane.rb +109 -48
- data/lib/core/controlplane_api.rb +7 -1
- data/lib/core/controlplane_api_cli.rb +3 -3
- data/lib/core/controlplane_api_direct.rb +1 -1
- data/lib/core/shell.rb +15 -9
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +48 -9
- data/lib/deprecated_commands.json +2 -1
- data/lib/generator_templates/controlplane.yml +2 -2
- data/script/check_cpln_links +3 -3
- data/templates/{gvc.yml → app.yml} +5 -0
- data/templates/secrets.yml +8 -0
- metadata +23 -26
- data/.rspec +0 -1
- data/lib/command/run_cleanup.rb +0 -116
- data/lib/command/run_detached.rb +0 -176
- data/lib/core/scripts.rb +0 -34
- data/templates/identity.yml +0 -3
- data/templates/secrets-policy.yml +0 -4
- /data/lib/generator_templates/templates/{gvc.yml → app.yml} +0 -0
data/lib/core/config.rb
CHANGED
@@ -174,7 +174,7 @@ class Config # rubocop:disable Metrics/ClassLength
|
|
174
174
|
end
|
175
175
|
|
176
176
|
def config_file_path # rubocop:disable Metrics/MethodLength
|
177
|
-
@config_file_path ||= begin
|
177
|
+
@config_file_path ||= ENV["CONFIG_FILE_PATH"] || begin
|
178
178
|
path = Pathname.new(".").expand_path
|
179
179
|
|
180
180
|
loop do
|
data/lib/core/controlplane.rb
CHANGED
@@ -19,19 +19,17 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
19
19
|
|
20
20
|
def profile_exists?(profile)
|
21
21
|
cmd = "cpln profile get #{profile} -o yaml"
|
22
|
-
perform_yaml(cmd).length.positive?
|
22
|
+
perform_yaml!(cmd).length.positive?
|
23
23
|
end
|
24
24
|
|
25
25
|
def profile_create(profile, token)
|
26
26
|
sensitive_data_pattern = /(?<=--token )(\S+)/
|
27
27
|
cmd = "cpln profile create #{profile} --token #{token}"
|
28
|
-
cmd += " > /dev/null" if Shell.should_hide_output?
|
29
28
|
perform!(cmd, sensitive_data_pattern: sensitive_data_pattern)
|
30
29
|
end
|
31
30
|
|
32
31
|
def profile_delete(profile)
|
33
32
|
cmd = "cpln profile delete #{profile}"
|
34
|
-
cmd += " > /dev/null" if Shell.should_hide_output?
|
35
33
|
perform!(cmd)
|
36
34
|
end
|
37
35
|
|
@@ -58,31 +56,31 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
58
56
|
image_push(image) if push
|
59
57
|
end
|
60
58
|
|
59
|
+
def fetch_image_details(image)
|
60
|
+
api.fetch_image_details(org: org, image: image)
|
61
|
+
end
|
62
|
+
|
61
63
|
def image_delete(image)
|
62
64
|
api.image_delete(org: org, image: image)
|
63
65
|
end
|
64
66
|
|
65
67
|
def image_login(org_name = config.org)
|
66
68
|
cmd = "cpln image docker-login --org #{org_name}"
|
67
|
-
cmd
|
68
|
-
perform!(cmd)
|
69
|
+
perform!(cmd, output_mode: :none)
|
69
70
|
end
|
70
71
|
|
71
72
|
def image_pull(image)
|
72
73
|
cmd = "docker pull #{image}"
|
73
|
-
cmd
|
74
|
-
perform!(cmd)
|
74
|
+
perform!(cmd, output_mode: :none)
|
75
75
|
end
|
76
76
|
|
77
77
|
def image_tag(old_tag, new_tag)
|
78
78
|
cmd = "docker tag #{old_tag} #{new_tag}"
|
79
|
-
cmd += " > /dev/null" if Shell.should_hide_output?
|
80
79
|
perform!(cmd)
|
81
80
|
end
|
82
81
|
|
83
82
|
def image_push(image)
|
84
83
|
cmd = "docker push #{image}"
|
85
|
-
cmd += " > /dev/null" if Shell.should_hide_output?
|
86
84
|
perform!(cmd)
|
87
85
|
end
|
88
86
|
|
@@ -98,7 +96,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
98
96
|
op = config.should_app_start_with?(app_name) ? "~" : "="
|
99
97
|
|
100
98
|
cmd = "cpln gvc query --org #{org} -o yaml --prop name#{op}#{app_name}"
|
101
|
-
perform_yaml(cmd)
|
99
|
+
perform_yaml!(cmd)
|
102
100
|
end
|
103
101
|
|
104
102
|
def fetch_gvc(a_gvc = gvc, a_org = org)
|
@@ -145,41 +143,38 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
145
143
|
api.query_workloads(org: a_org, gvc: a_gvc, workload: workload, gvc_op_type: gvc_op, workload_op_type: workload_op)
|
146
144
|
end
|
147
145
|
|
148
|
-
def
|
149
|
-
cmd = "cpln workload get
|
146
|
+
def fetch_workload_replicas(workload, location:)
|
147
|
+
cmd = "cpln workload replica get #{workload} #{gvc_org} --location #{location} -o yaml"
|
150
148
|
perform_yaml(cmd)
|
151
149
|
end
|
152
150
|
|
153
|
-
def
|
154
|
-
cmd = "cpln workload
|
155
|
-
cmd
|
156
|
-
|
157
|
-
Shell.debug("CMD", cmd)
|
158
|
-
|
159
|
-
result = `#{cmd}`
|
160
|
-
$CHILD_STATUS.success? ? YAML.safe_load(result) : nil
|
151
|
+
def stop_workload_replica(workload, replica, location:)
|
152
|
+
cmd = "cpln workload replica stop #{workload} #{gvc_org} --replica-name #{replica} --location #{location}"
|
153
|
+
perform(cmd, output_mode: :none)
|
161
154
|
end
|
162
155
|
|
163
156
|
def fetch_workload_deployments(workload)
|
164
157
|
api.workload_deployments(workload: workload, gvc: gvc, org: org)
|
165
158
|
end
|
166
159
|
|
167
|
-
def workload_deployment_version_ready?(version, next_version
|
160
|
+
def workload_deployment_version_ready?(version, next_version)
|
168
161
|
return false unless version["workload"] == next_version
|
169
162
|
|
170
163
|
version["containers"]&.all? do |_, container|
|
171
|
-
|
172
|
-
expected_status == true ? ready : !ready
|
164
|
+
container.dig("resources", "replicas") == container.dig("resources", "replicasReady")
|
173
165
|
end
|
174
166
|
end
|
175
167
|
|
176
|
-
def workload_deployments_ready?(workload, expected_status:)
|
168
|
+
def workload_deployments_ready?(workload, location:, expected_status:)
|
169
|
+
deployed_replicas = fetch_workload_replicas(workload, location: location)["items"].length
|
170
|
+
return deployed_replicas.zero? if expected_status == false
|
171
|
+
|
177
172
|
deployments = fetch_workload_deployments(workload)["items"]
|
178
173
|
deployments.all? do |deployment|
|
179
174
|
next_version = deployment.dig("status", "expectedDeploymentVersion")
|
180
175
|
|
181
176
|
deployment.dig("status", "versions")&.all? do |version|
|
182
|
-
workload_deployment_version_ready?(version, next_version
|
177
|
+
workload_deployment_version_ready?(version, next_version)
|
183
178
|
end
|
184
179
|
end
|
185
180
|
end
|
@@ -187,7 +182,6 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
187
182
|
def workload_set_image_ref(workload, container:, image:)
|
188
183
|
cmd = "cpln workload update #{workload} #{gvc_org}"
|
189
184
|
cmd += " --set spec.containers.#{container}.image=/org/#{config.org}/image/#{image}"
|
190
|
-
cmd += " > /dev/null" if Shell.should_hide_output?
|
191
185
|
perform!(cmd)
|
192
186
|
end
|
193
187
|
|
@@ -215,7 +209,6 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
215
209
|
|
216
210
|
def workload_force_redeployment(workload)
|
217
211
|
cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
|
218
|
-
cmd += " > /dev/null" if Shell.should_hide_output?
|
219
212
|
perform!(cmd)
|
220
213
|
end
|
221
214
|
|
@@ -227,14 +220,29 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
227
220
|
cmd = "cpln workload connect #{workload} #{gvc_org} --location #{location}"
|
228
221
|
cmd += " --container #{container}" if container
|
229
222
|
cmd += " --shell #{shell}" if shell
|
230
|
-
perform!(cmd)
|
223
|
+
perform!(cmd, output_mode: :all)
|
231
224
|
end
|
232
225
|
|
233
|
-
def workload_exec(workload, location:, container: nil, command: nil)
|
234
|
-
cmd = "cpln workload exec #{workload} #{gvc_org} --location #{location}"
|
226
|
+
def workload_exec(workload, replica, location:, container: nil, command: nil)
|
227
|
+
cmd = "cpln workload exec #{workload} #{gvc_org} --replica #{replica} --location #{location}"
|
235
228
|
cmd += " --container #{container}" if container
|
236
229
|
cmd += " -- #{command}"
|
237
|
-
perform!(cmd)
|
230
|
+
perform!(cmd, output_mode: :all)
|
231
|
+
end
|
232
|
+
|
233
|
+
def start_cron_workload(workload, job_start_yaml, location:)
|
234
|
+
Tempfile.create do |f|
|
235
|
+
f.write(job_start_yaml)
|
236
|
+
f.rewind
|
237
|
+
|
238
|
+
cmd = "cpln workload cron start #{workload} #{gvc_org} --file #{f.path} --location #{location} -o yaml"
|
239
|
+
perform_yaml(cmd)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def fetch_cron_workload(workload, location:)
|
244
|
+
cmd = "cpln workload cron get #{workload} #{gvc_org} --location #{location} -o yaml"
|
245
|
+
perform_yaml(cmd)
|
238
246
|
end
|
239
247
|
|
240
248
|
# volumeset
|
@@ -291,13 +299,17 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
291
299
|
|
292
300
|
# logs
|
293
301
|
|
294
|
-
def logs(workload:)
|
295
|
-
|
296
|
-
|
302
|
+
def logs(workload:, limit:, since:, replica: nil)
|
303
|
+
query_parts = ["gvc=\"#{gvc}\"", "workload=\"#{workload}\""]
|
304
|
+
query_parts.push("replica=\"#{replica}\"") if replica
|
305
|
+
query = "{#{query_parts.join(',')}}"
|
306
|
+
|
307
|
+
cmd = "cpln logs '#{query}' --org #{org} -t -o raw --limit #{limit} --since #{since}"
|
308
|
+
perform!(cmd, output_mode: :all)
|
297
309
|
end
|
298
310
|
|
299
|
-
def log_get(workload:, from:, to:)
|
300
|
-
api.log_get(org: org, gvc: gvc, workload: workload, from: from, to: to)
|
311
|
+
def log_get(workload:, from:, to:, replica: nil)
|
312
|
+
api.log_get(org: org, gvc: gvc, workload: workload, replica: replica, from: from, to: to)
|
301
313
|
end
|
302
314
|
|
303
315
|
# identities
|
@@ -314,7 +326,6 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
314
326
|
|
315
327
|
def bind_identity_to_policy(identity_link, policy)
|
316
328
|
cmd = "cpln policy add-binding #{policy} --org #{org} --identity #{identity_link} --permission reveal"
|
317
|
-
cmd += " > /dev/null" if Shell.should_hide_output?
|
318
329
|
perform!(cmd)
|
319
330
|
end
|
320
331
|
|
@@ -329,13 +340,17 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
329
340
|
|
330
341
|
Shell.debug("CMD", cmd)
|
331
342
|
|
332
|
-
result =
|
333
|
-
|
343
|
+
result = Shell.cmd(cmd)
|
344
|
+
parse_apply_result(result[:output]) if result[:success]
|
334
345
|
else
|
335
346
|
Shell.debug("CMD", cmd)
|
336
347
|
|
337
|
-
result =
|
338
|
-
|
348
|
+
result = Shell.cmd(cmd)
|
349
|
+
if result[:success]
|
350
|
+
parse_apply_result(result[:output])
|
351
|
+
else
|
352
|
+
Shell.abort("Command exited with non-zero status.")
|
353
|
+
end
|
339
354
|
end
|
340
355
|
end
|
341
356
|
end
|
@@ -379,23 +394,69 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
379
394
|
|
380
395
|
private
|
381
396
|
|
382
|
-
|
383
|
-
|
397
|
+
# `output_mode` can be :all, :errors_only or :none.
|
398
|
+
# If not provided, it will be determined based on the `HIDE_COMMAND_OUTPUT` env var
|
399
|
+
# or the return value of `Shell.should_hide_output?`.
|
400
|
+
def build_command(cmd, output_mode: nil) # rubocop:disable Metrics/MethodLength
|
401
|
+
output_mode ||= determine_command_output_mode
|
402
|
+
|
403
|
+
case output_mode
|
404
|
+
when :all
|
405
|
+
cmd
|
406
|
+
when :errors_only
|
407
|
+
"#{cmd} > /dev/null"
|
408
|
+
when :none
|
409
|
+
"#{cmd} > /dev/null 2>&1"
|
410
|
+
else
|
411
|
+
raise "Invalid command output mode '#{output_mode}'."
|
412
|
+
end
|
413
|
+
end
|
384
414
|
|
385
|
-
|
415
|
+
def determine_command_output_mode
|
416
|
+
if ENV.fetch("HIDE_COMMAND_OUTPUT", nil) == "true"
|
417
|
+
:none
|
418
|
+
elsif Shell.should_hide_output?
|
419
|
+
:errors_only
|
420
|
+
else
|
421
|
+
:all
|
422
|
+
end
|
386
423
|
end
|
387
424
|
|
388
|
-
def perform
|
425
|
+
def perform(cmd, output_mode: nil, sensitive_data_pattern: nil)
|
426
|
+
cmd = build_command(cmd, output_mode: output_mode)
|
427
|
+
|
389
428
|
Shell.debug("CMD", cmd, sensitive_data_pattern: sensitive_data_pattern)
|
390
429
|
|
391
|
-
|
430
|
+
kernel_system_with_pid_handling(cmd)
|
431
|
+
end
|
432
|
+
|
433
|
+
# NOTE: full analogue of Kernel.system which returns pids and saves it to child_pids for proper killing
|
434
|
+
def kernel_system_with_pid_handling(cmd)
|
435
|
+
pid = Process.spawn(cmd)
|
436
|
+
$child_pids << pid # rubocop:disable Style/GlobalVars
|
437
|
+
|
438
|
+
_, status = Process.wait2(pid)
|
439
|
+
$child_pids.delete(pid) # rubocop:disable Style/GlobalVars
|
440
|
+
|
441
|
+
status.exited? ? status.success? : nil
|
442
|
+
rescue SystemCallError
|
443
|
+
nil
|
444
|
+
end
|
445
|
+
|
446
|
+
def perform!(cmd, output_mode: nil, sensitive_data_pattern: nil)
|
447
|
+
success = perform(cmd, output_mode: output_mode, sensitive_data_pattern: sensitive_data_pattern)
|
448
|
+
success || Shell.abort("Command exited with non-zero status.")
|
392
449
|
end
|
393
450
|
|
394
451
|
def perform_yaml(cmd)
|
395
452
|
Shell.debug("CMD", cmd)
|
396
453
|
|
397
|
-
result =
|
398
|
-
|
454
|
+
result = Shell.cmd(cmd)
|
455
|
+
YAML.safe_load(result[:output], permitted_classes: [Time]) if result[:success]
|
456
|
+
end
|
457
|
+
|
458
|
+
def perform_yaml!(cmd)
|
459
|
+
perform_yaml(cmd) || Shell.abort("Command exited with non-zero status.")
|
399
460
|
end
|
400
461
|
|
401
462
|
def gvc_org
|
@@ -25,18 +25,24 @@ class ControlplaneApi # rubocop:disable Metrics/ClassLength
|
|
25
25
|
query("/org/#{org}/image", terms)
|
26
26
|
end
|
27
27
|
|
28
|
+
def fetch_image_details(org:, image:)
|
29
|
+
api_json("/org/#{org}/image/#{image}", method: :get)
|
30
|
+
end
|
31
|
+
|
28
32
|
def image_delete(org:, image:)
|
29
33
|
api_json("/org/#{org}/image/#{image}", method: :delete)
|
30
34
|
end
|
31
35
|
|
32
|
-
def log_get(org:, gvc:, workload: nil, from: nil, to: nil)
|
36
|
+
def log_get(org:, gvc:, workload: nil, replica: nil, from: nil, to: nil) # rubocop:disable Metrics/ParameterLists
|
33
37
|
query = { gvc: gvc }
|
34
38
|
query[:workload] = workload if workload
|
39
|
+
query[:replica] = replica if replica
|
35
40
|
query = query.map { |k, v| %(#{k}="#{v}") }.join(",").then { "{#{_1}}" }
|
36
41
|
|
37
42
|
params = { query: query }
|
38
43
|
params[:from] = "#{from}000000000" if from
|
39
44
|
params[:to] = "#{to}000000000" if to
|
45
|
+
params[:limit] = "5000"
|
40
46
|
# params << "delay_for=0"
|
41
47
|
# params << "limit=30"
|
42
48
|
# params << "direction=forward"
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
class ControlplaneApiCli
|
4
4
|
def call(url, method:)
|
5
|
-
|
6
|
-
raise(
|
5
|
+
result = Shell.cmd("cpln", "rest", method, url, "-o", "json", capture_stderr: true)
|
6
|
+
raise(result[:output]) unless result[:success]
|
7
7
|
|
8
|
-
JSON.parse(
|
8
|
+
JSON.parse(result[:output])
|
9
9
|
end
|
10
10
|
end
|
data/lib/core/shell.rb
CHANGED
@@ -9,10 +9,6 @@ class Shell
|
|
9
9
|
@shell ||= Thor::Shell::Color.new
|
10
10
|
end
|
11
11
|
|
12
|
-
def self.stderr
|
13
|
-
@stderr ||= $stderr
|
14
|
-
end
|
15
|
-
|
16
12
|
def self.use_tmp_stderr
|
17
13
|
@tmp_stderr = Tempfile.create
|
18
14
|
|
@@ -40,15 +36,16 @@ class Shell
|
|
40
36
|
end
|
41
37
|
|
42
38
|
def self.warn(message)
|
43
|
-
|
39
|
+
Kernel.warn(color("WARNING: #{message}", :yellow))
|
44
40
|
end
|
45
41
|
|
46
42
|
def self.warn_deprecated(message)
|
47
|
-
|
43
|
+
Kernel.warn(color("DEPRECATED: #{message}", :yellow))
|
48
44
|
end
|
49
45
|
|
50
|
-
def self.abort(message)
|
51
|
-
Kernel.
|
46
|
+
def self.abort(message, exit_status = ExitCode::ERROR_DEFAULT)
|
47
|
+
Kernel.warn(color("ERROR: #{message}", :red))
|
48
|
+
exit(exit_status)
|
52
49
|
end
|
53
50
|
|
54
51
|
def self.verbose_mode(verbose)
|
@@ -59,13 +56,22 @@ class Shell
|
|
59
56
|
return unless verbose
|
60
57
|
|
61
58
|
filtered_message = hide_sensitive_data(message, sensitive_data_pattern)
|
62
|
-
|
59
|
+
Kernel.warn("\n[#{color(prefix, :red)}] #{filtered_message}")
|
63
60
|
end
|
64
61
|
|
65
62
|
def self.should_hide_output?
|
66
63
|
tmp_stderr && !verbose
|
67
64
|
end
|
68
65
|
|
66
|
+
def self.cmd(*cmd_to_run, capture_stderr: false)
|
67
|
+
output, status = capture_stderr ? Open3.capture2e(*cmd_to_run) : Open3.capture2(*cmd_to_run)
|
68
|
+
|
69
|
+
{
|
70
|
+
output: output,
|
71
|
+
success: status.success?
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
69
75
|
#
|
70
76
|
# Hide sensitive data based on the passed pattern
|
71
77
|
#
|
data/lib/cpl/version.rb
CHANGED
data/lib/cpl.rb
CHANGED
@@ -6,11 +6,14 @@ require "cgi"
|
|
6
6
|
require "json"
|
7
7
|
require "jwt"
|
8
8
|
require "net/http"
|
9
|
+
require "open3"
|
9
10
|
require "pathname"
|
10
11
|
require "tempfile"
|
11
12
|
require "thor"
|
12
13
|
require "yaml"
|
13
14
|
|
15
|
+
require_relative "constants/exit_code"
|
16
|
+
|
14
17
|
# We need to require base before all commands, since the commands inherit from it
|
15
18
|
require_relative "command/base"
|
16
19
|
|
@@ -19,6 +22,14 @@ modules = Dir["#{__dir__}/**/*.rb"].reject do |file|
|
|
19
22
|
end
|
20
23
|
modules.sort.each { require(_1) }
|
21
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
|
+
|
22
33
|
# Fix for https://github.com/erikhuda/thor/issues/398
|
23
34
|
# Copied from https://github.com/rails/thor/issues/398#issuecomment-622988390
|
24
35
|
class Thor
|
@@ -59,9 +70,9 @@ module Cpl
|
|
59
70
|
|
60
71
|
@checked_cpln_version = true
|
61
72
|
|
62
|
-
result =
|
63
|
-
if
|
64
|
-
data = JSON.parse(result)
|
73
|
+
result = ::Shell.cmd("cpln", "--version", capture_stderr: true)
|
74
|
+
if result[:success]
|
75
|
+
data = JSON.parse(result[:output])
|
65
76
|
|
66
77
|
version = data["npm"]
|
67
78
|
min_version = Cpl::MIN_CPLN_VERSION
|
@@ -79,10 +90,10 @@ module Cpl
|
|
79
90
|
|
80
91
|
@checked_cpl_version = true
|
81
92
|
|
82
|
-
result =
|
83
|
-
return unless
|
93
|
+
result = ::Shell.cmd("gem", "search", "^cpl$", "--remote", capture_stderr: true)
|
94
|
+
return unless result[:success]
|
84
95
|
|
85
|
-
matches = result.match(/cpl \((.+)\)/)
|
96
|
+
matches = result[:output].match(/cpl \((.+)\)/)
|
86
97
|
return unless matches
|
87
98
|
|
88
99
|
version = Cpl::VERSION
|
@@ -135,6 +146,9 @@ module Cpl
|
|
135
146
|
::Command::Base.all_commands.merge(deprecated_commands)
|
136
147
|
end
|
137
148
|
|
149
|
+
@commands_with_required_options = []
|
150
|
+
@commands_with_extra_options = []
|
151
|
+
|
138
152
|
all_base_commands.each do |command_key, command_class| # rubocop:disable Metrics/BlockLength
|
139
153
|
deprecated = deprecated_commands[command_key]
|
140
154
|
|
@@ -161,12 +175,20 @@ module Cpl
|
|
161
175
|
long_desc(long_description)
|
162
176
|
|
163
177
|
command_options.each do |option|
|
164
|
-
|
178
|
+
params = option[:params]
|
179
|
+
|
180
|
+
# Ensures that if no value is provided for a non-boolean option (e.g., `cpl command --option`),
|
181
|
+
# it defaults to an empty string instead of the option name (which is the default Thor behavior)
|
182
|
+
params[:lazy_default] ||= "" if params[:type] != :boolean
|
183
|
+
|
184
|
+
method_option(option[:name], **params)
|
165
185
|
end
|
166
186
|
|
167
187
|
# We'll handle required options manually in `Config`
|
168
188
|
required_options = command_options.select { |option| option[:params][:required] }.map { |option| option[:name] }
|
169
|
-
|
189
|
+
@commands_with_required_options.push(name_for_method.to_sym) if required_options.any?
|
190
|
+
|
191
|
+
@commands_with_extra_options.push(name_for_method.to_sym) if accepts_extra_options
|
170
192
|
|
171
193
|
define_method(name_for_method) do |*provided_args| # rubocop:disable Metrics/MethodLength
|
172
194
|
if deprecated
|
@@ -186,6 +208,8 @@ module Cpl
|
|
186
208
|
end
|
187
209
|
|
188
210
|
begin
|
211
|
+
Cpl::Cli.validate_options!(options, command_options)
|
212
|
+
|
189
213
|
config = Config.new(args, options, required_options)
|
190
214
|
|
191
215
|
Cpl::Cli.show_info_header(config) if with_info_header
|
@@ -199,6 +223,21 @@ module Cpl
|
|
199
223
|
::Shell.abort("Unable to load command: #{e.message}")
|
200
224
|
end
|
201
225
|
|
226
|
+
disable_required_check!(*@commands_with_required_options)
|
227
|
+
check_unknown_options!(except: @commands_with_extra_options)
|
228
|
+
stop_on_unknown_option!
|
229
|
+
|
230
|
+
def self.validate_options!(options, command_options)
|
231
|
+
options.each do |name, value|
|
232
|
+
raise "No value provided for option '#{name}'." if value.to_s.strip.empty?
|
233
|
+
|
234
|
+
params = command_options.find { |option| option[:name].to_s == name }[:params]
|
235
|
+
next unless params[:valid_regex]
|
236
|
+
|
237
|
+
raise "Invalid value provided for option '#{name}'." unless value.match?(params[:valid_regex])
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
202
241
|
def self.show_info_header(config) # rubocop:disable Metrics/MethodLength
|
203
242
|
return if @showed_info_header
|
204
243
|
|
@@ -223,5 +262,5 @@ end
|
|
223
262
|
# nice Ctrl+C
|
224
263
|
trap "INT" do
|
225
264
|
puts
|
226
|
-
exit(
|
265
|
+
exit(ExitCode::INTERRUPT)
|
227
266
|
end
|
@@ -20,14 +20,14 @@ aliases:
|
|
20
20
|
|
21
21
|
# Workloads that are for the application itself and are using application Docker images.
|
22
22
|
# These are updated with the new image when running the `deploy-image` command,
|
23
|
-
# and are also used by the `info
|
23
|
+
# and are also used by the `info` and `ps:` commands in order to get all of the defined workloads.
|
24
24
|
# On the other hand, if you have a workload for Redis, that would NOT use the application Docker image
|
25
25
|
# and not be listed here.
|
26
26
|
app_workloads:
|
27
27
|
- rails
|
28
28
|
|
29
29
|
# Additional "service type" workloads, using non-application Docker images.
|
30
|
-
# These are only used by the `info
|
30
|
+
# These are only used by the `info` and `ps:` commands in order to get all of the defined workloads.
|
31
31
|
additional_workloads:
|
32
32
|
- postgres
|
33
33
|
|
data/script/check_cpln_links
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
2
|
|
3
|
-
bad_links=("controlplane.com/shakacode")
|
4
|
-
proper_links=("shakacode.controlplane.com")
|
3
|
+
bad_links=("controlplane.com/shakacode" "https://docs.controlplane.com")
|
4
|
+
proper_links=("shakacode.controlplane.com" "https://shakadocs.controlplane.com")
|
5
5
|
|
6
6
|
bold=$(tput bold)
|
7
7
|
normal=$(tput sgr0)
|
@@ -19,7 +19,7 @@ for ((idx = 0; idx < ${#bad_links[@]}; idx++)); do
|
|
19
19
|
--heading \
|
20
20
|
--color=always -- \
|
21
21
|
"${bad_links[idx]}" \
|
22
|
-
':!script/check_cpln_links')
|
22
|
+
':!script/check_cpln_links' '*.md')
|
23
23
|
|
24
24
|
# Line would become really unwieldly if everything was mushed into the
|
25
25
|
# conditional, so let's ignore this check here.
|
data/templates/secrets.yml
CHANGED