module KnuckleCluster

Constants

VERSION

Attributes

asg_name[R]
aws_vault_profile[R]
bastion[R]
cluster_name[R]
region[R]
rsa_key_location[R]
shortcuts[R]
spot_request_id[R]
ssh_username[R]
sudo[R]
tunnels[R]

Public Class Methods

connect_to_agents(command: nil, auto: false) click to toggle source
# File lib/knuckle_cluster.rb, line 54
def connect_to_agents(command: nil, auto: false)
  agent = select_agent(auto: auto)
  run_command_in_agent(agent: agent, command: command)
end
connect_to_container(name:, command: 'bash') click to toggle source
# File lib/knuckle_cluster.rb, line 64
def connect_to_container(name:, command: 'bash')
  if shortcut = shortcuts[name.to_sym]
    name = shortcut[:container]
    new_command = shortcut[:command]
    new_command += " #{command}" unless command == 'bash'
    command = new_command
  end

  container = find_container(name: name)
  run_command_in_container(container: container, command: command)
end
connect_to_containers(command: 'bash', auto: false) click to toggle source
# File lib/knuckle_cluster.rb, line 59
def connect_to_containers(command: 'bash', auto: false)
  container = select_container(auto: auto)
  run_command_in_container(container: container, command: command)
end
container_logs(name:) click to toggle source
# File lib/knuckle_cluster.rb, line 76
def container_logs(name:)
  container = find_container(name: name)
  subcommand = "#{'sudo ' if sudo}docker logs -f \\`#{get_container_id_command(container.name)}\\`"
  run_command_in_agent(agent: container.task.agent, command: subcommand)
end
new( cluster_name: nil, spot_request_id: nil, asg_name: nil, region: 'us-east-1', bastion: nil, rsa_key_location: nil, ssh_username: 'ec2-user', sudo: false, aws_vault_profile: nil, shortcuts: {}, tunnels: {}, hide: {}) click to toggle source
# File lib/knuckle_cluster.rb, line 22
def new(
    cluster_name: nil,
    spot_request_id: nil,
    asg_name: nil,
    region: 'us-east-1',
    bastion: nil,
    rsa_key_location: nil,
    ssh_username: 'ec2-user',
    sudo: false,
    aws_vault_profile: nil,
    shortcuts: {},
    tunnels: {},
    hide: {})
  @cluster_name      = cluster_name
  @spot_request_id   = spot_request_id
  @asg_name          = asg_name
  @region            = region
  @bastion           = bastion
  @rsa_key_location  = rsa_key_location
  @ssh_username      = ssh_username
  @sudo              = sudo
  @aws_vault_profile = aws_vault_profile
  @shortcuts         = shortcuts
  @tunnels           = tunnels
  @hide              = hide

  if @cluster_name.nil? && @spot_request_id.nil? && @asg_name.nil?
    raise "Must specify either cluster_name, spot_request_id or asg name"
  end
  self
end
open_tunnel(name:) click to toggle source
# File lib/knuckle_cluster.rb, line 82
def open_tunnel(name:)
  if tunnel = tunnels[name.to_sym]
    agent = select_agent(auto: true)
    open_tunnel_via_agent(**tunnel.merge(agent: agent))
  else
    puts "ERROR: A tunnel configuration was not found for '#{name}'"
  end
end

Private Class Methods

agent_registry() click to toggle source
# File lib/knuckle_cluster.rb, line 204
def agent_registry
  @agent_registry ||= (
    if @cluster_name
      EcsAgentRegistry.new(
        aws_client_config: aws_client_config,
        cluster_name:      cluster_name,
        hide:              @hide,
      )
    elsif @spot_request_id
      SpotRequestInstanceRegistry.new(
        aws_client_config: aws_client_config,
        spot_request_id:   spot_request_id,
      )
    elsif @asg_name
      AsgInstanceRegistry.new(
        aws_client_config: aws_client_config,
        asg_name:          asg_name,
      )
    end
  )
end
aws_client_config() click to toggle source
# File lib/knuckle_cluster.rb, line 174
def aws_client_config
  @aws_client_config ||= { region: region }.tap do |config|
    config.merge!(aws_vault_credentials) if aws_vault_profile
  end
end
aws_vault_credentials() click to toggle source
# File lib/knuckle_cluster.rb, line 180
def aws_vault_credentials
  environment = `aws-vault exec #{aws_vault_profile} -- env | grep AWS_`
  vars = environment.split.map { |pair| pair.split('=') }.group_by(&:first)
  {}.tap do |credentials|
    %i{access_key_id secret_access_key session_token}.map do |var_name|
      credentials[var_name] = vars["AWS_#{var_name.upcase}"]&.first&.last
    end
  end
end
find_container(name:) click to toggle source
# File lib/knuckle_cluster.rb, line 123
def find_container(name:)
  matching = containers.select { |container| container.name.include?(name) }
  puts "\nAttempting to find a container matching '#{name}'..."

  if matching.empty?
    puts "No container with a name matching '#{name}' was found"
    Process.exit
  end

  unique_names = matching.map(&:name).uniq

  if unique_names.uniq.count > 1
    puts "Containers with the following names were found, please be more specific:"
    puts unique_names
    Process.exit
  end

  # If there are multiple containers with the same name, choose any one
  container = matching.first
  puts "Found container #{container.name} on #{container.task.agent.instance_id}\n\n"
  container
end
generate_connection_string(agent:, subcommand: nil, port_forward: nil) click to toggle source
# File lib/knuckle_cluster.rb, line 190
def generate_connection_string(agent:, subcommand: nil, port_forward: nil)
  ip = bastion ? agent.private_ip : agent.public_ip
  command = "ssh #{ip} -l#{ssh_username}"
  command += " -i #{rsa_key_location}" if rsa_key_location
  if bastion.is_a? String
    command += " -o ProxyCommand='ssh -qxT #{bastion} nc #{ip} 22'"
  elsif bastion.is_a? Hash
    command += " -o Proxycommand='ssh -qxt #{bastion[:host]} -l#{bastion[:username]} -i #{bastion[:rsa_key_location]} nc #{ip} 22'"
  end
  command += " -L #{port_forward}" if port_forward
  command += " -t \"#{subcommand}\"" if subcommand
  command
end
get_container_id_command(container_name) click to toggle source
# File lib/knuckle_cluster.rb, line 151
def get_container_id_command(container_name)
  "#{'sudo ' if sudo}docker ps --filter 'label=com.amazonaws.ecs.container-name=#{container_name}' | tail -1 | awk '{print \\$1}'"
end
open_tunnel_via_agent(agent:, local_port:, remote_host:, remote_port:) click to toggle source
# File lib/knuckle_cluster.rb, line 160
    def open_tunnel_via_agent(agent:, local_port:, remote_host:, remote_port:)
      command = generate_connection_string(
        agent: agent,
        port_forward: [local_port, remote_host, remote_port].join(':'),
        subcommand: <<~SCRIPT
          echo ""
          echo "localhost:#{local_port} is now tunneled to #{remote_host}:#{remote_port}"
          echo "Press Enter to close the tunnel once you are finished."
          read
        SCRIPT
      )
      system(command)
    end
run_command_in_agent(agent:, command:) click to toggle source
# File lib/knuckle_cluster.rb, line 155
def run_command_in_agent(agent:, command:)
  command = generate_connection_string(agent: agent, subcommand: command)
  system(command)
end
run_command_in_container(container:, command:) click to toggle source
# File lib/knuckle_cluster.rb, line 146
def run_command_in_container(container:, command:)
  subcommand = "#{'sudo ' if sudo}docker exec -it \\`#{get_container_id_command(container.name)}\\` #{command}"
  run_command_in_agent(agent: container.task.agent, command: subcommand)
end
select_agent(auto: false) click to toggle source
# File lib/knuckle_cluster.rb, line 97
def select_agent(auto: false)
  return agents.first if auto

  puts "\nListing Agents"

  output_agents

  puts "\nConnect to which agent?"
  agents[STDIN.gets.strip.to_i - 1]
end
select_container(auto: false) click to toggle source
# File lib/knuckle_cluster.rb, line 108
def select_container(auto: false)
  return containers.first if auto

  puts "\nListing Containers"

  tp tasks,
     { task: { display_method: :name, width: 999 } },
     { agent: { display_method: 'agent.instance_id' } },
     { index: { display_method: 'containers.index' } },
     { container: { display_method: 'containers.name', width: 999 } }

  puts "\nConnect to which container?"
  containers[STDIN.gets.strip.to_i - 1]
end