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
@@ -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"