cpl 1.4.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 (61) 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 +8 -0
  9. data/CONTRIBUTING.md +32 -2
  10. data/Gemfile.lock +34 -29
  11. data/README.md +34 -25
  12. data/cpl.gemspec +1 -1
  13. data/docs/commands.md +54 -54
  14. data/docs/dns.md +6 -0
  15. data/docs/migrating.md +10 -10
  16. data/docs/tips.md +12 -10
  17. data/examples/circleci.yml +3 -3
  18. data/examples/controlplane.yml +25 -16
  19. data/lib/command/apply_template.rb +9 -9
  20. data/lib/command/base.rb +132 -37
  21. data/lib/command/build_image.rb +4 -9
  22. data/lib/command/cleanup_stale_apps.rb +1 -1
  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 +18 -3
  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/ps.rb +1 -1
  35. data/lib/command/ps_start.rb +2 -1
  36. data/lib/command/ps_stop.rb +40 -8
  37. data/lib/command/ps_wait.rb +3 -2
  38. data/lib/command/run.rb +430 -69
  39. data/lib/command/setup_app.rb +4 -1
  40. data/lib/constants/exit_code.rb +7 -0
  41. data/lib/core/config.rb +1 -1
  42. data/lib/core/controlplane.rb +109 -48
  43. data/lib/core/controlplane_api.rb +7 -1
  44. data/lib/core/controlplane_api_cli.rb +3 -3
  45. data/lib/core/controlplane_api_direct.rb +1 -1
  46. data/lib/core/shell.rb +15 -9
  47. data/lib/cpl/version.rb +1 -1
  48. data/lib/cpl.rb +48 -9
  49. data/lib/deprecated_commands.json +2 -1
  50. data/lib/generator_templates/controlplane.yml +2 -2
  51. data/script/check_cpln_links +3 -3
  52. data/templates/{gvc.yml → app.yml} +5 -0
  53. data/templates/secrets.yml +8 -0
  54. metadata +23 -26
  55. data/.rspec +0 -1
  56. data/lib/command/run_cleanup.rb +0 -116
  57. data/lib/command/run_detached.rb +0 -176
  58. data/lib/core/scripts.rb +0 -34
  59. data/templates/identity.yml +0 -3
  60. data/templates/secrets-policy.yml +0 -4
  61. /data/lib/generator_templates/templates/{gvc.yml → app.yml} +0 -0
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,66 +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.skip_secret_access_binding_option(required: false)
304
+ def self.logs_since_option(required: false)
253
305
  {
254
- name: :skip_secret_access_binding,
306
+ name: :since,
255
307
  params: {
256
- desc: "Skips secret access binding",
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
+ }
316
+ end
317
+
318
+ def self.interactive_option(required: false)
319
+ {
320
+ name: :interactive,
321
+ params: {
322
+ desc: "Runs interactive command",
257
323
  type: :boolean,
258
324
  required: required
259
325
  }
260
326
  }
261
327
  end
262
328
 
263
- def self.run_release_phase_option(required: false)
329
+ def self.detached_option(required: false)
264
330
  {
265
- name: :run_release_phase,
331
+ name: :detached,
266
332
  params: {
267
- desc: "Runs release phase",
333
+ desc: "Runs non-interactive command, detaches, and prints commands to log and stop the job",
268
334
  type: :boolean,
269
335
  required: required
270
336
  }
271
337
  }
272
338
  end
273
339
 
274
- def self.all_options
275
- methods.grep(/_option$/).map { |method| send(method.to_s) }
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
+ }
276
352
  end
277
353
 
278
- def self.all_options_by_key_name
279
- all_options.each_with_object({}) do |option, result|
280
- option[:params][:aliases]&.each { |current_alias| result[current_alias.to_s] = option }
281
- result["--#{option[:name]}"] = option
282
- end
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
+ }
283
366
  end
284
367
 
285
- def wait_for_workload(workload)
286
- step("Waiting for workload", retry_on_failure: true) do
287
- cp.fetch_workload(workload)
288
- end
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
+ }
289
380
  end
381
+ # rubocop:enable Metrics/MethodLength
290
382
 
291
- def wait_for_replica(workload, location)
292
- step("Waiting for replica", retry_on_failure: true) do
293
- cp.workload_get_replicas_safely(workload, location: location)&.dig("items", 0)
294
- end
383
+ def self.all_options
384
+ methods.grep(/_option$/).map { |method| send(method.to_s) }
295
385
  end
296
386
 
297
- def ensure_workload_deleted(workload)
298
- step("Deleting workload") do
299
- cp.delete_workload(workload)
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
300
391
  end
301
392
  end
302
393
 
@@ -312,8 +403,9 @@ module Command
312
403
  end
313
404
  end
314
405
 
315
- def latest_image(app = config.app, org = config.org)
406
+ def latest_image(app = config.app, org = config.org, refresh: false)
316
407
  @latest_image ||= {}
408
+ @latest_image[app] = nil if refresh
317
409
  @latest_image[app] ||=
318
410
  begin
319
411
  items = cp.query_images(app, org)["items"]
@@ -377,7 +469,7 @@ module Command
377
469
  if retry_on_failure
378
470
  until (success = yield)
379
471
  progress.print(".")
380
- sleep 1
472
+ Kernel.sleep(1)
381
473
  end
382
474
  else
383
475
  success = yield
@@ -394,10 +486,6 @@ module Command
394
486
  @cp ||= Controlplane.new(config)
395
487
  end
396
488
 
397
- def perform!(cmd)
398
- system(cmd) || exit(1)
399
- end
400
-
401
489
  def app_location_link
402
490
  "/org/#{config.org}/location/#{config.location}"
403
491
  end
@@ -415,11 +503,18 @@ module Command
415
503
  end
416
504
 
417
505
  def app_secrets
418
- "#{config.app_prefix}-secrets"
506
+ config.current[:secrets_name] || "#{config.app_prefix}-secrets"
419
507
  end
420
508
 
421
509
  def app_secrets_policy
422
- "#{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."
423
518
  end
424
519
 
425
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
@@ -21,7 +21,7 @@ module Command
21
21
 
22
22
  progress.puts("Stale apps:")
23
23
  stale_apps.each do |app|
24
- progress.puts(" #{app[:name]} (#{Shell.color((app[:date]).to_s, :red)})")
24
+ progress.puts(" - #{app[:name]} (#{Shell.color((app[:date]).to_s, :red)})")
25
25
  end
26
26
 
27
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
@@ -11,6 +11,7 @@ module Command
11
11
  LONG_DESCRIPTION = <<~DESC
12
12
  - Deploys the latest image to app workloads
13
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
14
15
  - The deploy will fail if the release script exits with a non-zero code or doesn't exist
15
16
  DESC
16
17
 
@@ -20,6 +21,11 @@ module Command
20
21
  deployed_endpoints = {}
21
22
 
22
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
23
29
 
24
30
  config[:app_workloads].each do |workload|
25
31
  workload_data = cp.fetch_workload!(workload)
@@ -42,15 +48,24 @@ module Command
42
48
 
43
49
  private
44
50
 
45
- def run_release_script
51
+ def run_release_script # rubocop:disable Metrics/MethodLength
46
52
  release_script_name = config[:release_script]
47
53
  release_script_path = Pathname.new("#{config.app_cpln_dir}/#{release_script_name}").expand_path
48
54
 
49
55
  raise "Can't find release script in '#{release_script_path}'." unless File.exist?(release_script_path)
50
56
 
51
57
  progress.puts("Running release script...\n\n")
52
- perform!("bash #{release_script_path}")
53
- progress.puts
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
54
69
  end
55
70
  end
56
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
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
@@ -6,6 +6,7 @@ module Command
6
6
  OPTIONS = [
7
7
  app_option(required: true),
8
8
  workload_option,
9
+ location_option,
9
10
  wait_option("workload to be ready")
10
11
  ].freeze
11
12
  DESCRIPTION = "Starts workloads in app"
@@ -42,7 +43,7 @@ module Command
42
43
 
43
44
  @workloads.reverse_each do |workload|
44
45
  step("Waiting for workload '#{workload}' to be ready", retry_on_failure: true) do
45
- cp.workload_deployments_ready?(workload, expected_status: true)
46
+ cp.workload_deployments_ready?(workload, location: config.location, expected_status: true)
46
47
  end
47
48
  end
48
49
  end