Class: OpsWalrus::Host

Inherits:
Object
  • Object
show all
Includes:
HostDSL
Defined in:
lib/opswalrus/host.rb

Instance Method Summary collapse

Methods included from HostDSL

#autoretry, #debug, #desc, #env, #host_prop, #params, #parse_stdout_and_script_return_value, #reboot, #reconnect, #run_ops, #sh, #sh?, #shell, #shell!, #ssh_session, #warn

Constructor Details

#initialize(ssh_uri, tags = [], props = {}, default_props = {}, hosts_file = nil) ⇒ Host

ssh_uri is a string of the form:

  • hostname

  • user@hostname

  • hostname:port

  • user@hostname:port



412
413
414
415
416
417
418
419
420
421
# File 'lib/opswalrus/host.rb', line 412

def initialize(ssh_uri, tags = [], props = {}, default_props = {}, hosts_file = nil)
  @ssh_uri = ssh_uri
  @host = nil
  @tags = tags.to_set
  @props = props.is_a?(Array) ? {"tags" => props} : props.to_h
  @default_props = default_props
  @hosts_file = hosts_file
  @tmp_ssh_key_files = []
  parse_ssh_uri!
end

Instance Method Details

#_cleanup_sessionObject



580
581
582
583
584
585
586
587
# File 'lib/opswalrus/host.rb', line 580

def _cleanup_session
  # todo: cleanup
  if @tmp_bundle_root_dir =~ /tmp/   # sanity check the temp path before we blow away something we don't intend
    execute(:rm, "-rf", "tmpops.zip", @tmp_bundle_root_dir)
  else
    execute(:rm, "-rf", "tmpops.zip")
  end
end

#_initialize_sessionObject

returns the temporary bundle root directory

Raises:



568
569
570
571
572
573
574
575
576
577
578
# File 'lib/opswalrus/host.rb', line 568

def _initialize_session
  # copy over ops bundle zip file
  zip_bundle_path = @runtime_env.zip_bundle_path
  upload_success = upload(zip_bundle_path, "tmpops.zip")
  raise Error, "Unable to upload ops bundle to remote host" unless upload_success

  stdout, _stderr, exit_status = run_ops(:bundle, "unzip tmpops.zip", in_bundle_root_dir: false)
  raise Error, "Unable to unzip ops bundle on remote host: #{stdout}" unless exit_status == 0
  tmp_bundle_root_dir = stdout.strip
  set_ssh_session_tmp_bundle_root_dir(tmp_bundle_root_dir)
end

#aliasObject



450
451
452
# File 'lib/opswalrus/host.rb', line 450

def alias
  @props["alias"] || @default_props["alias"]
end

#clear_ssh_sessionObject



589
590
591
592
593
594
595
596
# File 'lib/opswalrus/host.rb', line 589

def clear_ssh_session
  @runtime_env = nil
  @ops_file_script = nil
  @sshkit_backend = nil
  @tmp_bundle_root_dir = nil
  @tmp_ssh_key_files.each {|tmpfile| tmpfile.close() rescue nil; File.unlink(tmpfile) rescue nil }
  @tmp_ssh_key_files = []
end

#dereference_secret_if_needed(secret_ref) ⇒ Object

secret_ref: SecretRef returns the decrypted value referenced by the supplied SecretRef



433
434
435
436
437
438
439
440
# File 'lib/opswalrus/host.rb', line 433

def dereference_secret_if_needed(secret_ref)
  if secret_ref.is_a? SecretRef
    raise "Host #{self} not read from hosts file so no secrets can be dereferenced." unless @hosts_file
    @hosts_file.read_secret(secret_ref.to_s)
  else
    secret_ref
  end
end

#download(remote_path, local_path) ⇒ Object



630
631
632
# File 'lib/opswalrus/host.rb', line 630

def download(remote_path, local_path)
  @sshkit_backend.download!(remote_path.to_s, local_path.to_s)
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


482
483
484
# File 'lib/opswalrus/host.rb', line 482

def eql?(other)
  self.class == other.class && self.hash == other.hash
end

#execute(*args, input_mapping: nil, ops_prompt_for_sudo_password: false) ⇒ Object



598
599
600
601
602
603
604
605
606
607
# File 'lib/opswalrus/host.rb', line 598

def execute(*args, input_mapping: nil, ops_prompt_for_sudo_password: false)
  sudo_password_args = {}
  sudo_password_args[:sudo_password] = ssh_password unless ops_prompt_for_sudo_password
  sudo_password_args[:ops_sudo_password] = ssh_password if ops_prompt_for_sudo_password
  @runtime_env.handle_input(input_mapping, **sudo_password_args, inherit_existing_mappings: false) do |interaction_handler|
    # @sshkit_backend.capture(*args, interaction_handler: interaction_handler, verbosity: SSHKit.config.output_verbosity)
    App.instance.debug("Host#execute_cmd(#{args.inspect}) with input mappings #{interaction_handler.input_mappings.inspect} given sudo_password_args: #{sudo_password_args.inspect})")
    @sshkit_backend.capture(*args, interaction_handler: interaction_handler)
  end
end

#execute_cmd(*args, input_mapping: nil, ops_prompt_for_sudo_password: false) ⇒ Object

Returns a SSHKit::Command



610
611
612
613
614
615
616
617
618
619
620
621
622
623
# File 'lib/opswalrus/host.rb', line 610

def execute_cmd(*args, input_mapping: nil, ops_prompt_for_sudo_password: false)
  # we only want one of the sudo password interaction handlers:
  # if we're running an ops script on the remote host and we've passed the --pass flag in our invocation of the ops command on the remote host,
  #   then we want to specify the sudo password via the ops_sudo_password argument to #handle_input
  # if we're running a command on the remote host via #shell!, and we aren't running the ops command with the --pass flag,
  #   then we want to specify the sudo password via the sudo_password argument to #handle_input
  sudo_password_args = {}
  sudo_password_args[:sudo_password] = ssh_password unless ops_prompt_for_sudo_password
  sudo_password_args[:ops_sudo_password] = ssh_password if ops_prompt_for_sudo_password
  @runtime_env.handle_input(input_mapping, **sudo_password_args, inherit_existing_mappings: false) do |interaction_handler|
    App.instance.debug("Host#execute_cmd(#{args.inspect}) with input mappings #{interaction_handler.input_mappings.inspect} given sudo_password_args: #{sudo_password_args.inspect})")
    @sshkit_backend.execute_cmd(*args, interaction_handler: interaction_handler)
  end
end

#hashObject



478
479
480
# File 'lib/opswalrus/host.rb', line 478

def hash
  @ssh_uri.hash
end

#hostObject



446
447
448
# File 'lib/opswalrus/host.rb', line 446

def host
  @host
end

#ignore?Boolean

Returns:

  • (Boolean)


454
455
456
# File 'lib/opswalrus/host.rb', line 454

def ignore?
  @props["ignore"] || @default_props["ignore"]
end

#parse_ssh_uri!Object



423
424
425
426
427
428
429
# File 'lib/opswalrus/host.rb', line 423

def parse_ssh_uri!
  if match = /^\s*((?<user>.*?)@)?(?<host>.*?)(:(?<port>[0-9]+))?\s*$/.match(@ssh_uri)
    @host ||= match[:host] if match[:host]
    @props["user"] ||= match[:user] if match[:user]
    @props["port"] ||= match[:port].to_i if match[:port]
  end
end

#set_ops_file_script(ops_file_script) ⇒ Object



555
556
557
# File 'lib/opswalrus/host.rb', line 555

def set_ops_file_script(ops_file_script)
  @ops_file_script = ops_file_script
end

#set_runtime_env(runtime_env) ⇒ Object



551
552
553
# File 'lib/opswalrus/host.rb', line 551

def set_runtime_env(runtime_env)
  @runtime_env = runtime_env
end

#set_ssh_session_connection(sshkit_backend) ⇒ Object



559
560
561
# File 'lib/opswalrus/host.rb', line 559

def set_ssh_session_connection(sshkit_backend)
  @sshkit_backend = sshkit_backend
end

#set_ssh_session_tmp_bundle_root_dir(tmp_bundle_root_dir) ⇒ Object



563
564
565
# File 'lib/opswalrus/host.rb', line 563

def set_ssh_session_tmp_bundle_root_dir(tmp_bundle_root_dir)
  @tmp_bundle_root_dir = tmp_bundle_root_dir
end

#ssh_keyObject



474
475
476
# File 'lib/opswalrus/host.rb', line 474

def ssh_key
  @props["ssh-key"] || @default_props["ssh-key"]
end

#ssh_passwordObject



466
467
468
469
470
471
472
# File 'lib/opswalrus/host.rb', line 466

def ssh_password
  password = @props["password"] || @default_props["password"]
  password ||= begin
    @props["password"] = IO::console.getpass("[opswalrus] Please enter ssh password to connect to #{ssh_user}@#{host}:#{ssh_port}: ")
  end
  dereference_secret_if_needed(password)
end

#ssh_portObject



458
459
460
# File 'lib/opswalrus/host.rb', line 458

def ssh_port
  @props["port"] || @default_props["port"] || 22
end

#ssh_uriObject



442
443
444
# File 'lib/opswalrus/host.rb', line 442

def ssh_uri
  @ssh_uri
end

#ssh_userObject



462
463
464
# File 'lib/opswalrus/host.rb', line 462

def ssh_user
  @props["user"] || @default_props["user"]
end

#sshkit_hostObject



509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
# File 'lib/opswalrus/host.rb', line 509

def sshkit_host
  keys = case ssh_key
  when Array
    ssh_key
  else
    [ssh_key]
  end
  keys = write_temp_ssh_keys_if_needed(keys)

  # the various options for net-ssh are captured in https://net-ssh.github.io/ssh/v1/chapter-2.html
  @sshkit_host ||= ::SSHKit::Host.new({
    hostname: host,
    port: ssh_port,
    user: ssh_user || raise("No ssh user specified to connect to #{host}"),
    password: ssh_password,
    keys: keys
  })
end

#summary(verbose = false) ⇒ Object



501
502
503
504
505
506
507
# File 'lib/opswalrus/host.rb', line 501

def summary(verbose = false)
  report = "#{to_s}\n  tags: #{tags.sort.join(', ')}"
  if verbose
    @default_props.merge(@props).reject{|k,v| k == 'tags' }.each {|k,v| report << "\n  #{k}: #{v}" }
  end
  report
end

#tag!(*tags) ⇒ Object



490
491
492
493
494
495
# File 'lib/opswalrus/host.rb', line 490

def tag!(*tags)
  enumerables, scalars = tags.partition {|t| Enumerable === t }
  @tags.merge(scalars)
  enumerables.each {|enum| @tags.merge(enum) }
  @tags
end

#tagsObject



497
498
499
# File 'lib/opswalrus/host.rb', line 497

def tags
  @tags
end

#to_hObject



634
635
636
637
638
639
640
641
642
643
644
# File 'lib/opswalrus/host.rb', line 634

def to_h
  hash = {}
  hash["alias"] = @props["alias"] if @props["alias"]
  hash["ignore"] = @props["ignore"] if @props["ignore"]
  hash["user"] = @props["user"] if @props["user"]
  hash["port"] = @props["port"] if @props["port"]
  hash["password"] = @props["password"] if @props["password"]
  hash["ssh-key"] = @props["ssh-key"] if @props["ssh-key"]
  hash["tags"] = tags.to_a unless tags.empty?
  hash
end

#to_sObject



486
487
488
# File 'lib/opswalrus/host.rb', line 486

def to_s
  @ssh_uri
end

#upload(local_path_or_io, remote_path) ⇒ Object



625
626
627
628
# File 'lib/opswalrus/host.rb', line 625

def upload(local_path_or_io, remote_path)
  source = local_path_or_io.is_a?(IO) ? local_path_or_io : local_path_or_io.to_s
  @sshkit_backend.upload!(source, remote_path.to_s)
end

#write_temp_ssh_keys_if_needed(keys) ⇒ Object

keys is an Array ( String | SecretRef ) such that if a key is a String, then it is interpreted as a path to a key file and if the key is a SecretRef, then the secret’s plaintext value is interpreted as an ssh key string, and must thereforce be written to a tempfile so that net-ssh can use it via file reference (since net-ssh only allows the keys field to be an array of file paths).

returns an array of file paths to key files



535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
# File 'lib/opswalrus/host.rb', line 535

def write_temp_ssh_keys_if_needed(keys)
  keys.map do |key_file_path_or_in_memory_key_text|
    if key_file_path_or_in_memory_key_text.is_a? SecretRef    # we're dealing with an in-memory key file; we need to write it to a tempfile
      tempfile = Tempfile.create
      @tmp_ssh_key_files << tempfile
      raise "Host #{self} not read from hosts file so no secrets can be written." unless @hosts_file
      key_file_contents = @hosts_file.read_secret(key_file_path_or_in_memory_key_text.to_s)
      tempfile.write(key_file_contents)
      tempfile.close   # we want to close the file without unlinking so that the editor can write to it
      tempfile.path
    else    # we're dealing with a reference to a keyfile - a path - so return it
      key_file_path_or_in_memory_key_text
    end
  end
end