class ForemanRemoteExecutionCore::PollingScriptRunner

Constants

DEFAULT_REFRESH_INTERVAL

Public Class Methods

new(options, user_method, suspended_action: nil) click to toggle source
Calls superclass method
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 6
def initialize(options, user_method, suspended_action: nil)
  super(options, user_method, suspended_action: suspended_action)
  @callback_host = options[:callback_host]
  @task_id = options[:uuid]
  @step_id = options[:step_id]
  @otp = ForemanTasksCore::OtpManager.generate_otp(@task_id)
end

Public Instance Methods

callback_script() click to toggle source
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 101
    def callback_script
      <<-SCRIPT.gsub(/^ +\| /, '')
      | #!/bin/sh
      | exit_code=$(cat "#{@exit_code_path}")
      | url="#{@callback_host}/dynflow/tasks/#{@task_id}/done"
      | json="{ \\\"step_id\\\": #{@step_id} }"
      | if which curl >/dev/null; then
      |   curl -X POST -d "$json" -u "#{@task_id}:#{@otp}" "$url"
      | else
      |   echo 'curl is required' >&2
      |   exit 1
      | fi
      SCRIPT
    end
callback_scriptlet(callback_script_path = nil) click to toggle source
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 92
def callback_scriptlet(callback_script_path = nil)
  if @otp
    callback_script_path = cp_script_to_remote(callback_script, 'callback') if callback_script_path.nil?
    "#{@user_method.cli_command_prefix}#{callback_script_path}"
  else
    ':' # Shell synonym for "do nothing"
  end
end
close() click to toggle source
Calls superclass method
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 53
def close
  super
  ForemanTasksCore::OtpManager.drop_otp(@task_id, @otp) if @otp
end
control_script() click to toggle source
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 22
    def control_script
      close_stdin = '</dev/null'
      close_fds = close_stdin + ' >/dev/null 2>/dev/null'
      # pipe the output to tee while capturing the exit code in a file, don't wait for it to finish, output PID of the main command
      <<-SCRIPT.gsub(/^\s+\| /, '')
      | sh -c '(#{@user_method.cli_command_prefix}#{@remote_script} #{close_stdin}; echo $?>#{@exit_code_path}) | /usr/bin/tee #{@output_path} >/dev/null; #{callback_scriptlet}' #{close_fds} &
      | echo $! > '#{@pid_path}'
      SCRIPT
    end
prepare_retrieval() click to toggle source
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 58
    def prepare_retrieval
      # The script always outputs at least one line
      # First line of the output either has to begin with
      # "RUNNING" or "DONE $EXITCODE"
      # The following lines are treated as regular output
      base = File.dirname(@output_path)
      posfile = File.join(base, 'position')
      tmpfile = File.join(base, 'tmp')
      script = <<-SCRIPT.gsub(/^ +\| /, '')
      | #!/bin/sh
      | pid=$(cat "#{@pid_path}")
      | if ! pgrep --help 2>/dev/null >/dev/null; then
      |   echo DONE 1
      |   echo "pgrep is required" >&2
      |   exit 1
      | fi
      | if pgrep -P "$pid" >/dev/null 2>/dev/null; then
      |   echo RUNNING
      | else
      |   echo "DONE $(cat "#{@exit_code_path}" 2>/dev/null)"
      | fi
      | [ -f "#{@output_path}" ] || exit 0
      | [ -f "#{posfile}" ] || echo 1 > "#{posfile}"
      | position=$(cat "#{posfile}")
      | tail --bytes "+${position}" "#{@output_path}" > "#{tmpfile}"
      | bytes=$(cat "#{tmpfile}" | wc --bytes)
      | expr "${position}" + "${bytes}" > "#{posfile}"
      | cat "#{tmpfile}"
      SCRIPT
      @logger.debug("copying script:\n#{script.lines.map { |line| "    | #{line}" }.join}")
      cp_script_to_remote(script, 'retrieve')
      @prepared = true
    end
prepare_start() click to toggle source
Calls superclass method
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 14
def prepare_start
  super
  basedir         = File.dirname @remote_script
  @pid_path       = File.join(basedir, 'pid')
  @retrieval_script ||= File.join(basedir, 'retrieve')
  prepare_retrieval unless @prepared
end
refresh() click to toggle source
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 36
def refresh
  err = output = nil
  with_retries do
    _, output, err = run_sync("#{@user_method.cli_command_prefix} #{@retrieval_script}")
  end
  lines = output.lines
  result = lines.shift.match(/^DONE (\d+)?/)
  publish_data(lines.join, 'stdout') unless lines.empty?
  publish_data(err, 'stderr') unless err.empty?
  if result
    exitcode = result[1] || 0
    publish_exit_status(exitcode.to_i)
    run_sync("rm -rf \"#{remote_command_dir}\"") if @cleanup_working_dirs
  end
  destroy_session
end
trigger(*args) click to toggle source
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 32
def trigger(*args)
  run_sync(*args)
end

Private Instance Methods

destroy_session() click to toggle source
# File lib/foreman_remote_execution_core/polling_script_runner.rb, line 118
def destroy_session
  if @session
    @logger.debug("Closing session with #{@ssh_user}@#{@host}")
    @session.close
    @session = nil
  end
end