test-kitchen-rsync 3.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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