Class: OpsWalrus::RemoteInvocation

Inherits:
Object
  • Object
show all
Defined in:
lib/opswalrus/invocation.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host_proxy, ops_file, ops_prompt_for_sudo_password: nil) ⇒ RemoteInvocation

Returns a new instance of RemoteInvocation.



246
247
248
249
250
# File 'lib/opswalrus/invocation.rb', line 246

def initialize(host_proxy, ops_file, ops_prompt_for_sudo_password: nil)
  @host_proxy = host_proxy
  @ops_file = ops_file
  @ops_prompt_for_sudo_password = ops_prompt_for_sudo_password
end

Class Method Details

.parse_remote_script_invocation_result(json_string) ⇒ Object



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/opswalrus/invocation.rb', line 215

def self.parse_remote_script_invocation_result(json_string)
  retval = JSON.parse(json_string)
  case retval
  when Hash
    if retval["type"] == Invocation::Error::Type
      # this structure comes from OpsWalrus::Invocation::Error being serialized in App#print_script_result being called from App#run
      # {
      #   type: "Invocation::Error",
      #   error_variant: self.class.name,
      #   error_class: value.class.name,
      #   error: value,
      #   backtrace: value.is_a?(Exception) ? value.backtrace.take(10).join("\n") : nil,
      #   exit_status: exit_status
      # }
      # raise RemoteInvocationError.new("Remote Invocation Error:\n  #{retval["error_class"]}: #{retval["error"]}#{retval["backtrace"] && "\n  Backtrace: #{retval['backtrace']}"}")

      if retval["error_variant"] == Invocation::EarlyExitError.name && retval["exit_status"] == ExitCodeHostTemporarilyUnavailable
        raise RetriableRemoteInvocationError.new("Retriable Remote Invocation Error (exit_status=#{retval["exit_status"]}):\n  #{retval["error_class"]}: #{retval["error"]}#{retval["backtrace"] && "\n  Backtrace: #{retval['backtrace']}"}", retval)
      else
        raise RemoteInvocationError.new("Remote Invocation Error (exit_status=#{retval["exit_status"]}):\n  #{retval["error_class"]}: #{retval["error"]}#{retval["backtrace"] && "\n  Backtrace: #{retval['backtrace']}"}", retval)
      end
    else
      retval.with_indifferent_access.easynav
    end
  when Array
    retval.easynav
  else
    retval
  end
end

Instance Method Details

#invoke(*args, **kwargs) ⇒ Object



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/opswalrus/invocation.rb', line 252

def invoke(*args, **kwargs)
  # when there are args or kwargs, then the method invocation represents an attempt to run an OpsFile on a remote host,
  # so we want to build up a command and send it to the remote host via HostDSL#run_ops
  remote_run_command_args = @ops_file.relative_path_to_app_pwd.to_s

  unless args.empty?
    remote_run_command_args << " "
    remote_run_command_args << args.join(" ")
  end

  begin
    json = JSON.dump(kwargs) unless kwargs.empty?
    if json
      # write the kwargs to a tempfile
      json_kwargs_tempfile = Tempfile.create('ops_invoke_kwargs')
      json_kwargs_tempfile.write(json)
      json_kwargs_tempfile.close()   # we want to close the file without unlinking so that we can copy it to the remote host before deleting it

      # upload the kwargs file to the remote host
      json_kwargs_tempfile_path = json_kwargs_tempfile.path.to_pathname
      remote_json_kwargs_tempfile_basename = json_kwargs_tempfile_path.basename
      @host_proxy.upload(json_kwargs_tempfile_path, remote_json_kwargs_tempfile_basename)
    end

    # invoke the ops command on the remote host to run the specified ops script on the remote host
    ops_command_options = "--script"
    ops_command_options << " --pass" if @ops_prompt_for_sudo_password
    ops_command_options << " --params #{remote_json_kwargs_tempfile_basename}" if remote_json_kwargs_tempfile_basename

    output, stderr, exit_status = if ops_command_options.empty?
      @host_proxy.run_ops(:run, remote_run_command_args, ops_prompt_for_sudo_password: @ops_prompt_for_sudo_password)
    else
      @host_proxy.run_ops(:run, ops_command_options, remote_run_command_args, ops_prompt_for_sudo_password: @ops_prompt_for_sudo_password)
    end

    App.instance.debug("Remote invocation failed:\n  cmd: ops run #{ops_command_options.to_s} #{remote_run_command_args.to_s}\n  stdout: #{output}\n") unless exit_status == 0

    RemoteInvocation.parse_remote_script_invocation_result(output)
  ensure
    if json_kwargs_tempfile
      json_kwargs_tempfile.close rescue nil
      File.unlink(json_kwargs_tempfile) rescue nil
    end
    # todo: make sure this cleanup is present
    if remote_json_kwargs_tempfile_basename
      # this may fail if the remote host reboots and we try to subsequently connect and delete the file
      @host_proxy.execute(:rm, "-f", remote_json_kwargs_tempfile_basename) rescue nil
    end
  end
end