class EY::CLI

Constants

OPT_TO_ROLES

Public Class Methods

start(given_args=ARGV, config={}) click to toggle source
Calls superclass method
# File lib/engineyard/cli.rb, line 20
def self.start(given_args=ARGV, config={})
  Thor::Base.shell = EY::CLI::UI
  ui = EY::CLI::UI.new
  super(given_args, {shell: ui}.merge(config))
rescue Thor::Error, EY::Error, EY::CloudClient::Error => e
  ui.print_exception(e)
  raise
rescue Interrupt => e
  puts
  ui.print_exception(e)
  ui.say("Quitting...")
  raise
rescue SystemExit, Errno::EPIPE
  # don't print a message for safe exits
  raise
rescue Exception => e
  ui.print_exception(e)
  raise
end

Public Instance Methods

console() click to toggle source
# File lib/engineyard/cli.rb, line 539
def console
  app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
  instances = filter_servers(app_env.environment, options, default: {app_master: true})
  user = app_env.environment.username
  cmd = "cd /data/#{app_env.app.name}/current && current_user=#{api.current_user.name} bundle exec rails console"
  cmd = Escape.shell_command(['bash','-lc',cmd])

  ssh_cmd = ["ssh"]
  ssh_cmd += ["-t"]

  trap(:INT) { abort "Aborting..." }

  exits = []
  instances.each do |instance|
    host = instance.public_hostname
    name = instance.name ? "#{instance.role} (#{instance.name})" : instance.role
    ui.info "\nConnecting to #{name} #{host}..."
    unless cmd
      ui.info "Ctrl + C to abort"
      sleep 1.3
    end
    sshcmd = Escape.shell_command((ssh_cmd + ["#{user}@#{host}"] + [cmd]).compact)
    ui.debug "$ #{sshcmd}"
    system sshcmd
    exits << $?.exitstatus
  end

  exit exits.detect {|status| status != 0 } || 0
end
deploy() click to toggle source
# File lib/engineyard/cli.rb, line 123
    def deploy
      app_env = fetch_app_environment(options[:app], options[:environment], options[:account])

      env_config    = config.environment_config(app_env.environment_name)
      deploy_config = EY::DeployConfig.new(options, env_config, repo, ui)

      deployment = app_env.new_deployment({
        ref:                deploy_config.ref,
        migrate:            deploy_config.migrate,
        migrate_command:    deploy_config.migrate_command,
        extra_config:       deploy_config.extra_config,
        serverside_version: serverside_version,
      })

      runner = serverside_runner(app_env, deploy_config.verbose, deployment.serverside_version, options[:ignore_bad_master])

      out = EY::CLI::UI::Tee.new(ui.out, deployment.output)
      err = EY::CLI::UI::Tee.new(ui.err, deployment.output)

      ui.info  "Beginning deploy...", :green
      begin
        deployment.start
      rescue
        ui.error "Error encountered before deploy. Deploy not started."
        raise
      end

      begin
        ui.show_deployment(deployment)
        out << "Deploy initiated.\n"

        runner.deploy do |args|
          args.config  = deployment.config          if deployment.config
          if deployment.migrate
            args.migrate = deployment.migrate_command
          else
            args.migrate = false
          end
          args.ref     = deployment.resolved_ref
        end
        deployment.successful = runner.call(out, err)
      rescue Interrupt
        Signal.trap(:INT) { # The fingers you have used to dial are too fat...
          ui.info "\nRun `ey timeout-deploy` to mark an unfinished deployment as failed."
          exit 1
        }
        err << "Interrupted. Deployment halted.\n"
        ui.warn <<-WARN
Recording interruption of this unfinished deployment in Engine Yard Cloud...

WARNING: Interrupting again may prevent Engine Yard Cloud from recording this
         failed deployment. Unfinished deployments can block future deploys.
        WARN
        raise
      rescue StandardError => e
        deployment.err << "Error encountered during deploy.\n#{e.class} #{e}\n"
        ui.print_exception(e)
        raise
      ensure
        ui.info "Saving log... ", :green
        deployment.finished

        if deployment.successful?
          ui.info "Successful deployment recorded on Engine Yard Cloud.", :green
          ui.info "Run `ey launch` to open the application in a browser."
        else
          ui.info "Failed deployment recorded on Engine Yard Cloud", :green
          raise EY::Error, "Deploy failed"
        end
      end
    end
environments() click to toggle source
# File lib/engineyard/cli.rb, line 276
def environments
  if options[:all] && options[:simple]
    ui.print_simple_envs api.environments
  elsif options[:all]
    ui.print_envs api.apps
  else
    remotes = nil
    if options[:app] == ''
      repo.fail_on_no_remotes!
      remotes = repo.remotes
    end

    resolver = api.resolve_app_environments({
      account_name:     options[:account],
      app_name:         options[:app],
      environment_name: options[:environment],
      remotes:          remotes,
    })

    resolver.no_matches do |errors|
      messages = errors
      messages << "Use #{self.class.send(:banner_base)} environments --all to see all environments."
      raise EY::NoMatchesError.new(messages.join("\n"))
    end

    apps = resolver.matches.map { |app_env| app_env.app }.uniq

    if options[:simple]
      if apps.size > 1
        message = "# This app matches multiple Applications in Engine Yard Cloud:\n"
        apps.each { |app| message << "#\t#{app.name}\n" }
        message << "# The following environments contain those applications:\n\n"
        ui.warn(message)
      end
      ui.print_simple_envs(apps.map{ |app| app.environments }.flatten)
    else
      ui.print_envs(apps, config.default_environment)
    end
  end
end
filter_servers(environment, cli_opts, filter_opts) click to toggle source
# File lib/engineyard/cli.rb, line 649
def filter_servers(environment, cli_opts, filter_opts)
  if (cli_opts.keys.map(&:to_sym) & OPT_TO_ROLES.keys).any?
    options = cli_opts.dup
  else
    options = filter_opts[:default].dup
  end

  options.keep_if {|k,v| OPT_TO_ROLES.has_key?(k.to_sym) }

  if options[:all]
    instances = environment.instances
  else
    roles = {}
    options.each do |cli_opt,cli_val|
      if cli_val && OPT_TO_ROLES.has_key?(cli_opt.to_sym)
        OPT_TO_ROLES[cli_opt.to_sym].each do |role|
          roles[role] = cli_val # val is true or an array of strings
        end
      end
    end
    instances = environment.select_instances(roles)
  end

  if instances.empty?
    raise NoInstancesError.new(environment.name)
  end

  return instances
end
help(*cmds) click to toggle source
Calls superclass method Thor::help
# File lib/engineyard/cli.rb, line 722
def help(*cmds)
  if cmds.empty?
    base = self.class.send(:banner_base)
    list = self.class.printable_tasks

    ui.say "Usage:"
    ui.say "  #{base} [--help] [--version] COMMAND [ARGS]"
    ui.say

    ui.say "Deploy commands:"
    deploy_cmds = %w(deploy environments logs rebuild rollback status)
    deploy_cmds.map! do |name|
      list.find{|task| task[0] =~ /^#{base} #{name}/ }
    end
    list -= deploy_cmds

    ui.print_help(deploy_cmds)
    ui.say

    self.class.subcommands.each do |name|
      klass = self.class.subcommand_class_for(name)
      list.reject!{|cmd| cmd[0] =~ /^#{base} #{name}/}
      ui.say "#{name.capitalize} commands:"
      tasks = klass.printable_tasks.reject{|t| t[0] =~ /help$/ }
      ui.print_help(tasks)
      ui.say
    end

    %w(help version).each{|n| list.reject!{|c| c[0] =~ /^#{base} #{n}/ } }
    if list.any?
      ui.say "Other commands:"
      ui.print_help(list)
      ui.say
    end

    self.class.send(:class_options_help, shell)
    ui.say "See '#{base} help COMMAND' for more information on a specific command."
  elsif klass = self.class.subcommand_class_for(cmds.first)
    klass.new.help(*cmds[1..-1])
  else
    super
  end
end
init() click to toggle source
# File lib/engineyard/cli.rb, line 58
    def init
      unless EY::Repo.exist?
        raise EY::Error, "Working directory is not a repository. Aborting."
      end

      path = Pathname.new(options['path'] || EY::Config.pathname_for_write)

      existing = {}
      if path.exist?
        ui.warn "Reinitializing existing file: #{path}"
        existing = EY::Config.load_config
      end

      template = EY::Templates::EyYml.new(existing)
      template.write(path)

      ui.info <<-GIT

Configuration generated: #{path}
Go look at it, then add it to your repository!

\tgit add #{path}
\tgit commit -m "Add generated #{path} from ey init"

      GIT
    end
launch() click to toggle source
# File lib/engineyard/cli.rb, line 776
def launch
  app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
  Launchy.open(app_env.uri)
end
login() click to toggle source
# File lib/engineyard/cli.rb, line 800
def login
  whoami
end
logout() click to toggle source
# File lib/engineyard/cli.rb, line 805
def logout
  eyrc = EYRC.load
  if eyrc.delete_api_token
    ui.info "API token removed: #{eyrc.path}"
    ui.info "Run any other command to login again."
  else
    ui.info "Already logged out. Run any other command to login again."
  end
end
logs() click to toggle source
# File lib/engineyard/cli.rb, line 692
def logs
  environment = fetch_environment(options[:environment], options[:account])
  environment.logs.each do |log|
    ui.say "Instance: #{log.instance_name}"

    if log.main
      ui.say "Main logs for #{environment.name}:", :green
      ui.say  log.main
    end

    if log.custom
      ui.say "Custom logs for #{environment.name}:", :green
      ui.say  log.custom
    end
  end
end
rebuild() click to toggle source
# File lib/engineyard/cli.rb, line 391
def rebuild
  environment = fetch_environment(options[:environment], options[:account])
  ui.info "Updating instances on #{environment.hierarchy_name}"
  environment.rebuild
end
rollback() click to toggle source
# File lib/engineyard/cli.rb, line 417
def rollback
  app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
  env_config    = config.environment_config(app_env.environment_name)
  deploy_config = EY::DeployConfig.new(options, env_config, repo, ui)

  ui.info "Rolling back #{app_env.hierarchy_name}"

  runner = serverside_runner(app_env, deploy_config.verbose)
  runner.rollback do |args|
    args.config = {'deployed_by' => api.current_user.name, 'input_ref' => 'N/A'}.merge(deploy_config.extra_config || {})
  end

  if runner.call(ui.out, ui.err)
    ui.info "Rollback complete"
  else
    raise EY::Error, "Rollback failed"
  end
end
scp(from_path, to_path) click to toggle source
# File lib/engineyard/cli.rb, line 606
def scp(from_path, to_path)
  environment = fetch_environment(options[:environment], options[:account])
  instances   = filter_servers(environment, options, default: {app_master: true})
  user        = environment.username

  ui.info "Copying '#{from_path}' to '#{to_path}' on #{instances.count} server#{instances.count == 1 ? '' : 's'} serially..."

  # default to `scp FROM_PATH HOST:TO_PATH`
  unless [from_path, to_path].detect { |path| path =~ /HOST:/ }
    to_path = "HOST:#{to_path}"
  end

  exits = []
  instances.each do |instance|
    host = instance.public_hostname
    authority = "#{user}@#{host}:"

    name = instance.name ? "#{instance.role} (#{instance.name})" : instance.role
    ui.info "# #{name} #{host}"

    from = from_path.sub(/^HOST:/, authority)
    to   =   to_path.sub(/^HOST:/, authority)

    cmd  = Escape.shell_command(["scp", from, to])
    ui.debug "$ #{cmd}"
    system cmd
    exits << $?.exitstatus
  end

  exit exits.detect {|status| status != 0 } || 0
end
servers() click to toggle source
# File lib/engineyard/cli.rb, line 351
def servers
  if options[:environment] == '' && options[:account] == ''
    repo.fail_on_no_remotes!
  end

  environment = nil
  ui.mute_if(options[:simple] || options[:host]) do
    environment = fetch_environment(options[:environment], options[:account])
  end

  username = options[:user] && environment.username

  servers = filter_servers(environment, options, default: {all: true})

  if options[:host]
    ui.print_hostnames(servers, username)
  elsif options[:simple]
    ui.print_simple_servers(servers, username)
  else
    ui.print_servers(servers, environment.hierarchy_name, username)
  end
end
ssh(cmd=nil) click to toggle source
# File lib/engineyard/cli.rb, line 476
def ssh(cmd=nil)
  environment = fetch_environment(options[:environment], options[:account])
  instances = filter_servers(environment, options, default: {app_master: true})
  user = environment.username
  ssh_opts = []

  if cmd
    if options[:shell]
      cmd = Escape.shell_command([options[:shell],'-lc',cmd])
    end

    if options[:pty]
      ssh_opts = ["-t"]
    elsif cmd =~ /sudo/
      ui.warn "sudo commands often need a tty to run correctly. Use -t option to spawn a tty."
    end
  else
    if instances.size != 1 && options[:each] == false
      raise NoCommandError.new
    end

    if options[:bind_address]
      ssh_opts = ["-L", options[:bind_address]]
    end
  end

  ssh_cmd = ["ssh"]
  ssh_cmd += ssh_opts

  trap(:INT) { abort "Aborting..." }

  exits = []
  instances.each do |instance|
    host = instance.public_hostname
    name = instance.name ? "#{instance.role} (#{instance.name})" : instance.role
    ui.info "\nConnecting to #{name} #{host}..."
    unless cmd
      ui.info "Ctrl + C to abort"
      sleep 1.3
    end
    sshcmd = Escape.shell_command((ssh_cmd + ["#{user}@#{host}"] + [cmd]).compact)
    ui.debug "$ #{sshcmd}"
    system sshcmd
    exits << $?.exitstatus
  end

  exit exits.detect {|status| status != 0 } || 0
end
status() click to toggle source
# File lib/engineyard/cli.rb, line 247
def status
  app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
  deployment = app_env.last_deployment
  if deployment
    ui.deployment_status(deployment)
  else
    raise EY::Error, "Application #{app_env.app.name} has not been deployed on #{app_env.environment.name}."
  end
end
timeout_deploy() click to toggle source
# File lib/engineyard/cli.rb, line 216
def timeout_deploy
  app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
  deployment = app_env.last_deployment
  if deployment && !deployment.finished?
    begin
      ui.info  "Marking last deployment failed...", :green
      deployment.timeout
      ui.deployment_status(deployment)
    rescue EY::CloudClient::RequestFailed => e
      ui.error "Error encountered attempting to timeout previous deployment."
      raise
    end
  else
    raise EY::Error, "No unfinished deployment was found for #{app_env.hierarchy_name}."
  end
end
version() click to toggle source
# File lib/engineyard/cli.rb, line 716
def version
  ui.say %{engineyard version #{EY::VERSION}}
end
whoami() click to toggle source
# File lib/engineyard/cli.rb, line 782
def whoami
  current_user = api.current_user
  ui.say "#{current_user.name} (#{current_user.email})"
end