lib/command/run_detached.rb in cpl-1.2.0 vs lib/command/run_detached.rb in cpl-1.3.0
- old
+ new
@@ -8,72 +8,69 @@
OPTIONS = [
app_option(required: true),
image_option,
workload_option,
location_option,
- use_local_token_option
+ use_local_token_option,
+ clean_on_failure_option
].freeze
DESCRIPTION = "Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)"
LONG_DESCRIPTION = <<~DESC
- Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)
- Uses `Cron` workload type with log async fetching
- Implemented with only async execution methods, more suitable for production tasks
- Has alternative log fetch implementation with only JSON-polling and no WebSockets
- Less responsive but more stable, useful for CI tasks
+ - Deletes the workload whenever finished with success
+ - Deletes the workload whenever finished with failure by default
+ - Use `--no-clean-on-failure` to disable cleanup to help with debugging failed runs
DESC
EXAMPLES = <<~EX
```sh
cpl run:detached rails db:prepare -a $APP_NAME
# Need to quote COMMAND if setting ENV value or passing args.
- cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
-
- # COMMAND may also be passed at the end.
cpl run:detached -a $APP_NAME -- 'LOG_LEVEL=warn rails db:migrate'
# Uses a different image (which may not be promoted yet).
- cpl run:detached rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
- cpl run:detached rails db:migrate -a $APP_NAME --image latest # Latest sequential image
+ cpl run:detached -a $APP_NAME --image appimage:123 -- rails db:migrate # Exact image name
+ cpl run:detached -a $APP_NAME --image latest -- rails db:migrate # Latest sequential image
# Uses a different workload than `one_off_workload` from `.controlplane/controlplane.yml`.
- cpl run:detached rails db:migrate:status -a $APP_NAME -w other-workload
+ cpl run:detached -a $APP_NAME -w other-workload -- rails db:migrate:status
# Overrides remote CPLN_TOKEN env variable with local token.
# Useful when superuser rights are needed in remote container.
- cpl run:detached rails db:migrate:status -a $APP_NAME --use-local-token
+ cpl run:detached -a $APP_NAME --use-local-token -- rails db:migrate:status
```
EX
WORKLOAD_SLEEP_CHECK = 2
- attr_reader :location, :workload, :one_off, :container
+ attr_reader :location, :workload_to_clone, :workload_clone, :container
- def call # rubocop:disable Metrics/MethodLength
+ def call
@location = config.location
- @workload = config.options["workload"] || config[:one_off_workload]
- @one_off = "#{workload}-runner-#{rand(1000..9999)}"
+ @workload_to_clone = config.options["workload"] || config[:one_off_workload]
+ @workload_clone = "#{workload_to_clone}-runner-#{random_four_digits}"
- step("Cloning workload '#{workload}' on app '#{config.options[:app]}' to '#{one_off}'") do
+ step("Cloning workload '#{workload_to_clone}' on app '#{config.options[:app]}' to '#{workload_clone}'") do
clone_workload
end
- wait_for_workload(one_off)
+ wait_for_workload(workload_clone)
show_logs_waiting
ensure
- if cp.fetch_workload(one_off)
- progress.puts
- ensure_workload_deleted(one_off)
- end
exit(1) if @crashed
end
private
def clone_workload # rubocop:disable Metrics/MethodLength
# Get base specs of workload
- spec = cp.fetch_workload!(workload).fetch("spec")
- container_spec = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
+ spec = cp.fetch_workload!(workload_to_clone).fetch("spec")
+ container_spec = spec["containers"].detect { _1["name"] == workload_to_clone } || spec["containers"].first
@container = container_spec["name"]
# remove other containers if any
spec["containers"] = [container_spec]
@@ -103,11 +100,11 @@
container_spec["env"] ||= []
container_spec["env"] << { "name" => "CONTROLPLANE_TOKEN", "value" => ControlplaneApiDirect.new.api_token }
container_spec["env"] << { "name" => "CONTROLPLANE_RUNNER", "value" => runner_script }
# Create workload clone
- cp.apply_hash("kind" => "workload", "name" => one_off, "spec" => spec)
+ cp.apply_hash("kind" => "workload", "name" => workload_clone, "spec" => spec)
end
def runner_script # rubocop:disable Metrics/MethodLength
script = "echo '-- STARTED RUNNER SCRIPT --'\n"
script += Scripts.helpers_cleanup
@@ -117,25 +114,34 @@
CPLN_TOKEN=$CONTROLPLANE_TOKEN
SHELL
end
script += <<~SHELL
- if ! eval "#{args_join(config.args)}"; then echo "----- CRASHED -----"; fi
-
- echo "-- FINISHED RUNNER SCRIPT, DELETING WORKLOAD --"
- sleep 10 # grace time for logs propagation
- curl ${CPLN_ENDPOINT}${CPLN_WORKLOAD} -H "Authorization: ${CONTROLPLANE_TOKEN}" -X DELETE -s -o /dev/null
- while true; do sleep 1; done # wait for SIGTERM
+ crashed=0
+ if ! eval "#{args_join(config.args)}"; then
+ crashed=1
+ echo "----- CRASHED -----"
+ fi
+ clean_on_failure=#{config.options[:clean_on_failure] ? 1 : 0}
+ if [ $crashed -eq 0 ] || [ $clean_on_failure -eq 1 ]; then
+ echo "-- FINISHED RUNNER SCRIPT, DELETING WORKLOAD --"
+ sleep 30 # grace time for logs propagation
+ curl ${CPLN_ENDPOINT}${CPLN_WORKLOAD} -H "Authorization: ${CONTROLPLANE_TOKEN}" -X DELETE -s -o /dev/null
+ while true; do sleep 1; done # wait for SIGTERM
+ else
+ echo "-- FINISHED RUNNER SCRIPT --"
+ fi
SHELL
script
end
def show_logs_waiting # rubocop:disable Metrics/MethodLength
progress.puts("Scheduled, fetching logs (it's a cron job, so it may take up to a minute to start)...\n\n")
begin
- while cp.fetch_workload(one_off)
+ @finished = false
+ while cp.fetch_workload(workload_clone) && !@finished
sleep(WORKLOAD_SLEEP_CHECK)
print_uniq_logs
end
rescue RuntimeError => e
progress.puts(Shell.color("ERROR: #{e}", :red))
@@ -149,17 +155,18 @@
ts = Time.now.to_i
entries = normalized_log_entries(from: ts - 60, to: ts)
(entries - @printed_log_entries).sort.each do |(_ts, val)|
@crashed = true if val.match?(/^----- CRASHED -----$/)
+ @finished = true if val.match?(/^-- FINISHED RUNNER SCRIPT(, DELETING WORKLOAD)? --$/)
puts val
end
@printed_log_entries = entries # as well truncate old entries if any
end
def normalized_log_entries(from:, to:)
- log = cp.log_get(workload: one_off, from: from, to: to)
+ log = cp.log_get(workload: workload_clone, from: from, to: to)
log["data"]["result"]
.each_with_object([]) { |obj, result| result.concat(obj["values"]) }
.select { |ts, _val| ts[..-10].to_i > from }
end