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.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +8 -8
  3. data/.gitattributes +3 -0
  4. data/.github/ISSUE_TEMPLATE.md +55 -55
  5. data/.gitignore +28 -28
  6. data/.kitchen.ci.yml +23 -23
  7. data/.kitchen.proxy.yml +27 -27
  8. data/.rubocop.yml +3 -3
  9. data/.travis.yml +70 -70
  10. data/.yardopts +3 -3
  11. data/Berksfile +3 -3
  12. data/CHANGELOG.md +1090 -1083
  13. data/CONTRIBUTING.md +14 -14
  14. data/Gemfile +19 -19
  15. data/Gemfile.proxy_tests +4 -4
  16. data/Guardfile +42 -42
  17. data/LICENSE +15 -15
  18. data/MAINTAINERS.md +23 -23
  19. data/README.md +135 -135
  20. data/Rakefile +61 -61
  21. data/appveyor.yml +44 -44
  22. data/features/kitchen_action_commands.feature +164 -164
  23. data/features/kitchen_command.feature +16 -16
  24. data/features/kitchen_console_command.feature +34 -34
  25. data/features/kitchen_defaults.feature +38 -38
  26. data/features/kitchen_diagnose_command.feature +96 -96
  27. data/features/kitchen_driver_create_command.feature +64 -64
  28. data/features/kitchen_driver_discover_command.feature +25 -25
  29. data/features/kitchen_help_command.feature +16 -16
  30. data/features/kitchen_init_command.feature +274 -274
  31. data/features/kitchen_list_command.feature +104 -104
  32. data/features/kitchen_login_command.feature +62 -62
  33. data/features/kitchen_sink_command.feature +30 -30
  34. data/features/kitchen_test_command.feature +88 -88
  35. data/features/step_definitions/gem_steps.rb +36 -36
  36. data/features/step_definitions/git_steps.rb +5 -5
  37. data/features/step_definitions/output_steps.rb +5 -5
  38. data/features/support/env.rb +75 -75
  39. data/lib/kitchen.rb +150 -150
  40. data/lib/kitchen/base64_stream.rb +55 -55
  41. data/lib/kitchen/cli.rb +419 -419
  42. data/lib/kitchen/collection.rb +55 -55
  43. data/lib/kitchen/color.rb +65 -65
  44. data/lib/kitchen/command.rb +185 -185
  45. data/lib/kitchen/command/action.rb +45 -45
  46. data/lib/kitchen/command/console.rb +58 -58
  47. data/lib/kitchen/command/diagnose.rb +92 -92
  48. data/lib/kitchen/command/driver_discover.rb +105 -105
  49. data/lib/kitchen/command/exec.rb +41 -41
  50. data/lib/kitchen/command/list.rb +119 -119
  51. data/lib/kitchen/command/login.rb +43 -43
  52. data/lib/kitchen/command/sink.rb +54 -54
  53. data/lib/kitchen/command/test.rb +51 -51
  54. data/lib/kitchen/config.rb +322 -322
  55. data/lib/kitchen/configurable.rb +529 -529
  56. data/lib/kitchen/data_munger.rb +959 -959
  57. data/lib/kitchen/diagnostic.rb +141 -141
  58. data/lib/kitchen/driver.rb +56 -56
  59. data/lib/kitchen/driver/base.rb +134 -134
  60. data/lib/kitchen/driver/dummy.rb +108 -108
  61. data/lib/kitchen/driver/proxy.rb +72 -72
  62. data/lib/kitchen/driver/ssh_base.rb +357 -357
  63. data/lib/kitchen/errors.rb +229 -229
  64. data/lib/kitchen/generator/driver_create.rb +177 -177
  65. data/lib/kitchen/generator/init.rb +296 -296
  66. data/lib/kitchen/instance.rb +662 -662
  67. data/lib/kitchen/lazy_hash.rb +142 -142
  68. data/lib/kitchen/loader/yaml.rb +349 -349
  69. data/lib/kitchen/logger.rb +423 -423
  70. data/lib/kitchen/logging.rb +56 -56
  71. data/lib/kitchen/login_command.rb +52 -52
  72. data/lib/kitchen/metadata_chopper.rb +52 -52
  73. data/lib/kitchen/platform.rb +67 -67
  74. data/lib/kitchen/provisioner.rb +54 -54
  75. data/lib/kitchen/provisioner/base.rb +236 -236
  76. data/lib/kitchen/provisioner/chef/berkshelf.rb +114 -114
  77. data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -322
  78. data/lib/kitchen/provisioner/chef/librarian.rb +112 -112
  79. data/lib/kitchen/provisioner/chef_apply.rb +124 -124
  80. data/lib/kitchen/provisioner/chef_base.rb +341 -341
  81. data/lib/kitchen/provisioner/chef_solo.rb +88 -88
  82. data/lib/kitchen/provisioner/chef_zero.rb +245 -245
  83. data/lib/kitchen/provisioner/dummy.rb +79 -79
  84. data/lib/kitchen/provisioner/shell.rb +138 -138
  85. data/lib/kitchen/rake_tasks.rb +63 -63
  86. data/lib/kitchen/shell_out.rb +93 -93
  87. data/lib/kitchen/ssh.rb +276 -276
  88. data/lib/kitchen/state_file.rb +120 -120
  89. data/lib/kitchen/suite.rb +51 -51
  90. data/lib/kitchen/thor_tasks.rb +66 -66
  91. data/lib/kitchen/transport.rb +54 -54
  92. data/lib/kitchen/transport/base.rb +176 -176
  93. data/lib/kitchen/transport/dummy.rb +79 -79
  94. data/lib/kitchen/transport/ssh.rb +364 -364
  95. data/lib/kitchen/transport/winrm.rb +486 -486
  96. data/lib/kitchen/util.rb +147 -147
  97. data/lib/kitchen/verifier.rb +55 -55
  98. data/lib/kitchen/verifier/base.rb +235 -235
  99. data/lib/kitchen/verifier/busser.rb +277 -277
  100. data/lib/kitchen/verifier/dummy.rb +79 -79
  101. data/lib/kitchen/verifier/shell.rb +101 -101
  102. data/lib/kitchen/version.rb +21 -21
  103. data/lib/vendor/hash_recursive_merge.rb +82 -82
  104. data/spec/kitchen/base64_stream_spec.rb +77 -77
  105. data/spec/kitchen/cli_spec.rb +56 -56
  106. data/spec/kitchen/collection_spec.rb +80 -80
  107. data/spec/kitchen/color_spec.rb +54 -54
  108. data/spec/kitchen/config_spec.rb +408 -408
  109. data/spec/kitchen/configurable_spec.rb +1095 -1095
  110. data/spec/kitchen/data_munger_spec.rb +2694 -2694
  111. data/spec/kitchen/diagnostic_spec.rb +129 -129
  112. data/spec/kitchen/driver/base_spec.rb +121 -121
  113. data/spec/kitchen/driver/dummy_spec.rb +199 -199
  114. data/spec/kitchen/driver/proxy_spec.rb +138 -138
  115. data/spec/kitchen/driver/ssh_base_spec.rb +1115 -1115
  116. data/spec/kitchen/driver_spec.rb +112 -112
  117. data/spec/kitchen/errors_spec.rb +309 -309
  118. data/spec/kitchen/instance_spec.rb +1419 -1419
  119. data/spec/kitchen/lazy_hash_spec.rb +117 -117
  120. data/spec/kitchen/loader/yaml_spec.rb +774 -774
  121. data/spec/kitchen/logger_spec.rb +429 -429
  122. data/spec/kitchen/logging_spec.rb +59 -59
  123. data/spec/kitchen/login_command_spec.rb +68 -68
  124. data/spec/kitchen/metadata_chopper_spec.rb +82 -82
  125. data/spec/kitchen/platform_spec.rb +89 -89
  126. data/spec/kitchen/provisioner/base_spec.rb +386 -386
  127. data/spec/kitchen/provisioner/chef_apply_spec.rb +136 -136
  128. data/spec/kitchen/provisioner/chef_base_spec.rb +1161 -1161
  129. data/spec/kitchen/provisioner/chef_solo_spec.rb +557 -557
  130. data/spec/kitchen/provisioner/chef_zero_spec.rb +1001 -1001
  131. data/spec/kitchen/provisioner/dummy_spec.rb +99 -99
  132. data/spec/kitchen/provisioner/shell_spec.rb +566 -566
  133. data/spec/kitchen/provisioner_spec.rb +107 -107
  134. data/spec/kitchen/shell_out_spec.rb +150 -150
  135. data/spec/kitchen/ssh_spec.rb +693 -693
  136. data/spec/kitchen/state_file_spec.rb +129 -129
  137. data/spec/kitchen/suite_spec.rb +62 -62
  138. data/spec/kitchen/transport/base_spec.rb +89 -89
  139. data/spec/kitchen/transport/ssh_spec.rb +1255 -1255
  140. data/spec/kitchen/transport/winrm_spec.rb +1143 -1143
  141. data/spec/kitchen/transport_spec.rb +112 -112
  142. data/spec/kitchen/util_spec.rb +165 -165
  143. data/spec/kitchen/verifier/base_spec.rb +362 -362
  144. data/spec/kitchen/verifier/busser_spec.rb +610 -610
  145. data/spec/kitchen/verifier/dummy_spec.rb +99 -99
  146. data/spec/kitchen/verifier/shell_spec.rb +160 -160
  147. data/spec/kitchen/verifier_spec.rb +120 -120
  148. data/spec/kitchen_spec.rb +114 -114
  149. data/spec/spec_helper.rb +85 -85
  150. data/spec/support/powershell_max_size_spec.rb +40 -40
  151. data/support/busser_install_command.ps1 +14 -14
  152. data/support/busser_install_command.sh +14 -14
  153. data/support/chef-client-zero.rb +77 -77
  154. data/support/chef_base_init_command.ps1 +18 -18
  155. data/support/chef_base_init_command.sh +2 -2
  156. data/support/chef_base_install_command.ps1 +85 -85
  157. data/support/chef_base_install_command.sh +229 -229
  158. data/support/chef_zero_prepare_command_legacy.ps1 +9 -9
  159. data/support/chef_zero_prepare_command_legacy.sh +10 -10
  160. data/support/download_helpers.sh +109 -109
  161. data/support/dummy-validation.pem +27 -27
  162. data/templates/driver/CHANGELOG.md.erb +3 -3
  163. data/templates/driver/Gemfile.erb +3 -3
  164. data/templates/driver/README.md.erb +64 -64
  165. data/templates/driver/Rakefile.erb +21 -21
  166. data/templates/driver/driver.rb.erb +23 -23
  167. data/templates/driver/gemspec.erb +29 -29
  168. data/templates/driver/gitignore.erb +17 -17
  169. data/templates/driver/license_apachev2.erb +15 -15
  170. data/templates/driver/license_lgplv3.erb +16 -16
  171. data/templates/driver/license_mit.erb +22 -22
  172. data/templates/driver/license_reserved.erb +5 -5
  173. data/templates/driver/tailor.erb +4 -4
  174. data/templates/driver/travis.yml.erb +11 -11
  175. data/templates/driver/version.rb.erb +12 -12
  176. data/templates/init/chefignore.erb +1 -1
  177. data/templates/init/kitchen.yml.erb +18 -18
  178. data/test-kitchen.gemspec +62 -62
  179. data/test/integration/default/default_spec.rb +3 -3
  180. data/testing_windows.md +37 -37
  181. metadata +5 -4
@@ -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