test-kitchen-rsync 3.0.0.pre.1

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