cpl 0.5.1 → 0.7.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/Gemfile.lock +19 -4
- data/README.md +67 -20
- data/Rakefile +2 -2
- data/cpl.gemspec +3 -0
- data/docs/commands.md +108 -21
- data/examples/circleci.yml +1 -1
- data/examples/controlplane.yml +3 -0
- data/lib/command/{setup.rb → apply_template.rb} +89 -15
- data/lib/command/base.rb +68 -18
- data/lib/command/build_image.rb +15 -3
- data/lib/command/copy_image_from_upstream.rb +1 -1
- data/lib/command/info.rb +9 -5
- data/lib/command/maintenance.rb +37 -0
- data/lib/command/maintenance_off.rb +58 -0
- data/lib/command/maintenance_on.rb +58 -0
- data/lib/command/maintenance_set_page.rb +34 -0
- data/lib/command/no_command.rb +1 -1
- data/lib/command/promote_app_from_upstream.rb +2 -2
- data/lib/command/ps_start.rb +22 -5
- data/lib/command/ps_stop.rb +22 -5
- data/lib/command/run.rb +53 -14
- data/lib/command/run_cleanup.rb +99 -0
- data/lib/command/run_detached.rb +22 -14
- data/lib/command/setup_app.rb +29 -0
- data/lib/core/config.rb +39 -32
- data/lib/core/controlplane.rb +78 -16
- data/lib/core/controlplane_api.rb +39 -0
- data/lib/core/controlplane_api_direct.rb +13 -3
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +6 -1
- data/lib/deprecated_commands.json +2 -1
- data/templates/daily-task.yml +30 -0
- data/templates/maintenance.yml +24 -0
- metadata +54 -4
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]
|
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.
|
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.
|
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
|
-
|
205
|
-
image
|
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
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/command/build_image.rb
CHANGED
@@ -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
|
-
|
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]
|
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]
|
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]
|
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
|
-
|
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,
|
190
|
+
@missing_apps_starting_with.each do |app, _workloads|
|
187
191
|
app_with_suffix = "#{app}#{app.end_with?('-') ? '' : '-'}whatever"
|
188
|
-
puts " - `cpl setup
|
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
|
data/lib/command/no_command.rb
CHANGED
@@ -39,7 +39,7 @@ module Command
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def copy_image_from_upstream
|
42
|
-
|
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
|
-
|
55
|
+
Cpl::Cli.start(["deploy-image", "-a", config.app])
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
data/lib/command/ps_start.rb
CHANGED
@@ -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.
|
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
|
data/lib/command/ps_stop.rb
CHANGED
@@ -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.
|
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
|
-
|
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"] = [
|
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
|
-
|
71
|
-
|
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"]["
|
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
|
-
|
101
|
+
container_spec["image"] = image if image
|
85
102
|
|
86
103
|
# Set runner
|
87
|
-
|
88
|
-
|
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:
|
142
|
+
cp.workload_exec(one_off, location: location, container: container, command: command)
|
104
143
|
end
|
105
144
|
end
|
106
145
|
end
|