Class: ForemanRemoteExecutionCore::ScriptRunner

Inherits:
ForemanTasksCore::Runner::Base
  • Object
show all
Defined in:
lib/foreman_remote_execution_core/script_runner.rb

Direct Known Subclasses

PollingScriptRunner

Constant Summary collapse

EXPECTED_POWER_ACTION_MESSAGES =
['restart host', 'shutdown host'].freeze
DEFAULT_REFRESH_INTERVAL =
1
MAX_PROCESS_RETRIES =
4

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options, user_method, suspended_action: nil) ⇒ ScriptRunner

Returns a new instance of ScriptRunner.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 116

def initialize(options, user_method, suspended_action: nil)
  super suspended_action: suspended_action
  @host = options.fetch(:hostname)
  @script = options.fetch(:script)
  @ssh_user = options.fetch(:ssh_user, 'root')
  @ssh_port = options.fetch(:ssh_port, 22)
  @ssh_password = options.fetch(:secrets, {}).fetch(:ssh_password, nil)
  @key_passphrase = options.fetch(:secrets, {}).fetch(:key_passphrase, nil)
  @host_public_key = options.fetch(:host_public_key, nil)
  @verify_host = options.fetch(:verify_host, nil)
  @execution_timeout_interval = options.fetch(:execution_timeout_interval, nil)

  @client_private_key_file = settings.fetch(:ssh_identity_key_file)
  @local_working_dir = options.fetch(:local_working_dir, settings.fetch(:local_working_dir))
  @remote_working_dir = options.fetch(:remote_working_dir, settings.fetch(:remote_working_dir))
  @cleanup_working_dirs = options.fetch(:cleanup_working_dirs, settings.fetch(:cleanup_working_dirs))
  @user_method = user_method
end

Instance Attribute Details

#execution_timeout_intervalObject (readonly)

Returns the value of attribute execution_timeout_interval.



110
111
112
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 110

def execution_timeout_interval
  @execution_timeout_interval
end

Class Method Details

.build(options, suspended_action:) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 135

def self.build(options, suspended_action:)
  effective_user = options.fetch(:effective_user, nil)
  ssh_user = options.fetch(:ssh_user, 'root')
  effective_user_method = options.fetch(:effective_user_method, 'sudo')

  user_method = if effective_user.nil? || effective_user == ssh_user
                  NoopUserMethod.new
                elsif effective_user_method == 'sudo'
                  SudoUserMethod.new(effective_user, ssh_user,
                    options.fetch(:secrets, {}).fetch(:sudo_password, nil))
                elsif effective_user_method == 'dzdo'
                  DzdoUserMethod.new(effective_user, ssh_user,
                    options.fetch(:secrets, {}).fetch(:sudo_password, nil))
                elsif effective_user_method == 'su'
                  SuUserMethod.new(effective_user, ssh_user)
                else
                  raise "effective_user_method '#{effective_user_method}' not supported"
                end

  new(options, user_method, suspended_action: suspended_action)
end

Instance Method Details

#closeObject



247
248
249
250
251
252
253
254
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 247

def close
  run_sync("rm -rf \"#{remote_command_dir}\"") if should_cleanup?
rescue => e
  publish_exception('Error when removing remote working dir', e, false)
ensure
  @session.close if @session && !@session.closed?
  FileUtils.rm_rf(local_command_dir) if Dir.exist?(local_command_dir) && @cleanup_working_dirs
end

#initialization_scriptObject

the script that initiates the execution



178
179
180
181
182
183
184
185
186
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 178

def initialization_script
  # pipe the output to tee while capturing the exit code in a file
  <<-SCRIPT.gsub(/^\s+\| /, '')
  | sh <<WRAPPER
  | (#{@user_method.cli_command_prefix}#{@remote_script} < /dev/null; echo \\$?>#{@exit_code_path}) | /usr/bin/tee #{@output_path}
  | exit \\$(cat #{@exit_code_path})
  | WRAPPER
  SCRIPT
end

#killObject



200
201
202
203
204
205
206
207
208
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 200

def kill
  if @session
    run_sync("pkill -f #{remote_command_file('script')}")
  else
    logger.debug('connection closed')
  end
rescue => e
  publish_exception('Unexpected error', e, false)
end

#prepare_startObject



171
172
173
174
175
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 171

def prepare_start
  @remote_script = cp_script_to_remote
  @output_path = File.join(File.dirname(@remote_script), 'output')
  @exit_code_path = File.join(File.dirname(@remote_script), 'exit_code')
end

#publish_data(data, type) ⇒ Object



256
257
258
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 256

def publish_data(data, type)
  super(data.force_encoding('UTF-8'), type)
end

#refreshObject



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 188

def refresh
  return if @session.nil?

  with_retries do
    with_disconnect_handling do
      @session.process(0)
    end
  end
ensure
  check_expecting_disconnect
end

#startObject



157
158
159
160
161
162
163
164
165
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 157

def start
  prepare_start
  script = initialization_script
  logger.debug("executing script:\n#{indent_multiline(script)}")
  trigger(script)
rescue => e
  logger.error("error while initalizing command #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}")
  publish_exception('Error initializing command', e)
end

#timeoutObject



210
211
212
213
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 210

def timeout
  @logger.debug('job timed out')
  super
end

#timeout_intervalObject



215
216
217
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 215

def timeout_interval
  execution_timeout_interval
end

#trigger(*args) ⇒ Object



167
168
169
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 167

def trigger(*args)
  run_async(*args)
end

#with_disconnect_handlingObject



235
236
237
238
239
240
241
242
243
244
245
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 235

def with_disconnect_handling
  yield
rescue IOError, Net::SSH::Disconnect => e
  @session.shutdown!
  check_expecting_disconnect
  if @expecting_disconnect
    publish_exit_status(0)
  else
    publish_exception('Unexpected disconnect', e)
  end
end

#with_retriesObject



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/foreman_remote_execution_core/script_runner.rb', line 219

def with_retries
  tries = 0
  begin
    yield
  rescue => e
    logger.error("Unexpected error: #{e.class} #{e.message}\n #{e.backtrace.join("\n")}")
    tries += 1
    if tries <= MAX_PROCESS_RETRIES
      logger.error('Retrying')
      retry
    else
      publish_exception('Unexpected error', e)
    end
  end
end