cpl 1.3.0 → 2.0.0.rc.0

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