cpl 1.3.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/command_docs.yml +1 -1
  3. data/.github/workflows/rspec-shared.yml +56 -0
  4. data/.github/workflows/rspec.yml +19 -31
  5. data/.github/workflows/rubocop.yml +2 -10
  6. data/.gitignore +2 -0
  7. data/.simplecov_spawn.rb +10 -0
  8. data/CHANGELOG.md +28 -1
  9. data/CONTRIBUTING.md +32 -2
  10. data/Gemfile.lock +38 -29
  11. data/README.md +43 -17
  12. data/cpl.gemspec +2 -1
  13. data/docs/commands.md +68 -59
  14. data/docs/dns.md +6 -0
  15. data/docs/migrating.md +10 -10
  16. data/docs/tips.md +15 -3
  17. data/examples/circleci.yml +3 -3
  18. data/examples/controlplane.yml +35 -9
  19. data/lib/command/apply_template.rb +66 -18
  20. data/lib/command/base.rb +168 -27
  21. data/lib/command/build_image.rb +4 -9
  22. data/lib/command/cleanup_stale_apps.rb +1 -3
  23. data/lib/command/copy_image_from_upstream.rb +0 -7
  24. data/lib/command/delete.rb +39 -7
  25. data/lib/command/deploy_image.rb +35 -2
  26. data/lib/command/exists.rb +1 -1
  27. data/lib/command/generate.rb +1 -1
  28. data/lib/command/info.rb +7 -3
  29. data/lib/command/logs.rb +22 -2
  30. data/lib/command/maintenance_off.rb +1 -1
  31. data/lib/command/maintenance_on.rb +1 -1
  32. data/lib/command/open.rb +2 -2
  33. data/lib/command/open_console.rb +2 -2
  34. data/lib/command/promote_app_from_upstream.rb +5 -25
  35. data/lib/command/ps.rb +1 -1
  36. data/lib/command/ps_start.rb +2 -1
  37. data/lib/command/ps_stop.rb +40 -8
  38. data/lib/command/ps_wait.rb +3 -2
  39. data/lib/command/run.rb +430 -68
  40. data/lib/command/setup_app.rb +22 -2
  41. data/lib/constants/exit_code.rb +7 -0
  42. data/lib/core/config.rb +11 -3
  43. data/lib/core/controlplane.rb +126 -47
  44. data/lib/core/controlplane_api.rb +15 -1
  45. data/lib/core/controlplane_api_cli.rb +3 -3
  46. data/lib/core/controlplane_api_direct.rb +33 -5
  47. data/lib/core/shell.rb +15 -9
  48. data/lib/cpl/version.rb +1 -1
  49. data/lib/cpl.rb +50 -9
  50. data/lib/deprecated_commands.json +2 -1
  51. data/lib/generator_templates/controlplane.yml +5 -0
  52. data/lib/generator_templates/templates/{gvc.yml → app.yml} +4 -4
  53. data/lib/generator_templates/templates/postgres.yml +1 -1
  54. data/lib/generator_templates/templates/rails.yml +1 -1
  55. data/script/check_cpln_links +3 -3
  56. data/templates/app.yml +18 -0
  57. data/templates/daily-task.yml +3 -2
  58. data/templates/rails.yml +3 -2
  59. data/templates/secrets.yml +11 -0
  60. data/templates/sidekiq.yml +3 -2
  61. metadata +38 -25
  62. data/.rspec +0 -1
  63. data/lib/command/run_cleanup.rb +0 -116
  64. data/lib/command/run_detached.rb +0 -175
  65. data/lib/core/scripts.rb +0 -34
  66. data/templates/gvc.yml +0 -13
  67. data/templates/identity.yml +0 -2
data/lib/command/base.rb CHANGED
@@ -51,6 +51,7 @@ module Command
51
51
  [org_option, verbose_option, trace_option]
52
52
  end
53
53
 
54
+ # rubocop:disable Metrics/MethodLength
54
55
  def self.org_option(required: false)
55
56
  {
56
57
  name: :org,
@@ -90,6 +91,19 @@ module Command
90
91
  }
91
92
  end
92
93
 
94
+ def self.replica_option(required: false)
95
+ {
96
+ name: :replica,
97
+ params: {
98
+ aliases: ["-r"],
99
+ banner: "REPLICA_NAME",
100
+ desc: "Replica name",
101
+ type: :string,
102
+ required: required
103
+ }
104
+ }
105
+ end
106
+
93
107
  def self.image_option(required: false)
94
108
  {
95
109
  name: :image,
@@ -103,6 +117,20 @@ module Command
103
117
  }
104
118
  end
105
119
 
120
+ def self.log_method_option(required: false)
121
+ {
122
+ name: :log_method,
123
+ params: {
124
+ type: :numeric,
125
+ banner: "LOG_METHOD",
126
+ desc: "Log method",
127
+ required: required,
128
+ valid_values: [1, 2, 3],
129
+ default: 3
130
+ }
131
+ }
132
+ end
133
+
106
134
  def self.commit_option(required: false)
107
135
  {
108
136
  name: :commit,
@@ -198,7 +226,8 @@ module Command
198
226
  banner: "ROWS,COLS",
199
227
  desc: "Override remote terminal size (e.g. `--terminal-size 10,20`)",
200
228
  type: :string,
201
- required: required
229
+ required: required,
230
+ valid_regex: /^\d+,\d+$/
202
231
  }
203
232
  }
204
233
  end
@@ -237,44 +266,128 @@ module Command
237
266
  }
238
267
  end
239
268
 
240
- def self.clean_on_failure_option(required: false)
269
+ def self.skip_secret_access_binding_option(required: false)
241
270
  {
242
- name: :clean_on_failure,
271
+ name: :skip_secret_access_binding,
243
272
  params: {
244
- desc: "Deletes workload when finished with failure (success always deletes)",
273
+ desc: "Skips secret access binding",
245
274
  type: :boolean,
275
+ required: required
276
+ }
277
+ }
278
+ end
279
+
280
+ def self.run_release_phase_option(required: false)
281
+ {
282
+ name: :run_release_phase,
283
+ params: {
284
+ desc: "Runs release phase",
285
+ type: :boolean,
286
+ required: required
287
+ }
288
+ }
289
+ end
290
+
291
+ def self.logs_limit_option(required: false)
292
+ {
293
+ name: :limit,
294
+ params: {
295
+ banner: "NUMBER",
296
+ desc: "Limit on number of log entries to show",
297
+ type: :numeric,
246
298
  required: required,
247
- default: true
299
+ default: 200
248
300
  }
249
301
  }
250
302
  end
251
303
 
252
- def self.all_options
253
- methods.grep(/_option$/).map { |method| send(method.to_s) }
304
+ def self.logs_since_option(required: false)
305
+ {
306
+ name: :since,
307
+ params: {
308
+ banner: "DURATION",
309
+ desc: "Loopback window for showing logs " \
310
+ "(see https://www.npmjs.com/package/parse-duration for the accepted formats, e.g., '1h')",
311
+ type: :string,
312
+ required: required,
313
+ default: "1h"
314
+ }
315
+ }
254
316
  end
255
317
 
256
- def self.all_options_by_key_name
257
- all_options.each_with_object({}) do |option, result|
258
- option[:params][:aliases]&.each { |current_alias| result[current_alias.to_s] = option }
259
- result["--#{option[:name]}"] = option
260
- end
318
+ def self.interactive_option(required: false)
319
+ {
320
+ name: :interactive,
321
+ params: {
322
+ desc: "Runs interactive command",
323
+ type: :boolean,
324
+ required: required
325
+ }
326
+ }
261
327
  end
262
328
 
263
- def wait_for_workload(workload)
264
- step("Waiting for workload", retry_on_failure: true) do
265
- cp.fetch_workload(workload)
266
- end
329
+ def self.detached_option(required: false)
330
+ {
331
+ name: :detached,
332
+ params: {
333
+ desc: "Runs non-interactive command, detaches, and prints commands to log and stop the job",
334
+ type: :boolean,
335
+ required: required
336
+ }
337
+ }
267
338
  end
268
339
 
269
- def wait_for_replica(workload, location)
270
- step("Waiting for replica", retry_on_failure: true) do
271
- cp.workload_get_replicas_safely(workload, location: location)&.dig("items", 0)
272
- end
340
+ def self.cpu_option(required: false)
341
+ {
342
+ name: :cpu,
343
+ params: {
344
+ banner: "CPU",
345
+ desc: "Overrides CPU millicores " \
346
+ "(e.g., '100m' for 100 millicores, '1' for 1 core)",
347
+ type: :string,
348
+ required: required,
349
+ valid_regex: /^\d+m?$/
350
+ }
351
+ }
273
352
  end
274
353
 
275
- def ensure_workload_deleted(workload)
276
- step("Deleting workload") do
277
- cp.delete_workload(workload)
354
+ def self.memory_option(required: false)
355
+ {
356
+ name: :memory,
357
+ params: {
358
+ banner: "MEMORY",
359
+ desc: "Overrides memory size " \
360
+ "(e.g., '100Mi' for 100 mebibytes, '1Gi' for 1 gibibyte)",
361
+ type: :string,
362
+ required: required,
363
+ valid_regex: /^\d+[MG]i$/
364
+ }
365
+ }
366
+ end
367
+
368
+ def self.entrypoint_option(required: false)
369
+ {
370
+ name: :entrypoint,
371
+ params: {
372
+ banner: "ENTRYPOINT",
373
+ desc: "Overrides entrypoint " \
374
+ "(must be a single command or a script path that exists in the container)",
375
+ type: :string,
376
+ required: required,
377
+ valid_regex: /^\S+$/
378
+ }
379
+ }
380
+ end
381
+ # rubocop:enable Metrics/MethodLength
382
+
383
+ def self.all_options
384
+ methods.grep(/_option$/).map { |method| send(method.to_s) }
385
+ end
386
+
387
+ def self.all_options_by_key_name
388
+ all_options.each_with_object({}) do |option, result|
389
+ option[:params][:aliases]&.each { |current_alias| result[current_alias.to_s] = option }
390
+ result["--#{option[:name]}"] = option
278
391
  end
279
392
  end
280
393
 
@@ -290,8 +403,9 @@ module Command
290
403
  end
291
404
  end
292
405
 
293
- def latest_image(app = config.app, org = config.org)
406
+ def latest_image(app = config.app, org = config.org, refresh: false)
294
407
  @latest_image ||= {}
408
+ @latest_image[app] = nil if refresh
295
409
  @latest_image[app] ||=
296
410
  begin
297
411
  items = cp.query_images(app, org)["items"]
@@ -355,7 +469,7 @@ module Command
355
469
  if retry_on_failure
356
470
  until (success = yield)
357
471
  progress.print(".")
358
- sleep 1
472
+ Kernel.sleep(1)
359
473
  end
360
474
  else
361
475
  success = yield
@@ -372,8 +486,35 @@ module Command
372
486
  @cp ||= Controlplane.new(config)
373
487
  end
374
488
 
375
- def perform(cmd)
376
- system(cmd) || exit(false)
489
+ def app_location_link
490
+ "/org/#{config.org}/location/#{config.location}"
491
+ end
492
+
493
+ def app_image_link
494
+ "/org/#{config.org}/image/#{latest_image}"
495
+ end
496
+
497
+ def app_identity
498
+ "#{config.app}-identity"
499
+ end
500
+
501
+ def app_identity_link
502
+ "/org/#{config.org}/gvc/#{config.app}/identity/#{app_identity}"
503
+ end
504
+
505
+ def app_secrets
506
+ config.current[:secrets_name] || "#{config.app_prefix}-secrets"
507
+ end
508
+
509
+ def app_secrets_policy
510
+ config.current[:secrets_policy_name] || "#{app_secrets}-policy"
511
+ end
512
+
513
+ def ensure_docker_running!
514
+ result = Shell.cmd("docker", "version", capture_stderr: true)
515
+ return if result[:success]
516
+
517
+ raise "Can't run Docker. Please make sure that it's installed and started, then try again."
377
518
  end
378
519
 
379
520
  private
@@ -38,16 +38,11 @@ module Command
38
38
  docker_args: config.args,
39
39
  build_args: build_args)
40
40
 
41
- progress.puts("\nPushed image to '/org/#{config.org}/image/#{image_name}'.")
42
- end
43
-
44
- private
45
-
46
- def ensure_docker_running!
47
- `docker version > /dev/null 2>&1`
48
- return if $CHILD_STATUS.success?
41
+ progress.puts("\nPushed image to '/org/#{config.org}/image/#{image_name}'.\n\n")
49
42
 
50
- raise "Can't run Docker. Please make sure that it's installed and started, then try again."
43
+ step("Waiting for image to be available", retry_on_failure: true) do
44
+ image_name == latest_image(refresh: true)
45
+ end
51
46
  end
52
47
  end
53
48
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "date"
4
-
5
3
  module Command
6
4
  class CleanupStaleApps < Base
7
5
  NAME = "cleanup-stale-apps"
@@ -23,7 +21,7 @@ module Command
23
21
 
24
22
  progress.puts("Stale apps:")
25
23
  stale_apps.each do |app|
26
- progress.puts(" #{app[:name]} (#{Shell.color((app[:date]).to_s, :red)})")
24
+ progress.puts(" - #{app[:name]} (#{Shell.color((app[:date]).to_s, :red)})")
27
25
  end
28
26
 
29
27
  return unless confirm_delete
@@ -44,13 +44,6 @@ module Command
44
44
 
45
45
  private
46
46
 
47
- def ensure_docker_running!
48
- `docker version > /dev/null 2>&1`
49
- return if $CHILD_STATUS.success?
50
-
51
- raise "Can't run Docker. Please make sure that it's installed and started, then try again."
52
- end
53
-
54
47
  def ensure_upstream_org!
55
48
  return if @upstream_org
56
49
 
@@ -5,28 +5,54 @@ module Command
5
5
  NAME = "delete"
6
6
  OPTIONS = [
7
7
  app_option(required: true),
8
+ workload_option,
8
9
  skip_confirm_option
9
10
  ].freeze
10
- DESCRIPTION = "Deletes the whole app (GVC with all workloads, all volumesets and all images)"
11
+ DESCRIPTION = "Deletes the whole app (GVC with all workloads, all volumesets and all images) or a specific workload"
11
12
  LONG_DESCRIPTION = <<~DESC
12
- - Deletes the whole app (GVC with all workloads, all volumesets and all images)
13
+ - Deletes the whole app (GVC with all workloads, all volumesets and all images) or a specific workload
13
14
  - Will ask for explicit user confirmation
14
15
  DESC
16
+ EXAMPLES = <<~EX
17
+ ```sh
18
+ # Deletes the whole app (GVC with all workloads, all volumesets and all images).
19
+ cpl delete -a $APP_NAME
20
+
21
+ # Deletes a specific workload.
22
+ cpl delete -a $APP_NAME -w $WORKLOAD_NAME
23
+ ```
24
+ EX
15
25
 
16
26
  def call
27
+ workload = config.options[:workload]
28
+ if workload
29
+ delete_single_workload(workload)
30
+ else
31
+ delete_whole_app
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def delete_single_workload(workload)
38
+ return progress.puts("Workload '#{workload}' does not exist.") if cp.fetch_workload(workload).nil?
39
+ return unless confirm_delete(workload)
40
+
41
+ delete_workload(workload)
42
+ end
43
+
44
+ def delete_whole_app
17
45
  return progress.puts("App '#{config.app}' does not exist.") if cp.fetch_gvc.nil?
18
46
 
19
47
  check_volumesets
20
48
  check_images
21
- return unless confirm_delete
49
+ return unless confirm_delete(config.app)
22
50
 
23
51
  delete_volumesets
24
52
  delete_gvc
25
53
  delete_images
26
54
  end
27
55
 
28
- private
29
-
30
56
  def check_volumesets
31
57
  @volumesets = cp.fetch_volumesets["items"]
32
58
  return progress.puts("No volumesets to delete.") unless @volumesets.any?
@@ -46,10 +72,10 @@ module Command
46
72
  progress.puts("#{Shell.color(message, :red)}\n#{images_list}\n\n")
47
73
  end
48
74
 
49
- def confirm_delete
75
+ def confirm_delete(item)
50
76
  return true if config.options[:yes]
51
77
 
52
- confirmed = Shell.confirm("Are you sure you want to delete '#{config.app}'?")
78
+ confirmed = Shell.confirm("Are you sure you want to delete '#{item}'?")
53
79
  return false unless confirmed
54
80
 
55
81
  progress.puts
@@ -62,6 +88,12 @@ module Command
62
88
  end
63
89
  end
64
90
 
91
+ def delete_workload(workload)
92
+ step("Deleting workload '#{workload}'") do
93
+ cp.delete_workload(workload)
94
+ end
95
+ end
96
+
65
97
  def delete_volumesets
66
98
  @volumesets.each do |volumeset|
67
99
  step("Deleting volumeset '#{volumeset['name']}'") do
@@ -4,17 +4,28 @@ module Command
4
4
  class DeployImage < Base
5
5
  NAME = "deploy-image"
6
6
  OPTIONS = [
7
- app_option(required: true)
7
+ app_option(required: true),
8
+ run_release_phase_option
8
9
  ].freeze
9
- DESCRIPTION = "Deploys the latest image to app workloads"
10
+ DESCRIPTION = "Deploys the latest image to app workloads, and runs a release script (optional)"
10
11
  LONG_DESCRIPTION = <<~DESC
11
12
  - Deploys the latest image to app workloads
13
+ - Optionally runs a release script before deploying if specified through `release_script` in the `.controlplane/controlplane.yml` file and `--run-release-phase` is provided
14
+ - The release script is run in the context of `cpl run` with the latest image
15
+ - The deploy will fail if the release script exits with a non-zero code or doesn't exist
12
16
  DESC
13
17
 
14
18
  def call # rubocop:disable Metrics/MethodLength
19
+ run_release_script if config.options[:run_release_phase]
20
+
15
21
  deployed_endpoints = {}
16
22
 
17
23
  image = latest_image
24
+ if cp.fetch_image_details(image).nil?
25
+ raise "Image '#{image}' does not exist in the Docker repository on Control Plane " \
26
+ "(see https://console.cpln.io/console/org/#{config.org}/repository/#{config.app}). " \
27
+ "Use `cpl build-image` first."
28
+ end
18
29
 
19
30
  config[:app_workloads].each do |workload|
20
31
  workload_data = cp.fetch_workload!(workload)
@@ -34,5 +45,27 @@ module Command
34
45
  progress.puts(" - #{workload}: #{endpoint}")
35
46
  end
36
47
  end
48
+
49
+ private
50
+
51
+ def run_release_script # rubocop:disable Metrics/MethodLength
52
+ release_script_name = config[:release_script]
53
+ release_script_path = Pathname.new("#{config.app_cpln_dir}/#{release_script_name}").expand_path
54
+
55
+ raise "Can't find release script in '#{release_script_path}'." unless File.exist?(release_script_path)
56
+
57
+ progress.puts("Running release script...\n\n")
58
+
59
+ release_script = File.read(release_script_path)
60
+ begin
61
+ Cpl::Cli.start(["run", "-a", config.app, "--image", "latest", "--", release_script])
62
+ rescue SystemExit => e
63
+ progress.puts
64
+
65
+ raise "Failed to run release script." if e.status.nonzero?
66
+
67
+ progress.puts("Finished running release script.\n\n")
68
+ end
69
+ end
37
70
  end
38
71
  end
@@ -17,7 +17,7 @@ module Command
17
17
  EX
18
18
 
19
19
  def call
20
- exit(!cp.fetch_gvc.nil?)
20
+ exit(cp.fetch_gvc.nil? ? ExitCode::ERROR_DEFAULT : ExitCode::SUCCESS)
21
21
  end
22
22
  end
23
23
  end
@@ -5,7 +5,7 @@ module Command
5
5
  include Thor::Actions
6
6
 
7
7
  def copy_files
8
- directory("generator_templates", ".controlplane")
8
+ directory("generator_templates", ".controlplane", verbose: ENV.fetch("HIDE_COMMAND_OUTPUT", nil) != "true")
9
9
  end
10
10
 
11
11
  def self.source_root
data/lib/command/info.rb CHANGED
@@ -76,7 +76,7 @@ module Command
76
76
  result.push(config.org)
77
77
  else
78
78
  config.apps.each do |_, app_options|
79
- org = app_options[:cpln_org]
79
+ org = app_org(app_options)
80
80
  result.push(org) if org && !result.include?(org)
81
81
  end
82
82
  end
@@ -90,14 +90,18 @@ module Command
90
90
  config.apps.each do |app_name, app_options|
91
91
  next if config.app && !config.app_matches?(config.app, app_name, app_options)
92
92
 
93
- app_org = app_options[:cpln_org]
94
- result.push(app_name.to_s) if app_org == org
93
+ current_app_org = app_org(app_options)
94
+ result.push(app_name.to_s) if current_app_org == org
95
95
  end
96
96
 
97
97
  result += @app_workloads.keys.map(&:to_s)
98
98
  result.uniq.sort
99
99
  end
100
100
 
101
+ def app_org(app_options)
102
+ app_options[:cpln_org]
103
+ end
104
+
101
105
  def any_app_starts_with?(app)
102
106
  @app_workloads.keys.find { |app_name| config.app_matches?(app_name, app, config.apps[app.to_sym]) }
103
107
  end
data/lib/command/logs.rb CHANGED
@@ -5,11 +5,15 @@ module Command
5
5
  NAME = "logs"
6
6
  OPTIONS = [
7
7
  app_option(required: true),
8
- workload_option
8
+ workload_option,
9
+ replica_option,
10
+ logs_limit_option,
11
+ logs_since_option
9
12
  ].freeze
10
13
  DESCRIPTION = "Light wrapper to display tailed raw logs for app/workload syntax"
11
14
  LONG_DESCRIPTION = <<~DESC
12
15
  - Light wrapper to display tailed raw logs for app/workload syntax
16
+ - Defaults to showing the last 200 entries from the past 1 hour before tailing
13
17
  DESC
14
18
  EXAMPLES = <<~EX
15
19
  ```sh
@@ -18,12 +22,28 @@ module Command
18
22
 
19
23
  # Displays logs for a specific workload.
20
24
  cpl logs -a $APP_NAME -w $WORKLOAD_NAME
25
+
26
+ # Displays logs for a specific replica of a workload.
27
+ cpl logs -a $APP_NAME -w $WORKLOAD_NAME -r $REPLICA_NAME
28
+
29
+ # Uses a different limit on number of entries.
30
+ cpl logs -a $APP_NAME --limit 100
31
+
32
+ # Uses a different loopback window.
33
+ cpl logs -a $APP_NAME --since 30min
21
34
  ```
22
35
  EX
23
36
 
24
37
  def call
25
38
  workload = config.options[:workload] || config[:one_off_workload]
26
- cp.logs(workload: workload)
39
+ replica = config.options[:replica]
40
+ limit = config.options[:limit]
41
+ since = config.options[:since]
42
+
43
+ message = replica ? "replica '#{replica}'" : "workload '#{workload}'"
44
+ progress.puts("Fetching logs for #{message}...\n\n")
45
+
46
+ cp.logs(workload: workload, replica: replica, limit: limit, since: since)
27
47
  end
28
48
  end
29
49
  end
@@ -48,7 +48,7 @@ module Command
48
48
  cp.set_domain_workload(domain_data, one_off_workload)
49
49
 
50
50
  # Give it a bit of time for the domain to update
51
- sleep 30
51
+ Kernel.sleep(30)
52
52
  end
53
53
 
54
54
  progress.puts
@@ -48,7 +48,7 @@ module Command
48
48
  cp.set_domain_workload(domain_data, maintenance_workload)
49
49
 
50
50
  # Give it a bit of time for the domain to update
51
- sleep 30
51
+ Kernel.sleep(30)
52
52
  end
53
53
 
54
54
  progress.puts
data/lib/command/open.rb CHANGED
@@ -25,9 +25,9 @@ module Command
25
25
  workload = config.options[:workload] || config[:one_off_workload]
26
26
  data = cp.fetch_workload!(workload)
27
27
  url = data["status"]["endpoint"]
28
- opener = `which xdg-open open`.split("\n").grep_v("not found").first
28
+ opener = Shell.cmd("which", "xdg-open", "open")[:output].split("\n").grep_v("not found").first
29
29
 
30
- exec %(#{opener} "#{url}")
30
+ Kernel.exec(opener, url)
31
31
  end
32
32
  end
33
33
  end
@@ -18,9 +18,9 @@ module Command
18
18
  url = "https://console.cpln.io/console/org/#{config.org}/gvc/#{config.app}"
19
19
  url += "/workload/#{workload}" if workload
20
20
  url += "/-info"
21
- opener = `which xdg-open open`.split("\n").grep_v("not found").first
21
+ opener = Shell.cmd("which", "xdg-open", "open")[:output].split("\n").grep_v("not found").first
22
22
 
23
- exec %(#{opener} "#{url}")
23
+ Kernel.exec(opener, url)
24
24
  end
25
25
  end
26
26
  end
@@ -12,47 +12,27 @@ module Command
12
12
  - Copies the latest image from upstream, runs a release script (optional), and deploys the image
13
13
  - It performs the following steps:
14
14
  - Runs `cpl copy-image-from-upstream` to copy the latest image from upstream
15
- - Runs a release script if specified through `release_script` in the `.controlplane/controlplane.yml` file
16
15
  - Runs `cpl deploy-image` to deploy the image
16
+ - If `.controlplane/controlplane.yml` includes the `release_script`, `cpl deploy-image` will use the `--run-release-phase` option
17
+ - The deploy will fail if the release script exits with a non-zero code
17
18
  DESC
18
19
 
19
20
  def call
20
- check_release_script
21
21
  copy_image_from_upstream
22
- run_release_script
23
22
  deploy_image
24
23
  end
25
24
 
26
25
  private
27
26
 
28
- def check_release_script
29
- release_script_name = config.current[:release_script]
30
- unless release_script_name
31
- progress.puts("Can't find option 'release_script' for app '#{config.app}' in 'controlplane.yml'. " \
32
- "Skipping release script.\n\n")
33
- return
34
- end
35
-
36
- @release_script_path = Pathname.new("#{config.app_cpln_dir}/#{release_script_name}").expand_path
37
-
38
- raise "Can't find release script in '#{@release_script_path}'." unless File.exist?(@release_script_path)
39
- end
40
-
41
27
  def copy_image_from_upstream
42
28
  Cpl::Cli.start(["copy-image-from-upstream", "-a", config.app, "-t", config.options[:upstream_token]])
43
29
  progress.puts
44
30
  end
45
31
 
46
- def run_release_script
47
- return unless @release_script_path
48
-
49
- progress.puts("Running release script...\n\n")
50
- perform("bash #{@release_script_path}")
51
- progress.puts
52
- end
53
-
54
32
  def deploy_image
55
- Cpl::Cli.start(["deploy-image", "-a", config.app])
33
+ args = []
34
+ args.push("--run-release-phase") if config.current[:release_script]
35
+ Cpl::Cli.start(["deploy-image", "-a", config.app, *args])
56
36
  end
57
37
  end
58
38
  end
data/lib/command/ps.rb CHANGED
@@ -33,7 +33,7 @@ module Command
33
33
  workloads.each do |workload|
34
34
  cp.fetch_workload!(workload)
35
35
 
36
- result = cp.workload_get_replicas(workload, location: location)
36
+ result = cp.fetch_workload_replicas(workload, location: location)
37
37
  result["items"].each { |replica| puts replica }
38
38
  end
39
39
  end