class Sonic::Ssh

Public Class Methods

new(identifier, options) click to toggle source
# File lib/sonic/ssh.rb, line 9
def initialize(identifier, options)
  @options = options

  @user, @identifier = extract_user!(identifier) # extracts/strips user from identifier
  # While --user option is supported at the class level, don't expose at the CLI level
  # to encourage users to use user@host notation.
  @user ||= options[:user] || settings["ssh"]["user"]

  @service = @identifier # always set service even though it's not always used as the identifier
  map = settings["ecs_service_cluster_map"]
  @cluster = options[:cluster] || map[@service] || map["default"] || "default"
  @bastion = options[:bastion] || settings["bastion"]["host"]
end

Public Instance Methods

bastion_host() click to toggle source
# File lib/sonic/ssh.rb, line 29
def bastion_host
  return @identifier if @options[:noop] # for specs
  @bastion_host ||= build_bastion_host
end
build_bastion_host() click to toggle source
# File lib/sonic/ssh.rb, line 34
def build_bastion_host
  host = @bastion
  host = "#{@user}@#{host}" unless host.include?('@')
  host
end
build_ssh_host() click to toggle source
# File lib/sonic/ssh.rb, line 46
def build_ssh_host
  return @identifier if ENV['TEST']

  instance_id = detector.detect!
  instance_hostname(instance_id)
end
detector() click to toggle source
# File lib/sonic/ssh.rb, line 53
def detector
  @detector ||= Ssh::IdentifierDetector.new(@cluster, @service, @identifier, @options)
end
instance_hostname(ec2_instance_id) click to toggle source
# File lib/sonic/ssh.rb, line 58
def instance_hostname(ec2_instance_id)
  begin
    resp = ec2.describe_instances(instance_ids: [ec2_instance_id])
  rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
    # e.message: The instance ID 'i-027363802c6ff3141' does not exist
    UI.error e.message
    exit 1
  rescue Aws::Errors::NoSuchEndpointError, SocketError
    UI.error "It doesnt look like you have an internet connection. Please double check that you have an internet connection."
    exit 1
  end
  instance = resp.reservations[0].instances[0]
  # struct Aws::EC2::Types::Instance
  # http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Types/Instance.html
  if @bastion
    instance.private_ip_address
  else
    instance.public_ip_address
  end
end
kernel_exec(*args) click to toggle source

Will use Kernel.exec so that the ssh process takes over this ruby process.

# File lib/sonic/ssh.rb, line 80
def kernel_exec(*args)
  # append the optional command that can be provided to the ssh command
  full_command = args + @options[:command]
  puts "=> #{full_command.join(' ')}".color(:green)
  # https://ruby-doc.org/core-2.3.1/Kernel.html#method-i-exec
  # Using 2nd form
  Kernel.exec(*full_command) unless @options[:noop]
end
run() click to toggle source
# File lib/sonic/ssh.rb, line 23
def run
  ssh = build_ssh_command
  retry_until_success(*ssh) if @options[:retry]
  kernel_exec(*ssh) # must splat the Array here
end
ssh_host() click to toggle source

used by child Classes

# File lib/sonic/ssh.rb, line 41
def ssh_host
  return @identifier if @options[:noop] # for specs
  @ssh_host ||= build_ssh_host
end

Private Instance Methods

build_ssh_command() click to toggle source

Will prepend the bastion host if required When bastion set

ssh [options] -At [bastion_host] ssh -At [ssh_host]

When bastion not set

ssh [options] -At [ssh_host]

Builds up ssh command to be used with Kernel.exec. Will look something like this:

ssh -At ec2-user@34.211.223.3 ssh ec2-user@10.10.110.135

It is imporant to use an Array for the command so it gets intrepreted as if you are executing it from the shell directly. For example, globs gets expanded with the Array notation but not the String notation.

ssh options: -A = Enables forwarding of the authentication agent connection -t = Force pseudo-terminal allocati

# File lib/sonic/ssh.rb, line 130
def build_ssh_command
  command = ["ssh", "-t"] + ssh_options
  if @bastion
    # https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts
    # -J xxx is -o ProxyJump=xxx
    proxy_jump = ["-J", bastion_host]
    command += proxy_jump
  end
  command += ["-t", "#{@user}@#{ssh_host}"]
end
extract_user!(identifier) click to toggle source

Private: Extracts and strips the user from the identifier.

identifier - Can be a variety of things: instance_id, ecs service, ecs task, etc.

Examples

extract_user!("i-0f7f833131a51ce35")
# => [nil, "i-0f7f833131a51ce35"]

extract_user!("ec2-user@i-0f7f833131a51ce35")
# => ["ec2-user", "i-0f7f833131a51ce35"]

Returns the a tuple cotaining the user and identifier

# File lib/sonic/ssh.rb, line 154
def extract_user!(identifier)
  md = identifier.match(/(.*)@(.*)/)
  if md
    [md[1], md[2]]
  else
    [nil, identifier]
  end
end
host_key_check_options() click to toggle source

By default bypass strict host key checking for convenience. But user can overrride this.

# File lib/sonic/ssh.rb, line 104
def host_key_check_options
  if settings["bastion"]["host_key_check"] == true
    []
  else
    # settings["bastion"]["host_key_check"] nil will disable checking also
    # disables host key checking
    %w[-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null]
  end
end
retry_until_success(*command) click to toggle source
# File lib/sonic/ssh.rb, line 163
def retry_until_success(*command)
  retries = 0
  uptime = command + ['uptime', '2>&1']
  uptime = uptime.join(' ')
  out = `#{uptime}`
  while out !~ /load average/ do
    puts "Can't ssh into the server yet.  Retrying until success." if retries == 0
    print '.'
    retries += 1
    sleep 1
    out = `#{uptime}`
  end
  puts "" if @options[:retry] && retries > 0
end
settings() click to toggle source

direct access to settings data

# File lib/sonic/ssh.rb, line 91
def settings
  @settings ||= Setting.new.data
end
ssh_options() click to toggle source

Returns Array of flags. Example:

["-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"]
# File lib/sonic/ssh.rb, line 98
def ssh_options
  keys_option + host_key_check_options
end