lib/jamie.rb in jamie-0.1.0.beta2 vs lib/jamie.rb in jamie-0.1.0.beta3

- old
+ new

@@ -56,15 +56,24 @@ def crashes? ! crashes.empty? end def default_logger - env_log = ENV['JAMIE_LOG'] && ENV['JAMIE_LOG'].downcase.to_sym - env_log = Util.to_logger_level(env_log) unless env_log.nil? - Logger.new(:stdout => STDOUT, :level => env_log) end + + def default_file_logger + logfile = File.expand_path(File.join(".jamie", "logs", "jamie.log")) + Logger.new(:stdout => STDOUT, :logdev => logfile, :level => env_log) + end + + private + + def env_log + level = ENV['JAMIE_LOG'] && ENV['JAMIE_LOG'].downcase.to_sym + level = Util.to_logger_level(level) unless level.nil? + end end module Error ; end # Base exception class from which all Jamie exceptions derive. This class @@ -254,11 +263,10 @@ :driver => driver, :jr => Jr.new(suite.name), :logger => new_instance_logger(index) } - FileUtils.mkdir_p(log_root) new_instance_supervised_or_not(actor_name, opts) end def new_instance_supervised_or_not(actor_name, opts) if supervised @@ -387,15 +395,17 @@ # @author Fletcher Nichol <fnichol@nichol.ca> class Logger include ::Logger::Severity + attr_reader :logdev + def initialize(options = {}) color = options[:color] || :bright_white @loggers = [] - @loggers << logdev_logger(options[:logdev]) if options[:logdev] + @loggers << @logdev = logdev_logger(options[:logdev]) if options[:logdev] @loggers << stdout_logger(options[:stdout], color) if options[:stdout] @loggers << stdout_logger(STDOUT, color) if @loggers.empty? self.progname = options[:progname] || "Jamie" self.level = options[:level] || default_log_level @@ -431,15 +441,16 @@ end logger end def logdev_logger(filepath_or_logdev) - LogdevLogger.new(logdev(filepath_or_logdev)) + LogdevLogger.new(resolve_logdev(filepath_or_logdev)) end - def logdev(filepath_or_logdev) + def resolve_logdev(filepath_or_logdev) if filepath_or_logdev.is_a? String + FileUtils.mkdir_p(File.dirname(filepath_or_logdev)) file = File.open(File.expand_path(filepath_or_logdev), "ab") file.sync = true file else filepath_or_logdev @@ -752,11 +763,11 @@ destroy banner "Testing #{to_str}" verify destroy if destroy_mode == :passing end - info "Finished testing #{to_str} (#{elapsed.real} seconds)." + info "Finished testing #{to_str} #{Util.duration(elapsed.real)}." Actor.current ensure destroy if destroy_mode == :always end @@ -825,11 +836,11 @@ def perform_action(verb, output_verb) banner "#{output_verb} #{to_str}" elapsed = action(verb) { |state| driver.public_send(verb, state) } info("Finished #{output_verb.downcase} #{to_str}" + - " (#{elapsed.real} seconds).") + " #{Util.duration(elapsed.real)}.") yield if block_given? Actor.current end def action(what, &block) @@ -882,10 +893,15 @@ File.expand_path(File.join( driver[:jamie_root], ".jamie", "#{name}.yml" )) end + def banner(*args) + Jamie.logger.logdev && Jamie.logger.logdev.banner(*args) + super + end + # The simplest finite state machine pseudo-implementation needed to manage # an Instance. # # @author Fletcher Nichol <fnichol@nichol.ca> class FSM @@ -1104,10 +1120,16 @@ obj.inject([]) { |a, v| a << symbolized_hash(v) ; a } else obj end end + + def self.duration(total) + minutes = (total / 60).to_i + seconds = (total - (minutes * 60)) + "(%dm%.2fs)" % [ minutes, seconds ] + end end # Mixin that wraps a command shell out invocation, providing a #run_command # method. # @@ -1130,11 +1152,11 @@ subject = "[#{log_subject} command]" info("#{subject} BEGIN (#{display_cmd(cmd)})") sh = Mixlib::ShellOut.new(cmd, :live_stream => logger, :timeout => 60000) sh.run_command - info("#{subject} END (#{sh.execution_time} seconds)") + info("#{subject} END #{Util.duration(sh.execution_time)}") sh.error! rescue Mixlib::ShellOut::ShellCommandFailed => ex raise ShellCommandFailed, ex.message rescue Exception => error error.extend(Jamie::Error) @@ -1162,14 +1184,14 @@ require "jamie/driver/#{plugin}" str_const = Util.to_camel_case(plugin) klass = self.const_get(str_const) klass.new(config) + rescue UserError + raise rescue LoadError raise ClientError, "Could not require '#{plugin}' plugin from load path" - rescue NameError - raise ClientError, "No class 'Jamie::Driver::#{str_const}' could be found" rescue raise ClientError, "Failed to create a driver for '#{plugin}' plugin" end # Base class for a driver. A driver is responsible for carrying out the @@ -1191,10 +1213,13 @@ def initialize(config = {}) @config = config self.class.defaults.each do |attr, value| @config[attr] = value unless @config[attr] end + Array(self.class.validations).each do |tuple| + tuple.last.call(tuple.first, config[tuple.first]) + end end # Provides hash-like access to configuration keys. # # @param attr [Object] configuration key @@ -1275,10 +1300,27 @@ def self.default_config(attr, value) defaults[attr] = value end + def self.validations + @validations + end + + def self.required_config(attr, &block) + @validations = [] if @validations.nil? + if ! block_given? + klass = self + block = lambda do |attr, value| + if value.nil? || value.to_s.empty? + raise UserError, "#{klass}#config[:#{attr}] cannot be blank" + end + end + end + @validations << [ attr, block ] + end + def self.no_parallel_for(*methods) Array(methods).each do |meth| if ! ACTION_METHODS.include?(meth) raise ClientError, "##{meth} is not a valid no_parallel_for method" end @@ -1355,13 +1397,17 @@ def chef_home "/tmp/jamie-chef-solo".freeze end def install_omnibus(ssh_args) + flag = config[:require_chef_omnibus] + version = flag.is_a?(String) ? "-s -- -v #{flag}" : "" + ssh(ssh_args, <<-INSTALL.gsub(/^ {10}/, '')) if [ ! -d "/opt/chef" ] ; then - curl -sSL https://www.opscode.com/chef/install.sh | sudo bash + curl -sSL https://www.opscode.com/chef/install.sh \ + | sudo bash #{version} fi INSTALL end def prepare_chef_home(ssh_args) @@ -1606,9 +1652,9 @@ # Setup a collection of instance crash exceptions for error reporting Jamie.crashes = [] Celluloid.exception_handler do |exception| Jamie.logger.debug("An instance crashed because of #{exception.inspect}") - Jamie.crashes << exception + Jamie.mutex.synchronize { Jamie.crashes << exception } end Jamie.mutex = Mutex.new