test-kitchen 1.0.0.beta.4 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -1
  3. data/Gemfile +1 -1
  4. data/README.md +18 -7
  5. data/Rakefile +8 -1
  6. data/features/kitchen_init_command.feature +90 -11
  7. data/features/step_definitions/git_steps.rb +3 -0
  8. data/lib/kitchen/busser.rb +79 -45
  9. data/lib/kitchen/cli.rb +14 -13
  10. data/lib/kitchen/config.rb +79 -138
  11. data/lib/kitchen/data_munger.rb +224 -0
  12. data/lib/kitchen/driver/base.rb +4 -31
  13. data/lib/kitchen/driver/ssh_base.rb +6 -16
  14. data/lib/kitchen/driver.rb +4 -0
  15. data/lib/kitchen/generator/init.rb +20 -9
  16. data/lib/kitchen/instance.rb +53 -58
  17. data/lib/kitchen/lazy_hash.rb +50 -0
  18. data/lib/kitchen/platform.rb +2 -31
  19. data/lib/kitchen/provisioner/base.rb +55 -9
  20. data/lib/kitchen/provisioner/chef/berkshelf.rb +76 -0
  21. data/lib/kitchen/provisioner/chef/librarian.rb +72 -0
  22. data/lib/kitchen/provisioner/chef_base.rb +159 -78
  23. data/lib/kitchen/provisioner/chef_solo.rb +6 -36
  24. data/lib/kitchen/provisioner/chef_zero.rb +70 -59
  25. data/lib/kitchen/provisioner/dummy.rb +28 -0
  26. data/lib/kitchen/provisioner.rb +6 -4
  27. data/lib/kitchen/shell_out.rb +2 -5
  28. data/lib/kitchen/ssh.rb +1 -1
  29. data/lib/kitchen/suite.rb +10 -79
  30. data/lib/kitchen/util.rb +2 -2
  31. data/lib/kitchen/version.rb +2 -2
  32. data/lib/kitchen.rb +5 -0
  33. data/spec/kitchen/config_spec.rb +84 -123
  34. data/spec/kitchen/data_munger_spec.rb +1412 -0
  35. data/spec/kitchen/driver/base_spec.rb +30 -0
  36. data/spec/kitchen/instance_spec.rb +868 -86
  37. data/spec/kitchen/lazy_hash_spec.rb +63 -0
  38. data/spec/kitchen/platform_spec.rb +0 -22
  39. data/spec/kitchen/provisioner/base_spec.rb +210 -0
  40. data/spec/kitchen/provisioner_spec.rb +70 -0
  41. data/spec/kitchen/suite_spec.rb +25 -38
  42. data/spec/spec_helper.rb +1 -0
  43. data/support/chef-client-zero.rb +51 -35
  44. data/support/dummy-validation.pem +27 -0
  45. data/templates/init/kitchen.yml.erb +10 -22
  46. data/test-kitchen.gemspec +1 -2
  47. metadata +20 -18
@@ -18,6 +18,8 @@
18
18
 
19
19
  require 'thor/util'
20
20
 
21
+ require 'kitchen/lazy_hash'
22
+
21
23
  module Kitchen
22
24
 
23
25
  module Driver
@@ -37,7 +39,7 @@ module Kitchen
37
39
  end
38
40
 
39
41
  def initialize(config = {})
40
- @config = LazyDriverHash.new(config, self)
42
+ @config = LazyHash.new(config, self)
41
43
  self.class.defaults.each do |attr, value|
42
44
  @config[attr] = value unless @config.has_key?(attr)
43
45
  end
@@ -162,8 +164,7 @@ module Kitchen
162
164
  def busser
163
165
  @busser ||= begin
164
166
  raise ClientError, "Instance must be set for Driver" if instance.nil?
165
-
166
- Busser.new(instance.suite.name, config)
167
+ instance.busser
167
168
  end
168
169
  end
169
170
 
@@ -213,34 +214,6 @@ module Kitchen
213
214
  @serial_actions ||= []
214
215
  @serial_actions += methods
215
216
  end
216
-
217
- # A modifed Hash object that may contain procs as a value which must be
218
- # executed in the context of a Driver object.
219
- #
220
- # @author Fletcher Nichol <fnichol@nichol.ca>
221
- class LazyDriverHash < SimpleDelegator
222
-
223
- def initialize(obj, driver)
224
- @driver = driver
225
- super(obj)
226
- end
227
-
228
- def [](key)
229
- proc_or_val = __getobj__[key]
230
-
231
- if proc_or_val.respond_to?(:call)
232
- proc_or_val.call(@driver)
233
- else
234
- proc_or_val
235
- end
236
- end
237
-
238
- def to_hash
239
- hash = Hash.new
240
- __getobj__.keys.each { |key| hash[key] = self[key] }
241
- hash
242
- end
243
- end
244
217
  end
245
218
  end
246
219
  end
@@ -36,12 +36,13 @@ module Kitchen
36
36
  end
37
37
 
38
38
  def converge(state)
39
- provisioner = new_provisioner
39
+ provisioner = instance.provisioner
40
+ sandbox_dirs = Dir.glob("#{provisioner.create_sandbox}/*")
40
41
 
41
42
  Kitchen::SSH.new(*build_ssh_args(state)) do |conn|
42
43
  run_remote(provisioner.install_command, conn)
43
44
  run_remote(provisioner.init_command, conn)
44
- transfer_path(provisioner.create_sandbox, provisioner.home_path, conn)
45
+ transfer_path(sandbox_dirs, provisioner[:root_path], conn)
45
46
  run_remote(provisioner.prepare_command, conn)
46
47
  run_remote(provisioner.run_command, conn)
47
48
  end
@@ -78,12 +79,6 @@ module Kitchen
78
79
 
79
80
  protected
80
81
 
81
- def new_provisioner
82
- combined = config.dup
83
- combined[:log_level] = Util.from_logger_level(logger.level)
84
- Provisioner.for_plugin(combined[:provisioner], instance, combined)
85
- end
86
-
87
82
  def build_ssh_args(state)
88
83
  combined = config.to_hash.merge(state)
89
84
 
@@ -104,11 +99,6 @@ module Kitchen
104
99
  env << " http_proxy=#{config[:http_proxy]}" if config[:http_proxy]
105
100
  env << " https_proxy=#{config[:https_proxy]}" if config[:https_proxy]
106
101
 
107
- additional_paths = []
108
- additional_paths << config[:ruby_binpath] if config[:ruby_binpath]
109
- additional_paths << config[:path] if config[:path]
110
- env << " PATH=$PATH:#{additional_paths.join(':')}" if additional_paths.any?
111
-
112
102
  env == "env" ? cmd : "#{env} #{cmd}"
113
103
  end
114
104
 
@@ -120,10 +110,10 @@ module Kitchen
120
110
  raise ActionFailed, ex.message
121
111
  end
122
112
 
123
- def transfer_path(local, remote, connection)
124
- return if local.nil?
113
+ def transfer_path(locals, remote, connection)
114
+ return if locals.nil? || Array(locals).empty?
125
115
 
126
- connection.upload_path!(local, remote)
116
+ locals.each { |local| connection.upload_path!(local, remote) }
127
117
  rescue SSHFailed, Net::SSH::Exception => ex
128
118
  raise ActionFailed, ex.message
129
119
  end
@@ -26,9 +26,13 @@ module Kitchen
26
26
  # @author Fletcher Nichol <fnichol@nichol.ca>
27
27
  module Driver
28
28
 
29
+ # Default driver plugin to use
30
+ DEFAULT_PLUGIN = "dummy".freeze
31
+
29
32
  # Returns an instance of a driver given a plugin type string.
30
33
  #
31
34
  # @param plugin [String] a driver plugin type, which will be constantized
35
+ # @param config [Hash] a configuration hash to initialize the driver
32
36
  # @return [Driver::Base] a driver instance
33
37
  # @raise [ClientError] if a driver instance could not be created
34
38
  # @raise [UserError] if the driver's dependencies could not be met
@@ -37,6 +37,12 @@ module Kitchen
37
37
  Gemfile
38
38
  D
39
39
 
40
+ class_option :provisioner, :type => :string, :aliases => "-P",
41
+ :default => "chef_solo",
42
+ :desc => <<-D.gsub(/^\s+/, '').gsub(/\n/, ' ')
43
+ The default Kitchen Provisioner to use
44
+ D
45
+
40
46
  class_option :create_gemfile, :type => :boolean, :default => false,
41
47
  :desc => <<-D.gsub(/^\s+/, '').gsub(/\n/, ' ')
42
48
  Whether or not to create a Gemfile if one does not exist.
@@ -50,8 +56,10 @@ module Kitchen
50
56
  prepare_rakefile if init_rakefile?
51
57
  prepare_thorfile if init_thorfile?
52
58
  empty_directory "test/integration/default" if init_test_dir?
53
- append_to_gitignore(".kitchen/")
54
- append_to_gitignore(".kitchen.local.yml")
59
+ if init_git?
60
+ append_to_gitignore(".kitchen/")
61
+ append_to_gitignore(".kitchen.local.yml")
62
+ end
55
63
  prepare_gemfile if init_gemfile?
56
64
  add_drivers
57
65
 
@@ -68,11 +76,12 @@ module Kitchen
68
76
  else
69
77
  nil
70
78
  end
71
- run_list = cookbook_name ? "recipe[#{cookbook_name}]" : nil
79
+ run_list = cookbook_name ? "recipe[#{cookbook_name}::default]" : nil
72
80
  driver_plugin = Array(options[:driver]).first || 'dummy'
73
81
 
74
82
  template("kitchen.yml.erb", ".kitchen.yml", {
75
83
  :driver_plugin => driver_plugin.sub(/^kitchen-/, ''),
84
+ :provisioner => options[:provisioner],
76
85
  :run_list => Array(run_list)
77
86
  })
78
87
  end
@@ -96,6 +105,10 @@ module Kitchen
96
105
  Dir.glob("test/integration/*").select { |d| File.directory?(d) }.empty?
97
106
  end
98
107
 
108
+ def init_git?
109
+ File.directory?(File.join(destination_root, '.git'))
110
+ end
111
+
99
112
  def prepare_rakefile
100
113
  rakedoc = <<-RAKE.gsub(/^ {10}/, '')
101
114
 
@@ -142,9 +155,8 @@ module Kitchen
142
155
  end
143
156
 
144
157
  def add_gem_to_gemfile
145
- if not_in_file?("Gemfile", %r{gem 'test-kitchen'})
146
- append_to_file("Gemfile",
147
- %{gem 'test-kitchen', :group => :integration\n})
158
+ if not_in_file?("Gemfile", %r{gem ('|")test-kitchen('|")})
159
+ append_to_file("Gemfile", %{gem 'test-kitchen'\n})
148
160
  @display_bundle_msg = true
149
161
  end
150
162
  end
@@ -163,9 +175,8 @@ module Kitchen
163
175
  end
164
176
 
165
177
  def add_driver_to_gemfile(driver_gem)
166
- if not_in_file?("Gemfile", %r{gem '#{driver_gem}'})
167
- append_to_file("Gemfile",
168
- %{gem '#{driver_gem}', :group => :integration\n})
178
+ if not_in_file?("Gemfile", %r{gem ('|")#{driver_gem}('|")})
179
+ append_to_file("Gemfile", %{gem '#{driver_gem}'\n})
169
180
  @display_bundle_msg = true
170
181
  end
171
182
  end
@@ -18,7 +18,6 @@
18
18
 
19
19
  require 'benchmark'
20
20
  require 'fileutils'
21
- require 'vendor/hash_recursive_merge'
22
21
 
23
22
  module Kitchen
24
23
 
@@ -33,6 +32,10 @@ module Kitchen
33
32
 
34
33
  class << self
35
34
  attr_accessor :mutexes
35
+
36
+ def name_for(suite, platform)
37
+ "#{suite.name}-#{platform.name}".gsub(/_/, '-').gsub(/\./, '')
38
+ end
36
39
  end
37
40
 
38
41
  # @return [Suite] the test suite configuration
@@ -41,38 +44,53 @@ module Kitchen
41
44
  # @return [Platform] the target platform configuration
42
45
  attr_reader :platform
43
46
 
47
+ # @return [String] name of this instance
48
+ attr_reader :name
49
+
44
50
  # @return [Driver::Base] driver object which will manage this instance's
45
51
  # lifecycle actions
46
52
  attr_reader :driver
47
53
 
54
+ # @return [Provisioner::Base] provisioner object which will the setup
55
+ # and invocation instructions for configuration management and other
56
+ # automation tools
57
+ attr_reader :provisioner
58
+
59
+ # @return [Busser] busser object for instance to manage the busser
60
+ # installation on this instance
61
+ attr_reader :busser
62
+
48
63
  # @return [Logger] the logger for this instance
49
64
  attr_reader :logger
50
65
 
51
66
  # Creates a new instance, given a suite and a platform.
52
67
  #
53
68
  # @param [Hash] options configuration for a new suite
54
- # @option options [Suite] :suite the suite
55
- # @option options [Platform] :platform the platform
56
- # @option options [Driver::Base] :driver the driver
69
+ # @option options [Suite] :suite the suite (**Required)
70
+ # @option options [Platform] :platform the platform (**Required)
71
+ # @option options [Driver::Base] :driver the driver (**Required)
72
+ # @option options [Provisioner::Base] :provisioner the provisioner
73
+ # (**Required)
74
+ # @option options [Busser] :busser the busser logger (**Required**)
57
75
  # @option options [Logger] :logger the instance logger
76
+ # (default: Kitchen.logger)
77
+ # @option options [StateFile] :state_file the state file object to use
78
+ # when tracking instance state (**Required**)
79
+ # @raise [ClientError] if one or more required options are omitted
58
80
  def initialize(options = {})
59
- options = { :logger => Kitchen.logger }.merge(options)
60
81
  validate_options(options)
61
- logger = options[:logger]
62
-
63
- @suite = options[:suite]
64
- @platform = options[:platform]
65
- @driver = options[:driver]
66
- @logger = logger.is_a?(Proc) ? logger.call(name) : logger
67
82
 
68
- @driver.instance = self
69
- @driver.validate_config!
70
- setup_driver_mutex
71
- end
72
-
73
- # @return [String] name of this instance
74
- def name
75
- "#{suite.name}-#{platform.name}".gsub(/_/, '-').gsub(/\./, '')
83
+ @suite = options.fetch(:suite)
84
+ @platform = options.fetch(:platform)
85
+ @name = self.class.name_for(@suite, @platform)
86
+ @driver = options.fetch(:driver)
87
+ @provisioner = options.fetch(:provisioner)
88
+ @busser = options.fetch(:busser)
89
+ @logger = options.fetch(:logger) { Kitchen.logger }
90
+ @state_file = options.fetch(:state_file)
91
+
92
+ setup_driver
93
+ setup_provisioner
76
94
  end
77
95
 
78
96
  def to_str
@@ -179,45 +197,22 @@ module Kitchen
179
197
  state_file.read[:last_action]
180
198
  end
181
199
 
182
- # Extra instance methods used for accessing Puppet data such as a combined
183
- # run list, node attributes, etc.
184
- module Cheflike
185
-
186
- # Returns a combined run_list starting with the platform's run_list
187
- # followed by the suite's run_list.
188
- #
189
- # @return [Array] combined run_list from suite and platform
190
- def run_list
191
- Array(platform.run_list) + Array(suite.run_list)
192
- end
193
-
194
- # Returns a merged hash of Chef node attributes with values from the
195
- # suite overriding values from the platform.
196
- #
197
- # @return [Hash] merged hash of Chef node attributes
198
- def attributes
199
- platform.attributes.rmerge(suite.attributes)
200
- end
201
-
202
- def dna
203
- attributes.rmerge({ :run_list => run_list })
204
- end
205
- end
200
+ private
206
201
 
207
- # Extra instances methods used for accessing Puppet data such as a run,
208
- # node attributes, etc.
209
- module Puppetlike
202
+ attr_reader :state_file
210
203
 
211
- def manifest
204
+ def validate_options(options)
205
+ [:suite, :platform, :driver, :provisioner, :busser, :state_file].each do |k|
206
+ if !options.has_key?(k)
207
+ raise ClientError, "Instance#new requires option :#{k}"
208
+ end
212
209
  end
213
210
  end
214
211
 
215
- private
216
-
217
- def validate_options(opts)
218
- [:suite, :platform, :driver, :logger].each do |k|
219
- raise ClientError, "Instance#new requires option :#{k}" if opts[k].nil?
220
- end
212
+ def setup_driver
213
+ @driver.instance = self
214
+ @driver.validate_config!
215
+ setup_driver_mutex
221
216
  end
222
217
 
223
218
  def setup_driver_mutex
@@ -229,6 +224,10 @@ module Kitchen
229
224
  end
230
225
  end
231
226
 
227
+ def setup_provisioner
228
+ @provisioner.instance = self
229
+ end
230
+
232
231
  def transition_to(desired)
233
232
  result = nil
234
233
  FSM.actions(last_action, desired).each do |transition|
@@ -258,7 +257,7 @@ module Kitchen
258
257
  end
259
258
 
260
259
  def perform_action(verb, output_verb)
261
- banner "#{output_verb} #{to_str}"
260
+ banner "#{output_verb} #{to_str}..."
262
261
  elapsed = action(verb) { |state| driver.public_send(verb, state) }
263
262
  info("Finished #{output_verb.downcase} #{to_str}" +
264
263
  " #{Util.duration(elapsed.real)}.")
@@ -298,10 +297,6 @@ module Kitchen
298
297
  end
299
298
  end
300
299
 
301
- def state_file
302
- @state_file ||= StateFile.new(driver[:kitchen_root], name)
303
- end
304
-
305
300
  def banner(*args)
306
301
  Kitchen.logger.logdev && Kitchen.logger.logdev.banner(*args)
307
302
  super
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'delegate'
20
+
21
+ module Kitchen
22
+
23
+ # A modifed Hash object that may contain procs as a value which must be
24
+ # executed in the context of another object.
25
+ #
26
+ # @author Fletcher Nichol <fnichol@nichol.ca>
27
+ class LazyHash < SimpleDelegator
28
+
29
+ def initialize(obj, context)
30
+ @context = context
31
+ super(obj)
32
+ end
33
+
34
+ def [](key)
35
+ proc_or_val = __getobj__[key]
36
+
37
+ if proc_or_val.respond_to?(:call)
38
+ proc_or_val.call(@context)
39
+ else
40
+ proc_or_val
41
+ end
42
+ end
43
+
44
+ def to_hash
45
+ hash = Hash.new
46
+ __getobj__.keys.each { |key| hash[key] = self[key] }
47
+ hash
48
+ end
49
+ end
50
+ end
@@ -34,37 +34,8 @@ module Kitchen
34
34
  # @option options [String] :name logical name of this platform
35
35
  # (**Required**)
36
36
  def initialize(options = {})
37
- options = options.dup
38
- validate_options(options)
39
-
40
- @name = options.delete(:name)
41
- @data = options.reject do |key, value|
42
- [:driver_config, :driver_plugin, :provisioner].include?(key)
43
- end
44
- end
45
-
46
- # Extra platform methods used for accessing Chef data such as a run list,
47
- # node attributes, etc.
48
- module Cheflike
49
-
50
- # @return [Array] Array of Chef run_list items
51
- def run_list
52
- Array(data[:run_list])
53
- end
54
-
55
- # @return [Hash] Hash of Chef node attributes
56
- def attributes
57
- data[:attributes] || Hash.new
58
- end
59
- end
60
-
61
- private
62
-
63
- attr_reader :data
64
-
65
- def validate_options(opts)
66
- [:name].each do |k|
67
- raise ClientError, "Platform#new requires option :#{k}" if opts[k].nil?
37
+ @name = options.fetch(:name) do
38
+ raise ClientError, "Platform#new requires option :name"
68
39
  end
69
40
  end
70
41
  end