test-kitchen 1.7.0 → 1.7.1.dev
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 +8 -8
- data/.gitattributes +3 -0
- data/.github/ISSUE_TEMPLATE.md +55 -55
- data/.gitignore +28 -28
- data/.kitchen.ci.yml +23 -23
- data/.kitchen.proxy.yml +27 -27
- data/.rubocop.yml +3 -3
- data/.travis.yml +70 -70
- data/.yardopts +3 -3
- data/Berksfile +3 -3
- data/CHANGELOG.md +1090 -1083
- data/CONTRIBUTING.md +14 -14
- data/Gemfile +19 -19
- data/Gemfile.proxy_tests +4 -4
- data/Guardfile +42 -42
- data/LICENSE +15 -15
- data/MAINTAINERS.md +23 -23
- data/README.md +135 -135
- data/Rakefile +61 -61
- data/appveyor.yml +44 -44
- data/features/kitchen_action_commands.feature +164 -164
- data/features/kitchen_command.feature +16 -16
- data/features/kitchen_console_command.feature +34 -34
- data/features/kitchen_defaults.feature +38 -38
- data/features/kitchen_diagnose_command.feature +96 -96
- data/features/kitchen_driver_create_command.feature +64 -64
- data/features/kitchen_driver_discover_command.feature +25 -25
- data/features/kitchen_help_command.feature +16 -16
- data/features/kitchen_init_command.feature +274 -274
- data/features/kitchen_list_command.feature +104 -104
- data/features/kitchen_login_command.feature +62 -62
- data/features/kitchen_sink_command.feature +30 -30
- data/features/kitchen_test_command.feature +88 -88
- data/features/step_definitions/gem_steps.rb +36 -36
- data/features/step_definitions/git_steps.rb +5 -5
- data/features/step_definitions/output_steps.rb +5 -5
- data/features/support/env.rb +75 -75
- data/lib/kitchen.rb +150 -150
- data/lib/kitchen/base64_stream.rb +55 -55
- data/lib/kitchen/cli.rb +419 -419
- data/lib/kitchen/collection.rb +55 -55
- data/lib/kitchen/color.rb +65 -65
- data/lib/kitchen/command.rb +185 -185
- data/lib/kitchen/command/action.rb +45 -45
- data/lib/kitchen/command/console.rb +58 -58
- data/lib/kitchen/command/diagnose.rb +92 -92
- data/lib/kitchen/command/driver_discover.rb +105 -105
- data/lib/kitchen/command/exec.rb +41 -41
- data/lib/kitchen/command/list.rb +119 -119
- data/lib/kitchen/command/login.rb +43 -43
- data/lib/kitchen/command/sink.rb +54 -54
- data/lib/kitchen/command/test.rb +51 -51
- data/lib/kitchen/config.rb +322 -322
- data/lib/kitchen/configurable.rb +529 -529
- data/lib/kitchen/data_munger.rb +959 -959
- data/lib/kitchen/diagnostic.rb +141 -141
- data/lib/kitchen/driver.rb +56 -56
- data/lib/kitchen/driver/base.rb +134 -134
- data/lib/kitchen/driver/dummy.rb +108 -108
- data/lib/kitchen/driver/proxy.rb +72 -72
- data/lib/kitchen/driver/ssh_base.rb +357 -357
- data/lib/kitchen/errors.rb +229 -229
- data/lib/kitchen/generator/driver_create.rb +177 -177
- data/lib/kitchen/generator/init.rb +296 -296
- data/lib/kitchen/instance.rb +662 -662
- data/lib/kitchen/lazy_hash.rb +142 -142
- data/lib/kitchen/loader/yaml.rb +349 -349
- data/lib/kitchen/logger.rb +423 -423
- data/lib/kitchen/logging.rb +56 -56
- data/lib/kitchen/login_command.rb +52 -52
- data/lib/kitchen/metadata_chopper.rb +52 -52
- data/lib/kitchen/platform.rb +67 -67
- data/lib/kitchen/provisioner.rb +54 -54
- data/lib/kitchen/provisioner/base.rb +236 -236
- data/lib/kitchen/provisioner/chef/berkshelf.rb +114 -114
- data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -322
- data/lib/kitchen/provisioner/chef/librarian.rb +112 -112
- data/lib/kitchen/provisioner/chef_apply.rb +124 -124
- data/lib/kitchen/provisioner/chef_base.rb +341 -341
- data/lib/kitchen/provisioner/chef_solo.rb +88 -88
- data/lib/kitchen/provisioner/chef_zero.rb +245 -245
- data/lib/kitchen/provisioner/dummy.rb +79 -79
- data/lib/kitchen/provisioner/shell.rb +138 -138
- data/lib/kitchen/rake_tasks.rb +63 -63
- data/lib/kitchen/shell_out.rb +93 -93
- data/lib/kitchen/ssh.rb +276 -276
- data/lib/kitchen/state_file.rb +120 -120
- data/lib/kitchen/suite.rb +51 -51
- data/lib/kitchen/thor_tasks.rb +66 -66
- data/lib/kitchen/transport.rb +54 -54
- data/lib/kitchen/transport/base.rb +176 -176
- data/lib/kitchen/transport/dummy.rb +79 -79
- data/lib/kitchen/transport/ssh.rb +364 -364
- data/lib/kitchen/transport/winrm.rb +486 -486
- data/lib/kitchen/util.rb +147 -147
- data/lib/kitchen/verifier.rb +55 -55
- data/lib/kitchen/verifier/base.rb +235 -235
- data/lib/kitchen/verifier/busser.rb +277 -277
- data/lib/kitchen/verifier/dummy.rb +79 -79
- data/lib/kitchen/verifier/shell.rb +101 -101
- data/lib/kitchen/version.rb +21 -21
- data/lib/vendor/hash_recursive_merge.rb +82 -82
- data/spec/kitchen/base64_stream_spec.rb +77 -77
- data/spec/kitchen/cli_spec.rb +56 -56
- data/spec/kitchen/collection_spec.rb +80 -80
- data/spec/kitchen/color_spec.rb +54 -54
- data/spec/kitchen/config_spec.rb +408 -408
- data/spec/kitchen/configurable_spec.rb +1095 -1095
- data/spec/kitchen/data_munger_spec.rb +2694 -2694
- data/spec/kitchen/diagnostic_spec.rb +129 -129
- data/spec/kitchen/driver/base_spec.rb +121 -121
- data/spec/kitchen/driver/dummy_spec.rb +199 -199
- data/spec/kitchen/driver/proxy_spec.rb +138 -138
- data/spec/kitchen/driver/ssh_base_spec.rb +1115 -1115
- data/spec/kitchen/driver_spec.rb +112 -112
- data/spec/kitchen/errors_spec.rb +309 -309
- data/spec/kitchen/instance_spec.rb +1419 -1419
- data/spec/kitchen/lazy_hash_spec.rb +117 -117
- data/spec/kitchen/loader/yaml_spec.rb +774 -774
- data/spec/kitchen/logger_spec.rb +429 -429
- data/spec/kitchen/logging_spec.rb +59 -59
- data/spec/kitchen/login_command_spec.rb +68 -68
- data/spec/kitchen/metadata_chopper_spec.rb +82 -82
- data/spec/kitchen/platform_spec.rb +89 -89
- data/spec/kitchen/provisioner/base_spec.rb +386 -386
- data/spec/kitchen/provisioner/chef_apply_spec.rb +136 -136
- data/spec/kitchen/provisioner/chef_base_spec.rb +1161 -1161
- data/spec/kitchen/provisioner/chef_solo_spec.rb +557 -557
- data/spec/kitchen/provisioner/chef_zero_spec.rb +1001 -1001
- data/spec/kitchen/provisioner/dummy_spec.rb +99 -99
- data/spec/kitchen/provisioner/shell_spec.rb +566 -566
- data/spec/kitchen/provisioner_spec.rb +107 -107
- data/spec/kitchen/shell_out_spec.rb +150 -150
- data/spec/kitchen/ssh_spec.rb +693 -693
- data/spec/kitchen/state_file_spec.rb +129 -129
- data/spec/kitchen/suite_spec.rb +62 -62
- data/spec/kitchen/transport/base_spec.rb +89 -89
- data/spec/kitchen/transport/ssh_spec.rb +1255 -1255
- data/spec/kitchen/transport/winrm_spec.rb +1143 -1143
- data/spec/kitchen/transport_spec.rb +112 -112
- data/spec/kitchen/util_spec.rb +165 -165
- data/spec/kitchen/verifier/base_spec.rb +362 -362
- data/spec/kitchen/verifier/busser_spec.rb +610 -610
- data/spec/kitchen/verifier/dummy_spec.rb +99 -99
- data/spec/kitchen/verifier/shell_spec.rb +160 -160
- data/spec/kitchen/verifier_spec.rb +120 -120
- data/spec/kitchen_spec.rb +114 -114
- data/spec/spec_helper.rb +85 -85
- data/spec/support/powershell_max_size_spec.rb +40 -40
- data/support/busser_install_command.ps1 +14 -14
- data/support/busser_install_command.sh +14 -14
- data/support/chef-client-zero.rb +77 -77
- data/support/chef_base_init_command.ps1 +18 -18
- data/support/chef_base_init_command.sh +2 -2
- data/support/chef_base_install_command.ps1 +85 -85
- data/support/chef_base_install_command.sh +229 -229
- data/support/chef_zero_prepare_command_legacy.ps1 +9 -9
- data/support/chef_zero_prepare_command_legacy.sh +10 -10
- data/support/download_helpers.sh +109 -109
- data/support/dummy-validation.pem +27 -27
- data/templates/driver/CHANGELOG.md.erb +3 -3
- data/templates/driver/Gemfile.erb +3 -3
- data/templates/driver/README.md.erb +64 -64
- data/templates/driver/Rakefile.erb +21 -21
- data/templates/driver/driver.rb.erb +23 -23
- data/templates/driver/gemspec.erb +29 -29
- data/templates/driver/gitignore.erb +17 -17
- data/templates/driver/license_apachev2.erb +15 -15
- data/templates/driver/license_lgplv3.erb +16 -16
- data/templates/driver/license_mit.erb +22 -22
- data/templates/driver/license_reserved.erb +5 -5
- data/templates/driver/tailor.erb +4 -4
- data/templates/driver/travis.yml.erb +11 -11
- data/templates/driver/version.rb.erb +12 -12
- data/templates/init/chefignore.erb +1 -1
- data/templates/init/kitchen.yml.erb +18 -18
- data/test-kitchen.gemspec +62 -62
- data/test/integration/default/default_spec.rb +3 -3
- data/testing_windows.md +37 -37
- metadata +5 -4
data/lib/kitchen/instance.rb
CHANGED
|
@@ -1,662 +1,662 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
-
#
|
|
5
|
-
# Copyright (C) 2012, 2013, 2014, 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 "benchmark"
|
|
20
|
-
require "fileutils"
|
|
21
|
-
|
|
22
|
-
module Kitchen
|
|
23
|
-
|
|
24
|
-
# An instance of a suite running on a platform. A created instance may be a
|
|
25
|
-
# local virtual machine, cloud instance, container, or even a bare metal
|
|
26
|
-
# server, which is determined by the platform's driver.
|
|
27
|
-
#
|
|
28
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
29
|
-
class Instance
|
|
30
|
-
|
|
31
|
-
include Logging
|
|
32
|
-
|
|
33
|
-
class << self
|
|
34
|
-
|
|
35
|
-
# @return [Hash] a hash of mutxes, arranged by Driver class names
|
|
36
|
-
# @api private
|
|
37
|
-
attr_accessor :mutexes
|
|
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
|
|
44
|
-
def name_for(suite, platform)
|
|
45
|
-
"#{suite.name}-#{platform.name}".gsub(%r{[_,/]}, "-").gsub(/\./, "")
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# @return [Suite] the test suite configuration
|
|
50
|
-
attr_reader :suite
|
|
51
|
-
|
|
52
|
-
# @return [Platform] the target platform configuration
|
|
53
|
-
attr_reader :platform
|
|
54
|
-
|
|
55
|
-
# @return [String] name of this instance
|
|
56
|
-
attr_reader :name
|
|
57
|
-
|
|
58
|
-
# @return [Driver::Base] driver object which will manage this instance's
|
|
59
|
-
# lifecycle actions
|
|
60
|
-
attr_reader :driver
|
|
61
|
-
|
|
62
|
-
# @return [Provisioner::Base] provisioner object which will the setup
|
|
63
|
-
# and invocation instructions for configuration management and other
|
|
64
|
-
# automation tools
|
|
65
|
-
attr_reader :provisioner
|
|
66
|
-
|
|
67
|
-
# @return [Transport::Base] transport object which will communicate with
|
|
68
|
-
# an instance.
|
|
69
|
-
attr_reader :transport
|
|
70
|
-
|
|
71
|
-
# @return [Verifier] verifier object for instance to manage the verifier
|
|
72
|
-
# installation on this instance
|
|
73
|
-
attr_reader :verifier
|
|
74
|
-
|
|
75
|
-
# @return [Logger] the logger for this instance
|
|
76
|
-
attr_reader :logger
|
|
77
|
-
|
|
78
|
-
# Creates a new instance, given a suite and a platform.
|
|
79
|
-
#
|
|
80
|
-
# @param [Hash] options configuration for a new suite
|
|
81
|
-
# @option options [Suite] :suite the suite (**Required**)
|
|
82
|
-
# @option options [Platform] :platform the platform (**Required**)
|
|
83
|
-
# @option options [Driver::Base] :driver the driver (**Required**)
|
|
84
|
-
# @option options [Provisioner::Base] :provisioner the provisioner
|
|
85
|
-
# @option options [Transport::Base] :transport the transport
|
|
86
|
-
# (**Required**)
|
|
87
|
-
# @option options [Verifier] :verifier the verifier logger (**Required**)
|
|
88
|
-
# @option options [Logger] :logger the instance logger
|
|
89
|
-
# (default: Kitchen.logger)
|
|
90
|
-
# @option options [StateFile] :state_file the state file object to use
|
|
91
|
-
# when tracking instance state (**Required**)
|
|
92
|
-
# @raise [ClientError] if one or more required options are omitted
|
|
93
|
-
def initialize(options = {})
|
|
94
|
-
validate_options(options)
|
|
95
|
-
|
|
96
|
-
@suite = options.fetch(:suite)
|
|
97
|
-
@platform = options.fetch(:platform)
|
|
98
|
-
@name = self.class.name_for(@suite, @platform)
|
|
99
|
-
@driver = options.fetch(:driver)
|
|
100
|
-
@provisioner = options.fetch(:provisioner)
|
|
101
|
-
@transport = options.fetch(:transport)
|
|
102
|
-
@verifier = options.fetch(:verifier)
|
|
103
|
-
@logger = options.fetch(:logger) { Kitchen.logger }
|
|
104
|
-
@state_file = options.fetch(:state_file)
|
|
105
|
-
|
|
106
|
-
setup_driver
|
|
107
|
-
setup_provisioner
|
|
108
|
-
setup_transport
|
|
109
|
-
setup_verifier
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Returns a displayable representation of the instance.
|
|
113
|
-
#
|
|
114
|
-
# @return [String] an instance display string
|
|
115
|
-
def to_str
|
|
116
|
-
"<#{name}>"
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Creates this instance.
|
|
120
|
-
#
|
|
121
|
-
# @see Driver::Base#create
|
|
122
|
-
# @return [self] this instance, used to chain actions
|
|
123
|
-
#
|
|
124
|
-
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
125
|
-
# to gracfully stop action chaining
|
|
126
|
-
def create
|
|
127
|
-
transition_to(:create)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Converges this running instance.
|
|
131
|
-
#
|
|
132
|
-
# @see Provisioner::Base#call
|
|
133
|
-
# @return [self] this instance, used to chain actions
|
|
134
|
-
#
|
|
135
|
-
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
136
|
-
# to gracfully stop action chaining
|
|
137
|
-
def converge
|
|
138
|
-
transition_to(:converge)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Sets up this converged instance for suite tests.
|
|
142
|
-
#
|
|
143
|
-
# @see Driver::Base#setup
|
|
144
|
-
# @return [self] this instance, used to chain actions
|
|
145
|
-
#
|
|
146
|
-
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
147
|
-
# to gracfully stop action chaining
|
|
148
|
-
def setup
|
|
149
|
-
transition_to(:setup)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Verifies this set up instance by executing suite tests.
|
|
153
|
-
#
|
|
154
|
-
# @see Driver::Base#verify
|
|
155
|
-
# @return [self] this instance, used to chain actions
|
|
156
|
-
#
|
|
157
|
-
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
158
|
-
# to gracfully stop action chaining
|
|
159
|
-
def verify
|
|
160
|
-
transition_to(:verify)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Destroys this instance.
|
|
164
|
-
#
|
|
165
|
-
# @see Driver::Base#destroy
|
|
166
|
-
# @return [self] this instance, used to chain actions
|
|
167
|
-
#
|
|
168
|
-
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
169
|
-
# to gracfully stop action chaining
|
|
170
|
-
def destroy
|
|
171
|
-
transition_to(:destroy)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Tests this instance by creating, converging and verifying. If this
|
|
175
|
-
# instance is running, it will be pre-emptively destroyed to ensure a
|
|
176
|
-
# clean slate. The instance will be left post-verify in a running state.
|
|
177
|
-
#
|
|
178
|
-
# @param destroy_mode [Symbol] strategy used to cleanup after instance
|
|
179
|
-
# has finished verifying (default: `:passing`)
|
|
180
|
-
# @return [self] this instance, used to chain actions
|
|
181
|
-
#
|
|
182
|
-
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
183
|
-
# to gracfully stop action chaining
|
|
184
|
-
def test(destroy_mode = :passing)
|
|
185
|
-
elapsed = Benchmark.measure do
|
|
186
|
-
banner "Cleaning up any prior instances of #{to_str}"
|
|
187
|
-
destroy
|
|
188
|
-
banner "Testing #{to_str}"
|
|
189
|
-
verify
|
|
190
|
-
destroy if destroy_mode == :passing
|
|
191
|
-
end
|
|
192
|
-
info "Finished testing #{to_str} #{Util.duration(elapsed.real)}."
|
|
193
|
-
self
|
|
194
|
-
ensure
|
|
195
|
-
destroy if destroy_mode == :always
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Logs in to this instance by invoking a system command, provided by the
|
|
199
|
-
# instance's transport. This could be an SSH command, telnet, or serial
|
|
200
|
-
# console session.
|
|
201
|
-
#
|
|
202
|
-
# **Note** This method calls exec and will not return.
|
|
203
|
-
#
|
|
204
|
-
# @see Kitchen::LoginCommand
|
|
205
|
-
# @see Transport::Base::Connection#login_command
|
|
206
|
-
def login
|
|
207
|
-
state = state_file.read
|
|
208
|
-
if state[:last_action].nil?
|
|
209
|
-
raise UserError, "Instance #{to_str} has not yet been created"
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
lc = if legacy_ssh_base_driver?
|
|
213
|
-
legacy_ssh_base_login(state)
|
|
214
|
-
else
|
|
215
|
-
transport.connection(state).login_command
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
debug(%{Login command: #{lc.command} #{lc.arguments.join(" ")} } \
|
|
219
|
-
"(Options: #{lc.options})")
|
|
220
|
-
Kernel.exec(*lc.exec_args)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# Executes an arbitrary command on this instance.
|
|
224
|
-
#
|
|
225
|
-
# @param command [String] a command string to execute
|
|
226
|
-
def remote_exec(command)
|
|
227
|
-
transport.connection(state_file.read) do |conn|
|
|
228
|
-
conn.execute(command)
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Returns a Hash of configuration and other useful diagnostic information.
|
|
233
|
-
#
|
|
234
|
-
# @return [Hash] a diagnostic hash
|
|
235
|
-
def diagnose
|
|
236
|
-
result = Hash.new
|
|
237
|
-
[
|
|
238
|
-
:platform, :state_file, :driver, :provisioner, :transport, :verifier
|
|
239
|
-
].each do |sym|
|
|
240
|
-
obj = send(sym)
|
|
241
|
-
result[sym] = obj.respond_to?(:diagnose) ? obj.diagnose : :unknown
|
|
242
|
-
end
|
|
243
|
-
result
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Returns a Hash of configuration and other useful diagnostic information
|
|
247
|
-
# associated with plugins (such as loaded version, class name, etc.).
|
|
248
|
-
#
|
|
249
|
-
# @return [Hash] a diagnostic hash
|
|
250
|
-
def diagnose_plugins
|
|
251
|
-
result = Hash.new
|
|
252
|
-
[:driver, :provisioner, :verifier, :transport].each do |sym|
|
|
253
|
-
obj = send(sym)
|
|
254
|
-
result[sym] = if obj.respond_to?(:diagnose_plugin)
|
|
255
|
-
obj.diagnose_plugin
|
|
256
|
-
else
|
|
257
|
-
:unknown
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
result
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
# Returns the last successfully completed action state of the instance.
|
|
264
|
-
#
|
|
265
|
-
# @return [String] a named action which was last successfully completed
|
|
266
|
-
def last_action
|
|
267
|
-
state_file.read[:last_action]
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
# Clean up any per-instance resources before exiting.
|
|
271
|
-
#
|
|
272
|
-
# @return [void]
|
|
273
|
-
def cleanup!
|
|
274
|
-
@transport.cleanup! if @transport
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
private
|
|
278
|
-
|
|
279
|
-
# @return [StateFile] a state file object that can be read from or written
|
|
280
|
-
# to
|
|
281
|
-
# @api private
|
|
282
|
-
attr_reader :state_file
|
|
283
|
-
|
|
284
|
-
# Validate the initial internal state of this object and raising an
|
|
285
|
-
# exception if any preconditions are not met.
|
|
286
|
-
#
|
|
287
|
-
# @param options[Hash] options hash passed into the constructor
|
|
288
|
-
# @raise [ClientError] if any validations fail
|
|
289
|
-
# @api private
|
|
290
|
-
def validate_options(options)
|
|
291
|
-
[
|
|
292
|
-
:suite, :platform, :driver, :provisioner,
|
|
293
|
-
:transport, :verifier, :state_file
|
|
294
|
-
].each do |k|
|
|
295
|
-
next if options.key?(k)
|
|
296
|
-
|
|
297
|
-
raise ClientError, "Instance#new requires option :#{k}"
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
# Perform any final configuration or preparation needed for the driver
|
|
302
|
-
# object carry out its duties.
|
|
303
|
-
#
|
|
304
|
-
# @api private
|
|
305
|
-
def setup_driver
|
|
306
|
-
@driver.finalize_config!(self)
|
|
307
|
-
|
|
308
|
-
if driver.class.serial_actions
|
|
309
|
-
Kitchen.mutex.synchronize do
|
|
310
|
-
self.class.mutexes ||= Hash.new
|
|
311
|
-
self.class.mutexes[driver.class] = Mutex.new
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
# Perform any final configuration or preparation needed for the provisioner
|
|
317
|
-
# object carry out its duties.
|
|
318
|
-
#
|
|
319
|
-
# @api private
|
|
320
|
-
def setup_provisioner
|
|
321
|
-
@provisioner.finalize_config!(self)
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
# Perform any final configuration or preparation needed for the transport
|
|
325
|
-
# object carry out its duties.
|
|
326
|
-
#
|
|
327
|
-
# @api private
|
|
328
|
-
def setup_transport
|
|
329
|
-
transport.finalize_config!(self)
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
# Perform any final configuration or preparation needed for the verifier
|
|
333
|
-
# object carry out its duties.
|
|
334
|
-
#
|
|
335
|
-
# @api private
|
|
336
|
-
def setup_verifier
|
|
337
|
-
verifier.finalize_config!(self)
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
# Perform all actions in order from last state to desired state.
|
|
341
|
-
#
|
|
342
|
-
# @param desired [Symbol] a symbol representing the desired action state
|
|
343
|
-
# @return [self] this instance, used to chain actions
|
|
344
|
-
# @api private
|
|
345
|
-
def transition_to(desired)
|
|
346
|
-
result = nil
|
|
347
|
-
FSM.actions(last_action, desired).each do |transition|
|
|
348
|
-
result = send("#{transition}_action")
|
|
349
|
-
end
|
|
350
|
-
result
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# Perform the create action.
|
|
354
|
-
#
|
|
355
|
-
# @see Driver::Base#create
|
|
356
|
-
# @return [self] this instance, used to chain actions
|
|
357
|
-
# @api private
|
|
358
|
-
def create_action
|
|
359
|
-
perform_action(:create, "Creating")
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
# Perform the converge action.
|
|
363
|
-
#
|
|
364
|
-
# @see Provisioner::Base#call
|
|
365
|
-
# @return [self] this instance, used to chain actions
|
|
366
|
-
# @api private
|
|
367
|
-
def converge_action
|
|
368
|
-
banner "Converging #{to_str}..."
|
|
369
|
-
elapsed = action(:converge) do |state|
|
|
370
|
-
if legacy_ssh_base_driver?
|
|
371
|
-
legacy_ssh_base_converge(state)
|
|
372
|
-
else
|
|
373
|
-
provisioner.call(state)
|
|
374
|
-
end
|
|
375
|
-
end
|
|
376
|
-
info("Finished converging #{to_str} #{Util.duration(elapsed.real)}.")
|
|
377
|
-
self
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
# Perform the setup action.
|
|
381
|
-
#
|
|
382
|
-
# @see Driver::Base#setup
|
|
383
|
-
# @return [self] this instance, used to chain actions
|
|
384
|
-
# @api private
|
|
385
|
-
def setup_action
|
|
386
|
-
banner "Setting up #{to_str}..."
|
|
387
|
-
elapsed = action(:setup) do |state|
|
|
388
|
-
legacy_ssh_base_setup(state) if legacy_ssh_base_driver?
|
|
389
|
-
end
|
|
390
|
-
info("Finished setting up #{to_str} #{Util.duration(elapsed.real)}.")
|
|
391
|
-
self
|
|
392
|
-
end
|
|
393
|
-
|
|
394
|
-
# returns true, if the verifier is busser
|
|
395
|
-
def verifier_busser?(verifier)
|
|
396
|
-
!defined?(Kitchen::Verifier::Busser).nil? && verifier.is_a?(Kitchen::Verifier::Busser)
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
# returns true, if the verifier is dummy
|
|
400
|
-
def verifier_dummy?(verifier)
|
|
401
|
-
!defined?(Kitchen::Verifier::Dummy).nil? && verifier.is_a?(Kitchen::Verifier::Dummy)
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
def use_legacy_ssh_verifier?(verifier)
|
|
405
|
-
verifier_busser?(verifier) || verifier_dummy?(verifier)
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
# Perform the verify action.
|
|
409
|
-
#
|
|
410
|
-
# @see Driver::Base#verify
|
|
411
|
-
# @return [self] this instance, used to chain actions
|
|
412
|
-
# @api private
|
|
413
|
-
def verify_action
|
|
414
|
-
banner "Verifying #{to_str}..."
|
|
415
|
-
elapsed = action(:verify) do |state|
|
|
416
|
-
# use special handling for legacy driver
|
|
417
|
-
if legacy_ssh_base_driver? && use_legacy_ssh_verifier?(verifier)
|
|
418
|
-
legacy_ssh_base_verify(state)
|
|
419
|
-
elsif legacy_ssh_base_driver?
|
|
420
|
-
# read ssh options from legacy driver
|
|
421
|
-
verifier.call(driver.legacy_state(state))
|
|
422
|
-
else
|
|
423
|
-
verifier.call(state)
|
|
424
|
-
end
|
|
425
|
-
end
|
|
426
|
-
info("Finished verifying #{to_str} #{Util.duration(elapsed.real)}.")
|
|
427
|
-
self
|
|
428
|
-
end
|
|
429
|
-
|
|
430
|
-
# Perform the destroy action.
|
|
431
|
-
#
|
|
432
|
-
# @see Driver::Base#destroy
|
|
433
|
-
# @return [self] this instance, used to chain actions
|
|
434
|
-
# @api private
|
|
435
|
-
def destroy_action
|
|
436
|
-
perform_action(:destroy, "Destroying") { state_file.destroy }
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
# Perform an arbitrary action and provide useful logging.
|
|
440
|
-
#
|
|
441
|
-
# @param verb [Symbol] the action to be performed
|
|
442
|
-
# @param output_verb [String] a verb representing the action, suitable for
|
|
443
|
-
# use in output logging
|
|
444
|
-
# @yield perform optional work just after action has complted
|
|
445
|
-
# @return [self] this instance, used to chain actions
|
|
446
|
-
# @api private
|
|
447
|
-
def perform_action(verb, output_verb)
|
|
448
|
-
banner "#{output_verb} #{to_str}..."
|
|
449
|
-
elapsed = action(verb) { |state| driver.public_send(verb, state) }
|
|
450
|
-
info("Finished #{output_verb.downcase} #{to_str}" \
|
|
451
|
-
" #{Util.duration(elapsed.real)}.")
|
|
452
|
-
yield if block_given?
|
|
453
|
-
self
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
# Times a call to an action block and handles any raised exceptions. This
|
|
457
|
-
# method ensures that the last successfully completed action is persisted
|
|
458
|
-
# to the state file. The last action state will either be the desired
|
|
459
|
-
# action that is passed in or the previous action that was persisted to the
|
|
460
|
-
# state file.
|
|
461
|
-
#
|
|
462
|
-
# @param what [Symbol] the action to be performed
|
|
463
|
-
# @param block [Proc] a block to be called
|
|
464
|
-
# @return [Benchmark::Tms] timing information for the given action
|
|
465
|
-
# @raise [InstanceFailed] if a driver action fails to complete, signaled
|
|
466
|
-
# by a driver raising an ActionFailed exception. Typical reasons for this
|
|
467
|
-
# would be a driver create action failing, a chef convergence crashing
|
|
468
|
-
# in normal course of development, failing acceptance tests in the
|
|
469
|
-
# verify action, etc.
|
|
470
|
-
# @raise [ActionFailed] if an unforseen or unplanned exception is raised.
|
|
471
|
-
# This would usually indicate that a race condition was triggered, a
|
|
472
|
-
# bug exists in a driver, provisioner, or core, a transient IO error
|
|
473
|
-
# occured, etc.
|
|
474
|
-
# @api private
|
|
475
|
-
def action(what, &block)
|
|
476
|
-
state = state_file.read
|
|
477
|
-
elapsed = Benchmark.measure do
|
|
478
|
-
synchronize_or_call(what, state, &block)
|
|
479
|
-
end
|
|
480
|
-
state[:last_action] = what.to_s
|
|
481
|
-
elapsed
|
|
482
|
-
rescue ActionFailed => e
|
|
483
|
-
log_failure(what, e)
|
|
484
|
-
raise(InstanceFailure, failure_message(what) +
|
|
485
|
-
" Please see .kitchen/logs/#{name}.log for more details",
|
|
486
|
-
e.backtrace)
|
|
487
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
488
|
-
log_failure(what, e)
|
|
489
|
-
raise ActionFailed,
|
|
490
|
-
"Failed to complete ##{what} action: [#{e.message}]", e.backtrace
|
|
491
|
-
ensure
|
|
492
|
-
state_file.write(state)
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
# Runs a given action block through a common driver mutex if required or
|
|
496
|
-
# runs it directly otherwise. If a driver class' `.serial_actions` array
|
|
497
|
-
# includes the desired action, then the action must be run with a muxtex
|
|
498
|
-
# lock. Otherwise, it is assumed that the action can happen concurrently,
|
|
499
|
-
# or fully in parallel.
|
|
500
|
-
#
|
|
501
|
-
# @param what [Symbol] the action to be performed
|
|
502
|
-
# @param state [Hash] a mutable state hash for this instance
|
|
503
|
-
# @param block [Proc] a block to be called
|
|
504
|
-
# @api private
|
|
505
|
-
def synchronize_or_call(what, state, &block)
|
|
506
|
-
if Array(driver.class.serial_actions).include?(what)
|
|
507
|
-
debug("#{to_str} is synchronizing on #{driver.class}##{what}")
|
|
508
|
-
self.class.mutexes[driver.class].synchronize do
|
|
509
|
-
debug("#{to_str} is messaging #{driver.class}##{what}")
|
|
510
|
-
block.call(state)
|
|
511
|
-
end
|
|
512
|
-
else
|
|
513
|
-
block.call(state)
|
|
514
|
-
end
|
|
515
|
-
end
|
|
516
|
-
|
|
517
|
-
# Writes a high level message for logging and/or output.
|
|
518
|
-
#
|
|
519
|
-
# In this case, all instance banner messages will be written to the common
|
|
520
|
-
# Kitchen logger so that the high level flow of a run can be followed in
|
|
521
|
-
# the kitchen.log file.
|
|
522
|
-
#
|
|
523
|
-
# @api private
|
|
524
|
-
def banner(*args)
|
|
525
|
-
Kitchen.logger.logdev && Kitchen.logger.logdev.banner(*args)
|
|
526
|
-
super
|
|
527
|
-
end
|
|
528
|
-
|
|
529
|
-
# Logs a failure (message and backtrace) to the instance's file logger
|
|
530
|
-
# to help with debugging and diagnosing issues without overwhelming the
|
|
531
|
-
# console output in the default case (i.e. running kitchen with :info
|
|
532
|
-
# level debugging).
|
|
533
|
-
#
|
|
534
|
-
# @param what [String] an action
|
|
535
|
-
# @param e [Exception] an exception
|
|
536
|
-
# @api private
|
|
537
|
-
def log_failure(what, e)
|
|
538
|
-
return if logger.logdev.nil?
|
|
539
|
-
|
|
540
|
-
logger.logdev.error(failure_message(what))
|
|
541
|
-
Error.formatted_trace(e).each { |line| logger.logdev.error(line) }
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
# Returns a string explaining what action failed, at a high level. Used
|
|
545
|
-
# for displaying to end user.
|
|
546
|
-
#
|
|
547
|
-
# @param what [String] an action
|
|
548
|
-
# @return [String] a failure message
|
|
549
|
-
# @api private
|
|
550
|
-
def failure_message(what)
|
|
551
|
-
"#{what.capitalize} failed on instance #{to_str}."
|
|
552
|
-
end
|
|
553
|
-
|
|
554
|
-
# Invokes `Driver#converge` on a legacy Driver, which inherits from
|
|
555
|
-
# `Kitchen::Driver::SSHBase`.
|
|
556
|
-
#
|
|
557
|
-
# @param state [Hash] mutable instance state
|
|
558
|
-
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
559
|
-
# `#converge` method will no longer be called on the Driver.
|
|
560
|
-
# @api private
|
|
561
|
-
def legacy_ssh_base_converge(state)
|
|
562
|
-
warn("Running legacy converge for '#{driver.name}' Driver")
|
|
563
|
-
# TODO: Document upgrade path and provide link
|
|
564
|
-
# warn("Driver authors: please read http://example.com for more details.")
|
|
565
|
-
driver.converge(state)
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
# @return [TrueClass,FalseClass] whether or not the Driver inherits from
|
|
569
|
-
# `Kitchen::Driver::SSHBase`
|
|
570
|
-
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
571
|
-
# `#converge` method will no longer be called on the Driver.
|
|
572
|
-
# @api private
|
|
573
|
-
def legacy_ssh_base_driver?
|
|
574
|
-
driver.class < Kitchen::Driver::SSHBase
|
|
575
|
-
end
|
|
576
|
-
|
|
577
|
-
# Invokes `Driver#login_command` on a legacy Driver, which inherits from
|
|
578
|
-
# `Kitchen::Driver::SSHBase`.
|
|
579
|
-
#
|
|
580
|
-
# @param state [Hash] mutable instance state
|
|
581
|
-
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
582
|
-
# `#login_command` method will no longer be called on the Driver.
|
|
583
|
-
# @api private
|
|
584
|
-
def legacy_ssh_base_login(state)
|
|
585
|
-
warn("Running legacy login for '#{driver.name}' Driver")
|
|
586
|
-
# TODO: Document upgrade path and provide link
|
|
587
|
-
# warn("Driver authors: please read http://example.com for more details.")
|
|
588
|
-
driver.login_command(state)
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
# Invokes `Driver#setup` on a legacy Driver, which inherits from
|
|
592
|
-
# `Kitchen::Driver::SSHBase`.
|
|
593
|
-
#
|
|
594
|
-
# @param state [Hash] mutable instance state
|
|
595
|
-
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
596
|
-
# `#setup` method will no longer be called on the Driver.
|
|
597
|
-
# @api private
|
|
598
|
-
def legacy_ssh_base_setup(state)
|
|
599
|
-
warn("Running legacy setup for '#{driver.name}' Driver")
|
|
600
|
-
# TODO: Document upgrade path and provide link
|
|
601
|
-
# warn("Driver authors: please read http://example.com for more details.")
|
|
602
|
-
driver.setup(state)
|
|
603
|
-
end
|
|
604
|
-
|
|
605
|
-
# Invokes `Driver#verify` on a legacy Driver, which inherits from
|
|
606
|
-
# `Kitchen::Driver::SSHBase`.
|
|
607
|
-
#
|
|
608
|
-
# @param state [Hash] mutable instance state
|
|
609
|
-
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
610
|
-
# `#verify` method will no longer be called on the Driver.
|
|
611
|
-
# @api private
|
|
612
|
-
def legacy_ssh_base_verify(state)
|
|
613
|
-
warn("Running legacy verify for '#{driver.name}' Driver")
|
|
614
|
-
# TODO: Document upgrade path and provide link
|
|
615
|
-
# warn("Driver authors: please read http://example.com for more details.")
|
|
616
|
-
driver.verify(state)
|
|
617
|
-
end
|
|
618
|
-
|
|
619
|
-
# The simplest finite state machine pseudo-implementation needed to manage
|
|
620
|
-
# an Instance.
|
|
621
|
-
#
|
|
622
|
-
# @api private
|
|
623
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
624
|
-
class FSM
|
|
625
|
-
|
|
626
|
-
# Returns an Array of all transitions to bring an Instance from its last
|
|
627
|
-
# reported transistioned state into the desired transitioned state.
|
|
628
|
-
#
|
|
629
|
-
# @param last [String,Symbol,nil] the last known transitioned state of
|
|
630
|
-
# the Instance, defaulting to `nil` (for unknown or no history)
|
|
631
|
-
# @param desired [String,Symbol] the desired transitioned state for the
|
|
632
|
-
# Instance
|
|
633
|
-
# @return [Array<Symbol>] an Array of transition actions to perform
|
|
634
|
-
# @api private
|
|
635
|
-
def self.actions(last = nil, desired)
|
|
636
|
-
last_index = index(last)
|
|
637
|
-
desired_index = index(desired)
|
|
638
|
-
|
|
639
|
-
if last_index == desired_index || last_index > desired_index
|
|
640
|
-
Array(TRANSITIONS[desired_index])
|
|
641
|
-
else
|
|
642
|
-
TRANSITIONS.slice(last_index + 1, desired_index - last_index)
|
|
643
|
-
end
|
|
644
|
-
end
|
|
645
|
-
|
|
646
|
-
TRANSITIONS = [:destroy, :create, :converge, :setup, :verify]
|
|
647
|
-
|
|
648
|
-
# Determines the index of a state in the state lifecycle vector. Woah.
|
|
649
|
-
#
|
|
650
|
-
# @param transition [Symbol,#to_sym] a state
|
|
651
|
-
# @param [Integer] the index position
|
|
652
|
-
# @api private
|
|
653
|
-
def self.index(transition)
|
|
654
|
-
if transition.nil?
|
|
655
|
-
0
|
|
656
|
-
else
|
|
657
|
-
TRANSITIONS.find_index { |t| t == transition.to_sym }
|
|
658
|
-
end
|
|
659
|
-
end
|
|
660
|
-
end
|
|
661
|
-
end
|
|
662
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2012, 2013, 2014, 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 "benchmark"
|
|
20
|
+
require "fileutils"
|
|
21
|
+
|
|
22
|
+
module Kitchen
|
|
23
|
+
|
|
24
|
+
# An instance of a suite running on a platform. A created instance may be a
|
|
25
|
+
# local virtual machine, cloud instance, container, or even a bare metal
|
|
26
|
+
# server, which is determined by the platform's driver.
|
|
27
|
+
#
|
|
28
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
29
|
+
class Instance
|
|
30
|
+
|
|
31
|
+
include Logging
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
|
|
35
|
+
# @return [Hash] a hash of mutxes, arranged by Driver class names
|
|
36
|
+
# @api private
|
|
37
|
+
attr_accessor :mutexes
|
|
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
|
|
44
|
+
def name_for(suite, platform)
|
|
45
|
+
"#{suite.name}-#{platform.name}".gsub(%r{[_,/]}, "-").gsub(/\./, "")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Suite] the test suite configuration
|
|
50
|
+
attr_reader :suite
|
|
51
|
+
|
|
52
|
+
# @return [Platform] the target platform configuration
|
|
53
|
+
attr_reader :platform
|
|
54
|
+
|
|
55
|
+
# @return [String] name of this instance
|
|
56
|
+
attr_reader :name
|
|
57
|
+
|
|
58
|
+
# @return [Driver::Base] driver object which will manage this instance's
|
|
59
|
+
# lifecycle actions
|
|
60
|
+
attr_reader :driver
|
|
61
|
+
|
|
62
|
+
# @return [Provisioner::Base] provisioner object which will the setup
|
|
63
|
+
# and invocation instructions for configuration management and other
|
|
64
|
+
# automation tools
|
|
65
|
+
attr_reader :provisioner
|
|
66
|
+
|
|
67
|
+
# @return [Transport::Base] transport object which will communicate with
|
|
68
|
+
# an instance.
|
|
69
|
+
attr_reader :transport
|
|
70
|
+
|
|
71
|
+
# @return [Verifier] verifier object for instance to manage the verifier
|
|
72
|
+
# installation on this instance
|
|
73
|
+
attr_reader :verifier
|
|
74
|
+
|
|
75
|
+
# @return [Logger] the logger for this instance
|
|
76
|
+
attr_reader :logger
|
|
77
|
+
|
|
78
|
+
# Creates a new instance, given a suite and a platform.
|
|
79
|
+
#
|
|
80
|
+
# @param [Hash] options configuration for a new suite
|
|
81
|
+
# @option options [Suite] :suite the suite (**Required**)
|
|
82
|
+
# @option options [Platform] :platform the platform (**Required**)
|
|
83
|
+
# @option options [Driver::Base] :driver the driver (**Required**)
|
|
84
|
+
# @option options [Provisioner::Base] :provisioner the provisioner
|
|
85
|
+
# @option options [Transport::Base] :transport the transport
|
|
86
|
+
# (**Required**)
|
|
87
|
+
# @option options [Verifier] :verifier the verifier logger (**Required**)
|
|
88
|
+
# @option options [Logger] :logger the instance logger
|
|
89
|
+
# (default: Kitchen.logger)
|
|
90
|
+
# @option options [StateFile] :state_file the state file object to use
|
|
91
|
+
# when tracking instance state (**Required**)
|
|
92
|
+
# @raise [ClientError] if one or more required options are omitted
|
|
93
|
+
def initialize(options = {})
|
|
94
|
+
validate_options(options)
|
|
95
|
+
|
|
96
|
+
@suite = options.fetch(:suite)
|
|
97
|
+
@platform = options.fetch(:platform)
|
|
98
|
+
@name = self.class.name_for(@suite, @platform)
|
|
99
|
+
@driver = options.fetch(:driver)
|
|
100
|
+
@provisioner = options.fetch(:provisioner)
|
|
101
|
+
@transport = options.fetch(:transport)
|
|
102
|
+
@verifier = options.fetch(:verifier)
|
|
103
|
+
@logger = options.fetch(:logger) { Kitchen.logger }
|
|
104
|
+
@state_file = options.fetch(:state_file)
|
|
105
|
+
|
|
106
|
+
setup_driver
|
|
107
|
+
setup_provisioner
|
|
108
|
+
setup_transport
|
|
109
|
+
setup_verifier
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns a displayable representation of the instance.
|
|
113
|
+
#
|
|
114
|
+
# @return [String] an instance display string
|
|
115
|
+
def to_str
|
|
116
|
+
"<#{name}>"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Creates this instance.
|
|
120
|
+
#
|
|
121
|
+
# @see Driver::Base#create
|
|
122
|
+
# @return [self] this instance, used to chain actions
|
|
123
|
+
#
|
|
124
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
125
|
+
# to gracfully stop action chaining
|
|
126
|
+
def create
|
|
127
|
+
transition_to(:create)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Converges this running instance.
|
|
131
|
+
#
|
|
132
|
+
# @see Provisioner::Base#call
|
|
133
|
+
# @return [self] this instance, used to chain actions
|
|
134
|
+
#
|
|
135
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
136
|
+
# to gracfully stop action chaining
|
|
137
|
+
def converge
|
|
138
|
+
transition_to(:converge)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Sets up this converged instance for suite tests.
|
|
142
|
+
#
|
|
143
|
+
# @see Driver::Base#setup
|
|
144
|
+
# @return [self] this instance, used to chain actions
|
|
145
|
+
#
|
|
146
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
147
|
+
# to gracfully stop action chaining
|
|
148
|
+
def setup
|
|
149
|
+
transition_to(:setup)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Verifies this set up instance by executing suite tests.
|
|
153
|
+
#
|
|
154
|
+
# @see Driver::Base#verify
|
|
155
|
+
# @return [self] this instance, used to chain actions
|
|
156
|
+
#
|
|
157
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
158
|
+
# to gracfully stop action chaining
|
|
159
|
+
def verify
|
|
160
|
+
transition_to(:verify)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Destroys this instance.
|
|
164
|
+
#
|
|
165
|
+
# @see Driver::Base#destroy
|
|
166
|
+
# @return [self] this instance, used to chain actions
|
|
167
|
+
#
|
|
168
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
169
|
+
# to gracfully stop action chaining
|
|
170
|
+
def destroy
|
|
171
|
+
transition_to(:destroy)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Tests this instance by creating, converging and verifying. If this
|
|
175
|
+
# instance is running, it will be pre-emptively destroyed to ensure a
|
|
176
|
+
# clean slate. The instance will be left post-verify in a running state.
|
|
177
|
+
#
|
|
178
|
+
# @param destroy_mode [Symbol] strategy used to cleanup after instance
|
|
179
|
+
# has finished verifying (default: `:passing`)
|
|
180
|
+
# @return [self] this instance, used to chain actions
|
|
181
|
+
#
|
|
182
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
|
183
|
+
# to gracfully stop action chaining
|
|
184
|
+
def test(destroy_mode = :passing)
|
|
185
|
+
elapsed = Benchmark.measure do
|
|
186
|
+
banner "Cleaning up any prior instances of #{to_str}"
|
|
187
|
+
destroy
|
|
188
|
+
banner "Testing #{to_str}"
|
|
189
|
+
verify
|
|
190
|
+
destroy if destroy_mode == :passing
|
|
191
|
+
end
|
|
192
|
+
info "Finished testing #{to_str} #{Util.duration(elapsed.real)}."
|
|
193
|
+
self
|
|
194
|
+
ensure
|
|
195
|
+
destroy if destroy_mode == :always
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Logs in to this instance by invoking a system command, provided by the
|
|
199
|
+
# instance's transport. This could be an SSH command, telnet, or serial
|
|
200
|
+
# console session.
|
|
201
|
+
#
|
|
202
|
+
# **Note** This method calls exec and will not return.
|
|
203
|
+
#
|
|
204
|
+
# @see Kitchen::LoginCommand
|
|
205
|
+
# @see Transport::Base::Connection#login_command
|
|
206
|
+
def login
|
|
207
|
+
state = state_file.read
|
|
208
|
+
if state[:last_action].nil?
|
|
209
|
+
raise UserError, "Instance #{to_str} has not yet been created"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
lc = if legacy_ssh_base_driver?
|
|
213
|
+
legacy_ssh_base_login(state)
|
|
214
|
+
else
|
|
215
|
+
transport.connection(state).login_command
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
debug(%{Login command: #{lc.command} #{lc.arguments.join(" ")} } \
|
|
219
|
+
"(Options: #{lc.options})")
|
|
220
|
+
Kernel.exec(*lc.exec_args)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Executes an arbitrary command on this instance.
|
|
224
|
+
#
|
|
225
|
+
# @param command [String] a command string to execute
|
|
226
|
+
def remote_exec(command)
|
|
227
|
+
transport.connection(state_file.read) do |conn|
|
|
228
|
+
conn.execute(command)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Returns a Hash of configuration and other useful diagnostic information.
|
|
233
|
+
#
|
|
234
|
+
# @return [Hash] a diagnostic hash
|
|
235
|
+
def diagnose
|
|
236
|
+
result = Hash.new
|
|
237
|
+
[
|
|
238
|
+
:platform, :state_file, :driver, :provisioner, :transport, :verifier
|
|
239
|
+
].each do |sym|
|
|
240
|
+
obj = send(sym)
|
|
241
|
+
result[sym] = obj.respond_to?(:diagnose) ? obj.diagnose : :unknown
|
|
242
|
+
end
|
|
243
|
+
result
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Returns a Hash of configuration and other useful diagnostic information
|
|
247
|
+
# associated with plugins (such as loaded version, class name, etc.).
|
|
248
|
+
#
|
|
249
|
+
# @return [Hash] a diagnostic hash
|
|
250
|
+
def diagnose_plugins
|
|
251
|
+
result = Hash.new
|
|
252
|
+
[:driver, :provisioner, :verifier, :transport].each do |sym|
|
|
253
|
+
obj = send(sym)
|
|
254
|
+
result[sym] = if obj.respond_to?(:diagnose_plugin)
|
|
255
|
+
obj.diagnose_plugin
|
|
256
|
+
else
|
|
257
|
+
:unknown
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
result
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Returns the last successfully completed action state of the instance.
|
|
264
|
+
#
|
|
265
|
+
# @return [String] a named action which was last successfully completed
|
|
266
|
+
def last_action
|
|
267
|
+
state_file.read[:last_action]
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Clean up any per-instance resources before exiting.
|
|
271
|
+
#
|
|
272
|
+
# @return [void]
|
|
273
|
+
def cleanup!
|
|
274
|
+
@transport.cleanup! if @transport
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
private
|
|
278
|
+
|
|
279
|
+
# @return [StateFile] a state file object that can be read from or written
|
|
280
|
+
# to
|
|
281
|
+
# @api private
|
|
282
|
+
attr_reader :state_file
|
|
283
|
+
|
|
284
|
+
# Validate the initial internal state of this object and raising an
|
|
285
|
+
# exception if any preconditions are not met.
|
|
286
|
+
#
|
|
287
|
+
# @param options[Hash] options hash passed into the constructor
|
|
288
|
+
# @raise [ClientError] if any validations fail
|
|
289
|
+
# @api private
|
|
290
|
+
def validate_options(options)
|
|
291
|
+
[
|
|
292
|
+
:suite, :platform, :driver, :provisioner,
|
|
293
|
+
:transport, :verifier, :state_file
|
|
294
|
+
].each do |k|
|
|
295
|
+
next if options.key?(k)
|
|
296
|
+
|
|
297
|
+
raise ClientError, "Instance#new requires option :#{k}"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Perform any final configuration or preparation needed for the driver
|
|
302
|
+
# object carry out its duties.
|
|
303
|
+
#
|
|
304
|
+
# @api private
|
|
305
|
+
def setup_driver
|
|
306
|
+
@driver.finalize_config!(self)
|
|
307
|
+
|
|
308
|
+
if driver.class.serial_actions
|
|
309
|
+
Kitchen.mutex.synchronize do
|
|
310
|
+
self.class.mutexes ||= Hash.new
|
|
311
|
+
self.class.mutexes[driver.class] = Mutex.new
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Perform any final configuration or preparation needed for the provisioner
|
|
317
|
+
# object carry out its duties.
|
|
318
|
+
#
|
|
319
|
+
# @api private
|
|
320
|
+
def setup_provisioner
|
|
321
|
+
@provisioner.finalize_config!(self)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Perform any final configuration or preparation needed for the transport
|
|
325
|
+
# object carry out its duties.
|
|
326
|
+
#
|
|
327
|
+
# @api private
|
|
328
|
+
def setup_transport
|
|
329
|
+
transport.finalize_config!(self)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Perform any final configuration or preparation needed for the verifier
|
|
333
|
+
# object carry out its duties.
|
|
334
|
+
#
|
|
335
|
+
# @api private
|
|
336
|
+
def setup_verifier
|
|
337
|
+
verifier.finalize_config!(self)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Perform all actions in order from last state to desired state.
|
|
341
|
+
#
|
|
342
|
+
# @param desired [Symbol] a symbol representing the desired action state
|
|
343
|
+
# @return [self] this instance, used to chain actions
|
|
344
|
+
# @api private
|
|
345
|
+
def transition_to(desired)
|
|
346
|
+
result = nil
|
|
347
|
+
FSM.actions(last_action, desired).each do |transition|
|
|
348
|
+
result = send("#{transition}_action")
|
|
349
|
+
end
|
|
350
|
+
result
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Perform the create action.
|
|
354
|
+
#
|
|
355
|
+
# @see Driver::Base#create
|
|
356
|
+
# @return [self] this instance, used to chain actions
|
|
357
|
+
# @api private
|
|
358
|
+
def create_action
|
|
359
|
+
perform_action(:create, "Creating")
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Perform the converge action.
|
|
363
|
+
#
|
|
364
|
+
# @see Provisioner::Base#call
|
|
365
|
+
# @return [self] this instance, used to chain actions
|
|
366
|
+
# @api private
|
|
367
|
+
def converge_action
|
|
368
|
+
banner "Converging #{to_str}..."
|
|
369
|
+
elapsed = action(:converge) do |state|
|
|
370
|
+
if legacy_ssh_base_driver?
|
|
371
|
+
legacy_ssh_base_converge(state)
|
|
372
|
+
else
|
|
373
|
+
provisioner.call(state)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
info("Finished converging #{to_str} #{Util.duration(elapsed.real)}.")
|
|
377
|
+
self
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Perform the setup action.
|
|
381
|
+
#
|
|
382
|
+
# @see Driver::Base#setup
|
|
383
|
+
# @return [self] this instance, used to chain actions
|
|
384
|
+
# @api private
|
|
385
|
+
def setup_action
|
|
386
|
+
banner "Setting up #{to_str}..."
|
|
387
|
+
elapsed = action(:setup) do |state|
|
|
388
|
+
legacy_ssh_base_setup(state) if legacy_ssh_base_driver?
|
|
389
|
+
end
|
|
390
|
+
info("Finished setting up #{to_str} #{Util.duration(elapsed.real)}.")
|
|
391
|
+
self
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# returns true, if the verifier is busser
|
|
395
|
+
def verifier_busser?(verifier)
|
|
396
|
+
!defined?(Kitchen::Verifier::Busser).nil? && verifier.is_a?(Kitchen::Verifier::Busser)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# returns true, if the verifier is dummy
|
|
400
|
+
def verifier_dummy?(verifier)
|
|
401
|
+
!defined?(Kitchen::Verifier::Dummy).nil? && verifier.is_a?(Kitchen::Verifier::Dummy)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def use_legacy_ssh_verifier?(verifier)
|
|
405
|
+
verifier_busser?(verifier) || verifier_dummy?(verifier)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Perform the verify action.
|
|
409
|
+
#
|
|
410
|
+
# @see Driver::Base#verify
|
|
411
|
+
# @return [self] this instance, used to chain actions
|
|
412
|
+
# @api private
|
|
413
|
+
def verify_action
|
|
414
|
+
banner "Verifying #{to_str}..."
|
|
415
|
+
elapsed = action(:verify) do |state|
|
|
416
|
+
# use special handling for legacy driver
|
|
417
|
+
if legacy_ssh_base_driver? && use_legacy_ssh_verifier?(verifier)
|
|
418
|
+
legacy_ssh_base_verify(state)
|
|
419
|
+
elsif legacy_ssh_base_driver?
|
|
420
|
+
# read ssh options from legacy driver
|
|
421
|
+
verifier.call(driver.legacy_state(state))
|
|
422
|
+
else
|
|
423
|
+
verifier.call(state)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
info("Finished verifying #{to_str} #{Util.duration(elapsed.real)}.")
|
|
427
|
+
self
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Perform the destroy action.
|
|
431
|
+
#
|
|
432
|
+
# @see Driver::Base#destroy
|
|
433
|
+
# @return [self] this instance, used to chain actions
|
|
434
|
+
# @api private
|
|
435
|
+
def destroy_action
|
|
436
|
+
perform_action(:destroy, "Destroying") { state_file.destroy }
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Perform an arbitrary action and provide useful logging.
|
|
440
|
+
#
|
|
441
|
+
# @param verb [Symbol] the action to be performed
|
|
442
|
+
# @param output_verb [String] a verb representing the action, suitable for
|
|
443
|
+
# use in output logging
|
|
444
|
+
# @yield perform optional work just after action has complted
|
|
445
|
+
# @return [self] this instance, used to chain actions
|
|
446
|
+
# @api private
|
|
447
|
+
def perform_action(verb, output_verb)
|
|
448
|
+
banner "#{output_verb} #{to_str}..."
|
|
449
|
+
elapsed = action(verb) { |state| driver.public_send(verb, state) }
|
|
450
|
+
info("Finished #{output_verb.downcase} #{to_str}" \
|
|
451
|
+
" #{Util.duration(elapsed.real)}.")
|
|
452
|
+
yield if block_given?
|
|
453
|
+
self
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# Times a call to an action block and handles any raised exceptions. This
|
|
457
|
+
# method ensures that the last successfully completed action is persisted
|
|
458
|
+
# to the state file. The last action state will either be the desired
|
|
459
|
+
# action that is passed in or the previous action that was persisted to the
|
|
460
|
+
# state file.
|
|
461
|
+
#
|
|
462
|
+
# @param what [Symbol] the action to be performed
|
|
463
|
+
# @param block [Proc] a block to be called
|
|
464
|
+
# @return [Benchmark::Tms] timing information for the given action
|
|
465
|
+
# @raise [InstanceFailed] if a driver action fails to complete, signaled
|
|
466
|
+
# by a driver raising an ActionFailed exception. Typical reasons for this
|
|
467
|
+
# would be a driver create action failing, a chef convergence crashing
|
|
468
|
+
# in normal course of development, failing acceptance tests in the
|
|
469
|
+
# verify action, etc.
|
|
470
|
+
# @raise [ActionFailed] if an unforseen or unplanned exception is raised.
|
|
471
|
+
# This would usually indicate that a race condition was triggered, a
|
|
472
|
+
# bug exists in a driver, provisioner, or core, a transient IO error
|
|
473
|
+
# occured, etc.
|
|
474
|
+
# @api private
|
|
475
|
+
def action(what, &block)
|
|
476
|
+
state = state_file.read
|
|
477
|
+
elapsed = Benchmark.measure do
|
|
478
|
+
synchronize_or_call(what, state, &block)
|
|
479
|
+
end
|
|
480
|
+
state[:last_action] = what.to_s
|
|
481
|
+
elapsed
|
|
482
|
+
rescue ActionFailed => e
|
|
483
|
+
log_failure(what, e)
|
|
484
|
+
raise(InstanceFailure, failure_message(what) +
|
|
485
|
+
" Please see .kitchen/logs/#{name}.log for more details",
|
|
486
|
+
e.backtrace)
|
|
487
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
488
|
+
log_failure(what, e)
|
|
489
|
+
raise ActionFailed,
|
|
490
|
+
"Failed to complete ##{what} action: [#{e.message}]", e.backtrace
|
|
491
|
+
ensure
|
|
492
|
+
state_file.write(state)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Runs a given action block through a common driver mutex if required or
|
|
496
|
+
# runs it directly otherwise. If a driver class' `.serial_actions` array
|
|
497
|
+
# includes the desired action, then the action must be run with a muxtex
|
|
498
|
+
# lock. Otherwise, it is assumed that the action can happen concurrently,
|
|
499
|
+
# or fully in parallel.
|
|
500
|
+
#
|
|
501
|
+
# @param what [Symbol] the action to be performed
|
|
502
|
+
# @param state [Hash] a mutable state hash for this instance
|
|
503
|
+
# @param block [Proc] a block to be called
|
|
504
|
+
# @api private
|
|
505
|
+
def synchronize_or_call(what, state, &block)
|
|
506
|
+
if Array(driver.class.serial_actions).include?(what)
|
|
507
|
+
debug("#{to_str} is synchronizing on #{driver.class}##{what}")
|
|
508
|
+
self.class.mutexes[driver.class].synchronize do
|
|
509
|
+
debug("#{to_str} is messaging #{driver.class}##{what}")
|
|
510
|
+
block.call(state)
|
|
511
|
+
end
|
|
512
|
+
else
|
|
513
|
+
block.call(state)
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Writes a high level message for logging and/or output.
|
|
518
|
+
#
|
|
519
|
+
# In this case, all instance banner messages will be written to the common
|
|
520
|
+
# Kitchen logger so that the high level flow of a run can be followed in
|
|
521
|
+
# the kitchen.log file.
|
|
522
|
+
#
|
|
523
|
+
# @api private
|
|
524
|
+
def banner(*args)
|
|
525
|
+
Kitchen.logger.logdev && Kitchen.logger.logdev.banner(*args)
|
|
526
|
+
super
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# Logs a failure (message and backtrace) to the instance's file logger
|
|
530
|
+
# to help with debugging and diagnosing issues without overwhelming the
|
|
531
|
+
# console output in the default case (i.e. running kitchen with :info
|
|
532
|
+
# level debugging).
|
|
533
|
+
#
|
|
534
|
+
# @param what [String] an action
|
|
535
|
+
# @param e [Exception] an exception
|
|
536
|
+
# @api private
|
|
537
|
+
def log_failure(what, e)
|
|
538
|
+
return if logger.logdev.nil?
|
|
539
|
+
|
|
540
|
+
logger.logdev.error(failure_message(what))
|
|
541
|
+
Error.formatted_trace(e).each { |line| logger.logdev.error(line) }
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# Returns a string explaining what action failed, at a high level. Used
|
|
545
|
+
# for displaying to end user.
|
|
546
|
+
#
|
|
547
|
+
# @param what [String] an action
|
|
548
|
+
# @return [String] a failure message
|
|
549
|
+
# @api private
|
|
550
|
+
def failure_message(what)
|
|
551
|
+
"#{what.capitalize} failed on instance #{to_str}."
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# Invokes `Driver#converge` on a legacy Driver, which inherits from
|
|
555
|
+
# `Kitchen::Driver::SSHBase`.
|
|
556
|
+
#
|
|
557
|
+
# @param state [Hash] mutable instance state
|
|
558
|
+
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
559
|
+
# `#converge` method will no longer be called on the Driver.
|
|
560
|
+
# @api private
|
|
561
|
+
def legacy_ssh_base_converge(state)
|
|
562
|
+
warn("Running legacy converge for '#{driver.name}' Driver")
|
|
563
|
+
# TODO: Document upgrade path and provide link
|
|
564
|
+
# warn("Driver authors: please read http://example.com for more details.")
|
|
565
|
+
driver.converge(state)
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# @return [TrueClass,FalseClass] whether or not the Driver inherits from
|
|
569
|
+
# `Kitchen::Driver::SSHBase`
|
|
570
|
+
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
571
|
+
# `#converge` method will no longer be called on the Driver.
|
|
572
|
+
# @api private
|
|
573
|
+
def legacy_ssh_base_driver?
|
|
574
|
+
driver.class < Kitchen::Driver::SSHBase
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# Invokes `Driver#login_command` on a legacy Driver, which inherits from
|
|
578
|
+
# `Kitchen::Driver::SSHBase`.
|
|
579
|
+
#
|
|
580
|
+
# @param state [Hash] mutable instance state
|
|
581
|
+
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
582
|
+
# `#login_command` method will no longer be called on the Driver.
|
|
583
|
+
# @api private
|
|
584
|
+
def legacy_ssh_base_login(state)
|
|
585
|
+
warn("Running legacy login for '#{driver.name}' Driver")
|
|
586
|
+
# TODO: Document upgrade path and provide link
|
|
587
|
+
# warn("Driver authors: please read http://example.com for more details.")
|
|
588
|
+
driver.login_command(state)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# Invokes `Driver#setup` on a legacy Driver, which inherits from
|
|
592
|
+
# `Kitchen::Driver::SSHBase`.
|
|
593
|
+
#
|
|
594
|
+
# @param state [Hash] mutable instance state
|
|
595
|
+
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
596
|
+
# `#setup` method will no longer be called on the Driver.
|
|
597
|
+
# @api private
|
|
598
|
+
def legacy_ssh_base_setup(state)
|
|
599
|
+
warn("Running legacy setup for '#{driver.name}' Driver")
|
|
600
|
+
# TODO: Document upgrade path and provide link
|
|
601
|
+
# warn("Driver authors: please read http://example.com for more details.")
|
|
602
|
+
driver.setup(state)
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# Invokes `Driver#verify` on a legacy Driver, which inherits from
|
|
606
|
+
# `Kitchen::Driver::SSHBase`.
|
|
607
|
+
#
|
|
608
|
+
# @param state [Hash] mutable instance state
|
|
609
|
+
# @deprecated When legacy Driver::SSHBase support is removed, the
|
|
610
|
+
# `#verify` method will no longer be called on the Driver.
|
|
611
|
+
# @api private
|
|
612
|
+
def legacy_ssh_base_verify(state)
|
|
613
|
+
warn("Running legacy verify for '#{driver.name}' Driver")
|
|
614
|
+
# TODO: Document upgrade path and provide link
|
|
615
|
+
# warn("Driver authors: please read http://example.com for more details.")
|
|
616
|
+
driver.verify(state)
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# The simplest finite state machine pseudo-implementation needed to manage
|
|
620
|
+
# an Instance.
|
|
621
|
+
#
|
|
622
|
+
# @api private
|
|
623
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
624
|
+
class FSM
|
|
625
|
+
|
|
626
|
+
# Returns an Array of all transitions to bring an Instance from its last
|
|
627
|
+
# reported transistioned state into the desired transitioned state.
|
|
628
|
+
#
|
|
629
|
+
# @param last [String,Symbol,nil] the last known transitioned state of
|
|
630
|
+
# the Instance, defaulting to `nil` (for unknown or no history)
|
|
631
|
+
# @param desired [String,Symbol] the desired transitioned state for the
|
|
632
|
+
# Instance
|
|
633
|
+
# @return [Array<Symbol>] an Array of transition actions to perform
|
|
634
|
+
# @api private
|
|
635
|
+
def self.actions(last = nil, desired)
|
|
636
|
+
last_index = index(last)
|
|
637
|
+
desired_index = index(desired)
|
|
638
|
+
|
|
639
|
+
if last_index == desired_index || last_index > desired_index
|
|
640
|
+
Array(TRANSITIONS[desired_index])
|
|
641
|
+
else
|
|
642
|
+
TRANSITIONS.slice(last_index + 1, desired_index - last_index)
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
TRANSITIONS = [:destroy, :create, :converge, :setup, :verify]
|
|
647
|
+
|
|
648
|
+
# Determines the index of a state in the state lifecycle vector. Woah.
|
|
649
|
+
#
|
|
650
|
+
# @param transition [Symbol,#to_sym] a state
|
|
651
|
+
# @param [Integer] the index position
|
|
652
|
+
# @api private
|
|
653
|
+
def self.index(transition)
|
|
654
|
+
if transition.nil?
|
|
655
|
+
0
|
|
656
|
+
else
|
|
657
|
+
TRANSITIONS.find_index { |t| t == transition.to_sym }
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
end
|