cpl 0.4.1 → 0.5.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.overcommit.yml +10 -0
  3. data/Gemfile.lock +10 -3
  4. data/README.md +6 -0
  5. data/cpl.gemspec +1 -0
  6. data/docs/commands.md +51 -3
  7. data/googlee2da545df05d92f9.html +1 -0
  8. data/lib/command/base.rb +65 -7
  9. data/lib/command/build_image.rb +6 -5
  10. data/lib/command/cleanup_old_images.rb +8 -7
  11. data/lib/command/cleanup_stale_apps.rb +11 -9
  12. data/lib/command/config.rb +30 -15
  13. data/lib/command/copy_image_from_upstream.rb +110 -0
  14. data/lib/command/delete.rb +10 -12
  15. data/lib/command/deploy_image.rb +6 -5
  16. data/lib/command/env.rb +2 -2
  17. data/lib/command/exists.rb +4 -4
  18. data/lib/command/info.rb +233 -0
  19. data/lib/command/latest_image.rb +2 -2
  20. data/lib/command/logs.rb +4 -4
  21. data/lib/command/no_command.rb +3 -3
  22. data/lib/command/open.rb +4 -4
  23. data/lib/command/promote_app_from_upstream.rb +58 -0
  24. data/lib/command/ps.rb +10 -13
  25. data/lib/command/ps_restart.rb +9 -6
  26. data/lib/command/ps_start.rb +7 -6
  27. data/lib/command/ps_stop.rb +7 -6
  28. data/lib/command/run.rb +5 -5
  29. data/lib/command/run_detached.rb +7 -5
  30. data/lib/command/setup.rb +71 -13
  31. data/lib/command/test.rb +2 -2
  32. data/lib/command/version.rb +2 -2
  33. data/lib/core/config.rb +26 -19
  34. data/lib/core/controlplane.rb +77 -11
  35. data/lib/core/controlplane_api.rb +12 -0
  36. data/lib/core/controlplane_api_cli.rb +1 -1
  37. data/lib/core/controlplane_api_direct.rb +2 -2
  38. data/lib/core/shell.rb +25 -3
  39. data/lib/cpl/version.rb +1 -1
  40. data/lib/cpl.rb +19 -10
  41. data/lib/deprecated_commands.json +6 -0
  42. data/script/add_command +37 -0
  43. data/script/generate_commands_docs +5 -5
  44. data/script/rename_command +43 -0
  45. metadata +24 -2
@@ -7,14 +7,14 @@ module Command
7
7
  app_option(required: true)
8
8
  ].freeze
9
9
  DESCRIPTION = "Shell-checks if an application (GVC) exists, useful in scripts"
10
- LONG_DESCRIPTION = <<~HEREDOC
10
+ LONG_DESCRIPTION = <<~DESC
11
11
  - Shell-checks if an application (GVC) exists, useful in scripts, e.g.:
12
- HEREDOC
13
- EXAMPLES = <<~HEREDOC
12
+ DESC
13
+ EXAMPLES = <<~EX
14
14
  ```sh
15
15
  if [ cpl exists -a $APP_NAME ]; ...
16
16
  ```
17
- HEREDOC
17
+ EX
18
18
 
19
19
  def call
20
20
  exit(!cp.fetch_gvc.nil?)
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Info < Base # rubocop:disable Metrics/ClassLength
5
+ NAME = "info"
6
+ OPTIONS = [
7
+ org_option,
8
+ app_option
9
+ ].freeze
10
+ DESCRIPTION = "Displays the diff between defined/available apps/workloads (apps equal GVCs)"
11
+ LONG_DESCRIPTION = <<~DESC
12
+ - Displays the diff between defined/available apps/workloads (apps equal GVCs)
13
+ - Apps that are defined but not available are displayed in red
14
+ - Apps that are available but not defined are displayed in green
15
+ - Apps that are both defined and available are displayed in white
16
+ - The diff is based on what's defined in the `.controlplane/controlplane.yml` file
17
+ DESC
18
+ EXAMPLES = <<~EX
19
+ ```sh
20
+ # Shows diff for all apps in all orgs.
21
+ cpl info
22
+
23
+ # Shows diff for all apps in a specific org.
24
+ cpl info -o $ORG_NAME
25
+
26
+ # Shows diff for a specific app.
27
+ cpl info -a $APP_NAME
28
+ ```
29
+ EX
30
+
31
+ def call
32
+ @missing_apps_workloads = {}
33
+ @missing_apps_starting_with = {}
34
+
35
+ if config.app && !config.current[:match_if_app_name_starts_with]
36
+ single_app_info
37
+ else
38
+ multiple_apps_info
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def app_matches?(app, app_name, app_options)
45
+ app == app_name.to_s || (app_options[:match_if_app_name_starts_with] && app.start_with?(app_name.to_s))
46
+ end
47
+
48
+ def find_app_options(app)
49
+ @app_options ||= {}
50
+ @app_options[app] ||= config.apps.find do |app_name, app_options|
51
+ app_matches?(app, app_name, app_options)
52
+ end&.last
53
+ end
54
+
55
+ def find_workloads(app)
56
+ app_options = find_app_options(app)
57
+ return [] if app_options.nil?
58
+
59
+ (app_options[:app_workloads] + app_options[:additional_workloads] + [app_options[:one_off_workload]]).uniq
60
+ end
61
+
62
+ def fetch_workloads(app)
63
+ cp.fetch_workloads(app)["items"].map { |workload| workload["name"] }
64
+ end
65
+
66
+ def fetch_app_workloads(org) # rubocop:disable Metrics/MethodLength
67
+ result = {}
68
+
69
+ workloads = cp.fetch_workloads_by_org(org)["items"]
70
+ workloads.each do |workload|
71
+ app = workload["links"].find { |link| link["rel"] == "gvc" }["href"].split("/").last
72
+
73
+ result[app] ||= []
74
+ result[app].push(workload["name"])
75
+ end
76
+
77
+ if config.app
78
+ result.select { |app, _| app_matches?(app, config.app, config.current) }
79
+ else
80
+ result.reject { |app, _| find_app_options(app).nil? }
81
+ end
82
+ end
83
+
84
+ def orgs # rubocop:disable Metrics/MethodLength
85
+ result = []
86
+
87
+ if config.options[:org]
88
+ result.push(config.options[:org])
89
+ else
90
+ config.apps.each do |app_name, app_options|
91
+ next if config.app && !app_matches?(config.app, app_name, app_options)
92
+
93
+ org = app_options[:cpln_org] || app_options[:org]
94
+ result.push(org) unless result.include?(org)
95
+ end
96
+ end
97
+
98
+ result.sort
99
+ end
100
+
101
+ def apps(org)
102
+ result = []
103
+
104
+ config.apps.each do |app_name, app_options|
105
+ next if config.app && !app_matches?(config.app, app_name, app_options)
106
+
107
+ app_org = app_options[:cpln_org] || app_options[:org]
108
+ result.push(app_name.to_s) if app_org == org
109
+ end
110
+
111
+ result += @app_workloads.keys.map(&:to_s)
112
+ result.uniq.sort
113
+ end
114
+
115
+ def should_app_start_with?(app)
116
+ config.apps[app.to_sym]&.dig(:match_if_app_name_starts_with)
117
+ end
118
+
119
+ def any_app_starts_with?(app)
120
+ @app_workloads.keys.find { |app_name| app_matches?(app_name, app, config.apps[app.to_sym]) }
121
+ end
122
+
123
+ def check_any_app_starts_with(app)
124
+ if any_app_starts_with?(app)
125
+ false
126
+ else
127
+ @missing_apps_starting_with[app] ||= ["gvc"]
128
+
129
+ puts " - #{Shell.color("Any app starting with '#{app}'", :red)}"
130
+ true
131
+ end
132
+ end
133
+
134
+ def add_to_missing_workloads(app, workload)
135
+ if should_app_start_with?(app)
136
+ @missing_apps_starting_with[app] ||= []
137
+ @missing_apps_starting_with[app].push(workload)
138
+ else
139
+ @missing_apps_workloads[app] ||= []
140
+ @missing_apps_workloads[app].push(workload)
141
+ end
142
+ end
143
+
144
+ def print_app(app, org)
145
+ if should_app_start_with?(app)
146
+ check_any_app_starts_with(app)
147
+ elsif cp.fetch_gvc(app, org).nil?
148
+ @missing_apps_workloads[app] = ["gvc"]
149
+
150
+ puts " - #{Shell.color(app, :red)}"
151
+ true
152
+ else
153
+ puts " - #{app}"
154
+ true
155
+ end
156
+ end
157
+
158
+ def print_workload(app, workload)
159
+ if @defined_workloads.include?(workload) && !@available_workloads.include?(workload)
160
+ add_to_missing_workloads(app, workload)
161
+
162
+ puts " - #{Shell.color(workload, :red)}"
163
+ elsif !@defined_workloads.include?(workload) && @available_workloads.include?(workload)
164
+ puts " - #{Shell.color(workload, :green)}"
165
+ else
166
+ puts " - #{workload}"
167
+ end
168
+ end
169
+
170
+ def print_missing_apps_workloads
171
+ return if @missing_apps_workloads.empty?
172
+
173
+ puts "\nSome apps/workloads are missing. Please create them with:"
174
+
175
+ @missing_apps_workloads.each do |app, workloads|
176
+ puts " - `cpl setup #{workloads.join(' ')} -a #{app}`"
177
+ end
178
+ end
179
+
180
+ def print_missing_apps_starting_with
181
+ return if @missing_apps_starting_with.empty?
182
+
183
+ puts "\nThere are no apps starting with some names. If you wish to create any, do so with " \
184
+ "(replace 'whatever' with whatever suffix you want):"
185
+
186
+ @missing_apps_starting_with.each do |app, workloads|
187
+ app_with_suffix = "#{app}#{app.end_with?('-') ? '' : '-'}whatever"
188
+ puts " - `cpl setup #{workloads.join(' ')} -a #{app_with_suffix}`"
189
+ end
190
+ end
191
+
192
+ def single_app_info
193
+ puts "#{Shell.color(config.org, :blue)}:"
194
+
195
+ print_app(config.app, config.org)
196
+
197
+ @defined_workloads = find_workloads(config.app)
198
+ @available_workloads = fetch_workloads(config.app)
199
+
200
+ workloads = (@defined_workloads + @available_workloads).uniq.sort
201
+ workloads.each do |workload|
202
+ print_workload(config.app, workload)
203
+ end
204
+
205
+ print_missing_apps_workloads
206
+ end
207
+
208
+ def multiple_apps_info # rubocop:disable Metrics/MethodLength
209
+ orgs.each do |org|
210
+ puts "#{Shell.color(org, :blue)}:"
211
+
212
+ @app_workloads = fetch_app_workloads(org)
213
+
214
+ apps(org).each do |app|
215
+ next unless print_app(app, org)
216
+
217
+ @defined_workloads = find_workloads(app)
218
+ @available_workloads = @app_workloads[app] || []
219
+
220
+ workloads = (@defined_workloads + @available_workloads).uniq.sort
221
+ workloads.each do |workload|
222
+ print_workload(app, workload)
223
+ end
224
+ end
225
+
226
+ puts
227
+ end
228
+
229
+ print_missing_apps_workloads
230
+ print_missing_apps_starting_with
231
+ end
232
+ end
233
+ end
@@ -7,9 +7,9 @@ module Command
7
7
  app_option(required: true)
8
8
  ].freeze
9
9
  DESCRIPTION = "Displays the latest image name"
10
- LONG_DESCRIPTION = <<~HEREDOC
10
+ LONG_DESCRIPTION = <<~DESC
11
11
  - Displays the latest image name
12
- HEREDOC
12
+ DESC
13
13
 
14
14
  def call
15
15
  puts latest_image
data/lib/command/logs.rb CHANGED
@@ -8,10 +8,10 @@ module Command
8
8
  workload_option
9
9
  ].freeze
10
10
  DESCRIPTION = "Light wrapper to display tailed raw logs for app/workload syntax"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - Light wrapper to display tailed raw logs for app/workload syntax
13
- HEREDOC
14
- EXAMPLES = <<~HEREDOC
13
+ DESC
14
+ EXAMPLES = <<~EX
15
15
  ```sh
16
16
  # Displays logs for the default workload (`one_off_workload`).
17
17
  cpl logs -a $APP_NAME
@@ -19,7 +19,7 @@ module Command
19
19
  # Displays logs for a specific workload.
20
20
  cpl logs -a $APP_NAME -w $WORKLOAD_NAME
21
21
  ```
22
- HEREDOC
22
+ EX
23
23
 
24
24
  def call
25
25
  workload = config.options[:workload] || config[:one_off_workload]
@@ -5,15 +5,15 @@ module Command
5
5
  NAME = "no-command"
6
6
  OPTIONS = [version_option].freeze
7
7
  DESCRIPTION = "Called when no command was specified"
8
- LONG_DESCRIPTION = <<~HEREDOC
8
+ LONG_DESCRIPTION = <<~DESC
9
9
  - Called when no command was specified
10
- HEREDOC
10
+ DESC
11
11
  HIDE = true
12
12
 
13
13
  def call
14
14
  return unless config.options[:version]
15
15
 
16
- Version.new(config).call
16
+ perform("cpl version")
17
17
  end
18
18
  end
19
19
  end
data/lib/command/open.rb CHANGED
@@ -8,10 +8,10 @@ module Command
8
8
  workload_option
9
9
  ].freeze
10
10
  DESCRIPTION = "Opens the app endpoint URL in the default browser"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - Opens the app endpoint URL in the default browser
13
- HEREDOC
14
- EXAMPLES = <<~HEREDOC
13
+ DESC
14
+ EXAMPLES = <<~EX
15
15
  ```sh
16
16
  # Opens the endpoint of the default workload (`one_off_workload`).
17
17
  cpl open -a $APP_NAME
@@ -19,7 +19,7 @@ module Command
19
19
  # Opens the endpoint of a specific workload.
20
20
  cpl open -a $APP_NAME -w $WORKLOAD_NAME
21
21
  ```
22
- HEREDOC
22
+ EX
23
23
 
24
24
  def call
25
25
  workload = config.options[:workload] || config[:one_off_workload]
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class PromoteAppFromUpstream < Base
5
+ NAME = "promote-app-from-upstream"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ upstream_token_option(required: true)
9
+ ].freeze
10
+ DESCRIPTION = "Copies the latest image from upstream, runs a release script (optional), and deploys the image"
11
+ LONG_DESCRIPTION = <<~DESC
12
+ - Copies the latest image from upstream, runs a release script (optional), and deploys the image
13
+ - It performs the following steps:
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
+ - Runs `cpl deploy-image` to deploy the image
17
+ DESC
18
+
19
+ def call
20
+ check_release_script
21
+ copy_image_from_upstream
22
+ run_release_script
23
+ deploy_image
24
+ end
25
+
26
+ private
27
+
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
+ def copy_image_from_upstream
42
+ perform("cpl copy-image-from-upstream -a #{config.app} -t #{config.options[:upstream_token]}")
43
+ progress.puts
44
+ end
45
+
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
+ def deploy_image
55
+ perform("cpl deploy-image -a #{config.app}")
56
+ end
57
+ end
58
+ end
data/lib/command/ps.rb CHANGED
@@ -8,10 +8,10 @@ module Command
8
8
  workload_option
9
9
  ].freeze
10
10
  DESCRIPTION = "Shows running replicas in app"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - Shows running replicas in app
13
- HEREDOC
14
- EXAMPLES = <<~HEREDOC
13
+ DESC
14
+ EXAMPLES = <<~EX
15
15
  ```sh
16
16
  # Shows running replicas in app, for all workloads.
17
17
  cpl ps -a $APP_NAME
@@ -19,21 +19,18 @@ module Command
19
19
  # Shows running replicas in app, for a specific workload.
20
20
  cpl ps -a $APP_NAME -w $WORKLOAD_NAME
21
21
  ```
22
- HEREDOC
22
+ EX
23
+
24
+ def call
25
+ cp.fetch_gvc!
23
26
 
24
- def call # rubocop:disable Metrics/MethodLength
25
27
  workloads = [config.options[:workload]] if config.options[:workload]
26
28
  workloads ||= config[:app_workloads] + config[:additional_workloads]
27
-
28
29
  workloads.each do |workload|
30
+ cp.fetch_workload!(workload)
31
+
29
32
  result = cp.workload_get_replicas(workload, location: config[:default_location])
30
- if result.nil?
31
- puts "#{workload}: no workload"
32
- elsif result["items"].nil?
33
- puts "#{workload}: no replicas"
34
- else
35
- result["items"].each { |replica| puts replica }
36
- end
33
+ result["items"].each { |replica| puts replica }
37
34
  end
38
35
  end
39
36
  end
@@ -8,10 +8,10 @@ module Command
8
8
  workload_option
9
9
  ].freeze
10
10
  DESCRIPTION = "Forces redeploy of workloads in app"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - Forces redeploy of workloads in app
13
- HEREDOC
14
- EXAMPLES = <<~HEREDOC
13
+ DESC
14
+ EXAMPLES = <<~EX
15
15
  ```sh
16
16
  # Forces redeploy of all workloads in app.
17
17
  cpl ps:restart -a $APP_NAME
@@ -19,15 +19,18 @@ module Command
19
19
  # Forces redeploy of a specific workload in app.
20
20
  cpl ps:restart -a $APP_NAME -w $WORKLOAD_NAME
21
21
  ```
22
- HEREDOC
22
+ EX
23
23
 
24
24
  def call
25
25
  workloads = [config.options[:workload]] if config.options[:workload]
26
26
  workloads ||= config[:app_workloads] + config[:additional_workloads]
27
27
 
28
28
  workloads.each do |workload|
29
- cp.workload_force_redeployment(workload)
30
- progress.puts "#{workload} restarted"
29
+ step("Restarting workload '#{workload}'") do
30
+ cp.fetch_workload!(workload)
31
+
32
+ cp.workload_force_redeployment(workload)
33
+ end
31
34
  end
32
35
  end
33
36
  end
@@ -8,10 +8,10 @@ module Command
8
8
  workload_option
9
9
  ].freeze
10
10
  DESCRIPTION = "Starts workloads in app"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - Starts workloads in app
13
- HEREDOC
14
- EXAMPLES = <<~HEREDOC
13
+ DESC
14
+ EXAMPLES = <<~EX
15
15
  ```sh
16
16
  # Starts all workloads in app.
17
17
  cpl ps:start -a $APP_NAME
@@ -19,15 +19,16 @@ module Command
19
19
  # Starts a specific workload in app.
20
20
  cpl ps:start -a $APP_NAME -w $WORKLOAD_NAME
21
21
  ```
22
- HEREDOC
22
+ EX
23
23
 
24
24
  def call
25
25
  workloads = [config.options[:workload]] if config.options[:workload]
26
26
  workloads ||= config[:app_workloads] + config[:additional_workloads]
27
27
 
28
28
  workloads.reverse_each do |workload|
29
- cp.workload_set_suspend(workload, false)
30
- progress.puts "#{workload} started"
29
+ step("Starting workload '#{workload}'") do
30
+ cp.workload_set_suspend(workload, false)
31
+ end
31
32
  end
32
33
  end
33
34
  end
@@ -8,10 +8,10 @@ module Command
8
8
  workload_option
9
9
  ].freeze
10
10
  DESCRIPTION = "Stops workloads in app"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - Stops workloads in app
13
- HEREDOC
14
- EXAMPLES = <<~HEREDOC
13
+ DESC
14
+ EXAMPLES = <<~EX
15
15
  ```sh
16
16
  # Stops all workloads in app.
17
17
  cpl ps:stop -a $APP_NAME
@@ -19,15 +19,16 @@ module Command
19
19
  # Stops a specific workload in app.
20
20
  cpl ps:stop -a $APP_NAME -w $WORKLOAD_NAME
21
21
  ```
22
- HEREDOC
22
+ EX
23
23
 
24
24
  def call
25
25
  workloads = [config.options[:workload]] if config.options[:workload]
26
26
  workloads ||= config[:app_workloads] + config[:additional_workloads]
27
27
 
28
28
  workloads.each do |workload|
29
- cp.workload_set_suspend(workload, true)
30
- progress.puts "#{workload} stopped"
29
+ step("Stopping workload '#{workload}'") do
30
+ cp.workload_set_suspend(workload, true)
31
+ end
31
32
  end
32
33
  end
33
34
  end
data/lib/command/run.rb CHANGED
@@ -11,15 +11,15 @@ module Command
11
11
  image_option
12
12
  ].freeze
13
13
  DESCRIPTION = "Runs one-off **_interactive_** replicas (analog of `heroku run`)"
14
- LONG_DESCRIPTION = <<~HEREDOC
14
+ LONG_DESCRIPTION = <<~DESC
15
15
  - Runs one-off **_interactive_** replicas (analog of `heroku run`)
16
16
  - Uses `Standard` workload type and `cpln exec` as the execution method, with CLI streaming
17
17
  - May not work correctly with tasks that last over 5 minutes (there's a Control Plane scaling bug at the moment)
18
18
 
19
19
  > **IMPORTANT:** Useful for development where it's needed for interaction, and where network connection drops and
20
20
  > task crashing are tolerable. For production tasks, it's better to use `cpl run:detached`.
21
- HEREDOC
22
- EXAMPLES = <<~HEREDOC
21
+ DESC
22
+ EXAMPLES = <<~EX
23
23
  ```sh
24
24
  # Opens shell (bash by default).
25
25
  cpl run -a $APP_NAME
@@ -35,7 +35,7 @@ module Command
35
35
  cpl run rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
36
36
  cpl run rails db:migrate -a $APP_NAME --image latest # Latest sequential image
37
37
  ```
38
- HEREDOC
38
+ EX
39
39
 
40
40
  attr_reader :location, :workload, :one_off
41
41
 
@@ -80,7 +80,7 @@ module Command
80
80
 
81
81
  # Override image if specified
82
82
  image = config.options[:image]
83
- image = "/org/#{config[:cpln_org]}/image/#{latest_image}" if image == "latest"
83
+ image = "/org/#{config.org}/image/#{latest_image}" if image == "latest"
84
84
  container["image"] = image if image
85
85
 
86
86
  # Set runner
@@ -10,16 +10,18 @@ module Command
10
10
  image_option
11
11
  ].freeze
12
12
  DESCRIPTION = "Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)"
13
- LONG_DESCRIPTION = <<~HEREDOC
13
+ LONG_DESCRIPTION = <<~DESC
14
14
  - Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)
15
15
  - Uses `Cron` workload type with log async fetching
16
16
  - Implemented with only async execution methods, more suitable for production tasks
17
17
  - Has alternative log fetch implementation with only JSON-polling and no WebSockets
18
18
  - Less responsive but more stable, useful for CI tasks
19
- HEREDOC
20
- EXAMPLES = <<~HEREDOC
19
+ DESC
20
+ EXAMPLES = <<~EX
21
21
  ```sh
22
22
  cpl run:detached rails db:prepare -a $APP_NAME
23
+
24
+ # Need to quote COMMAND if setting ENV value or passing args to command to run
23
25
  cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
24
26
 
25
27
  # Uses some other image.
@@ -32,7 +34,7 @@ module Command
32
34
  cpl run:detached rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
33
35
  cpl run:detached rails db:migrate -a $APP_NAME --image latest # Latest sequential image
34
36
  ```
35
- HEREDOC
37
+ EX
36
38
 
37
39
  WORKLOAD_SLEEP_CHECK = 2
38
40
 
@@ -77,7 +79,7 @@ module Command
77
79
 
78
80
  # Override image if specified
79
81
  image = config.options[:image]
80
- image = "/org/#{config[:cpln_org]}/image/#{latest_image}" if image == "latest"
82
+ image = "/org/#{config.org}/image/#{latest_image}" if image == "latest"
81
83
  container["image"] = image if image
82
84
 
83
85
  # Set cron job props