Class: Command::Run

Inherits:
Base
  • Object
show all
Defined in:
lib/command/run.rb

Overview

rubocop:disable Metrics/ClassLength

Constant Summary collapse

INTERACTIVE_COMMANDS =
[
  "bash",
  "rails console",
  "rails c",
  "rails dbconsole",
  "rails db"
].freeze
NAME =
"run"
USAGE =
"run COMMAND"
REQUIRES_ARGS =
true
DEFAULT_ARGS =
["bash"].freeze
OPTIONS =
[
  app_option(required: true),
  image_option,
  log_method_option,
  workload_option,
  location_option,
  use_local_token_option,
  terminal_size_option,
  interactive_option,
  detached_option,
  cpu_option,
  memory_option,
  entrypoint_option
].freeze
DESCRIPTION =
"Runs one-off interactive or non-interactive replicas (analog of `heroku run`)"
LONG_DESCRIPTION =
<<~DESC
  - Runs one-off interactive or non-interactive replicas (analog of `heroku run`)
  - Uses `Cron` workload type and either:
  - - `cpln workload exec` for interactive mode, with CLI streaming
  - - log async fetching for non-interactive mode
  - The Dockerfile entrypoint is used as the command by default, which assumes `exec "${@}"` to be present,
    and the args ["bash", "-c", cmd_to_run] are passed
  - The entrypoint can be overridden through `--entrypoint`, which must be a single command or a script path that exists in the container,
    and the args ["bash", "-c", cmd_to_run] are passed,
    unless the entrypoint is `bash`, in which case the args ["-c", cmd_to_run] are passed
  - Providing `--entrypoint none` sets the entrypoint to `bash` by default
  - 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 overridden through `--terminal-size`)
  - By default, all jobs use a CPU size of 1 (1 core) and a memory size of 2Gi (2 gibibytes)
    (can be configured through `runner_job_default_cpu` and `runner_job_default_memory` in `controlplane.yml`,
    and also overridden per job through `--cpu` and `--memory`)
  - By default, the job is stopped if it takes longer than 6 hours to finish
    (can be configured though `runner_job_timeout` in `controlplane.yml`)
  - Non-interactive jobs return the Control Plane cron job status even when the job finishes before
    Control Plane exposes a runner replica to attach logs to
DESC
EXAMPLES =
<<~EX.freeze
  ```sh
  # Opens shell (bash by default).
  cpflow run -a $APP_NAME

  # Runs interactive command, keeps shell open, and stops job when exiting.
  cpflow run -a $APP_NAME --interactive -- rails c

  # Some commands are automatically detected as interactive, so no need to pass `--interactive`.
  #{INTERACTIVE_COMMANDS.map { |cmd| "cpflow run -a $APP_NAME -- #{cmd}" }.join("\n      ")}

  # Runs non-interactive command, outputs logs, exits with the exit code of the command and stops job.
  cpflow run -a $APP_NAME -- rails db:migrate

  # Runs non-iteractive command, detaches, exits with 0, and prints commands to:
  # - see logs from the job
  # - stop the job
  cpflow run -a $APP_NAME --detached -- rails db:migrate

  # The command needs to be quoted if setting an env variable or passing args.
  cpflow run -a $APP_NAME -- 'SOME_ENV_VAR=some_value rails db:migrate'

  # Uses a different image (which may not be promoted yet).
  cpflow run -a $APP_NAME --image appimage:123 -- rails db:migrate # Exact image name
  cpflow run -a $APP_NAME --image latest -- rails db:migrate       # Latest sequential image

  # Uses a different workload than `one_off_workload` from `.controlplane/controlplane.yml`.
  cpflow run -a $APP_NAME -w other-workload -- bash

  # Overrides remote CPLN_TOKEN env variable with local token.
  # Useful when superuser rights are needed in remote container.
  cpflow run -a $APP_NAME --use-local-token -- bash

  # Replaces the existing Dockerfile entrypoint with `bash`.
  cpflow run -a $APP_NAME --entrypoint none -- rails db:migrate

  # Replaces the existing Dockerfile entrypoint.
  cpflow run -a $APP_NAME --entrypoint /app/alternative-entrypoint.sh -- rails db:migrate
  ```
EX
DEFAULT_JOB_CPU =
"1"
DEFAULT_JOB_MEMORY =
"2Gi"
DEFAULT_JOB_TIMEOUT =

6 hours

21_600
DEFAULT_JOB_HISTORY_LIMIT =
10
MAGIC_END =
"---cpflow run command finished---"

Constants inherited from Base

Base::ACCEPTS_EXTRA_OPTIONS, Base::ALL_VALIDATIONS, Base::HIDE, Base::REQUIRES_STARTUP_CHECKS, Base::SUBCOMMAND_NAME, Base::VALIDATIONS, Base::VALIDATIONS_WITHOUT_ADDITIONAL_OPTIONS, Base::VALIDATIONS_WITH_ADDITIONAL_OPTIONS, Base::WITH_INFO_HEADER

Instance Attribute Summary collapse

Attributes inherited from Base

#config

Instance Method Summary collapse

Methods inherited from Base

add_app_identity_option, all_commands, all_options, all_options_by_key_name, app_option, #args_join, #bind_shared_secret_policy_grant, #bind_shared_secret_policy_grants, commit_option, common_options, #cp, cpu_option, detached_option, dir_option, docker_context_option, domain_option, #ensure_docker_running!, #ensure_shared_secret_policy_targets_secret!, entrypoint_option, force_option, #identity_bound_to_policy_with_reveal?, #identity_policy_permissions, image_option, #initialize, interactive_option, location_option, log_method_option, logs_limit_option, logs_since_option, memory_option, org_option, #progress, replica_option, #resolve_shared_secret_policy_grant, #resolve_shared_secret_policy_grants, #run_command_in_latest_image, #run_cpflow_command, run_release_phase_option, #shared_secret_policy_missing_message, #shared_secret_policy_target_links, #shared_secret_policy_targets_secret?, skip_confirm_option, skip_post_creation_hook_option, skip_pre_deletion_hook_option, skip_secret_access_binding_option, skip_secrets_setup_option, staging_branch_option, #step, #step_finish, terminal_size_option, trace_option, upstream_token_option, use_digest_image_ref_option, use_local_token_option, validations_option, verbose_option, version_option, wait_option, #with_retry, workload_option

Methods included from Helpers

normalize_command_name, normalize_option_name, random_four_digits, strip_str_and_validate

Constructor Details

This class inherits a constructor from Command::Base

Instance Attribute Details

#commandObject (readonly)

Returns the value of attribute command.



100
101
102
# File 'lib/command/run.rb', line 100

def command
  @command
end

#containerObject (readonly)

Returns the value of attribute container.



100
101
102
# File 'lib/command/run.rb', line 100

def container
  @container
end

#default_cpuObject (readonly)

Returns the value of attribute default_cpu.



100
101
102
# File 'lib/command/run.rb', line 100

def default_cpu
  @default_cpu
end

#default_imageObject (readonly)

Returns the value of attribute default_image.



100
101
102
# File 'lib/command/run.rb', line 100

def default_image
  @default_image
end

#default_memoryObject (readonly)

Returns the value of attribute default_memory.



100
101
102
# File 'lib/command/run.rb', line 100

def default_memory
  @default_memory
end

#detachedObject (readonly)

Returns the value of attribute detached.



100
101
102
# File 'lib/command/run.rb', line 100

def detached
  @detached
end

#interactiveObject (readonly)

Returns the value of attribute interactive.



100
101
102
# File 'lib/command/run.rb', line 100

def interactive
  @interactive
end

#jobObject (readonly)

Returns the value of attribute job.



100
101
102
# File 'lib/command/run.rb', line 100

def job
  @job
end

#job_completed_before_replica_exit_statusObject (readonly)

Returns the value of attribute job_completed_before_replica_exit_status.



100
101
102
# File 'lib/command/run.rb', line 100

def job_completed_before_replica_exit_status
  @job_completed_before_replica_exit_status
end

#job_history_limitObject (readonly)

Returns the value of attribute job_history_limit.



100
101
102
# File 'lib/command/run.rb', line 100

def job_history_limit
  @job_history_limit
end

#job_timeoutObject (readonly)

Returns the value of attribute job_timeout.



100
101
102
# File 'lib/command/run.rb', line 100

def job_timeout
  @job_timeout
end

#locationObject (readonly)

Returns the value of attribute location.



100
101
102
# File 'lib/command/run.rb', line 100

def location
  @location
end

#original_workloadObject (readonly)

Returns the value of attribute original_workload.



100
101
102
# File 'lib/command/run.rb', line 100

def original_workload
  @original_workload
end

#replicaObject (readonly)

Returns the value of attribute replica.



100
101
102
# File 'lib/command/run.rb', line 100

def replica
  @replica
end

#runner_workloadObject (readonly)

Returns the value of attribute runner_workload.



100
101
102
# File 'lib/command/run.rb', line 100

def runner_workload
  @runner_workload
end

Instance Method Details

#callObject

rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/command/run.rb', line 104

def call # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
  @interactive = config.options[:interactive] || interactive_command?
  @detached = config.options[:detached]
  @log_method = config.options[:log_method]

  @location = config.location
  @original_workload = config.options[:workload] || config[:one_off_workload]
  @runner_workload = "#{original_workload}-runner"
  @default_image = "#{config.app}:#{Controlplane::NO_IMAGE_AVAILABLE}"
  @default_cpu = config.current[:runner_job_default_cpu] || DEFAULT_JOB_CPU
  @default_memory = config.current[:runner_job_default_memory] || DEFAULT_JOB_MEMORY
  @job_timeout = config.current[:runner_job_timeout] || DEFAULT_JOB_TIMEOUT
  @job_history_limit = DEFAULT_JOB_HISTORY_LIMIT

  unless interactive
    @internal_sigint = false

    # Catch Ctrl+C in the main process
    trap("SIGINT") do
      unless @internal_sigint
        print_detached_commands
        exit(ExitCode::INTERRUPT)
      end
    end
  end

  create_runner_workload if cp.fetch_workload(runner_workload).nil?
  update_runner_workload
  start_job
  wait_for_replica_for_job
  exit(job_completed_before_replica_exit_status) if job_completed_before_replica_exit_status

  progress.puts
  if interactive
    run_interactive
  else
    run_non_interactive
  end
end