test-kitchen 1.2.1 → 1.3.0
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/.cane +1 -1
- data/.rubocop.yml +3 -0
- data/.travis.yml +20 -9
- data/CHANGELOG.md +219 -108
- data/Gemfile +10 -6
- data/Guardfile +38 -9
- data/README.md +11 -1
- data/Rakefile +21 -37
- data/bin/kitchen +4 -4
- data/features/kitchen_action_commands.feature +161 -0
- data/features/kitchen_console_command.feature +34 -0
- data/features/kitchen_diagnose_command.feature +64 -0
- data/features/kitchen_init_command.feature +29 -17
- data/features/kitchen_list_command.feature +2 -2
- data/features/kitchen_login_command.feature +56 -0
- data/features/{sink_command.feature → kitchen_sink_command.feature} +0 -0
- data/features/kitchen_test_command.feature +88 -0
- data/features/step_definitions/gem_steps.rb +8 -6
- data/features/step_definitions/git_steps.rb +4 -2
- data/features/step_definitions/output_steps.rb +5 -0
- data/features/support/env.rb +12 -9
- data/lib/kitchen.rb +60 -38
- data/lib/kitchen/base64_stream.rb +55 -0
- data/lib/kitchen/busser.rb +124 -58
- data/lib/kitchen/cli.rb +121 -38
- data/lib/kitchen/collection.rb +3 -3
- data/lib/kitchen/color.rb +4 -4
- data/lib/kitchen/command.rb +78 -11
- data/lib/kitchen/command/action.rb +3 -2
- data/lib/kitchen/command/console.rb +12 -5
- data/lib/kitchen/command/diagnose.rb +17 -3
- data/lib/kitchen/command/driver_discover.rb +26 -7
- data/lib/kitchen/command/exec.rb +41 -0
- data/lib/kitchen/command/list.rb +44 -14
- data/lib/kitchen/command/login.rb +2 -1
- data/lib/kitchen/command/sink.rb +2 -1
- data/lib/kitchen/command/test.rb +5 -4
- data/lib/kitchen/config.rb +146 -14
- data/lib/kitchen/configurable.rb +314 -0
- data/lib/kitchen/data_munger.rb +522 -18
- data/lib/kitchen/diagnostic.rb +43 -4
- data/lib/kitchen/driver.rb +4 -4
- data/lib/kitchen/driver/base.rb +80 -115
- data/lib/kitchen/driver/dummy.rb +34 -6
- data/lib/kitchen/driver/proxy.rb +14 -3
- data/lib/kitchen/driver/ssh_base.rb +61 -7
- data/lib/kitchen/errors.rb +109 -9
- data/lib/kitchen/generator/driver_create.rb +39 -5
- data/lib/kitchen/generator/init.rb +130 -45
- data/lib/kitchen/instance.rb +162 -28
- data/lib/kitchen/lazy_hash.rb +79 -7
- data/lib/kitchen/loader/yaml.rb +159 -27
- data/lib/kitchen/logger.rb +267 -21
- data/lib/kitchen/logging.rb +30 -3
- data/lib/kitchen/login_command.rb +11 -2
- data/lib/kitchen/metadata_chopper.rb +2 -2
- data/lib/kitchen/provisioner.rb +4 -4
- data/lib/kitchen/provisioner/base.rb +107 -103
- data/lib/kitchen/provisioner/chef/berkshelf.rb +36 -8
- data/lib/kitchen/provisioner/chef/librarian.rb +40 -11
- data/lib/kitchen/provisioner/chef_base.rb +206 -167
- data/lib/kitchen/provisioner/chef_solo.rb +25 -7
- data/lib/kitchen/provisioner/chef_zero.rb +105 -29
- data/lib/kitchen/provisioner/dummy.rb +1 -1
- data/lib/kitchen/provisioner/shell.rb +21 -6
- data/lib/kitchen/rake_tasks.rb +8 -3
- data/lib/kitchen/shell_out.rb +15 -18
- data/lib/kitchen/ssh.rb +122 -27
- data/lib/kitchen/state_file.rb +24 -7
- data/lib/kitchen/thor_tasks.rb +9 -4
- data/lib/kitchen/util.rb +43 -118
- data/lib/kitchen/version.rb +1 -1
- data/lib/vendor/hash_recursive_merge.rb +10 -2
- data/spec/kitchen/base64_stream_spec.rb +77 -0
- data/spec/kitchen/busser_spec.rb +490 -0
- data/spec/kitchen/collection_spec.rb +10 -10
- data/spec/kitchen/color_spec.rb +2 -2
- data/spec/kitchen/config_spec.rb +234 -62
- data/spec/kitchen/configurable_spec.rb +490 -0
- data/spec/kitchen/data_munger_spec.rb +1070 -862
- data/spec/kitchen/diagnostic_spec.rb +79 -0
- data/spec/kitchen/driver/base_spec.rb +80 -85
- data/spec/kitchen/driver/dummy_spec.rb +43 -14
- data/spec/kitchen/driver/proxy_spec.rb +134 -0
- data/spec/kitchen/driver/ssh_base_spec.rb +644 -0
- data/spec/kitchen/driver_spec.rb +15 -15
- data/spec/kitchen/errors_spec.rb +309 -0
- data/spec/kitchen/instance_spec.rb +143 -46
- data/spec/kitchen/lazy_hash_spec.rb +36 -9
- data/spec/kitchen/loader/yaml_spec.rb +237 -226
- data/spec/kitchen/logger_spec.rb +419 -0
- data/spec/kitchen/logging_spec.rb +59 -0
- data/spec/kitchen/login_command_spec.rb +49 -0
- data/spec/kitchen/metadata_chopper_spec.rb +82 -0
- data/spec/kitchen/platform_spec.rb +4 -4
- data/spec/kitchen/provisioner/base_spec.rb +65 -125
- data/spec/kitchen/provisioner/chef_base_spec.rb +798 -0
- data/spec/kitchen/provisioner/chef_solo_spec.rb +316 -0
- data/spec/kitchen/provisioner/chef_zero_spec.rb +624 -0
- data/spec/kitchen/provisioner/shell_spec.rb +269 -0
- data/spec/kitchen/provisioner_spec.rb +6 -6
- data/spec/kitchen/shell_out_spec.rb +143 -0
- data/spec/kitchen/ssh_spec.rb +683 -0
- data/spec/kitchen/state_file_spec.rb +28 -21
- data/spec/kitchen/suite_spec.rb +7 -7
- data/spec/kitchen/util_spec.rb +68 -10
- data/spec/kitchen_spec.rb +107 -0
- data/spec/spec_helper.rb +18 -13
- data/support/chef-client-zero.rb +10 -9
- data/support/chef_helpers.sh +16 -0
- data/support/download_helpers.sh +109 -0
- data/test-kitchen.gemspec +42 -33
- metadata +107 -33
@@ -16,7 +16,8 @@
|
|
16
16
|
# See the License for the specific language governing permissions and
|
17
17
|
# limitations under the License.
|
18
18
|
|
19
|
-
require
|
19
|
+
require "rubygems/gem_runner"
|
20
|
+
require "thor/group"
|
20
21
|
|
21
22
|
module Kitchen
|
22
23
|
|
@@ -30,143 +31,202 @@ module Kitchen
|
|
30
31
|
|
31
32
|
include Thor::Actions
|
32
33
|
|
33
|
-
class_option :driver,
|
34
|
+
class_option :driver,
|
35
|
+
:type => :array,
|
36
|
+
:aliases => "-D",
|
34
37
|
:default => "kitchen-vagrant",
|
35
|
-
:desc => <<-D.gsub(/^\s+/,
|
38
|
+
:desc => <<-D.gsub(/^\s+/, "").gsub(/\n/, " ")
|
36
39
|
One or more Kitchen Driver gems to be installed or added to a
|
37
40
|
Gemfile
|
38
41
|
D
|
39
42
|
|
40
|
-
class_option :provisioner,
|
43
|
+
class_option :provisioner,
|
44
|
+
:type => :string,
|
45
|
+
:aliases => "-P",
|
41
46
|
:default => "chef_solo",
|
42
|
-
:desc => <<-D.gsub(/^\s+/,
|
47
|
+
:desc => <<-D.gsub(/^\s+/, "").gsub(/\n/, " ")
|
43
48
|
The default Kitchen Provisioner to use
|
44
49
|
D
|
45
50
|
|
46
|
-
class_option :create_gemfile,
|
47
|
-
:
|
51
|
+
class_option :create_gemfile,
|
52
|
+
:type => :boolean,
|
53
|
+
:default => false,
|
54
|
+
:desc => <<-D.gsub(/^\s+/, "").gsub(/\n/, " ")
|
48
55
|
Whether or not to create a Gemfile if one does not exist.
|
49
56
|
Default: false
|
50
57
|
D
|
51
58
|
|
59
|
+
# Invoke the command.
|
52
60
|
def init
|
53
61
|
self.class.source_root(Kitchen.source_root.join("templates", "init"))
|
54
62
|
|
55
63
|
create_kitchen_yaml
|
56
|
-
prepare_rakefile
|
57
|
-
prepare_thorfile
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
append_to_gitignore(".kitchen.local.yml")
|
62
|
-
end
|
63
|
-
prepare_gemfile if init_gemfile?
|
64
|
+
prepare_rakefile
|
65
|
+
prepare_thorfile
|
66
|
+
create_test_dir
|
67
|
+
prepare_gitignore
|
68
|
+
prepare_gemfile
|
64
69
|
add_drivers
|
65
|
-
|
66
|
-
if @display_bundle_msg
|
67
|
-
say "You must run `bundle install' to fetch any new gems.", :red
|
68
|
-
end
|
70
|
+
display_bundle_message
|
69
71
|
end
|
70
72
|
|
71
73
|
private
|
72
74
|
|
75
|
+
# Creates the `.kitchen.yml` file.
|
76
|
+
#
|
77
|
+
# @api private
|
73
78
|
def create_kitchen_yaml
|
74
|
-
cookbook_name = if File.
|
75
|
-
MetadataChopper.extract(
|
79
|
+
cookbook_name = if File.exist?(File.expand_path("metadata.rb"))
|
80
|
+
MetadataChopper.extract("metadata.rb").first
|
76
81
|
else
|
77
82
|
nil
|
78
83
|
end
|
79
84
|
run_list = cookbook_name ? "recipe[#{cookbook_name}::default]" : nil
|
80
|
-
driver_plugin = Array(options[:driver]).first ||
|
85
|
+
driver_plugin = Array(options[:driver]).first || "dummy"
|
81
86
|
|
82
|
-
template("kitchen.yml.erb", ".kitchen.yml",
|
83
|
-
:driver_plugin => driver_plugin.sub(/^kitchen-/,
|
87
|
+
template("kitchen.yml.erb", ".kitchen.yml",
|
88
|
+
:driver_plugin => driver_plugin.sub(/^kitchen-/, ""),
|
84
89
|
:provisioner => options[:provisioner],
|
85
90
|
:run_list => Array(run_list)
|
86
|
-
|
91
|
+
)
|
87
92
|
end
|
88
93
|
|
94
|
+
# @return [true,false] whether or not a Gemfile needs to be initialized
|
95
|
+
# @api private
|
89
96
|
def init_gemfile?
|
90
|
-
File.
|
97
|
+
File.exist?(File.join(destination_root, "Gemfile")) ||
|
91
98
|
options[:create_gemfile]
|
92
99
|
end
|
93
100
|
|
101
|
+
# @return [true,false] whether or not a Rakefile needs to be initialized
|
102
|
+
# @api private
|
94
103
|
def init_rakefile?
|
95
|
-
File.
|
104
|
+
File.exist?(File.join(destination_root, "Rakefile")) &&
|
96
105
|
not_in_file?("Rakefile", %r{require 'kitchen/rake_tasks'})
|
97
106
|
end
|
98
107
|
|
108
|
+
# @return [true,false] whether or not a Thorfile needs to be initialized
|
109
|
+
# @api private
|
99
110
|
def init_thorfile?
|
100
|
-
File.
|
111
|
+
File.exist?(File.join(destination_root, "Thorfile")) &&
|
101
112
|
not_in_file?("Thorfile", %r{require 'kitchen/thor_tasks'})
|
102
113
|
end
|
103
114
|
|
115
|
+
# @return [true,false] whether or not a test directory needs to be
|
116
|
+
# initialized
|
117
|
+
# @api private
|
104
118
|
def init_test_dir?
|
105
119
|
Dir.glob("test/integration/*").select { |d| File.directory?(d) }.empty?
|
106
120
|
end
|
107
121
|
|
122
|
+
# @return [true,false] whether or not a `.gitignore` file needs to be
|
123
|
+
# initialized
|
124
|
+
# @api private
|
108
125
|
def init_git?
|
109
|
-
File.directory?(File.join(destination_root,
|
126
|
+
File.directory?(File.join(destination_root, ".git"))
|
110
127
|
end
|
111
128
|
|
129
|
+
# Prepares a Rakefile.
|
130
|
+
#
|
131
|
+
# @api private
|
112
132
|
def prepare_rakefile
|
113
|
-
|
133
|
+
return unless init_rakefile?
|
134
|
+
|
135
|
+
rakedoc = <<-RAKE.gsub(/^ {10}/, "")
|
114
136
|
|
115
137
|
begin
|
116
|
-
require
|
138
|
+
require "kitchen/rake_tasks"
|
117
139
|
Kitchen::RakeTasks.new
|
118
140
|
rescue LoadError
|
119
|
-
puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV[
|
141
|
+
puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV["CI"]
|
120
142
|
end
|
121
143
|
RAKE
|
122
144
|
append_to_file(File.join(destination_root, "Rakefile"), rakedoc)
|
123
145
|
end
|
124
146
|
|
147
|
+
# Prepares a Thorfile.
|
148
|
+
#
|
149
|
+
# @api private
|
125
150
|
def prepare_thorfile
|
126
|
-
|
151
|
+
return unless init_thorfile?
|
152
|
+
|
153
|
+
thordoc = <<-THOR.gsub(/^ {10}/, "")
|
127
154
|
|
128
155
|
begin
|
129
|
-
require
|
156
|
+
require "kitchen/thor_tasks"
|
130
157
|
Kitchen::ThorTasks.new
|
131
158
|
rescue LoadError
|
132
|
-
puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV[
|
159
|
+
puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV["CI"]
|
133
160
|
end
|
134
161
|
THOR
|
135
162
|
append_to_file(File.join(destination_root, "Thorfile"), thordoc)
|
136
163
|
end
|
137
164
|
|
165
|
+
# Create the default test directory
|
166
|
+
#
|
167
|
+
# @api private
|
168
|
+
def create_test_dir
|
169
|
+
empty_directory "test/integration/default" if init_test_dir?
|
170
|
+
end
|
171
|
+
|
172
|
+
# Prepares the .gitignore file
|
173
|
+
#
|
174
|
+
# @api private
|
175
|
+
def prepare_gitignore
|
176
|
+
return unless init_git?
|
177
|
+
|
178
|
+
append_to_gitignore(".kitchen/")
|
179
|
+
append_to_gitignore(".kitchen.local.yml")
|
180
|
+
end
|
181
|
+
|
182
|
+
# Appends a line to the .gitignore file.
|
183
|
+
#
|
184
|
+
# @api private
|
138
185
|
def append_to_gitignore(line)
|
139
|
-
create_file(".gitignore") unless File.
|
186
|
+
create_file(".gitignore") unless File.exist?(File.join(destination_root, ".gitignore"))
|
140
187
|
|
141
188
|
if IO.readlines(File.join(destination_root, ".gitignore")).grep(%r{^#{line}}).empty?
|
142
189
|
append_to_file(".gitignore", "#{line}\n")
|
143
190
|
end
|
144
191
|
end
|
145
192
|
|
193
|
+
# Prepares a Gemfile.
|
194
|
+
#
|
195
|
+
# @api private
|
146
196
|
def prepare_gemfile
|
197
|
+
return unless init_gemfile?
|
198
|
+
|
147
199
|
create_gemfile_if_missing
|
148
200
|
add_gem_to_gemfile
|
149
201
|
end
|
150
202
|
|
203
|
+
# Creates a Gemfile if missing
|
204
|
+
#
|
205
|
+
# @api private
|
151
206
|
def create_gemfile_if_missing
|
152
|
-
unless File.
|
153
|
-
create_file("Gemfile", %{source
|
207
|
+
unless File.exist?(File.join(destination_root, "Gemfile"))
|
208
|
+
create_file("Gemfile", %{source "https://rubygems.org"\n\n})
|
154
209
|
end
|
155
210
|
end
|
156
211
|
|
212
|
+
# Appends entries to a Gemfile.
|
213
|
+
#
|
214
|
+
# @api private
|
157
215
|
def add_gem_to_gemfile
|
158
216
|
if not_in_file?("Gemfile", %r{gem ('|")test-kitchen('|")})
|
159
|
-
append_to_file("Gemfile", %{gem
|
217
|
+
append_to_file("Gemfile", %{gem "test-kitchen"\n})
|
160
218
|
@display_bundle_msg = true
|
161
219
|
end
|
162
220
|
end
|
163
221
|
|
222
|
+
# Appends driver gems to a Gemfile or installs them.
|
223
|
+
#
|
224
|
+
# @api private
|
164
225
|
def add_drivers
|
165
226
|
return if options[:driver].nil? || options[:driver].empty?
|
166
|
-
display_warning = false
|
167
227
|
|
168
228
|
Array(options[:driver]).each do |driver_gem|
|
169
|
-
if File.
|
229
|
+
if File.exist?(File.join(destination_root, "Gemfile")) || options[:create_gemfile]
|
170
230
|
add_driver_to_gemfile(driver_gem)
|
171
231
|
else
|
172
232
|
install_gem(driver_gem)
|
@@ -174,27 +234,52 @@ module Kitchen
|
|
174
234
|
end
|
175
235
|
end
|
176
236
|
|
237
|
+
# Appends a driver gem to a Gemfile.
|
238
|
+
#
|
239
|
+
# @api private
|
177
240
|
def add_driver_to_gemfile(driver_gem)
|
178
241
|
if not_in_file?("Gemfile", %r{gem ('|")#{driver_gem}('|")})
|
179
|
-
append_to_file("Gemfile", %{gem
|
242
|
+
append_to_file("Gemfile", %{gem "#{driver_gem}"\n})
|
180
243
|
@display_bundle_msg = true
|
181
244
|
end
|
182
245
|
end
|
183
246
|
|
247
|
+
# Installs a driver gem.
|
248
|
+
#
|
249
|
+
# @api private
|
184
250
|
def install_gem(driver_gem)
|
185
|
-
unbundlerize
|
186
|
-
|
251
|
+
unbundlerize { Gem::GemRunner.new.run(["install", driver_gem]) }
|
252
|
+
rescue Gem::SystemExitException => e
|
253
|
+
raise unless e.exit_code == 0
|
254
|
+
end
|
255
|
+
|
256
|
+
# Displays a bundle warning message to the user.
|
257
|
+
#
|
258
|
+
# @api private
|
259
|
+
def display_bundle_message
|
260
|
+
if @display_bundle_msg
|
261
|
+
say "You must run `bundle install' to fetch any new gems.", :red
|
187
262
|
end
|
188
263
|
end
|
189
264
|
|
265
|
+
# Determines whether or not a pattern is found in a file.
|
266
|
+
#
|
267
|
+
# @param filename [String] filename to read
|
268
|
+
# @param regexp [Regexp] a regular expression
|
269
|
+
# @return [true,false] whether or not a pattern is found in a file
|
270
|
+
# @api private
|
190
271
|
def not_in_file?(filename, regexp)
|
191
272
|
IO.readlines(File.join(destination_root, filename)).grep(regexp).empty?
|
192
273
|
end
|
193
274
|
|
275
|
+
# Save off any Bundler/Ruby-related environment variables so that the
|
276
|
+
# yielded block can run "bundler-free" (and restore at the end).
|
277
|
+
#
|
278
|
+
# @api private
|
194
279
|
def unbundlerize
|
195
280
|
keys = ENV.keys.select { |key| key =~ /^BUNDLER?_/ } + %w[RUBYOPT]
|
196
281
|
|
197
|
-
keys.each { |key| ENV["__#{key}"] = ENV[key]
|
282
|
+
keys.each { |key| ENV["__#{key}"] = ENV[key]; ENV.delete(key) }
|
198
283
|
yield
|
199
284
|
keys.each { |key| ENV[key] = ENV.delete("__#{key}") }
|
200
285
|
end
|
data/lib/kitchen/instance.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
4
4
|
#
|
5
|
-
# Copyright (C) 2012, Fletcher Nichol
|
5
|
+
# Copyright (C) 2012, 2013, 2014, Fletcher Nichol
|
6
6
|
#
|
7
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
8
|
# you may not use this file except in compliance with the License.
|
@@ -16,8 +16,8 @@
|
|
16
16
|
# See the License for the specific language governing permissions and
|
17
17
|
# limitations under the License.
|
18
18
|
|
19
|
-
require
|
20
|
-
require
|
19
|
+
require "benchmark"
|
20
|
+
require "fileutils"
|
21
21
|
|
22
22
|
module Kitchen
|
23
23
|
|
@@ -31,10 +31,18 @@ module Kitchen
|
|
31
31
|
include Logging
|
32
32
|
|
33
33
|
class << self
|
34
|
+
|
35
|
+
# @return [Hash] a hash of mutxes, arranged by Driver class names
|
36
|
+
# @api private
|
34
37
|
attr_accessor :mutexes
|
35
38
|
|
39
|
+
# Generates a name for an instance given a suite and platform.
|
40
|
+
#
|
41
|
+
# @param suite [Suite,#name] a Suite
|
42
|
+
# @param platform [Platform,#name] a Platform
|
43
|
+
# @return [String] a normalized, consistent name for an instance
|
36
44
|
def name_for(suite, platform)
|
37
|
-
"#{suite.name}-#{platform.name}".gsub(
|
45
|
+
"#{suite.name}-#{platform.name}".gsub(%r{[_,/]}, "-").gsub(/\./, "")
|
38
46
|
end
|
39
47
|
end
|
40
48
|
|
@@ -66,11 +74,11 @@ module Kitchen
|
|
66
74
|
# Creates a new instance, given a suite and a platform.
|
67
75
|
#
|
68
76
|
# @param [Hash] options configuration for a new suite
|
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)
|
77
|
+
# @option options [Suite] :suite the suite (**Required**)
|
78
|
+
# @option options [Platform] :platform the platform (**Required**)
|
79
|
+
# @option options [Driver::Base] :driver the driver (**Required**)
|
72
80
|
# @option options [Provisioner::Base] :provisioner the provisioner
|
73
|
-
# (**Required)
|
81
|
+
# (**Required**)
|
74
82
|
# @option options [Busser] :busser the busser logger (**Required**)
|
75
83
|
# @option options [Logger] :logger the instance logger
|
76
84
|
# (default: Kitchen.logger)
|
@@ -93,6 +101,9 @@ module Kitchen
|
|
93
101
|
setup_provisioner
|
94
102
|
end
|
95
103
|
|
104
|
+
# Returns a displayable representation of the instance.
|
105
|
+
#
|
106
|
+
# @return [String] an instance display string
|
96
107
|
def to_str
|
97
108
|
"<#{name}>"
|
98
109
|
end
|
@@ -185,12 +196,24 @@ module Kitchen
|
|
185
196
|
# @see Driver::LoginCommand
|
186
197
|
# @see Driver::Base#login_command
|
187
198
|
def login
|
188
|
-
|
189
|
-
|
199
|
+
state = state_file.read
|
200
|
+
if state[:last_action].nil?
|
201
|
+
raise UserError, "Instance #{to_str} has not yet been created"
|
202
|
+
end
|
203
|
+
|
204
|
+
login_command = driver.login_command(state)
|
205
|
+
cmd, *args = login_command.cmd_array
|
190
206
|
options = login_command.options
|
191
207
|
|
192
|
-
debug(
|
193
|
-
Kernel.exec(
|
208
|
+
debug(%{Login command: #{cmd} #{args.join(" ")} (Options: #{options})})
|
209
|
+
Kernel.exec(cmd, *args, options)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Executes an arbitrary command on this instance.
|
213
|
+
#
|
214
|
+
# @param command [String] a command string to execute
|
215
|
+
def remote_exec(command)
|
216
|
+
driver.remote_command(state_file.read, command)
|
194
217
|
end
|
195
218
|
|
196
219
|
# Returns a Hash of configuration and other useful diagnostic information.
|
@@ -205,29 +228,43 @@ module Kitchen
|
|
205
228
|
result
|
206
229
|
end
|
207
230
|
|
231
|
+
# Returns the last successfully completed action state of the instance.
|
232
|
+
#
|
233
|
+
# @return [String] a named action which was last successfully completed
|
208
234
|
def last_action
|
209
235
|
state_file.read[:last_action]
|
210
236
|
end
|
211
237
|
|
212
238
|
private
|
213
239
|
|
240
|
+
# @return [StateFile] a state file object that can be read from or written
|
241
|
+
# to
|
242
|
+
# @api private
|
214
243
|
attr_reader :state_file
|
215
244
|
|
245
|
+
# Validate the initial internal state of this object and raising an
|
246
|
+
# exception if any preconditions are not met.
|
247
|
+
#
|
248
|
+
# @param options[Hash] options hash passed into the constructor
|
249
|
+
# @raise [ClientError] if any validations fail
|
250
|
+
# @api private
|
216
251
|
def validate_options(options)
|
217
|
-
[
|
218
|
-
|
219
|
-
|
220
|
-
|
252
|
+
[
|
253
|
+
:suite, :platform, :driver, :provisioner, :busser, :state_file
|
254
|
+
].each do |k|
|
255
|
+
next if options.key?(k)
|
256
|
+
|
257
|
+
raise ClientError, "Instance#new requires option :#{k}"
|
221
258
|
end
|
222
259
|
end
|
223
260
|
|
261
|
+
# Perform any final configuration or preparation needed for the driver
|
262
|
+
# object carry out its duties.
|
263
|
+
#
|
264
|
+
# @api private
|
224
265
|
def setup_driver
|
225
|
-
@driver.
|
226
|
-
@driver.validate_config!
|
227
|
-
setup_driver_mutex
|
228
|
-
end
|
266
|
+
@driver.finalize_config!(self)
|
229
267
|
|
230
|
-
def setup_driver_mutex
|
231
268
|
if driver.class.serial_actions
|
232
269
|
Kitchen.mutex.synchronize do
|
233
270
|
self.class.mutexes ||= Hash.new
|
@@ -236,10 +273,19 @@ module Kitchen
|
|
236
273
|
end
|
237
274
|
end
|
238
275
|
|
276
|
+
# Perform any final configuration or preparation needed for the provisioner
|
277
|
+
# object carry out its duties.
|
278
|
+
#
|
279
|
+
# @api private
|
239
280
|
def setup_provisioner
|
240
|
-
@provisioner.
|
281
|
+
@provisioner.finalize_config!(self)
|
241
282
|
end
|
242
283
|
|
284
|
+
# Perform all actions in order from last state to desired state.
|
285
|
+
#
|
286
|
+
# @param desired [Symbol] a symbol representing the desired action state
|
287
|
+
# @return [self] this instance, used to chain actions
|
288
|
+
# @api private
|
243
289
|
def transition_to(desired)
|
244
290
|
result = nil
|
245
291
|
FSM.actions(last_action, desired).each do |transition|
|
@@ -248,35 +294,87 @@ module Kitchen
|
|
248
294
|
result
|
249
295
|
end
|
250
296
|
|
297
|
+
# Perform the create action.
|
298
|
+
#
|
299
|
+
# @see Driver::Base#create
|
300
|
+
# @return [self] this instance, used to chain actions
|
301
|
+
# @api private
|
251
302
|
def create_action
|
252
303
|
perform_action(:create, "Creating")
|
253
304
|
end
|
254
305
|
|
306
|
+
# Perform the converge action.
|
307
|
+
#
|
308
|
+
# @see Driver::Base#converge
|
309
|
+
# @return [self] this instance, used to chain actions
|
310
|
+
# @api private
|
255
311
|
def converge_action
|
256
312
|
perform_action(:converge, "Converging")
|
257
313
|
end
|
258
314
|
|
315
|
+
# Perform the setup action.
|
316
|
+
#
|
317
|
+
# @see Driver::Base#setup
|
318
|
+
# @return [self] this instance, used to chain actions
|
319
|
+
# @api private
|
259
320
|
def setup_action
|
260
321
|
perform_action(:setup, "Setting up")
|
261
322
|
end
|
262
323
|
|
324
|
+
# Perform the verify action.
|
325
|
+
#
|
326
|
+
# @see Driver::Base#verify
|
327
|
+
# @return [self] this instance, used to chain actions
|
328
|
+
# @api private
|
263
329
|
def verify_action
|
264
330
|
perform_action(:verify, "Verifying")
|
265
331
|
end
|
266
332
|
|
333
|
+
# Perform the destroy action.
|
334
|
+
#
|
335
|
+
# @see Driver::Base#destroy
|
336
|
+
# @return [self] this instance, used to chain actions
|
337
|
+
# @api private
|
267
338
|
def destroy_action
|
268
339
|
perform_action(:destroy, "Destroying") { state_file.destroy }
|
269
340
|
end
|
270
341
|
|
342
|
+
# Perform an arbitrary action and provide useful logging.
|
343
|
+
#
|
344
|
+
# @param verb [Symbol] the action to be performed
|
345
|
+
# @param output_verb [String] a verb representing the action, suitable for
|
346
|
+
# use in output logging
|
347
|
+
# @yield perform optional work just after action has complted
|
348
|
+
# @return [self] this instance, used to chain actions
|
349
|
+
# @api private
|
271
350
|
def perform_action(verb, output_verb)
|
272
351
|
banner "#{output_verb} #{to_str}..."
|
273
352
|
elapsed = action(verb) { |state| driver.public_send(verb, state) }
|
274
|
-
info("Finished #{output_verb.downcase} #{to_str}"
|
353
|
+
info("Finished #{output_verb.downcase} #{to_str}" \
|
275
354
|
" #{Util.duration(elapsed.real)}.")
|
276
355
|
yield if block_given?
|
277
356
|
self
|
278
357
|
end
|
279
358
|
|
359
|
+
# Times a call to an action block and handles any raised exceptions. This
|
360
|
+
# method ensures that the last successfully completed action is persisted
|
361
|
+
# to the state file. The last action state will either be the desired
|
362
|
+
# action that is passed in or the previous action that was persisted to the
|
363
|
+
# state file.
|
364
|
+
#
|
365
|
+
# @param what [Symbol] the action to be performed
|
366
|
+
# @param block [Proc] a block to be called
|
367
|
+
# @return [Benchmark::Tms] timing information for the given action
|
368
|
+
# @raise [InstanceFailed] if a driver action fails to complete, signaled
|
369
|
+
# by a driver raising an ActionFailed exception. Typical reasons for this
|
370
|
+
# would be a driver create action failing, a chef convergence crashing
|
371
|
+
# in normal course of development, failing acceptance tests in the
|
372
|
+
# verify action, etc.
|
373
|
+
# @raise [ActionFailed] if an unforseen or unplanned exception is raised.
|
374
|
+
# This would usually indicate that a race condition was triggered, a
|
375
|
+
# bug exists in a driver, provisioner, or core, a transient IO error
|
376
|
+
# occured, etc.
|
377
|
+
# @api private
|
280
378
|
def action(what, &block)
|
281
379
|
state = state_file.read
|
282
380
|
elapsed = Benchmark.measure do
|
@@ -287,9 +385,9 @@ module Kitchen
|
|
287
385
|
rescue ActionFailed => e
|
288
386
|
log_failure(what, e)
|
289
387
|
raise(InstanceFailure, failure_message(what) +
|
290
|
-
" Please see .kitchen/logs/#{
|
388
|
+
" Please see .kitchen/logs/#{name}.log for more details",
|
291
389
|
e.backtrace)
|
292
|
-
rescue Exception => e
|
390
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
293
391
|
log_failure(what, e)
|
294
392
|
raise ActionFailed,
|
295
393
|
"Failed to complete ##{what} action: [#{e.message}]", e.backtrace
|
@@ -297,6 +395,16 @@ module Kitchen
|
|
297
395
|
state_file.write(state)
|
298
396
|
end
|
299
397
|
|
398
|
+
# Runs a given action block through a common driver mutex if required or
|
399
|
+
# runs it directly otherwise. If a driver class' `.serial_actions` array
|
400
|
+
# includes the desired action, then the action must be run with a muxtex
|
401
|
+
# lock. Otherwise, it is assumed that the action can happen concurrently,
|
402
|
+
# or fully in parallel.
|
403
|
+
#
|
404
|
+
# @param what [Symbol] the action to be performed
|
405
|
+
# @param state [Hash] a mutable state hash for this instance
|
406
|
+
# @param block [Proc] a block to be called
|
407
|
+
# @api private
|
300
408
|
def synchronize_or_call(what, state, &block)
|
301
409
|
if Array(driver.class.serial_actions).include?(what)
|
302
410
|
debug("#{to_str} is synchronizing on #{driver.class}##{what}")
|
@@ -309,11 +417,26 @@ module Kitchen
|
|
309
417
|
end
|
310
418
|
end
|
311
419
|
|
420
|
+
# Writes a high level message for logging and/or output.
|
421
|
+
#
|
422
|
+
# In this case, all instance banner messages will be written to the common
|
423
|
+
# Kitchen logger so that the high level flow of a run can be followed in
|
424
|
+
# the kitchen.log file.
|
425
|
+
#
|
426
|
+
# @api private
|
312
427
|
def banner(*args)
|
313
428
|
Kitchen.logger.logdev && Kitchen.logger.logdev.banner(*args)
|
314
429
|
super
|
315
430
|
end
|
316
431
|
|
432
|
+
# Logs a failure (message and backtrace) to the instance's file logger
|
433
|
+
# to help with debugging and diagnosing issues without overwhelming the
|
434
|
+
# console output in the default case (i.e. running kitchen with :info
|
435
|
+
# level debugging).
|
436
|
+
#
|
437
|
+
# @param what [String] an action
|
438
|
+
# @param e [Exception] an exception
|
439
|
+
# @api private
|
317
440
|
def log_failure(what, e)
|
318
441
|
return if logger.logdev.nil?
|
319
442
|
|
@@ -321,13 +444,20 @@ module Kitchen
|
|
321
444
|
Error.formatted_trace(e).each { |line| logger.logdev.error(line) }
|
322
445
|
end
|
323
446
|
|
447
|
+
# Returns a string explaining what action failed, at a high level. Used
|
448
|
+
# for displaying to end user.
|
449
|
+
#
|
450
|
+
# @param what [String] an action
|
451
|
+
# @return [String] a failure message
|
452
|
+
# @api private
|
324
453
|
def failure_message(what)
|
325
|
-
"#{what.capitalize} failed on instance #{
|
454
|
+
"#{what.capitalize} failed on instance #{to_str}."
|
326
455
|
end
|
327
456
|
|
328
457
|
# The simplest finite state machine pseudo-implementation needed to manage
|
329
458
|
# an Instance.
|
330
459
|
#
|
460
|
+
# @api private
|
331
461
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
332
462
|
class FSM
|
333
463
|
|
@@ -339,6 +469,7 @@ module Kitchen
|
|
339
469
|
# @param desired [String,Symbol] the desired transitioned state for the
|
340
470
|
# Instance
|
341
471
|
# @return [Array<Symbol>] an Array of transition actions to perform
|
472
|
+
# @api private
|
342
473
|
def self.actions(last = nil, desired)
|
343
474
|
last_index = index(last)
|
344
475
|
desired_index = index(desired)
|
@@ -350,10 +481,13 @@ module Kitchen
|
|
350
481
|
end
|
351
482
|
end
|
352
483
|
|
353
|
-
private
|
354
|
-
|
355
484
|
TRANSITIONS = [:destroy, :create, :converge, :setup, :verify]
|
356
485
|
|
486
|
+
# Determines the index of a state in the state lifecycle vector. Woah.
|
487
|
+
#
|
488
|
+
# @param transition [Symbol,#to_sym] a state
|
489
|
+
# @param [Integer] the index position
|
490
|
+
# @api private
|
357
491
|
def self.index(transition)
|
358
492
|
if transition.nil?
|
359
493
|
0
|