tomlobato/con_ssh

View on GitHub
lib/con_ssh.rb

Summary

Maintainability
A
3 hrs
Test Coverage

require 'ostruct'
require 'con_ssh/core_extensions'

class SSHCon
  CONF_PATH = File.expand_path '~/.con'
  CONF_LINE_FIELDS = %w(shortcut conn_desc host user port knock unknock)
  DEFAULT_PORT = "22"

  def initialize
    @conn_confs = {}
  end

  def run *args
    # setup
    if args[0] == 'setup'
      ign_arg_warn args, 1
      install_sample_conf

    # knock / unknock
    elsif ['knock', 'unknock'].include? args[1] 
      ign_arg_warn args, 2
      parse_conf
      conn_conf = @conn_confs[args[0]]
      unless conn_conf
        warn "Shorcut #{args[0]} not found. See #{CONF_PATH}."
        exit 1
      end
      ports = conn_conf[args[1]]
      if ports.nil? || ports.empty?
        warn "#{conn_conf.conn_desc} has no #{args[1]} configuration."
        exit 1
      end
      knock conn_conf.host, ports

    # help
    elsif ['-h'].include? args[0]
      ign_arg_warn args, 1
      print_help

    # connect
    elsif args[0]
      ign_arg_warn args, 1
      parse_conf
      conn_conf = @conn_confs[args[0]]
      unless conn_conf
        warn "Shorcut #{args[0]} not found. See #{CONF_PATH}."
        exit 1
      end
      ssh conn_conf

    # fail
    else
      warn "Invalid arguments."
      print_help
      exit 1
    end
  end

  private

  def ssh c
    port = (c.port && c.port != DEFAULT_PORT) ? "-p #{ c.port } " : ''
    user = c.user ? "#{ c.user }@" : ''

    if c.knock
      puts "knock`ing"
      knock c.host, c.knock 
      sleep 0.5
    end

    cmd = "ssh #{port}#{user}#{c.host}"
    puts cmd
    system cmd

    if c.unknock
      puts "unknock`ing"
      knock c.host, c.unknock 
    end
  end

  def knock host, ports
    unless has_knock
        msg = "Skipping knock! 
             The connection configuration has ports knock set but you don`t have 'knock' installed on your system. 
             Please install: 'apt-get install knockd', 'brew install knock', ...".strip_text
        warn msg
      return
    end
    run_cmd "knock #{ host } #{ ports.join ' ' }", false
  end


  # Conf

  def parse_conf
    unless File.exists? CONF_PATH
      raise "#{CONF_PATH} not found. Install a sample with 'con setup'."
    end
    File.open(CONF_PATH).readlines.each do |line|
      add_connection line.strip     
    end
  end

  def add_connection line
    return if skip_line? line
    
    values = line.split(/\s+/)
    
    conn_conf = OpenStruct.new
    CONF_LINE_FIELDS.each_with_index do |field, idx|
      conn_conf[field.to_sym] = values[idx]
    end

    unless valid? conn_conf
      warn "WARNING: Invalid line, skipping: '#{line}'"
      return
    end

    adjust_conn_conf conn_conf

    if @conn_confs[conn_conf.shortcut]
      warn "WARNING: Shorcut '#{conn_conf.shortcut} is duplicated, skipping line: '#{line}'"
      return
    end

    @conn_confs[conn_conf.shortcut] = conn_conf
  end

  def valid? c
    c.shortcut &&
    c.conn_desc &&
    c.host
  end

  def adjust_conn_conf c
    c.knock  = c.knock.split ',' if c.knock 
    c.unknock = c.unknock.split ',' if c.unknock 
  end

  def install_sample_conf
    if File.exists? CONF_PATH
      warn "#{CONF_PATH} already exists. Skipping install."
      return false
    end
    sample_conf = "# #{ CONF_LINE_FIELDS.join ' ' }
    # *connection_desc must have no spaces.
    # *user, port, knock, unknock are optionals.

    s1  server_1  123.234.35.456
    s1r server_1r 123.234.35.456
    s3  server_3  123.234.35.456  username  2222  123,234,456 567,678,789"
    File.write CONF_PATH, sample_conf.strip_text
    true
  end


  # Util
  
  def ign_arg_warn args, max
    warn "Ignoring arguments after #{args[0]}." if args.length > max
  end

  def warn desc
    $stderr.puts desc
  end

  def skip_line? line
    line =~ /^\s*($|#)/
  end

  def has_knock
    !`which knock`.strip.empty?
  end

  def print_help
    puts "Usage: con <shortcut>|setup [knock|unknock]".strip_text
  end

  def run_cmd cmd, print = true
    puts "run: #{cmd}" if print
    output = `#{cmd} 2>&1`
    exit_status = $?.to_i
    unless exit_status == 0
      warn "Non-zero output: #{output}"
    end
    [exit_status, output]
  end
end