class Loom::Runner

TODO: Make Loom::Runner a module and move this to Loom::Runner::Core. Loom::RunnerModule is set up as a temporary namespace until that happens.

Constants

FailFastExecutionError
PatternExecutionError

Public Class Methods

new(loom_config, pattern_slugs=[], other_facts={}) click to toggle source
# File lib/loom/runner.rb, line 12
def initialize(loom_config, pattern_slugs=[], other_facts={})
  @pattern_slugs = pattern_slugs
  @loom_config = loom_config
  @other_facts = other_facts

  @run_failures = []
  @result_reports = []

  # these are initialized in +load+
  @inventory_list = nil
  @active_hosts = nil
  @pattern_refs = nil
  @mod_loader = nil

  Loom.log.debug1(self) do
    "initialized runner with config => #{loom_config.dump}"
  end

  @caught_sig_int = false
end

Public Instance Methods

run(dry_run) click to toggle source
# File lib/loom/runner.rb, line 33
def run(dry_run)
  install_signal_traps

  begin
    load

    if @pattern_refs.empty?
      Loom.log.warn "no patterns given, there's no work to do"
      return
    end
    if @active_hosts.empty?
      Loom.log.warn "no hosts in the active inventory"
      return
    end

    hostnames = @active_hosts.map(&:hostname)
    Loom.log.info do
      "executing patterns #{@pattern_slugs} across hosts #{hostnames}"
    end

    run_internal dry_run

    unless @run_failures.empty?
      raise PatternExecutionError, @run_failures
    end
  rescue Loom::Trap::SignalExit => e
    Loom.log.error "exiting on signal  => #{e.signal}"
    # Exit with the signal code or 40 for unknown Signal
    code = Signal.list[e.signal] || 40
    exit code
  rescue PatternExecutionError => e
    num_patterns_failed = @run_failures.size
    Loom.log.error "error executing #{num_patterns_failed} patterns => #{e}, run with -d for more"
    Loom.log.debug e.backtrace.join "\n\t"
    # TODO: I think the max return code is 255. Cap this if so.
    exit 100 + num_patterns_failed
  rescue SSHKit::Runner::ExecuteError => e
    Loom.log.error "wrapped SSHKit::Runner::ExecuteError, run with -d for more"
    Loom.log.error e.cause
    Loom.log.debug e.cause.backtrace.join "\n\t"
    Loom.log.debug1(self) { "Original error:" }
    Loom.log.debug1(self) { e.inspect }
    Loom.log.debug1(self) { e.backtrace.join "\n\t" }
    exit 97
  rescue Loom::LoomError => e
    Loom.log.error "Loom::LoomError => #{e.inspect}, run with -d for more"
    backtrace = e.cause.nil? ? e.backtrace : e.cause.backtrace
    Loom.log.debug backtrace.join "\n\t"
    Loom.log.debug backtrace.join "\n\t"
    exit 98
  rescue => e
    # TODO: Make error/exception logging look like this everywhere
    Loom.log.fatal "fatal error =>\n#{e.inspect}\n\t#{e.backtrace.join("\n\t\t")}"

    loom_files = @loom_config.files.loom_files
    loom_errors = e.backtrace.select { |line| line =~ /(#{loom_files.join("|")})/ }
    Loom.log.error "Loom file errors: \n\t" + loom_errors.join("\n\t")
    exit 99
  end
end

Private Instance Methods

execute_pattern(pattern_ref, shell, fact_set) click to toggle source
# File lib/loom/runner.rb, line 196
def execute_pattern(pattern_ref, shell, fact_set)
  shell_session = shell.session
  hostname = fact_set.hostname
  result_reporter = Loom::Pattern::ResultReporter.new(
    @loom_config, pattern_ref.slug, hostname, shell_session)

  # TODO: This is a crappy mechanism for tracking errors - hency my crappy
  # error reporting :( - there should be an exception thrown inside of Shell
  # when a command fails and pattern execution should stop. All errors
  # should come from exceptions. This needs to be thought about wrt to the
  # defined `failure_strategy`
  # @see the TODO at Loom::Shell::Core+test+
  run_failure = []
  begin
    pattern_ref.call(shell.shell_api, fact_set)
  rescue Loom::ExecutionError => e
    Loom.log.debug e.backtrace.join "\n\t"
    run_failure << e
  ensure
    # TODO[P0]: this prints out [Result: OK] even if an exception is
    # raised... this is really annoying.
    result_reporter.write_report

    # TODO: this is not the correct error condition.
    unless shell_session.success?
      run_failure << result_reporter.failure_summary
      handle_host_failure_strategy hostname, result_reporter.failure_summary
    end
    @result_reports << result_reporter
    @run_failures << run_failure unless run_failure.empty?
  end
  run_failure
end
handle_host_failure_strategy(hostname, failure_summary=nil) click to toggle source
# File lib/loom/runner.rb, line 235
def handle_host_failure_strategy(hostname, failure_summary=nil)
  failure_strategy = @loom_config.run_failure_strategy.to_sym

  case failure_strategy
  when :exclude_host
    Loom.log.warn "disabling host per :run_failure_strategy => #{failure_strategy}"
    @inventory_list.disable hostname
  when :fail_fast
    Loom.log.error "erroring out of failed pattern per :run_failure_strategy"
    raise FailFastExecutionError, failure_summary
  when :cowboy
    Loom.log.warn "continuing on past failed pattern per :run_failure_strategy"
  else
    raise ConfigError, "unknown failure_strategy: #{failure_stratgy}"
  end
end
install_signal_traps() click to toggle source
# File lib/loom/runner.rb, line 96
def install_signal_traps
  signal_handler = Loom::Trap::Handler.new do |sig, count|
    case sig
    when Loom::Trap::Sig::INT
      @caught_sig_int = true
      if count == 1
        puts "Caught #{sig}, exiting after current pattern completion"
        puts "Ctrl-C again to exit immediately"
      else
        puts "Caught #{sig}"
        raise Loom::Trap::SignalExit.new sig
      end
    else
      puts "Caught unhandled signal #{sig}"
      raise Loom::Trap::SignalExit.new sig
    end
  end
  Loom::Trap.install(Loom::Trap::Sig::INT, signal_handler)
end
load() click to toggle source
# File lib/loom/runner.rb, line 116
def load
  @inventory_list =
    Loom::Inventory::InventoryList.active_inventory @loom_config
  @active_hosts = @inventory_list.hosts

  # TODO: the naming inconsistency between Pattern::Loader and
  # Mods::ModLoader bothers me... :(
  pattern_loader = Loom::Pattern::Loader.load @loom_config
  @pattern_refs = pattern_loader.patterns @pattern_slugs

  @loom_config[:loomfile_autoloads].each do |path|
    Loom.log.debug { "requiring config[:loomfile_autoloads]: #{path}" }
    require path
  end
  @mod_loader = Loom::Mods::ModLoader.new @loom_config
end
log_token(pattern_ref, hostname) click to toggle source
# File lib/loom/runner.rb, line 230
def log_token(pattern_ref, hostname)
  slug = pattern_ref.slug
  "[#{hostname} => #{slug}]"
end
run_execution_phase(pattern_ref, host_spec, sshkit_backend, dry_run) click to toggle source
# File lib/loom/runner.rb, line 170
def run_execution_phase(pattern_ref, host_spec, sshkit_backend, dry_run)
  log_token = log_token(pattern_ref, host_spec.hostname)
  Loom.log.debug "collecting facts for => #{log_token}"
  # Collects facts for each pattern run on each host, this way if one
  # pattern run updates would be facts, the next pattern will see the
  # new fact.
  fact_shell = Loom::Shell.create @mod_loader, sshkit_backend, dry_run
  fact_set = Loom::Facts.fact_set(host_spec, fact_shell, @loom_config)
    .merge @other_facts

  Loom.log.info "running pattern => #{log_token}"
  # Creates a new shell for executing the pattern, so as not to be
  # tainted by the fact finding shell above.
  pattern_shell = Loom::Shell.create @mod_loader, sshkit_backend, dry_run

  Loom.log.warn "dry run only => #{log_token}" if dry_run
  failures = execute_pattern pattern_ref, pattern_shell, fact_set

  if failures.empty?
    Loom.log.debug "success on => #{log_token}"
  else
    Loom.log.error "failures on => #{log_token}:\n\t%s" % (
      failures.join("\n\t"))
  end
end
run_internal(dry_run) click to toggle source
# File lib/loom/runner.rb, line 133
    def run_internal(dry_run)
      # TODO: fix the bindings in the block below so we don't need
      # this alias
      inventory_list = @inventory_list

      on_host @active_hosts do |sshkit_backend, host_spec|
        hostname = host_spec.hostname

        begin
          @pattern_refs.each do |pattern_ref|
            if @caught_sig_int
              Loom.log.warn "caught SIGINT, skipping #{log_token(pattern_ref, hostname)}"
              next
            elsif inventory_list.disabled? hostname
              Loom.log.warn "host disabled due to previous failure, " +
                            "skipping: #{log_token(pattern_ref, hostname)}"
              next
            end

            # wip-patternbuilder is adding this feature
#            run_analysis_phase(pattern_ref, host_spec, sshkit_backend)
            run_execution_phase(pattern_ref, host_spec, sshkit_backend, dry_run)
          end
        rescue IOError => e
          # TODO: Try to patch SSHKit for a more specific error for unexpected
          # SSH disconnections
          Loom.log.error "unexpected SSH disconnect => #{hostname}"
          Loom.log.debug e
          handle_host_failure_strategy hostname, e.message
        rescue Errno::ECONNREFUSED => e
          Loom.log.error "unable to connect to host => #{hostname}"
          Loom.log.debug e
          handle_host_failure_strategy hostname, e.message
        end
      end
    end