jamie 0.1.0.alpha18 → 0.1.0.alpha19

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,33 +27,37 @@ module Jamie
27
27
  # @author Fletcher Nichol <fnichol@nichol.ca>
28
28
  class Dummy < Jamie::Driver::Base
29
29
 
30
- def create(instance, state)
30
+ default_config 'sleep', 0
31
+
32
+ def create(state)
31
33
  state['my_id'] = "#{instance.name}-#{Time.now.to_i}"
32
- report(:create, instance, state)
34
+ report(:create, state)
33
35
  end
34
36
 
35
- def converge(instance, state)
36
- report(:converge, instance, state)
37
+ def converge(state)
38
+ report(:converge, state)
37
39
  end
38
40
 
39
- def setup(instance, state)
40
- report(:setup, instance, state)
41
+ def setup(state)
42
+ report(:setup, state)
41
43
  end
42
44
 
43
- def verify(instance, state)
44
- report(:verify, instance, state)
45
+ def verify(state)
46
+ report(:verify, state)
45
47
  end
46
48
 
47
- def destroy(instance, state)
48
- report(:destroy, instance, state)
49
+ def destroy(state)
50
+ report(:destroy, state)
49
51
  state.delete('my_id')
50
52
  end
51
53
 
52
54
  private
53
55
 
54
- def report(action, instance, state)
55
- puts "[Dummy] Action ##{action} called on " +
56
- "instance=#{instance} with state=#{state}"
56
+ def report(action, state)
57
+ info("[Dummy] Action ##{action} called on " +
58
+ "instance=#{instance} with state=#{state}")
59
+ sleep(config['sleep'].to_f) if config['sleep'].to_f > 0.0
60
+ debug("[Dummy] Action ##{action} completed (#{config['sleep']}s).")
57
61
  end
58
62
  end
59
63
  end
data/lib/jamie/version.rb CHANGED
@@ -18,5 +18,5 @@
18
18
 
19
19
  module Jamie
20
20
 
21
- VERSION = "0.1.0.alpha18"
21
+ VERSION = "0.1.0.alpha19"
22
22
  end
data/lib/jamie.rb CHANGED
@@ -17,11 +17,13 @@
17
17
  # limitations under the License.
18
18
 
19
19
  require 'base64'
20
+ require 'benchmark'
20
21
  require 'delegate'
21
22
  require 'digest'
22
23
  require 'erb'
23
24
  require 'fileutils'
24
25
  require 'json'
26
+ require 'logger'
25
27
  require 'mixlib/shellout'
26
28
  require 'net/https'
27
29
  require 'net/scp'
@@ -36,11 +38,22 @@ require 'jamie/version'
36
38
 
37
39
  module Jamie
38
40
 
39
- # Returns the root path of the Jamie gem source code.
40
- #
41
- # @return [Pathname] root path of gem
42
- def self.source_root
43
- @source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
41
+ class << self
42
+
43
+ attr_accessor :logger
44
+
45
+ # Returns the root path of the Jamie gem source code.
46
+ #
47
+ # @return [Pathname] root path of gem
48
+ def source_root
49
+ @source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
50
+ end
51
+
52
+ def default_logger
53
+ env_log = ENV['JAMIE_LOG'] && ENV['JAMIE_LOG'].downcase.to_sym
54
+
55
+ Logger.new(:console => STDOUT, :level => env_log)
56
+ end
44
57
  end
45
58
 
46
59
  # Base configuration class for Jamie. This class exposes configuration such
@@ -58,11 +71,8 @@ module Jamie
58
71
  # Default path to the Jamie YAML file
59
72
  DEFAULT_YAML_FILE = File.join(Dir.pwd, '.jamie.yml').freeze
60
73
 
61
- # Default log level verbosity
62
- DEFAULT_LOG_LEVEL = :info
63
-
64
74
  # Default driver plugin to use
65
- DEFAULT_DRIVER_PLUGIN = "vagrant".freeze
75
+ DEFAULT_DRIVER_PLUGIN = "dummy".freeze
66
76
 
67
77
  # Default base path which may contain `data_bags/` directories
68
78
  DEFAULT_TEST_BASE_PATH = File.join(Dir.pwd, 'test/integration').freeze
@@ -92,7 +102,7 @@ module Jamie
92
102
  # suite combinations
93
103
  def instances
94
104
  @instances ||= Collection.new(suites.map { |suite|
95
- platforms.map { |platform| Instance.new(suite, platform) }
105
+ platforms.map { |platform| new_instance(suite, platform) }
96
106
  }.flatten)
97
107
  end
98
108
 
@@ -103,7 +113,10 @@ module Jamie
103
113
 
104
114
  # @return [Symbol] log level verbosity
105
115
  def log_level
106
- @log_level ||= DEFAULT_LOG_LEVEL
116
+ @log_level ||= begin
117
+ ENV['JAMIE_LOG'] && ENV['JAMIE_LOG'].downcase.to_sym ||
118
+ Jamie::DEFAULT_LOG_LEVEL
119
+ end
107
120
  end
108
121
 
109
122
  # @return [String] base path that may contain a common `data_bags/`
@@ -160,15 +173,45 @@ module Jamie
160
173
  end
161
174
 
162
175
  def new_platform(hash)
163
- mpc = merge_platform_config(hash)
164
- mpc['driver_config'] ||= Hash.new
165
- mpc['driver_config']['jamie_root'] = File.dirname(yaml_file)
166
- mpc['driver'] = new_driver(mpc['driver_plugin'], mpc['driver_config'])
167
- Platform.new(mpc)
176
+ Platform.new(hash)
177
+ end
178
+
179
+ def new_driver(hash)
180
+ hash['driver_config'] ||= Hash.new
181
+ hash['driver_config']['jamie_root'] = jamie_root
182
+ Driver.for_plugin(hash['driver_plugin'], hash['driver_config'])
168
183
  end
169
184
 
170
- def new_driver(plugin, config)
171
- Driver.for_plugin(plugin, config)
185
+ def new_instance(suite, platform)
186
+ log_root = File.expand_path(File.join(jamie_root, ".jamie", "logs"))
187
+ platform_hash = platform_driver_hash(platform.name)
188
+ driver = new_driver(merge_driver_hash(platform_hash))
189
+ FileUtils.mkdir_p(log_root)
190
+
191
+ Instance.new(
192
+ 'suite' => suite,
193
+ 'platform' => platform,
194
+ 'driver' => driver,
195
+ 'jr' => Jr.new(suite.name),
196
+ 'logger' => new_instance_logger(log_root)
197
+ )
198
+ end
199
+
200
+ def platform_driver_hash(platform_name)
201
+ h = yaml['platforms'].find { |p| p['name'] == platform_name } || Hash.new
202
+
203
+ h.select { |key, value| %w(driver_plugin driver_config).include?(key) }
204
+ end
205
+
206
+ def new_instance_logger(log_root)
207
+ level = Util.to_logger_level(self.log_level)
208
+
209
+ lambda do |name|
210
+ logfile = File.join(log_root, "#{name}.log")
211
+
212
+ Logger.new(:stdout => STDOUT, :logdev => logfile,
213
+ :level => level, :progname => name)
214
+ end
172
215
  end
173
216
 
174
217
  def yaml
@@ -194,8 +237,12 @@ module Jamie
194
237
  end
195
238
  end
196
239
 
197
- def merge_platform_config(platform_config)
198
- default_driver_config.rmerge(common_driver_config.rmerge(platform_config))
240
+ def jamie_root
241
+ File.dirname(yaml_file)
242
+ end
243
+
244
+ def merge_driver_hash(driver_hash)
245
+ default_driver_hash.rmerge(common_driver_hash.rmerge(driver_hash))
199
246
  end
200
247
 
201
248
  def calculate_roles_path(suite_name)
@@ -230,15 +277,143 @@ module Jamie
230
277
  end
231
278
  end
232
279
 
233
- def default_driver_config
234
- { 'driver_plugin' => DEFAULT_DRIVER_PLUGIN }
280
+ def default_driver_hash
281
+ { 'driver_plugin' => DEFAULT_DRIVER_PLUGIN, 'driver_config' => {} }
235
282
  end
236
283
 
237
- def common_driver_config
284
+ def common_driver_hash
238
285
  yaml.select { |key, value| %w(driver_plugin driver_config).include?(key) }
239
286
  end
240
287
  end
241
288
 
289
+ # Default log level verbosity
290
+ DEFAULT_LOG_LEVEL = :info
291
+
292
+ # Logging implementation for Jamie. By default the console/stdout output will
293
+ # be displayed differently than the file log output. Therefor, this class
294
+ # wraps multiple loggers that conform to the stdlib `Logger` class behavior.
295
+ #
296
+ # @author Fletcher Nichol <fnichol@nichol.ca>
297
+ class Logger
298
+
299
+ include ::Logger::Severity
300
+
301
+ def initialize(options = {})
302
+ @loggers = []
303
+ @loggers << logdev_logger(options[:logdev]) if options[:logdev]
304
+ @loggers << stdout_logger(options[:stdout]) if options[:stdout]
305
+ @loggers << stdout_logger(STDOUT) if @loggers.empty?
306
+
307
+ self.progname = options[:progname] || "Jamie"
308
+ self.level = options[:level] || default_log_level
309
+ end
310
+
311
+ %w{ level progname datetime_format debug? info? error? warn? fatal?
312
+ }.each do |meth|
313
+ define_method(meth) do |*args|
314
+ @loggers.first.public_send(meth, *args)
315
+ end
316
+ end
317
+
318
+ %w{ level= progname= datetime_format= add <<
319
+ banner debug info error warn fatal unknown close
320
+ }.map(&:to_sym).each do |meth|
321
+ define_method(meth) do |*args|
322
+ result = nil
323
+ @loggers.each { |l| result = l.public_send(meth, *args) }
324
+ result
325
+ end
326
+ end
327
+
328
+ private
329
+
330
+ def default_log_level
331
+ Util.to_logger_level(Jamie::DEFAULT_LOG_LEVEL)
332
+ end
333
+
334
+ def stdout_logger(stdout)
335
+ logger = StdoutLogger.new(stdout)
336
+ logger.formatter = proc do |severity, datetime, progname, msg|
337
+ "#{msg}\n"
338
+ end
339
+ logger
340
+ end
341
+
342
+ def logdev_logger(filepath_or_logdev)
343
+ LogdevLogger.new(logdev(filepath_or_logdev))
344
+ end
345
+
346
+ def logdev(filepath_or_logdev)
347
+ if filepath_or_logdev.is_a? String
348
+ file = File.open(File.expand_path(filepath_or_logdev), "ab")
349
+ file.sync = true
350
+ file
351
+ else
352
+ filepath_or_logdev
353
+ end
354
+ end
355
+
356
+ # Internal class which adds a #banner method call that displays the
357
+ # message with a callout arrow.
358
+ class LogdevLogger < ::Logger
359
+
360
+ alias_method :super_info, :info
361
+
362
+ def <<(msg)
363
+ msg =~ /\n/ ? msg.split("\n").each { |l| format_line(l) } : super
364
+ end
365
+
366
+ def banner(msg = nil, &block)
367
+ super_info("-----> #{msg}", &block)
368
+ end
369
+
370
+ private
371
+
372
+ def format_line(line)
373
+ case line
374
+ when %r{^-----> } then banner(line.gsub(%r{^[ >-]{6} }, ''))
375
+ when %r{^>>>>>> } then error(line.gsub(%r{^[ >-]{6} }, ''))
376
+ when %r{^ } then info(line.gsub(%r{^[ >-]{6} }, ''))
377
+ else info(line)
378
+ end
379
+ end
380
+ end
381
+
382
+ # Internal class which reformats logging methods for display as console
383
+ # output.
384
+ class StdoutLogger < LogdevLogger
385
+
386
+ def debug(msg = nil, &block)
387
+ super("D #{msg}", &block)
388
+ end
389
+
390
+ def info(msg = nil, &block)
391
+ super(" #{msg}", &block)
392
+ end
393
+
394
+ def warn(msg = nil, &block)
395
+ super("$$$$$$ #{msg}", &block)
396
+ end
397
+
398
+ def error(msg = nil, &block)
399
+ super(">>>>>> #{msg}", &block)
400
+ end
401
+
402
+ def fatal(msg = nil, &block)
403
+ super("!!!!!! #{msg}", &block)
404
+ end
405
+ end
406
+ end
407
+
408
+ module Logging
409
+
410
+ %w{banner debug info warn error fatal}.map(&:to_sym).each do |meth|
411
+ define_method(meth) do |*args|
412
+ logger.public_send(meth, *args)
413
+ end
414
+ end
415
+ end
416
+
242
417
  # A Chef run_list and attribute hash that will be used in a convergence
243
418
  # integration.
244
419
  #
@@ -299,10 +474,6 @@ module Jamie
299
474
  # @return [String] logical name of this platform
300
475
  attr_reader :name
301
476
 
302
- # @return [Driver::Base] driver object which will manage this platform's
303
- # lifecycle actions
304
- attr_reader :driver
305
-
306
477
  # @return [Array] Array of Chef run_list items
307
478
  attr_reader :run_list
308
479
 
@@ -314,8 +485,6 @@ module Jamie
314
485
  # @param [Hash] options configuration for a new platform
315
486
  # @option options [String] :name logical name of this platform
316
487
  # (**Required**)
317
- # @option options [Driver::Base] :driver subclass of Driver::Base which
318
- # will manage this platform's lifecycle actions (**Required**)
319
488
  # @option options [Array<String>] :run_list Array of Chef run_list
320
489
  # items
321
490
  # @option options [Hash] :attributes Hash of Chef node attributes
@@ -323,7 +492,6 @@ module Jamie
323
492
  validate_options(options)
324
493
 
325
494
  @name = options['name']
326
- @driver = options['driver']
327
495
  @run_list = Array(options['run_list'])
328
496
  @attributes = options['attributes'] || Hash.new
329
497
  end
@@ -331,7 +499,7 @@ module Jamie
331
499
  private
332
500
 
333
501
  def validate_options(opts)
334
- %w(name driver).each do |k|
502
+ %w(name).each do |k|
335
503
  raise ArgumentError, "Attribute '#{k}' is required." if opts[k].nil?
336
504
  end
337
505
  end
@@ -344,25 +512,44 @@ module Jamie
344
512
  # @author Fletcher Nichol <fnichol@nichol.ca>
345
513
  class Instance
346
514
 
515
+ include Logging
516
+
347
517
  # @return [Suite] the test suite configuration
348
518
  attr_reader :suite
349
519
 
350
520
  # @return [Platform] the target platform configuration
351
521
  attr_reader :platform
352
522
 
523
+ # @return [Driver::Base] driver object which will manage this instance's
524
+ # lifecycle actions
525
+ attr_reader :driver
526
+
353
527
  # @return [Jr] jr command string generator
354
528
  attr_reader :jr
355
529
 
530
+ # @return [Logger] the logger for this instance
531
+ attr_reader :logger
532
+
356
533
  # Creates a new instance, given a suite and a platform.
357
534
  #
358
- # @param suite [Suite] a suite
359
- # @param platform [Platform] a platform
360
- def initialize(suite, platform)
361
- validate_options(suite, platform)
535
+ # @param [Hash] options configuration for a new suite
536
+ # @option options [Suite] :suite the suite
537
+ # @option options [Platform] :platform the platform
538
+ # @option options [Driver::Base] :driver the driver
539
+ # @option options [Jr] :jr the jr command string generator
540
+ # @option options [Logger] :logger the instance logger
541
+ def initialize(options = {})
542
+ options = { 'logger' => Jamie.logger }.merge(options)
543
+ validate_options(options)
544
+ logger = options['logger']
545
+
546
+ @suite = options['suite']
547
+ @platform = options['platform']
548
+ @driver = options['driver']
549
+ @jr = options['jr']
550
+ @logger = logger.is_a?(Proc) ? logger.call(name) : logger
362
551
 
363
- @suite = suite
364
- @platform = platform
365
- @jr = Jr.new(@suite.name)
552
+ @driver.instance = self
366
553
  end
367
554
 
368
555
  # @return [String] name of this instance
@@ -370,6 +557,10 @@ module Jamie
370
557
  "#{suite.name}-#{platform.name}".gsub(/_/, '-').gsub(/\./, '')
371
558
  end
372
559
 
560
+ def to_s
561
+ "<#{name}>"
562
+ end
563
+
373
564
  # Returns a combined run_list starting with the platform's run_list
374
565
  # followed by the suite's run_list.
375
566
  #
@@ -456,12 +647,14 @@ module Jamie
456
647
  # @todo rescue Driver::ActionFailed and return some kind of null object
457
648
  # to gracfully stop action chaining
458
649
  def test(destroy_mode = :passing)
459
- puts "-----> Cleaning up any prior instances of #{name}"
460
- destroy
461
- puts "-----> Testing instance #{name}"
462
- verify
463
- destroy if destroy_mode == :passing
464
- puts " Testing of instance #{name} complete."
650
+ elapsed = Benchmark.measure do
651
+ banner "Cleaning up any prior instances of #{self}"
652
+ destroy
653
+ banner "Testing #{self}"
654
+ verify
655
+ destroy if destroy_mode == :passing
656
+ end
657
+ info "Testing of #{self} complete (#{elapsed.real} seconds)."
465
658
  self
466
659
  ensure
467
660
  destroy if destroy_mode == :always
@@ -469,57 +662,63 @@ module Jamie
469
662
 
470
663
  private
471
664
 
472
- def validate_options(suite, platform)
473
- raise ArgumentError, "Attribute 'suite' is required." if suite.nil?
474
- raise ArgumentError, "Attribute 'platform' is required." if platform.nil?
665
+ def validate_options(opts)
666
+ %w(suite platform driver jr logger).each do |k|
667
+ raise ArgumentError, "Attribute '#{k}' is required." if opts[k].nil?
668
+ end
475
669
  end
476
670
 
477
671
  def transition_to(desired)
672
+ result = nil
478
673
  FSM.actions(last_action, desired).each do |transition|
479
- send("#{transition}_action")
674
+ result = send("#{transition}_action")
480
675
  end
676
+ result
481
677
  end
482
678
 
483
679
  def create_action
484
- puts "-----> Creating instance #{name}"
485
- action(:create) { |state| platform.driver.create(self, state) }
486
- puts " Creation of instance #{name} complete."
680
+ banner "Creating #{self}"
681
+ elapsed = action(:create) { |state| driver.create(state) }
682
+ info "Creation of #{self} complete (#{elapsed.real} seconds)."
487
683
  self
488
684
  end
489
685
 
490
686
  def converge_action
491
- puts "-----> Converging instance #{name}"
492
- action(:converge) { |state| platform.driver.converge(self, state) }
493
- puts " Convergence of instance #{name} complete."
687
+ banner "Converging #{self}"
688
+ elapsed = action(:converge) { |state| driver.converge(state) }
689
+ info "Convergence of #{self} complete (#{elapsed.real} seconds)."
494
690
  self
495
691
  end
496
692
 
497
693
  def setup_action
498
- puts "-----> Setting up instance #{name}"
499
- action(:setup) { |state| platform.driver.setup(self, state) }
500
- puts " Setup of instance #{name} complete."
694
+ banner "Setting up #{self}"
695
+ elapsed = action(:setup) { |state| driver.setup(state) }
696
+ info "Setup of #{self} complete (#{elapsed.real} seconds)."
501
697
  self
502
698
  end
503
699
 
504
700
  def verify_action
505
- puts "-----> Verifying instance #{name}"
506
- action(:verify) { |state| platform.driver.verify(self, state) }
507
- puts " Verification of instance #{name} complete."
701
+ banner "Verifying #{self}"
702
+ elapsed = action(:verify) { |state| driver.verify(state) }
703
+ info "Verification of #{self} complete (#{elapsed.real} seconds)."
508
704
  self
509
705
  end
510
706
 
511
707
  def destroy_action
512
- puts "-----> Destroying instance #{name}"
513
- action(:destroy) { |state| platform.driver.destroy(self, state) }
708
+ banner "Destroying #{self}"
709
+ elapsed = action(:destroy) { |state| driver.destroy(state) }
514
710
  destroy_state
515
- puts " Destruction of instance #{name} complete."
711
+ info "Destruction of #{self} complete (#{elapsed.real} seconds)."
516
712
  self
517
713
  end
518
714
 
519
715
  def action(what)
520
716
  state = load_state
521
- yield state if block_given?
717
+ elapsed = Benchmark.measure do
718
+ yield state if block_given?
719
+ end
522
720
  state['last_action'] = what.to_s
721
+ elapsed
523
722
  ensure
524
723
  dump_state(state)
525
724
  end
@@ -541,7 +740,7 @@ module Jamie
541
740
 
542
741
  def statefile
543
742
  File.expand_path(File.join(
544
- platform.driver['jamie_root'], ".jamie", "#{name}.yml"
743
+ driver['jamie_root'], ".jamie", "#{name}.yml"
545
744
  ))
546
745
  end
547
746
 
@@ -706,7 +905,7 @@ module Jamie
706
905
  jr_stream_file = "#{jr_bin} stream-file #{remote_path} #{md5} #{perms}"
707
906
 
708
907
  <<-STREAMFILE.gsub(/^ {8}/, '')
709
- echo " Uploading #{remote_path} (mode=#{perms})"
908
+ echo "Uploading #{remote_path} (mode=#{perms})"
710
909
  cat <<"__EOFSTREAM__" | #{sudo}#{jr_stream_file}
711
910
  #{Base64.encode64(local_file)}
712
911
  __EOFSTREAM__
@@ -745,6 +944,22 @@ module Jamie
745
944
  gsub(/([a-z\d])([A-Z])/, '\1_\2').
746
945
  downcase
747
946
  end
947
+
948
+ def self.to_logger_level(symbol)
949
+ return nil unless [:debug, :info, :warn, :error, :fatal].include?(symbol)
950
+
951
+ Logger.const_get(symbol.to_s.upcase)
952
+ end
953
+
954
+ def self.from_logger_level(const)
955
+ case const
956
+ when Logger::DEBUG then :debug
957
+ when Logger::INFO then :info
958
+ when Logger::WARN then :warn
959
+ when Logger::ERROR then :error
960
+ else :fatal
961
+ end
962
+ end
748
963
  end
749
964
 
750
965
  # Mixin that wraps a command shell out invocation, providing a #run_command
@@ -764,12 +979,12 @@ module Jamie
764
979
  # and context
765
980
  def run_command(cmd, use_sudo = false, log_subject = "local")
766
981
  cmd = "sudo #{cmd}" if use_sudo
767
- subject = " [#{log_subject} command]"
982
+ subject = "[#{log_subject} command]"
768
983
 
769
- $stdout.puts "#{subject} (#{display_cmd(cmd)})"
770
- sh = Mixlib::ShellOut.new(cmd, :live_stream => $stdout, :timeout => 60000)
984
+ info("#{subject} BEGIN (#{display_cmd(cmd)})")
985
+ sh = Mixlib::ShellOut.new(cmd, :live_stream => logger, :timeout => 60000)
771
986
  sh.run_command
772
- puts "#{subject} ran in #{sh.execution_time} seconds."
987
+ info("#{subject} END (#{sh.execution_time} seconds)")
773
988
  sh.error!
774
989
  rescue Mixlib::ShellOut::ShellCommandFailed => ex
775
990
  raise ShellCommandFailed, ex.message
@@ -809,6 +1024,9 @@ module Jamie
809
1024
  class Base
810
1025
 
811
1026
  include ShellOut
1027
+ include Logging
1028
+
1029
+ attr_writer :instance
812
1030
 
813
1031
  def initialize(config)
814
1032
  @config = config
@@ -827,42 +1045,49 @@ module Jamie
827
1045
 
828
1046
  # Creates an instance.
829
1047
  #
830
- # @param instance [Instance] an instance
831
1048
  # @param state [Hash] mutable instance and driver state
832
1049
  # @raise [ActionFailed] if the action could not be completed
833
- def create(instance, state) ; end
1050
+ def create(state) ; end
834
1051
 
835
1052
  # Converges a running instance.
836
1053
  #
837
- # @param instance [Instance] an instance
838
1054
  # @param state [Hash] mutable instance and driver state
839
1055
  # @raise [ActionFailed] if the action could not be completed
840
- def converge(instance, state) ; end
1056
+ def converge(state) ; end
841
1057
 
842
1058
  # Sets up an instance.
843
1059
  #
844
- # @param instance [Instance] an instance
845
1060
  # @param state [Hash] mutable instance and driver state
846
1061
  # @raise [ActionFailed] if the action could not be completed
847
- def setup(instance, state) ; end
1062
+ def setup(state) ; end
848
1063
 
849
1064
  # Verifies a converged instance.
850
1065
  #
851
- # @param instance [Instance] an instance
852
1066
  # @param state [Hash] mutable instance and driver state
853
1067
  # @raise [ActionFailed] if the action could not be completed
854
- def verify(instance, state) ; end
1068
+ def verify(state) ; end
855
1069
 
856
1070
  # Destroys an instance.
857
1071
  #
858
- # @param instance [Instance] an instance
859
1072
  # @param state [Hash] mutable instance and driver state
860
1073
  # @raise [ActionFailed] if the action could not be completed
861
- def destroy(instance, state) ; end
1074
+ def destroy(state) ; end
862
1075
 
863
1076
  protected
864
1077
 
865
- attr_reader :config
1078
+ attr_reader :config, :instance
1079
+
1080
+ def logger
1081
+ instance.logger
1082
+ end
1083
+
1084
+ def puts(msg)
1085
+ info(msg)
1086
+ end
1087
+
1088
+ def print(msg)
1089
+ info(msg)
1090
+ end
866
1091
 
867
1092
  def run_command(cmd, use_sudo = nil, log_subject = nil)
868
1093
  use_sudo = config['use_sudo'] if use_sudo.nil?
@@ -882,26 +1107,26 @@ module Jamie
882
1107
 
883
1108
  # Base class for a driver that uses SSH to communication with an instance.
884
1109
  # A subclass must implement the following methods:
885
- # * #create(instance, state)
886
- # * #destroy(instance, state)
1110
+ # * #create(state)
1111
+ # * #destroy(state)
887
1112
  #
888
1113
  # @author Fletcher Nichol <fnichol@nichol.ca>
889
1114
  class SSHBase < Base
890
1115
 
891
- def create(instance, state)
1116
+ def create(state)
892
1117
  raise NotImplementedError, "#create must be implemented by subclass."
893
1118
  end
894
1119
 
895
- def converge(instance, state)
1120
+ def converge(state)
896
1121
  ssh_args = build_ssh_args(state)
897
1122
 
898
1123
  install_omnibus(ssh_args) if config['require_chef_omnibus']
899
1124
  prepare_chef_home(ssh_args)
900
- upload_chef_data(ssh_args, instance)
1125
+ upload_chef_data(ssh_args)
901
1126
  run_chef_solo(ssh_args)
902
1127
  end
903
1128
 
904
- def setup(instance, state)
1129
+ def setup(state)
905
1130
  ssh_args = build_ssh_args(state)
906
1131
 
907
1132
  if instance.jr.setup_cmd
@@ -909,7 +1134,7 @@ module Jamie
909
1134
  end
910
1135
  end
911
1136
 
912
- def verify(instance, state)
1137
+ def verify(state)
913
1138
  ssh_args = build_ssh_args(state)
914
1139
 
915
1140
  if instance.jr.run_cmd
@@ -918,7 +1143,7 @@ module Jamie
918
1143
  end
919
1144
  end
920
1145
 
921
- def destroy(instance, state)
1146
+ def destroy(state)
922
1147
  raise NotImplementedError, "#destroy must be implemented by subclass."
923
1148
  end
924
1149
 
@@ -926,6 +1151,8 @@ module Jamie
926
1151
 
927
1152
  def build_ssh_args(state)
928
1153
  opts = Hash.new
1154
+ opts[:user_known_hosts_file] = "/dev/null"
1155
+ opts[:paranoid] = false
929
1156
  opts[:password] = config['password'] if config['password']
930
1157
  opts[:keys] = Array(config['ssh_key']) if config['ssh_key']
931
1158
 
@@ -948,7 +1175,7 @@ module Jamie
948
1175
  ssh(ssh_args, "sudo rm -rf #{chef_home} && mkdir -p #{chef_home}/cache")
949
1176
  end
950
1177
 
951
- def upload_chef_data(ssh_args, instance)
1178
+ def upload_chef_data(ssh_args)
952
1179
  Jamie::ChefDataUploader.new(
953
1180
  instance, ssh_args, config['jamie_root'], chef_home
954
1181
  ).upload
@@ -956,7 +1183,8 @@ module Jamie
956
1183
 
957
1184
  def run_chef_solo(ssh_args)
958
1185
  ssh(ssh_args, <<-RUN_SOLO)
959
- sudo chef-solo -c #{chef_home}/solo.rb -j #{chef_home}/dna.json
1186
+ sudo chef-solo -c #{chef_home}/solo.rb -j #{chef_home}/dna.json \
1187
+ --log_level #{Util.from_logger_level(logger.level)}
960
1188
  RUN_SOLO
961
1189
  end
962
1190
 
@@ -980,11 +1208,11 @@ module Jamie
980
1208
  channel.exec(cmd) do |ch, success|
981
1209
 
982
1210
  channel.on_data do |ch, data|
983
- $stdout.print data
1211
+ logger << data
984
1212
  end
985
1213
 
986
1214
  channel.on_extended_data do |ch, type, data|
987
- $stderr.print data
1215
+ logger << data
988
1216
  end
989
1217
 
990
1218
  channel.on_request("exit-status") do |ch, data|
@@ -997,7 +1225,7 @@ module Jamie
997
1225
  end
998
1226
 
999
1227
  def wait_for_sshd(hostname)
1000
- print "." until test_ssh(hostname)
1228
+ logger << "." until test_ssh(hostname)
1001
1229
  end
1002
1230
 
1003
1231
  def test_ssh(hostname)
@@ -1022,6 +1250,7 @@ module Jamie
1022
1250
  class ChefDataUploader
1023
1251
 
1024
1252
  include ShellOut
1253
+ include Logging
1025
1254
 
1026
1255
  def initialize(instance, ssh_args, jamie_root, chef_home)
1027
1256
  @instance = instance
@@ -1044,6 +1273,10 @@ module Jamie
1044
1273
 
1045
1274
  attr_reader :instance, :ssh_args, :jamie_root, :chef_home
1046
1275
 
1276
+ def logger
1277
+ instance.logger
1278
+ end
1279
+
1047
1280
  def upload_json(scp)
1048
1281
  json_file = StringIO.new(instance.dna.to_json)
1049
1282
  scp.upload!(json_file, "#{chef_home}/dna.json")
@@ -1055,24 +1288,26 @@ module Jamie
1055
1288
  end
1056
1289
 
1057
1290
  def upload_cookbooks(scp)
1058
- cookbooks_dir = local_cookbooks
1059
- scp.upload!(cookbooks_dir, "#{chef_home}/cookbooks",
1291
+ ckbks_dir = local_cookbooks
1292
+ scp.upload!(ckbks_dir, "#{chef_home}/cookbooks",
1060
1293
  :recursive => true
1061
1294
  ) do |ch, name, sent, total|
1062
- file = name.sub(%r{^#{cookbooks_dir}/}, '')
1063
- puts " #{file}: #{sent}/#{total}"
1295
+ if sent == total
1296
+ info("Uploaded #{name.sub(%r{^#{ckbks_dir}/}, '')} (#{total} bytes)")
1297
+ end
1064
1298
  end
1065
1299
  ensure
1066
- FileUtils.rmtree(cookbooks_dir)
1300
+ FileUtils.rmtree(ckbks_dir)
1067
1301
  end
1068
1302
 
1069
1303
  def upload_data_bags(scp)
1070
- data_bags_dir = instance.suite.data_bags_path
1071
- scp.upload!(data_bags_dir, "#{chef_home}/data_bags",
1304
+ dbags_dir = instance.suite.data_bags_path
1305
+ scp.upload!(dbags_dir, "#{chef_home}/data_bags",
1072
1306
  :recursive => true
1073
1307
  ) do |ch, name, sent, total|
1074
- file = name.sub(%r{^#{data_bags_dir}/}, '')
1075
- puts " #{file}: #{sent}/#{total}"
1308
+ if sent == total
1309
+ info("Uploaded #{name.sub(%r{^#{dbags_dir}/}, '')} (#{total} bytes)")
1310
+ end
1076
1311
  end
1077
1312
  end
1078
1313
 
@@ -1081,8 +1316,9 @@ module Jamie
1081
1316
  scp.upload!(roles_dir, "#{chef_home}/roles",
1082
1317
  :recursive => true
1083
1318
  ) do |ch, name, sent, total|
1084
- file = name.sub(%r{^#{roles_dir}/}, '')
1085
- puts " #{file}: #{sent}/#{total}"
1319
+ if sent == total
1320
+ info("Uploaded #{name.sub(%r{^#{roles_dir}/}, '')} (#{total} bytes)")
1321
+ end
1086
1322
  end
1087
1323
  end
1088
1324
 
@@ -1095,7 +1331,6 @@ module Jamie
1095
1331
  if instance.suite.data_bags_path
1096
1332
  solo << %{data_bag_path "#{chef_home}/data_bags"}
1097
1333
  end
1098
- solo << %{log_level :info}
1099
1334
  solo.join("\n")
1100
1335
  end
1101
1336
 
@@ -1182,3 +1417,5 @@ module Jamie
1182
1417
  end
1183
1418
  end
1184
1419
  end
1420
+
1421
+ Jamie.logger = Jamie.default_logger
data/spec/jamie_spec.rb CHANGED
@@ -29,8 +29,10 @@ SimpleCov.start 'gem'
29
29
 
30
30
  require 'fakefs/spec_helpers'
31
31
  require 'minitest/autorun'
32
+ require 'ostruct'
32
33
 
33
34
  require 'jamie'
35
+ require 'jamie/driver/dummy'
34
36
 
35
37
  # Nasty hack to redefine IO.read in terms of File#read for fakefs
36
38
  class IO
@@ -44,13 +46,14 @@ describe Jamie::Config do
44
46
 
45
47
  let(:config) { Jamie::Config.new("/tmp/.jamie.yml") }
46
48
 
49
+ before do
50
+ FileUtils.mkdir_p("/tmp")
51
+ end
52
+
47
53
  describe "#platforms" do
48
54
 
49
55
  it "returns platforms loaded from a jamie.yml" do
50
- stub_yaml!({'platforms' => [
51
- { 'name' => 'one', 'driver_plugin' => 'dummy' },
52
- { 'name' => 'two', 'driver_plugin' => 'dummy' },
53
- ]})
56
+ stub_yaml!({'platforms' => [ { 'name' => 'one' }, { 'name' => 'two' } ]})
54
57
  config.platforms.size.must_equal 2
55
58
  config.platforms[0].name.must_equal 'one'
56
59
  config.platforms[1].name.must_equal 'two'
@@ -60,29 +63,6 @@ describe Jamie::Config do
60
63
  stub_yaml!({})
61
64
  config.platforms.must_equal []
62
65
  end
63
-
64
- it "returns a platform containing a driver instance" do
65
- stub_yaml!({'platforms' => [
66
- { 'name' => 'platform', 'driver_plugin' => 'dummy' }
67
- ]})
68
- config.platforms.first.driver.must_be_instance_of Jamie::Driver::Dummy
69
- end
70
-
71
- it "returns a platform with a driver initialized with jamie_root" do
72
- stub_yaml!({'platforms' => [
73
- { 'name' => 'platform', 'driver_plugin' => 'dummy' }
74
- ]})
75
- config.platforms.first.driver['jamie_root'].must_equal "/tmp"
76
- end
77
-
78
- it "returns a platform with a driver initialized with passed in config" do
79
- stub_yaml!({'platforms' => [
80
- { 'name' => 'platform', 'driver_plugin' => 'dummy',
81
- 'driver_config' => { 'foo' => 'bar' }
82
- }
83
- ]})
84
- config.platforms.first.driver['foo'].must_equal "bar"
85
- end
86
66
  end
87
67
 
88
68
  describe "#suites" do
@@ -146,8 +126,8 @@ describe Jamie::Config do
146
126
  it "returns instances loaded from a jamie.yml" do
147
127
  stub_yaml!({
148
128
  'platforms' => [
149
- { 'name' => 'p1', 'driver_plugin' => 'dummy' },
150
- { 'name' => 'p2', 'driver_plugin' => 'dummy' },
129
+ { 'name' => 'p1' },
130
+ { 'name' => 'p2' },
151
131
  ],
152
132
  'suites' => [
153
133
  { 'name' => 's1', 'run_list' => [] },
@@ -157,29 +137,58 @@ describe Jamie::Config do
157
137
  config.instances.size.must_equal 4
158
138
  config.instances.map { |i| i.name }.must_equal %w{s1-p1 s1-p2 s2-p1 s2-p2}
159
139
  end
140
+
141
+ it "returns an instance containing a driver instance" do
142
+ stub_yaml!({
143
+ 'platforms' => [ { 'name' => 'platform', 'driver_plugin' => 'dummy' } ],
144
+ 'suites' => [ { 'name' => 'suite', 'run_list' => [] }]
145
+ })
146
+ config.instances.first.driver.must_be_instance_of Jamie::Driver::Dummy
147
+ end
148
+
149
+ it "returns an instance with a driver initialized with jamie_root" do
150
+ stub_yaml!({
151
+ 'platforms' => [ { 'name' => 'platform', 'driver_plugin' => 'dummy' } ],
152
+ 'suites' => [ { 'name' => 'suite', 'run_list' => [] }]
153
+ })
154
+ config.instances.first.driver['jamie_root'].must_equal "/tmp"
155
+ end
156
+
157
+ it "returns an instance with a driver initialized with passed in config" do
158
+ stub_yaml!({
159
+ 'platforms' => [
160
+ { 'name' => 'platform', 'driver_plugin' => 'dummy',
161
+ 'driver_config' => { 'foo' => 'bar' } }
162
+ ],
163
+ 'suites' => [ { 'name' => 'suite', 'run_list' => [] }]
164
+ })
165
+ config.instances.first.driver['foo'].must_equal "bar"
166
+ end
160
167
  end
161
168
 
162
169
  describe "jamie.local.yml" do
163
170
 
164
171
  it "merges in configuration with jamie.yml" do
165
172
  stub_yaml!(".jamie.yml", {
166
- 'platforms' => [ { 'name' => 'p1', 'driver_plugin' => 'dummy' } ]
173
+ 'platforms' => [ { 'name' => 'p1', 'driver_plugin' => 'dummy' } ],
174
+ 'suites' => [ { 'name' => 's1', 'run_list' => [] } ]
167
175
  })
168
176
  stub_yaml!(".jamie.local.yml", {
169
177
  'driver_config' => { 'foo' => 'bar' }
170
178
  })
171
- config.platforms.first.driver['foo'].must_equal 'bar'
179
+ config.instances.first.driver['foo'].must_equal 'bar'
172
180
  end
173
181
 
174
182
  it "merges over configuration in jamie.yml" do
175
183
  stub_yaml!(".jamie.yml", {
176
184
  'driver_config' => { 'foo' => 'nope' },
177
- 'platforms' => [ { 'name' => 'p1', 'driver_plugin' => 'dummy' } ]
185
+ 'platforms' => [ { 'name' => 'p1', 'driver_plugin' => 'dummy' } ],
186
+ 'suites' => [ { 'name' => 's1', 'run_list' => [] } ]
178
187
  })
179
188
  stub_yaml!(".jamie.local.yml", {
180
189
  'driver_config' => { 'foo' => 'bar' }
181
190
  })
182
- config.platforms.first.driver['foo'].must_equal 'bar'
191
+ config.instances.first.driver['foo'].must_equal 'bar'
183
192
  end
184
193
  end
185
194
 
@@ -200,7 +209,8 @@ describe Jamie::Config do
200
209
 
201
210
  it "evaluates jamie.local.yml through erb before loading" do
202
211
  stub_yaml!({
203
- 'platforms' => [ { 'name' => 'p1', 'driver_plugin' => 'dummy' } ]
212
+ 'platforms' => [ { 'name' => 'p1', 'driver_plugin' => 'dummy' } ],
213
+ 'suites' => [ { 'name' => 's1', 'run_list' => [] } ]
204
214
  })
205
215
  FileUtils.mkdir_p "/tmp"
206
216
  File.open("/tmp/.jamie.local.yml", "wb") do |f|
@@ -212,8 +222,8 @@ describe Jamie::Config do
212
222
  <% end %>
213
223
  YAML
214
224
  end
215
- config.platforms.first.driver['noodle'].must_equal "soup"
216
- config.platforms.first.driver['mushroom'].must_equal "soup"
225
+ config.instances.first.driver['noodle'].must_equal "soup"
226
+ config.instances.first.driver['mushroom'].must_equal "soup"
217
227
  end
218
228
  end
219
229
 
@@ -335,7 +345,7 @@ end
335
345
 
336
346
  describe Jamie::Platform do
337
347
 
338
- let(:opts) do ; { 'name' => 'plata', 'driver' => 'imadriver' } ; end
348
+ let(:opts) do ; { 'name' => 'plata' } ; end
339
349
  let(:platform) { Jamie::Platform.new(opts) }
340
350
 
341
351
  it "raises an ArgumentError if name is missing" do
@@ -343,11 +353,6 @@ describe Jamie::Platform do
343
353
  proc { Jamie::Platform.new(opts) }.must_raise ArgumentError
344
354
  end
345
355
 
346
- it "raises an ArgumentError if driver is missing" do
347
- opts.delete('driver')
348
- proc { Jamie::Platform.new(opts) }.must_raise ArgumentError
349
- end
350
-
351
356
  it "returns an empty Array given no run_list" do
352
357
  platform.run_list.must_equal []
353
358
  end
@@ -359,7 +364,6 @@ describe Jamie::Platform do
359
364
  it "returns attributes from constructor" do
360
365
  opts.merge!({ 'run_list' => [ 'a', 'b' ], 'attributes' => { 'c' => 'd' }})
361
366
  platform.name.must_equal 'plata'
362
- platform.driver.must_equal 'imadriver'
363
367
  platform.run_list.must_equal [ 'a', 'b' ]
364
368
  platform.attributes.must_equal({ 'c' => 'd' })
365
369
  end
@@ -373,18 +377,28 @@ describe Jamie::Instance do
373
377
  end
374
378
 
375
379
  let(:platform) do
376
- Jamie::Platform.new({ 'name' => 'platform', 'driver' => 'driver',
380
+ Jamie::Platform.new({ 'name' => 'platform',
377
381
  'run_list' => 'platform_list', 'attributes' => { 'p' => 'pp' } })
378
382
  end
379
383
 
380
- let(:instance) { Jamie::Instance.new(suite, platform) }
384
+ let(:driver) { Jamie::Driver::Dummy.new({}) }
385
+
386
+ let(:jr) { Jamie::Jr.new(suite.name) }
387
+
388
+ let(:opts) do
389
+ { 'suite' => suite, 'platform' => platform, 'driver' => driver, 'jr' => jr }
390
+ end
391
+
392
+ let(:instance) { Jamie::Instance.new(opts) }
381
393
 
382
394
  it "raises an ArgumentError if suite is missing" do
383
- proc { Jamie::Instance.new(nil, platform) }.must_raise ArgumentError
395
+ opts.delete('suite')
396
+ proc { Jamie::Instance.new(opts) }.must_raise ArgumentError
384
397
  end
385
398
 
386
399
  it "raises an ArgumentError if platform is missing" do
387
- proc { Jamie::Instance.new(suite, nil) }.must_raise ArgumentError
400
+ opts.delete('platform')
401
+ proc { Jamie::Instance.new(opts) }.must_raise ArgumentError
388
402
  end
389
403
 
390
404
  it "returns suite" do
@@ -402,10 +416,13 @@ describe Jamie::Instance do
402
416
  describe "#name" do
403
417
 
404
418
  def combo(suite_name, platform_name)
405
- Jamie::Instance.new(
406
- Jamie::Suite.new({ 'name' => suite_name, 'run_list' => [] }),
407
- Jamie::Platform.new({ 'name' => platform_name, 'driver' => 'd' })
419
+ opts['suite'] = Jamie::Suite.new(
420
+ 'name' => suite_name, 'run_list' => []
421
+ )
422
+ opts['platform'] = Jamie::Platform.new(
423
+ 'name' => platform_name
408
424
  )
425
+ Jamie::Instance.new(opts)
409
426
  end
410
427
 
411
428
  it "combines the suite and platform names with a dash" do
@@ -428,11 +445,13 @@ describe Jamie::Instance do
428
445
  describe "#run_list" do
429
446
 
430
447
  def combo(suite_list, platform_list)
431
- Jamie::Instance.new(
432
- Jamie::Suite.new({ 'name' => 'suite', 'run_list' => suite_list }),
433
- Jamie::Platform.new({ 'name' => 'platform', 'driver' => 'd',
434
- 'run_list' => platform_list })
448
+ opts['suite'] = Jamie::Suite.new(
449
+ 'name' => 'suite', 'run_list' => suite_list
435
450
  )
451
+ opts['platform'] = Jamie::Platform.new(
452
+ 'name' => 'platform', 'run_list' => platform_list
453
+ )
454
+ Jamie::Instance.new(opts)
436
455
  end
437
456
 
438
457
  it "combines the platform then suite run_lists" do
@@ -451,12 +470,13 @@ describe Jamie::Instance do
451
470
  describe "#attributes" do
452
471
 
453
472
  def combo(suite_attrs, platform_attrs)
454
- Jamie::Instance.new(
455
- Jamie::Suite.new({ 'name' => 'suite', 'run_list' => [],
456
- 'attributes' => suite_attrs }),
457
- Jamie::Platform.new({ 'name' => 'platform', 'driver' => 'd',
458
- 'attributes' => platform_attrs })
473
+ opts['suite'] = Jamie::Suite.new(
474
+ 'name' => 'suite', 'run_list' => [], 'attributes' => suite_attrs
475
+ )
476
+ opts['platform'] = Jamie::Platform.new(
477
+ 'name' => 'platform', 'attributes' => platform_attrs
459
478
  )
479
+ Jamie::Instance.new(opts)
460
480
  end
461
481
 
462
482
  it "merges suite and platform hashes together" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jamie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha18
4
+ version: 0.1.0.alpha19
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-30 00:00:00.000000000 Z
12
+ date: 2013-01-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor