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