cpl 0.5.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/command/base.rb CHANGED
@@ -142,13 +142,47 @@ module Command
142
142
  }
143
143
  end
144
144
 
145
+ def self.use_local_token_option(required: false)
146
+ {
147
+ name: :use_local_token,
148
+ params: {
149
+ desc: "Override remote CPLN_TOKEN with local token",
150
+ type: :boolean,
151
+ required: required
152
+ }
153
+ }
154
+ end
155
+
156
+ def self.terminal_size_option(required: false)
157
+ {
158
+ name: :terminal_size,
159
+ params: {
160
+ banner: "ROWS,COLS",
161
+ desc: "Override remote terminal size (e.g. `--terminal-size 10,20`)",
162
+ type: :string,
163
+ required: required
164
+ }
165
+ }
166
+ end
167
+
168
+ def self.wait_option(title = "", required: false)
169
+ {
170
+ name: :wait,
171
+ params: {
172
+ desc: "Waits for #{title}",
173
+ type: :boolean,
174
+ required: required
175
+ }
176
+ }
177
+ end
178
+
145
179
  def self.all_options
146
180
  methods.grep(/_option$/).map { |method| send(method.to_s) }
147
181
  end
148
182
 
149
183
  def self.all_options_by_key_name
150
184
  all_options.each_with_object({}) do |option, result|
151
- option[:params][:aliases].each { |current_alias| result[current_alias.to_s] = option }
185
+ option[:params][:aliases]&.each { |current_alias| result[current_alias.to_s] = option }
152
186
  result["--#{option[:name]}"] = option
153
187
  end
154
188
  end
@@ -168,13 +202,13 @@ module Command
168
202
 
169
203
  def wait_for_replica(workload, location)
170
204
  wait_for("replica") do
171
- cp.workload_get_replicas(workload, location: location)&.dig("items", 0)
205
+ cp.workload_get_replicas_safely(workload, location: location)&.dig("items", 0)
172
206
  end
173
207
  end
174
208
 
175
209
  def ensure_workload_deleted(workload)
176
210
  progress.puts "- Ensure workload is deleted"
177
- cp.workload_delete(workload)
211
+ cp.delete_workload(workload)
178
212
  end
179
213
 
180
214
  def latest_image_from(items, app_name: config.app, name_only: true)
@@ -201,8 +235,9 @@ module Command
201
235
  def latest_image_next(app = config.app, org = config.org)
202
236
  @latest_image_next ||= {}
203
237
  @latest_image_next[app] ||= begin
204
- image = latest_image(app, org).split(":").first
205
- image += ":#{extract_image_number(latest_image) + 1}"
238
+ latest_image_name = latest_image(app, org)
239
+ image = latest_image_name.split(":").first
240
+ image += ":#{extract_image_number(latest_image_name) + 1}"
206
241
  image += "_#{config.options[:commit]}" if config.options[:commit]
207
242
  image
208
243
  end
@@ -218,29 +253,44 @@ module Command
218
253
  $stderr
219
254
  end
220
255
 
221
- def step(message, abort_on_error: true) # rubocop:disable Metrics/MethodLength
256
+ def step_error(error, abort_on_error: true)
257
+ message = error.message
258
+ if abort_on_error
259
+ progress.puts(" #{Shell.color('failed!', :red)}\n\n")
260
+ Shell.abort(message)
261
+ else
262
+ Shell.write_to_tmp_stderr(message)
263
+ end
264
+ end
265
+
266
+ def step_finish(success)
267
+ if success
268
+ progress.puts(" #{Shell.color('done!', :green)}")
269
+ else
270
+ progress.puts(" #{Shell.color('failed!', :red)}\n\n#{Shell.read_from_tmp_stderr}\n\n")
271
+ end
272
+ end
273
+
274
+ def step(message, abort_on_error: true, retry_on_failure: false) # rubocop:disable Metrics/MethodLength
222
275
  progress.print("#{message}...")
223
276
 
224
277
  Shell.use_tmp_stderr do
225
278
  success = false
226
279
 
227
280
  begin
228
- success = yield
229
- rescue RuntimeError => e
230
- message = e.message
231
- if abort_on_error
232
- progress.puts(" #{Shell.color('failed!', :red)}\n\n")
233
- Shell.abort(message)
281
+ if retry_on_failure
282
+ until (success = yield)
283
+ progress.print(".")
284
+ sleep 1
285
+ end
234
286
  else
235
- Shell.write_to_tmp_stderr(message)
287
+ success = yield
236
288
  end
289
+ rescue RuntimeError => e
290
+ step_error(e, abort_on_error: abort_on_error)
237
291
  end
238
292
 
239
- if success
240
- progress.puts(" #{Shell.color('done!', :green)}")
241
- else
242
- progress.puts(" #{Shell.color('failed!', :red)}\n\n#{Shell.read_from_tmp_stderr}\n\n")
243
- end
293
+ step_finish(success)
244
294
  end
245
295
  end
246
296
 
@@ -11,18 +11,30 @@ module Command
11
11
  LONG_DESCRIPTION = <<~DESC
12
12
  - Builds and pushes the image to Control Plane
13
13
  - Automatically assigns image numbers, e.g., `app:1`, `app:2`, etc.
14
- - Uses `.controlplane/Dockerfile`
14
+ - Uses `.controlplane/Dockerfile` or a different Dockerfile specified through `dockerfile` in the `.controlplane/controlplane.yml` file
15
+ - If a commit is provided through `--commit` or `-c`, it will be set as the runtime env var `GIT_COMMIT`
15
16
  DESC
16
17
 
17
- def call
18
+ def call # rubocop:disable Metrics/MethodLength
18
19
  ensure_docker_running!
19
20
 
20
21
  dockerfile = config.current[:dockerfile] || "Dockerfile"
21
22
  dockerfile = "#{config.app_cpln_dir}/#{dockerfile}"
22
23
 
24
+ raise "Can't find Dockerfile at '#{dockerfile}'." unless File.exist?(dockerfile)
25
+
23
26
  progress.puts("Building image from Dockerfile '#{dockerfile}'...\n\n")
24
27
 
25
- cp.image_build(latest_image_next, dockerfile: dockerfile)
28
+ image_name = latest_image_next
29
+ image_url = "#{config.org}.registry.cpln.io/#{image_name}"
30
+
31
+ commit = config.options[:commit]
32
+ build_args = []
33
+ build_args.push("GIT_COMMIT=#{commit}") if commit
34
+
35
+ cp.image_build(image_url, dockerfile: dockerfile, build_args: build_args)
36
+
37
+ progress.puts("\nPushed image to '/org/#{config.org}/image/#{image_name}'.")
26
38
  end
27
39
 
28
40
  private
@@ -29,7 +29,7 @@ module Command
29
29
  ensure_docker_running!
30
30
 
31
31
  @upstream = config[:upstream]
32
- @upstream_org = config.apps[@upstream.to_sym][:cpln_org] || config.apps[@upstream.to_sym][:org]
32
+ @upstream_org = config.apps[@upstream.to_sym][:cpln_org]
33
33
  ensure_upstream_org!
34
34
 
35
35
  create_upstream_profile
data/lib/command/info.rb CHANGED
@@ -90,7 +90,7 @@ module Command
90
90
  config.apps.each do |app_name, app_options|
91
91
  next if config.app && !app_matches?(config.app, app_name, app_options)
92
92
 
93
- org = app_options[:cpln_org] || app_options[:org]
93
+ org = app_options[:cpln_org]
94
94
  result.push(org) unless result.include?(org)
95
95
  end
96
96
  end
@@ -104,7 +104,7 @@ module Command
104
104
  config.apps.each do |app_name, app_options|
105
105
  next if config.app && !app_matches?(config.app, app_name, app_options)
106
106
 
107
- app_org = app_options[:cpln_org] || app_options[:org]
107
+ app_org = app_options[:cpln_org]
108
108
  result.push(app_name.to_s) if app_org == org
109
109
  end
110
110
 
@@ -173,7 +173,11 @@ module Command
173
173
  puts "\nSome apps/workloads are missing. Please create them with:"
174
174
 
175
175
  @missing_apps_workloads.each do |app, workloads|
176
- puts " - `cpl setup #{workloads.join(' ')} -a #{app}`"
176
+ if workloads.include?("gvc")
177
+ puts " - `cpl setup-app -a #{app}`"
178
+ else
179
+ puts " - `cpl apply-template #{workloads.join(' ')} -a #{app}`"
180
+ end
177
181
  end
178
182
  end
179
183
 
@@ -183,9 +187,9 @@ module Command
183
187
  puts "\nThere are no apps starting with some names. If you wish to create any, do so with " \
184
188
  "(replace 'whatever' with whatever suffix you want):"
185
189
 
186
- @missing_apps_starting_with.each do |app, workloads|
190
+ @missing_apps_starting_with.each do |app, _workloads|
187
191
  app_with_suffix = "#{app}#{app.end_with?('-') ? '' : '-'}whatever"
188
- puts " - `cpl setup #{workloads.join(' ')} -a #{app_with_suffix}`"
192
+ puts " - `cpl setup-app -a #{app_with_suffix}`"
189
193
  end
190
194
  end
191
195
 
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Maintenance < Base
5
+ NAME = "maintenance"
6
+ OPTIONS = [
7
+ app_option(required: true)
8
+ ].freeze
9
+ DESCRIPTION = "Checks if maintenance mode is on or off for an app"
10
+ LONG_DESCRIPTION = <<~DESC
11
+ - Checks if maintenance mode is on or off for an app
12
+ - Outputs 'on' or 'off'
13
+ - Specify the one-off workload through `one_off_workload` in the `.controlplane/controlplane.yml` file
14
+ - Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
15
+ - Maintenance mode is only supported for domains that use path based routing mode and have a route configured for the prefix '/' on either port 80 or 443
16
+ DESC
17
+
18
+ def call # rubocop:disable Metrics/MethodLength
19
+ one_off_workload = config[:one_off_workload]
20
+ maintenance_workload = config.current[:maintenance_workload] || "maintenance"
21
+
22
+ domain_data = cp.find_domain_for([one_off_workload, maintenance_workload])
23
+ unless domain_data
24
+ raise "Can't find domain. " \
25
+ "Maintenance mode is only supported for domains that use path based routing mode " \
26
+ "and have a route configured for the prefix '/' on either port 80 or 443."
27
+ end
28
+
29
+ domain_workload = cp.get_domain_workload(domain_data)
30
+ if domain_workload == maintenance_workload
31
+ puts "on"
32
+ else
33
+ puts "off"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class MaintenanceOff < Base
5
+ NAME = "maintenance:off"
6
+ OPTIONS = [
7
+ app_option(required: true)
8
+ ].freeze
9
+ DESCRIPTION = "Disables maintenance mode for an app"
10
+ LONG_DESCRIPTION = <<~DESC
11
+ - Disables maintenance mode for an app
12
+ - Specify the one-off workload through `one_off_workload` in the `.controlplane/controlplane.yml` file
13
+ - Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
14
+ - Maintenance mode is only supported for domains that use path based routing mode and have a route configured for the prefix '/' on either port 80 or 443
15
+ DESC
16
+
17
+ def call # rubocop:disable Metrics/MethodLength
18
+ one_off_workload = config[:one_off_workload]
19
+ maintenance_workload = config.current[:maintenance_workload] || "maintenance"
20
+
21
+ domain_data = cp.find_domain_for([one_off_workload, maintenance_workload])
22
+ unless domain_data
23
+ raise "Can't find domain. " \
24
+ "Maintenance mode is only supported for domains that use path based routing mode " \
25
+ "and have a route configured for the prefix '/' on either port 80 or 443."
26
+ end
27
+
28
+ domain = domain_data["name"]
29
+ domain_workload = cp.get_domain_workload(domain_data)
30
+ if domain_workload == one_off_workload
31
+ progress.puts("Maintenance mode is already disabled for app '#{config.app}'.")
32
+ return
33
+ end
34
+
35
+ cp.fetch_workload!(maintenance_workload)
36
+
37
+ # Start all other workloads
38
+ Cpl::Cli.start(["ps:start", "-a", config.app, "--wait"])
39
+
40
+ progress.puts
41
+
42
+ # Switch domain workload
43
+ step("Switching workload for domain '#{domain}' to '#{one_off_workload}'") do
44
+ cp.set_domain_workload(domain_data, one_off_workload)
45
+
46
+ # Give it a bit of time for the domain to update
47
+ sleep 30
48
+ end
49
+
50
+ progress.puts
51
+
52
+ # Stop maintenance workload
53
+ Cpl::Cli.start(["ps:stop", "-a", config.app, "-w", maintenance_workload, "--wait"])
54
+
55
+ progress.puts("\nMaintenance mode disabled for app '#{config.app}'.")
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class MaintenanceOn < Base
5
+ NAME = "maintenance:on"
6
+ OPTIONS = [
7
+ app_option(required: true)
8
+ ].freeze
9
+ DESCRIPTION = "Enables maintenance mode for an app"
10
+ LONG_DESCRIPTION = <<~DESC
11
+ - Enables maintenance mode for an app
12
+ - Specify the one-off workload through `one_off_workload` in the `.controlplane/controlplane.yml` file
13
+ - Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
14
+ - Maintenance mode is only supported for domains that use path based routing mode and have a route configured for the prefix '/' on either port 80 or 443
15
+ DESC
16
+
17
+ def call # rubocop:disable Metrics/MethodLength
18
+ one_off_workload = config[:one_off_workload]
19
+ maintenance_workload = config.current[:maintenance_workload] || "maintenance"
20
+
21
+ domain_data = cp.find_domain_for([one_off_workload, maintenance_workload])
22
+ unless domain_data
23
+ raise "Can't find domain. " \
24
+ "Maintenance mode is only supported for domains that use path based routing mode " \
25
+ "and have a route configured for the prefix '/' on either port 80 or 443."
26
+ end
27
+
28
+ domain = domain_data["name"]
29
+ domain_workload = cp.get_domain_workload(domain_data)
30
+ if domain_workload == maintenance_workload
31
+ progress.puts("Maintenance mode is already enabled for app '#{config.app}'.")
32
+ return
33
+ end
34
+
35
+ cp.fetch_workload!(maintenance_workload)
36
+
37
+ # Start maintenance workload
38
+ Cpl::Cli.start(["ps:start", "-a", config.app, "-w", maintenance_workload, "--wait"])
39
+
40
+ progress.puts
41
+
42
+ # Switch domain workload
43
+ step("Switching workload for domain '#{domain}' to '#{maintenance_workload}'") do
44
+ cp.set_domain_workload(domain_data, maintenance_workload)
45
+
46
+ # Give it a bit of time for the domain to update
47
+ sleep 30
48
+ end
49
+
50
+ progress.puts
51
+
52
+ # Stop all other workloads
53
+ Cpl::Cli.start(["ps:stop", "-a", config.app, "--wait"])
54
+
55
+ progress.puts("\nMaintenance mode enabled for app '#{config.app}'.")
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class MaintenanceSetPage < Base
5
+ NAME = "maintenance:set-page"
6
+ USAGE = "maintenance:set-page PAGE_URL"
7
+ REQUIRES_ARGS = true
8
+ OPTIONS = [
9
+ app_option(required: true)
10
+ ].freeze
11
+ DESCRIPTION = "Sets the page for maintenance mode"
12
+ LONG_DESCRIPTION = <<~DESC
13
+ - Sets the page for maintenance mode
14
+ - Only works if the maintenance workload uses the `shakacode/maintenance-mode` image
15
+ - Will set the URL as an env var `PAGE_URL` on the maintenance workload
16
+ - Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
17
+ DESC
18
+
19
+ def call
20
+ maintenance_workload = config.current[:maintenance_workload] || "maintenance"
21
+
22
+ maintenance_workload_data = cp.fetch_workload!(maintenance_workload)
23
+ maintenance_workload_data.dig("spec", "containers").each do |container|
24
+ next unless container["image"].match?(%r{^shakacode/maintenance-mode})
25
+
26
+ container_name = container["name"]
27
+ page_url = config.args.first
28
+ step("Setting '#{page_url}' as the page for maintenance mode") do
29
+ cp.set_workload_env_var(maintenance_workload, container: container_name, name: "PAGE_URL", value: page_url)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -13,7 +13,7 @@ module Command
13
13
  def call
14
14
  return unless config.options[:version]
15
15
 
16
- perform("cpl version")
16
+ Cpl::Cli.start(["version"])
17
17
  end
18
18
  end
19
19
  end
@@ -39,7 +39,7 @@ module Command
39
39
  end
40
40
 
41
41
  def copy_image_from_upstream
42
- perform("cpl copy-image-from-upstream -a #{config.app} -t #{config.options[:upstream_token]}")
42
+ Cpl::Cli.start(["copy-image-from-upstream", "-a", config.app, "-t", config.options[:upstream_token]])
43
43
  progress.puts
44
44
  end
45
45
 
@@ -52,7 +52,7 @@ module Command
52
52
  end
53
53
 
54
54
  def deploy_image
55
- perform("cpl deploy-image -a #{config.app}")
55
+ Cpl::Cli.start(["deploy-image", "-a", config.app])
56
56
  end
57
57
  end
58
58
  end
@@ -5,7 +5,8 @@ module Command
5
5
  NAME = "ps:start"
6
6
  OPTIONS = [
7
7
  app_option(required: true),
8
- workload_option
8
+ workload_option,
9
+ wait_option("workload to be ready")
9
10
  ].freeze
10
11
  DESCRIPTION = "Starts workloads in app"
11
12
  LONG_DESCRIPTION = <<~DESC
@@ -22,12 +23,28 @@ module Command
22
23
  EX
23
24
 
24
25
  def call
25
- workloads = [config.options[:workload]] if config.options[:workload]
26
- workloads ||= config[:app_workloads] + config[:additional_workloads]
26
+ @workloads = [config.options[:workload]] if config.options[:workload]
27
+ @workloads ||= config[:app_workloads] + config[:additional_workloads]
27
28
 
28
- workloads.reverse_each do |workload|
29
+ @workloads.reverse_each do |workload|
29
30
  step("Starting workload '#{workload}'") do
30
- cp.workload_set_suspend(workload, false)
31
+ cp.set_workload_suspend(workload, false)
32
+ end
33
+ end
34
+
35
+ wait_for_ready if config.options[:wait]
36
+ end
37
+
38
+ private
39
+
40
+ def wait_for_ready
41
+ progress.puts
42
+
43
+ @workloads.reverse_each do |workload|
44
+ step("Waiting for workload '#{workload}' to be ready", retry_on_failure: true) do
45
+ cp.fetch_workload_deployments(workload)&.dig("items")&.any? do |item|
46
+ item.dig("status", "ready")
47
+ end
31
48
  end
32
49
  end
33
50
  end
@@ -5,7 +5,8 @@ module Command
5
5
  NAME = "ps:stop"
6
6
  OPTIONS = [
7
7
  app_option(required: true),
8
- workload_option
8
+ workload_option,
9
+ wait_option("workload to be not ready")
9
10
  ].freeze
10
11
  DESCRIPTION = "Stops workloads in app"
11
12
  LONG_DESCRIPTION = <<~DESC
@@ -22,12 +23,28 @@ module Command
22
23
  EX
23
24
 
24
25
  def call
25
- workloads = [config.options[:workload]] if config.options[:workload]
26
- workloads ||= config[:app_workloads] + config[:additional_workloads]
26
+ @workloads = [config.options[:workload]] if config.options[:workload]
27
+ @workloads ||= config[:app_workloads] + config[:additional_workloads]
27
28
 
28
- workloads.each do |workload|
29
+ @workloads.each do |workload|
29
30
  step("Stopping workload '#{workload}'") do
30
- cp.workload_set_suspend(workload, true)
31
+ cp.set_workload_suspend(workload, true)
32
+ end
33
+ end
34
+
35
+ wait_for_not_ready if config.options[:wait]
36
+ end
37
+
38
+ private
39
+
40
+ def wait_for_not_ready
41
+ progress.puts
42
+
43
+ @workloads.each do |workload|
44
+ step("Waiting for workload '#{workload}' to be not ready", retry_on_failure: true) do
45
+ cp.fetch_workload_deployments(workload)&.dig("items")&.all? do |item|
46
+ !item.dig("status", "ready")
47
+ end
31
48
  end
32
49
  end
33
50
  end
data/lib/command/run.rb CHANGED
@@ -8,13 +8,17 @@ module Command
8
8
  DEFAULT_ARGS = ["bash"].freeze
9
9
  OPTIONS = [
10
10
  app_option(required: true),
11
- image_option
11
+ image_option,
12
+ workload_option,
13
+ use_local_token_option,
14
+ terminal_size_option
12
15
  ].freeze
13
16
  DESCRIPTION = "Runs one-off **_interactive_** replicas (analog of `heroku run`)"
14
17
  LONG_DESCRIPTION = <<~DESC
15
18
  - Runs one-off **_interactive_** replicas (analog of `heroku run`)
16
19
  - Uses `Standard` workload type and `cpln exec` as the execution method, with CLI streaming
17
20
  - May not work correctly with tasks that last over 5 minutes (there's a Control Plane scaling bug at the moment)
21
+ - If `fix_terminal_size` is `true` in the `.controlplane/controlplane.yml` file, the remote terminal size will be fixed to match the local terminal size (may also be overriden through `--terminal-size`)
18
22
 
19
23
  > **IMPORTANT:** Useful for development where it's needed for interaction, and where network connection drops and
20
24
  > task crashing are tolerable. For production tasks, it's better to use `cpl run:detached`.
@@ -24,6 +28,12 @@ module Command
24
28
  # Opens shell (bash by default).
25
29
  cpl run -a $APP_NAME
26
30
 
31
+ # Need to quote COMMAND if setting ENV value or passing args.
32
+ cpl run 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
33
+
34
+ # COMMAND may also be passed at the end (in this case, no need to quote).
35
+ cpl run -a $APP_NAME -- rails db:migrate
36
+
27
37
  # Runs command, displays output, and exits shell.
28
38
  cpl run ls / -a $APP_NAME
29
39
  cpl run rails db:migrate:status -a $APP_NAME
@@ -34,19 +44,25 @@ module Command
34
44
  # Uses a different image (which may not be promoted yet).
35
45
  cpl run rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
36
46
  cpl run rails db:migrate -a $APP_NAME --image latest # Latest sequential image
47
+
48
+ # Uses a different workload than `one_off_workload` from `.controlplane/controlplane.yml`.
49
+ cpl run bash -a $APP_NAME -w other-workload
50
+
51
+ # Overrides remote CPLN_TOKEN env variable with local token.
52
+ # Useful when superuser rights are needed in remote container.
53
+ cpl run bash -a $APP_NAME --use-local-token
37
54
  ```
38
55
  EX
39
56
 
40
- attr_reader :location, :workload, :one_off
57
+ attr_reader :location, :workload, :one_off, :container
41
58
 
42
59
  def call
43
60
  @location = config[:default_location]
44
- @workload = config[:one_off_workload]
61
+ @workload = config.options["workload"] || config[:one_off_workload]
45
62
  @one_off = "#{workload}-run-#{rand(1000..9999)}"
46
63
 
47
64
  clone_workload
48
65
  wait_for_workload(one_off)
49
- sleep 2 # sometimes replica query lags workload creation, despite ok by prev query
50
66
  wait_for_replica(one_off, location)
51
67
  run_in_replica
52
68
  ensure
@@ -60,39 +76,62 @@ module Command
60
76
 
61
77
  # Create a base copy of workload props
62
78
  spec = cp.fetch_workload!(workload).fetch("spec")
63
- container = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
79
+ container_spec = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
80
+ @container = container_spec["name"]
64
81
 
65
82
  # remove other containers if any
66
- spec["containers"] = [container]
83
+ spec["containers"] = [container_spec]
67
84
 
68
85
  # Stub workload command with dummy server that just responds to port
69
86
  # Needed to avoid execution of ENTRYPOINT and CMD of Dockerfile
70
- container["command"] = "ruby"
71
- container["args"] = ["-e", Scripts.http_dummy_server_ruby]
87
+ container_spec["command"] = "ruby"
88
+ container_spec["args"] = ["-e", Scripts.http_dummy_server_ruby]
72
89
 
73
90
  # Ensure one-off workload will be running
74
91
  spec["defaultOptions"]["suspend"] = false
75
92
 
76
93
  # Ensure no scaling
77
94
  spec["defaultOptions"]["autoscaling"]["minScale"] = 1
78
- spec["defaultOptions"]["autoscaling"]["minScale"] = 1
95
+ spec["defaultOptions"]["autoscaling"]["maxScale"] = 1
79
96
  spec["defaultOptions"]["capacityAI"] = false
80
97
 
81
98
  # Override image if specified
82
99
  image = config.options[:image]
83
100
  image = "/org/#{config.org}/image/#{latest_image}" if image == "latest"
84
- container["image"] = image if image
101
+ container_spec["image"] = image if image
85
102
 
86
103
  # Set runner
87
- container["env"] ||= []
88
- container["env"] << { "name" => "CONTROLPLANE_RUNNER", "value" => runner_script }
104
+ container_spec["env"] ||= []
105
+ container_spec["env"] << { "name" => "CONTROLPLANE_RUNNER", "value" => runner_script }
106
+
107
+ if config.options["use_local_token"]
108
+ container_spec["env"] << { "name" => "CONTROLPLANE_TOKEN", "value" => ControlplaneApiDirect.new.api_token }
109
+ end
89
110
 
90
111
  # Create workload clone
91
112
  cp.apply("kind" => "workload", "name" => one_off, "spec" => spec)
92
113
  end
93
114
 
94
- def runner_script
115
+ def runner_script # rubocop:disable Metrics/MethodLength
95
116
  script = Scripts.helpers_cleanup
117
+
118
+ if config.options["use_local_token"]
119
+ script += <<~SHELL
120
+ CPLN_TOKEN=$CONTROLPLANE_TOKEN
121
+ unset CONTROLPLANE_TOKEN
122
+ SHELL
123
+ end
124
+
125
+ # NOTE: fixes terminal size to match local terminal
126
+ if config.current[:fix_terminal_size] || config.options[:terminal_size]
127
+ if config.options[:terminal_size]
128
+ rows, cols = config.options[:terminal_size].split(",")
129
+ else
130
+ rows, cols = `stty -a`.match(/(\d+)\s*rows;\s*(\d+)\s*columns/).captures
131
+ end
132
+ script += "stty rows #{rows}\nstty cols #{cols}\n" if rows && cols
133
+ end
134
+
96
135
  script += args_join(config.args)
97
136
  script
98
137
  end
@@ -100,7 +139,7 @@ module Command
100
139
  def run_in_replica
101
140
  progress.puts "- Connecting"
102
141
  command = %(bash -c 'eval "$CONTROLPLANE_RUNNER"')
103
- cp.workload_exec(one_off, location: location, container: workload, command: command)
142
+ cp.workload_exec(one_off, location: location, container: container, command: command)
104
143
  end
105
144
  end
106
145
  end