class Slugforge::Host

Attributes

pattern[R]
server[R]
slug_name[R]
status[R]

Public Class Methods

new(pattern, server=nil) click to toggle source
# File lib/slugforge/models/host.rb, line 8
def initialize(pattern, server=nil)
  @pattern = pattern
  @server  = server
  @deploy_results  = []
  @timeline = []
  @start_time = Time.now
  @actions = []
  self
end

Public Instance Methods

add_action(action) click to toggle source
# File lib/slugforge/models/host.rb, line 76
def add_action(action)
  @actions << action
end
complete?() click to toggle source
# File lib/slugforge/models/host.rb, line 64
def complete?
  success? || failed?
end
deploy(slug_name, logger, opts) click to toggle source
# File lib/slugforge/models/host.rb, line 109
def deploy(slug_name, logger, opts)
  begin
    record_event :started
    if opts[:pretend]
      logger.log "not actually #{effective_action} slug (#{name})", {:color => :yellow, :status => :pretend}
    else
      logger.say_status :deploy, "#{effective_action} to host #{name} as #{username(opts)}", :green
      Net::SSH.start(ssh_host, username(opts), ssh_opts(opts)) do |ssh|
        host_slug = detect_slug(ssh, slug_name, logger) unless opts[:force]
        host_slug ||= copy_slug(ssh, slug_name, logger, opts)
        explode_slug(ssh, host_slug, logger, opts) if stage?
        install_slug(ssh, host_slug, logger, opts) if install?
      end
    end
    record_event :deployed
  rescue => e
    record_event :failed
    message = "#{e.class.to_s}: #{e.to_s}"
    logger.log "#{message} (#{ip}: #{name})", {:color => :red, :status => :fail}
    @deploy_results << {:output => message}
  end
  logger.say_status :deploy, "#{effective_action} complete for host: #{name}", success? ? :green : :red
  @deploy_results
end
effective_action() click to toggle source
# File lib/slugforge/models/host.rb, line 96
def effective_action
  return "installing" if @actions.include?(:install)
  "staging"
end
elapsed_time() click to toggle source
# File lib/slugforge/models/host.rb, line 51
def elapsed_time
  Time.at(Time.now - @start_time).strftime('%M:%S')
end
failed?() click to toggle source
# File lib/slugforge/models/host.rb, line 72
def failed?
  @status == :failed?
end
has_action?(action) click to toggle source
# File lib/slugforge/models/host.rb, line 80
def has_action?(action)
  @actions.include?(action)
end
id() click to toggle source
# File lib/slugforge/models/host.rb, line 30
def id
  nil
end
install?() click to toggle source
# File lib/slugforge/models/host.rb, line 92
def install?
  @actions.include?(:install)
end
ip() click to toggle source
# File lib/slugforge/models/host.rb, line 22
def ip
  @pattern
end
is_autoscaled?() click to toggle source
# File lib/slugforge/models/host.rb, line 34
def is_autoscaled?
  false
end
name() click to toggle source
# File lib/slugforge/models/host.rb, line 18
def name
  "name:#{@pattern}"
end
output() click to toggle source
# File lib/slugforge/models/host.rb, line 105
def output
  @deploy_results.map { |result| result[:output] unless result[:exit_code] == 0 }.compact
end
record_event(status) click to toggle source
# File lib/slugforge/models/host.rb, line 55
def record_event(status)
  @status = status
  @timeline << {:status => status, :elapsed_time => elapsed_time}
end
remove_action(action) click to toggle source
# File lib/slugforge/models/host.rb, line 84
def remove_action(action)
  @actions.delete(action)
end
ssh_host() click to toggle source
# File lib/slugforge/models/host.rb, line 26
def ssh_host
  @pattern
end
stage?() click to toggle source
# File lib/slugforge/models/host.rb, line 88
def stage?
  @actions.include?(:stage)
end
success?() click to toggle source
# File lib/slugforge/models/host.rb, line 68
def success?
  [:deployed, :terminated].include?(@status) && output.empty?
end
terminated?() click to toggle source
# File lib/slugforge/models/host.rb, line 101
def terminated?
  @status == :terminated?
end
timeline() click to toggle source
# File lib/slugforge/models/host.rb, line 60
def timeline
  @timeline.map { |event| "#{event[:status]} @ #{event[:elapsed_time]}" }.join ', '
end
to_status() click to toggle source
# File lib/slugforge/models/host.rb, line 38
def to_status
  {
    :name       => name,
    :ip         => ip,
    :pattern    => @pattern,
    :slug_name  => @slug_name,
    :status     => @status.to_s,
    :output     => @deploy_results,
    :start_time => @start_time,
    :timeline   => timeline,
  }
end

Private Instance Methods

copy_slug(ssh, slug_name, logger, opts) click to toggle source
# File lib/slugforge/models/host.rb, line 155
def copy_slug(ssh, slug_name, logger, opts)
  slug_name_with_path = "/mnt/#{slug_name}"
  case opts[:copy_type]
  when :ssh
    logger.log "interactive mode enabled (be sure to set slug_name)", {:color => :yellow, :status => :copy, :log_level => :verbose}
    binding.pry
  when :scp
    logger.log "copying slug to host via SCP (#{name})", {:color => :green, :status => :copy, :log_level => :verbose}
    scp_upload ip, username(opts), opts[:filename], "#{slug_name}", logger, :ssh => ssh_opts
    logger.log "moving slug from ~ to /mnt as root", {:color => :green, :status => :copy, :log_level => :verbose}
    @deploy_results << ssh_command(ssh, "sudo mv #{slug_name} #{slug_name_with_path}", logger)
  else # AWS S3 command line by default
    logger.log "copying slug to host via aws s3 command (#{name})", {:color => :green, :status => :copy, :log_level => :verbose}
    @deploy_results << ssh_command(ssh, "sudo -- sh -c 'export AWS_ACCESS_KEY_ID=#{opts[:aws_session][:aws_access_key_id]}; export AWS_SECRET_ACCESS_KEY=#{opts[:aws_session][:aws_secret_access_key]}; export AWS_SECURITY_TOKEN=#{opts[:aws_session][:aws_session_token]}; export AWS_DEFAULT_REGION=#{opts[:aws_session][:aws_region]}; aws s3 cp #{opts[:s3_url]} #{slug_name_with_path}'; echo \"SSH_COMMAND_EXIT_CODE=$?\"", logger)
  end
  record_event :copied
  slug_name_with_path
end
detect_slug(ssh, slug_name, logger) click to toggle source
# File lib/slugforge/models/host.rb, line 144
def detect_slug(ssh, slug_name, logger)
  found_path = ['/tmp', '/mnt'].select do |path|
    file_count(ssh, path, slug_name) > 0
  end.compact
  return nil if found_path.empty?
  slug_name_with_path = "#{found_path.first}/#{slug_name}"
  logger.log "found existing slug (#{slug_name_with_path}) on host (#{name}); use --force to overwrite slug", {:color => :yellow, :status => :detect, :log_level => :vervose}
  record_event :detected
  slug_name_with_path
end
explode_slug(ssh, slug_name_with_path, logger, opts) click to toggle source
# File lib/slugforge/models/host.rb, line 178
def explode_slug(ssh, slug_name_with_path, logger, opts)
  logger.log "exploding package as root #{"for user " + opts[:owner] if opts[:owner]}", {:color => :green, :status => :stage, :log_level => :verbose}
  @deploy_results << ssh_command(ssh, slug_install_command(slug_name_with_path, opts[:deploy_dir], {:unpack => true, :owner => opts[:owner], :env => opts[:env]}), logger)
  @slug_name = slug_name
end
file_count(ssh, path, file) click to toggle source
# File lib/slugforge/models/host.rb, line 191
def file_count(ssh, path, file)
  ssh.exec!("find #{path} -maxdepth 1 -name '#{file}' -type f -size +0 | wc -l").to_i
end
install_slug(ssh, slug_name_with_path, logger, opts) click to toggle source
# File lib/slugforge/models/host.rb, line 184
def install_slug(ssh, slug_name_with_path, logger, opts)
  logger.log "installing package as root #{"for user " + opts[:owner] if opts[:owner]}", {:color => :green, :status => :install, :log_level => :verbose}
  @deploy_results << ssh_command(ssh, slug_install_command(slug_name_with_path, opts[:deploy_dir], {:owner => opts[:owner], :env => opts[:env], :force => opts[:force]}), logger)
  @slug_name = slug_name
  record_event :installed
end
scp_upload(host, user, source, dest, logger, opts) click to toggle source
# File lib/slugforge/models/host.rb, line 195
def scp_upload(host, user, source, dest, logger, opts)
  logger.log "SCP: #{source} to #{host}:#{dest}"
  Net::SCP.upload!(host, user, source, dest, opts) do | ch, name, sent, total |
    logger.log "\r#{name}: #{(sent * 100.0 / total).to_i}% "
  end
  logger.log
end
slug_install_command(slug_name_with_path, deploy_dir, opts = {}) click to toggle source
# File lib/slugforge/models/host.rb, line 217
def slug_install_command(slug_name_with_path, deploy_dir, opts = {})
  [ opts[:prefix],
    "TERM=dumb sudo bash -l -c 'date >> /var/log/slug_deploy.log ; ",
    "chmod +x #{slug_name_with_path} ",
    "&& #{opts[:env]} #{slug_name_with_path} ",
    '-y ', #always clobber existing installs
    "-i #{deploy_dir} ",
    opts[:owner] ? "-o #{opts[:owner]} " : '',
    opts[:force] ? '-f ' : '',
    opts[:unpack] ? '-u ' : '',
    '-v ', #verbose
    '| tee -a /var/log/slug_deploy.log ',
    "; echo \"SSH_COMMAND_EXIT_CODE=$?\"'"
  ].join('')
end
ssh_command(ssh, command, logger) click to toggle source
# File lib/slugforge/models/host.rb, line 203
def ssh_command(ssh, command, logger)
  output = ssh.exec!("#{command}")
  exit_code_matches = /^SSH_COMMAND_EXIT_CODE=(\d+)$/.match(output)
  exit_code = exit_code_matches ? exit_code_matches[1].to_i : 0
  logger_opts = if exit_code == 0
                  {:color => :green, :log_level => :verbose}
                else
                  {:color => :red}
                end.merge({:status => :command})
  logger.log "#{command}", logger_opts
  logger.log "Output:\n#{output}", logger_opts
  {:exit_code => exit_code, :output => output, :command => command, :username => ssh.options[:user]}
end
ssh_opts(opts = {}) click to toggle source
# File lib/slugforge/models/host.rb, line 135
def ssh_opts(opts = {})
  ssh_opts = { :forward_agent => true }
  if opts[:identity]
    ssh_opts[:key_data] = File.read(opts[:identity])
    ssh_opts[:keys_only] = true
  end
  ssh_opts
end
username(opts) click to toggle source
# File lib/slugforge/models/host.rb, line 174
def username(opts)
  opts[:username] || Net::SSH.configuration_for(ip)[:user] || `whoami`.chomp
end