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

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.
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
@@ -27,6 +27,8 @@ module Kitchen
27
27
  # @author Fletcher Nichol <fnichol@nichol.ca>
28
28
  class ChefSolo < ChefBase
29
29
 
30
+ default_config :solo_rb, {}
31
+
30
32
  def create_sandbox
31
33
  create_chef_sandbox { prepare_solo_rb }
32
34
  end
@@ -34,52 +36,20 @@ module Kitchen
34
36
  def run_command
35
37
  [
36
38
  sudo('chef-solo'),
37
- "--config #{home_path}/solo.rb",
38
- "--json-attributes #{home_path}/dna.json",
39
+ "--config #{config[:root_path]}/solo.rb",
40
+ "--json-attributes #{config[:root_path]}/dna.json",
39
41
  config[:log_file] ? "--logfile #{config[:log_file]}" : nil,
40
42
  "--log_level #{config[:log_level]}"
41
43
  ].join(" ")
42
44
  end
43
45
 
44
- def home_path
45
- "/tmp/kitchen-chef-solo".freeze
46
- end
47
-
48
46
  private
49
47
 
50
48
  def prepare_solo_rb
51
- solo = []
52
- solo << %{node_name "#{instance.name}"}
53
- solo << %{file_cache_path "#{home_path}/cache"}
54
- solo << %{cookbook_path ["#{home_path}/cookbooks","#{home_path}/site-cookbooks"]}
55
- solo << %{role_path "#{home_path}/roles"}
56
- if config[:chef]
57
- if config[:chef][:http_proxy]
58
- solo << %{http_proxy "#{config[:chef][:http_proxy]}"}
59
- end
60
- if config[:chef][:https_proxy]
61
- solo << %{https_proxy "#{config[:chef][:https_proxy]}"}
62
- end
63
- if config[:chef][:no_proxy]
64
- solo << %{no_proxy "#{config[:chef][:no_proxy]}"}
65
- end
66
- end
67
- if instance.suite.data_bags_path
68
- solo << %{data_bag_path "#{home_path}/data_bags"}
69
- end
70
- if instance.suite.environments_path
71
- solo << %{environment_path "#{home_path}/environments"}
72
- end
73
- if instance.suite.environment
74
- solo << %{environment "#{instance.suite.environment}"}
75
- end
76
- if instance.suite.encrypted_data_bag_secret_key_path
77
- secret = "#{home_path}/encrypted_data_bag_secret"
78
- solo << %{encrypted_data_bag_secret "#{secret}"}
79
- end
49
+ data = default_config_rb.merge(config[:solo_rb])
80
50
 
81
51
  File.open(File.join(tmpdir, "solo.rb"), "wb") do |file|
82
- file.write(solo.join("\n"))
52
+ file.write(format_config_file(data))
83
53
  end
84
54
  end
85
55
  end
@@ -27,97 +27,108 @@ module Kitchen
27
27
  # @author Fletcher Nichol <fnichol@nichol.ca>
28
28
  class ChefZero < ChefBase
29
29
 
30
- def initialize(instance, config)
31
- super
32
- @ruby_binpath = config.fetch(:ruby_binpath, DEFAULT_RUBY_BINPATH)
33
- end
30
+ default_config :client_rb, {}
31
+ default_config :ruby_bindir, "/opt/chef/embedded/bin"
34
32
 
35
33
  def create_sandbox
36
34
  create_chef_sandbox do
37
35
  prepare_chef_client_zero_rb
36
+ prepare_validation_pem
38
37
  prepare_client_rb
39
38
  end
40
39
  end
41
40
 
42
41
  def prepare_command
42
+ return if local_mode_supported?
43
+
44
+ ruby_bin = config[:ruby_bindir]
45
+
43
46
  # use Bourne (/bin/sh) as Bash does not exist on all Unix flavors
47
+ #
48
+ # * we are installing latest chef in order to get chef-zero and
49
+ # Chef::ChefFS only. The version of Chef that gets run will be
50
+ # the installed omnibus package. Yep, this is funky :)
44
51
  <<-PREPARE.gsub(/^ {10}/, '')
45
52
  sh -c '
46
- #{sandbox_env(true)}
47
- if ! #{sudo(gem_bin)} list chef-zero -i >/dev/null; then
48
- echo "-----> Installing chef-zero and knife-essentials gems"
49
- #{sudo(gem_bin)} install \
50
- chef-zero knife-essentials --no-ri --no-rdoc
53
+ #{chef_client_zero_env(:export)}
54
+ if ! #{sudo("#{ruby_bin}/gem")} list chef-zero -i >/dev/null; then
55
+ echo ">>>>>> Attempting to use chef-zero with old version of Chef"
56
+ echo "-----> Installing chef zero dependencies"
57
+ #{sudo("#{ruby_bin}/gem")} install chef --no-ri --no-rdoc --conservative
51
58
  fi'
52
59
  PREPARE
53
60
  end
54
61
 
55
62
  def run_command
56
- [
57
- sandbox_env,
58
- sudo(ruby_bin),
59
- "#{home_path}/chef-client-zero.rb",
60
- "--config #{home_path}/client.rb",
61
- "--json-attributes #{home_path}/dna.json",
63
+ args = [
64
+ "--config #{config[:root_path]}/client.rb",
65
+ "--json-attributes #{config[:root_path]}/dna.json",
62
66
  "--log_level #{config[:log_level]}"
63
- ].join(" ")
64
- end
65
-
66
- def home_path
67
- "/tmp/kitchen-chef-zero".freeze
68
- end
69
-
70
- def gem_bin
71
- @gem_bin ||= File.join(ruby_binpath, 'gem')
72
- end
73
-
74
- def ruby_bin
75
- @ruby_bin ||= File.join(ruby_binpath, 'ruby')
76
- end
77
-
78
- private
79
-
80
- DEFAULT_RUBY_BINPATH = "/opt/chef/embedded/bin".freeze
81
-
82
- attr_reader :ruby_binpath
83
-
84
- def sandbox_env(export=false)
85
- env = [
86
- "GEM_HOME=#{home_path}/gems",
87
- "GEM_PATH=$GEM_HOME",
88
- "GEM_CACHE=$GEM_HOME/cache",
89
- "PATH=$PATH:$GEM_HOME/bin"
90
67
  ]
91
68
 
92
- if export
93
- env << "; export GEM_HOME GEM_PATH GEM_CACHE PATH;"
69
+ if local_mode_supported?
70
+ ["#{sudo('chef-client')} -z"].concat(args).join(" ")
71
+ else
72
+ [
73
+ chef_client_zero_env,
74
+ sudo("#{config[:ruby_bindir]}/ruby"),
75
+ "#{config[:root_path]}/chef-client-zero.rb"
76
+ ].concat(args).join(" ")
94
77
  end
95
-
96
- env.join(" ")
97
78
  end
98
79
 
80
+ private
81
+
99
82
  def prepare_chef_client_zero_rb
83
+ return if local_mode_supported?
84
+
100
85
  source = File.join(File.dirname(__FILE__),
101
86
  %w{.. .. .. support chef-client-zero.rb})
102
87
  FileUtils.cp(source, File.join(tmpdir, "chef-client-zero.rb"))
103
88
  end
104
89
 
90
+ def prepare_validation_pem
91
+ source = File.join(File.dirname(__FILE__),
92
+ %w{.. .. .. support dummy-validation.pem})
93
+ FileUtils.cp(source, File.join(tmpdir, "validation.pem"))
94
+ end
95
+
105
96
  def prepare_client_rb
106
- client = []
107
- client << %{node_name "#{instance.name}"}
108
- client << %{file_cache_path "#{home_path}/cache"}
109
- client << %{cookbook_path "#{home_path}/cookbooks"}
110
- client << %{node_path "#{home_path}/nodes"}
111
- client << %{client_path "#{home_path}/clients"}
112
- client << %{role_path "#{home_path}/roles"}
113
- client << %{data_bag_path "#{home_path}/data_bags"}
114
- if instance.suite.encrypted_data_bag_secret_key_path
115
- secret = "#{home_path}/encrypted_data_bag_secret"
116
- client << %{encrypted_data_bag_secret "#{secret}"}
117
- end
97
+ data = default_config_rb.merge(config[:client_rb])
118
98
 
119
99
  File.open(File.join(tmpdir, "client.rb"), "wb") do |file|
120
- file.write(client.join("\n"))
100
+ file.write(format_config_file(data))
101
+ end
102
+ end
103
+
104
+ def chef_client_zero_env(extra = nil)
105
+ args = [
106
+ %{CHEF_REPO_PATH="#{config[:root_path]}"},
107
+ %{GEM_HOME="#{config[:root_path]}/chef-client-zero-gems"},
108
+ %{GEM_PATH="#{config[:root_path]}/chef-client-zero-gems"},
109
+ %{GEM_CACHE="#{config[:root_path]}/chef-client-zero-gems/cache"}
110
+ ]
111
+ if extra == :export
112
+ args << %{; export CHEF_REPO_PATH GEM_HOME GEM_PATH GEM_CACHE;}
113
+ end
114
+ args.join(" ")
115
+ end
116
+
117
+ # Determines whether or not local mode (a.k.a chef zero mode) is
118
+ # supported in the version of Chef as determined by inspecting the
119
+ # require_chef_omnibus config variable.
120
+ #
121
+ # The only way this method returns false is if require_chef_omnibus has
122
+ # an explicit version set to less than 11.8.0, when chef zero mode was
123
+ # introduced. Otherwise a modern Chef installation is assumed.
124
+ def local_mode_supported?
125
+ version = config[:require_chef_omnibus]
126
+
127
+ case version
128
+ when nil, false, true, "latest"
129
+ true
130
+ else
131
+ Gem::Version.new(version) >= Gem::Version.new("11.8.0") ? true : false
121
132
  end
122
133
  end
123
134
  end
@@ -0,0 +1,28 @@
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 'kitchen'
20
+
21
+ module Kitchen
22
+
23
+ module Provisioner
24
+
25
+ class Dummy < Kitchen::Provisioner::Base
26
+ end
27
+ end
28
+ end
@@ -27,19 +27,21 @@ module Kitchen
27
27
  # @author Fletcher Nichol <fnichol@nichol.ca>
28
28
  module Provisioner
29
29
 
30
+ # Default provisioner to use
31
+ DEFAULT_PLUGIN = "chef_solo".freeze
32
+
30
33
  # Returns an instance of a provisioner given a plugin type string.
31
34
  #
32
35
  # @param plugin [String] a provisioner plugin type, to be constantized
36
+ # @param config [Hash] a configuration hash to initialize the provisioner
33
37
  # @return [Provisioner::Base] a driver instance
34
38
  # @raise [ClientError] if a provisioner instance could not be created
35
- def self.for_plugin(plugin, instance, config)
39
+ def self.for_plugin(plugin, config)
36
40
  require("kitchen/provisioner/#{plugin}")
37
41
 
38
42
  str_const = Thor::Util.camel_case(plugin)
39
43
  klass = self.const_get(str_const)
40
- klass.new(instance, config)
41
- rescue UserError
42
- raise
44
+ klass.new(config)
43
45
  rescue LoadError, NameError
44
46
  raise ClientError,
45
47
  "Could not load the '#{plugin}' provisioner from the load path." +
@@ -37,8 +37,6 @@ module Kitchen
37
37
  # sudo
38
38
  # @option options [String] :log_subject used in the output or log header
39
39
  # for clarity and context. Default is "local".
40
- # @option options [TrueClass, FalseClass] :quiet whether or not to echo
41
- # logging commands. Default is false.
42
40
  # @option options [String] :cwd the directory to chdir to before running
43
41
  # the command
44
42
  # @option options [Hash] :environment a Hash of environment variables to
@@ -59,14 +57,13 @@ module Kitchen
59
57
  # @raise [Error] for all other unexpected exceptions
60
58
  def run_command(cmd, options = {})
61
59
  use_sudo = options[:use_sudo].nil? ? false : options[:use_sudo]
62
- quiet = options[:quiet]
63
60
  cmd = "sudo -E #{cmd}" if use_sudo
64
61
  subject = "[#{options[:log_subject] || "local"} command]"
65
62
 
66
- info("#{subject} BEGIN (#{display_cmd(cmd)})") unless quiet
63
+ debug("#{subject} BEGIN (#{display_cmd(cmd)})")
67
64
  sh = Mixlib::ShellOut.new(cmd, shell_opts(options))
68
65
  sh.run_command
69
- info("#{subject} END #{Util.duration(sh.execution_time)}") unless quiet
66
+ debug("#{subject} END #{Util.duration(sh.execution_time)}")
70
67
  sh.error!
71
68
  sh.stdout
72
69
  rescue Mixlib::ShellOut::ShellCommandFailed => ex
data/lib/kitchen/ssh.rb CHANGED
@@ -62,7 +62,7 @@ module Kitchen
62
62
  if progress.nil?
63
63
  progress = lambda { |ch, name, sent, total|
64
64
  if sent == total
65
- logger.info("Uploaded #{name} (#{total} bytes)")
65
+ logger.debug("Uploaded #{name} (#{total} bytes)")
66
66
  end
67
67
  }
68
68
  end
data/lib/kitchen/suite.rb CHANGED
@@ -18,8 +18,8 @@
18
18
 
19
19
  module Kitchen
20
20
 
21
- # A Chef run_list and attribute hash that will be used in a convergence
22
- # integration.
21
+ # A logical configuration representing a test case or fixture that will be
22
+ # executed on a platform.
23
23
  #
24
24
  # @author Fletcher Nichol <fnichol@nichol.ca>
25
25
  class Suite
@@ -30,91 +30,22 @@ module Kitchen
30
30
  # @return [Array] Array of names of excluded platforms
31
31
  attr_reader :excludes
32
32
 
33
- # @return [Hash] suite specific driver_config hash
34
- attr_reader :driver_config
33
+ # @return [Array] Array of names of only included platforms
34
+ attr_reader :includes
35
35
 
36
36
  # Constructs a new suite.
37
37
  #
38
38
  # @param [Hash] options configuration for a new suite
39
39
  # @option options [String] :name logical name of this suit (**Required**)
40
40
  # @option options [String] :excludes Array of names of excluded platforms
41
+ # @option options [String] :includes Array of names of only included
42
+ # platforms
41
43
  def initialize(options = {})
42
- options = options.dup
43
- validate_options(options)
44
-
45
- @name = options.delete(:name)
46
- @excludes = Array(options[:excludes])
47
- @driver_config = options.delete(:driver_config) || {}
48
- @data = options
49
- end
50
-
51
- # Extra suite methods used for accessing Chef data such as a run list,
52
- # node attributes, etc.
53
- module Cheflike
54
-
55
- # @return [Array] Array of Chef run_list items
56
- def run_list
57
- Array(data[:run_list])
58
- end
59
-
60
- # @return [Hash] Hash of Chef node attributes
61
- def attributes
62
- data[:attributes] || Hash.new
63
- end
64
-
65
- # @return [String] local path to the suite's data bags, or nil if one
66
- # does not exist
67
- def data_bags_path
68
- data[:data_bags_path]
69
- end
70
-
71
- # @return [String] local path to the suite's encrypted data bag secret
72
- # key path, or nil if one does not exist
73
- def encrypted_data_bag_secret_key_path
74
- data[:encrypted_data_bag_secret_key_path]
75
- end
76
-
77
- # @return [String] local path to the suite's roles, or nil if one does
78
- # not exist
79
- def roles_path
80
- data[:roles_path]
81
- end
82
-
83
- # @return [String] local path to the suite's nodes, or nil if one does
84
- # not exist
85
- def nodes_path
86
- data[:nodes_path]
87
- end
88
-
89
- # @return [String] local path to the suite's environments, or nil if one does
90
- # not exist
91
- def environments_path
92
- data[:environments_path]
93
- end
94
-
95
- # @return [String] the suite's environment, or nil if one does
96
- # not exist
97
- def environment
98
- data[:environment]
99
- end
100
-
101
- end
102
-
103
- # Extra suite methods used for accessing Puppet data such as a manifest.
104
- module Puppetlike
105
-
106
- def manifest
107
- end
108
- end
109
-
110
- private
111
-
112
- attr_reader :data
113
-
114
- def validate_options(opts)
115
- [:name].each do |k|
116
- raise ClientError, "Suite#new requires option :#{k}" if opts[k].nil?
44
+ @name = options.fetch(:name) do
45
+ raise ClientError, "Suite#new requires option :name"
117
46
  end
47
+ @excludes = options.fetch(:excludes, [])
48
+ @includes = options.fetch(:includes, [])
118
49
  end
119
50
  end
120
51
  end
data/lib/kitchen/util.rb CHANGED
@@ -154,7 +154,7 @@ module Kitchen
154
154
  return 0
155
155
  }
156
156
 
157
- # do_curl URL FILENAME
157
+ # do_perl URL FILENAME
158
158
  do_perl() {
159
159
  echo "trying perl..."
160
160
  perl -e "use LWP::Simple; getprint($ARGV[0]);" "$1" > "$2"
@@ -168,7 +168,7 @@ module Kitchen
168
168
  return 0
169
169
  }
170
170
 
171
- # do_curl URL FILENAME
171
+ # do_python URL FILENAME
172
172
  do_python() {
173
173
  echo "trying python..."
174
174
  python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2"