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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -1
- data/Gemfile +1 -1
- data/README.md +18 -7
- data/Rakefile +8 -1
- data/features/kitchen_init_command.feature +90 -11
- data/features/step_definitions/git_steps.rb +3 -0
- data/lib/kitchen/busser.rb +79 -45
- data/lib/kitchen/cli.rb +14 -13
- data/lib/kitchen/config.rb +79 -138
- data/lib/kitchen/data_munger.rb +224 -0
- data/lib/kitchen/driver/base.rb +4 -31
- data/lib/kitchen/driver/ssh_base.rb +6 -16
- data/lib/kitchen/driver.rb +4 -0
- data/lib/kitchen/generator/init.rb +20 -9
- data/lib/kitchen/instance.rb +53 -58
- data/lib/kitchen/lazy_hash.rb +50 -0
- data/lib/kitchen/platform.rb +2 -31
- data/lib/kitchen/provisioner/base.rb +55 -9
- data/lib/kitchen/provisioner/chef/berkshelf.rb +76 -0
- data/lib/kitchen/provisioner/chef/librarian.rb +72 -0
- data/lib/kitchen/provisioner/chef_base.rb +159 -78
- data/lib/kitchen/provisioner/chef_solo.rb +6 -36
- data/lib/kitchen/provisioner/chef_zero.rb +70 -59
- data/lib/kitchen/provisioner/dummy.rb +28 -0
- data/lib/kitchen/provisioner.rb +6 -4
- data/lib/kitchen/shell_out.rb +2 -5
- data/lib/kitchen/ssh.rb +1 -1
- data/lib/kitchen/suite.rb +10 -79
- data/lib/kitchen/util.rb +2 -2
- data/lib/kitchen/version.rb +2 -2
- data/lib/kitchen.rb +5 -0
- data/spec/kitchen/config_spec.rb +84 -123
- data/spec/kitchen/data_munger_spec.rb +1412 -0
- data/spec/kitchen/driver/base_spec.rb +30 -0
- data/spec/kitchen/instance_spec.rb +868 -86
- data/spec/kitchen/lazy_hash_spec.rb +63 -0
- data/spec/kitchen/platform_spec.rb +0 -22
- data/spec/kitchen/provisioner/base_spec.rb +210 -0
- data/spec/kitchen/provisioner_spec.rb +70 -0
- data/spec/kitchen/suite_spec.rb +25 -38
- data/spec/spec_helper.rb +1 -0
- data/support/chef-client-zero.rb +51 -35
- data/support/dummy-validation.pem +27 -0
- data/templates/init/kitchen.yml.erb +10 -22
- data/test-kitchen.gemspec +1 -2
- 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 #{
|
38
|
-
"--json-attributes #{
|
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
|
-
|
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(
|
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
|
-
|
31
|
-
|
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
|
-
#{
|
47
|
-
if ! #{sudo(
|
48
|
-
echo "
|
49
|
-
|
50
|
-
|
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
|
-
|
58
|
-
|
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
|
93
|
-
|
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
|
-
|
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(
|
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
|
data/lib/kitchen/provisioner.rb
CHANGED
@@ -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,
|
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(
|
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." +
|
data/lib/kitchen/shell_out.rb
CHANGED
@@ -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
|
-
|
63
|
+
debug("#{subject} BEGIN (#{display_cmd(cmd)})")
|
67
64
|
sh = Mixlib::ShellOut.new(cmd, shell_opts(options))
|
68
65
|
sh.run_command
|
69
|
-
|
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
data/lib/kitchen/suite.rb
CHANGED
@@ -18,8 +18,8 @@
|
|
18
18
|
|
19
19
|
module Kitchen
|
20
20
|
|
21
|
-
# A
|
22
|
-
#
|
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 [
|
34
|
-
attr_reader :
|
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
|
-
|
43
|
-
|
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
|
-
#
|
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
|
-
#
|
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"
|