jamie 0.1.0.alpha18 → 0.1.0.alpha19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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