kitchen-vagrant 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f38360fb2e76468a3b8fec2cc9605529a3627fb6887fc0054db601ef2b14c1b
4
- data.tar.gz: 8697570d8be74b4e5f674707be02a1aa45b21acd2836b033075e0bea0667bf76
3
+ metadata.gz: cf0fbd431e7e5a52a144f6ce2e79edea1d2783c74c0ef212c3674a12e147683e
4
+ data.tar.gz: fdde8621c74520cf97a5367bfa3098aedc814d8037ad564ef8cada2056237807
5
5
  SHA512:
6
- metadata.gz: 2742d9a6cf27c11dd986ff5054c9c389c94637b7cfc50bb677ca43411e8b3d336d3adce1bf50be6a20ab524f41c8cfa24237b2591d265a0b46843adaffb429e0
7
- data.tar.gz: 02cab0d3a12310cf6da7efdfc62011a621b9daa991430fc1efce6041ebbc40d18d4c5c661562eac4d055db020c0bc06f308b98bb089daf17fd579fe222d454ad
6
+ metadata.gz: 1877e0240f40b4d4880ba3032f9801a984252051a88ba2a8fc0bd5af9d314ac5aa701b1116e9df747614b6ca3adacbbf898be6fdfe1d3d104f41f2321d2aceaa
7
+ data.tar.gz: eef20bc7471c539f0b11cbb31d80337bf7037905e7a1fecc9f7104d9b9335f57bd5d5437e376bb19942d5e80dcffb5193ba5ddab7836d8c3cb315aced9149361
@@ -15,30 +15,90 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require "base64" unless defined?(Base64)
18
19
  require "mixlib/shellout" unless defined?(Mixlib::ShellOut)
19
20
  require "fileutils" unless defined?(FileUtils)
20
21
  require "json" unless defined?(JSON)
21
22
 
22
23
  module Kitchen
24
+ # Test Kitchen driver plugins.
23
25
  module Driver
26
+ # Helpers for talking to Hyper-V from the Vagrant driver.
27
+ #
28
+ # Hyper-V has no command line interface of its own, so everything here
29
+ # funnels through PowerShell: a script is wrapped so that it dot-sources
30
+ # the bundled `support/hyperv.ps1`, base64-encoded for `-EncodedCommand`
31
+ # (which sidesteps all shell quoting problems), executed, and its JSON
32
+ # output parsed back into Ruby.
33
+ #
34
+ # The module expects to be mixed into a {Kitchen::Configurable} that also
35
+ # provides {Kitchen::Logging}, which the Vagrant driver does.
36
+ #
37
+ # @author Steven Murawski <smurawski@chef.io>
24
38
  module HypervHelpers
39
+ # Encodes a PowerShell script for `powershell.exe -EncodedCommand`.
40
+ #
41
+ # PowerShell requires UTF-16LE, not UTF-8, and strict (unwrapped) base64.
42
+ #
43
+ # @param script [String] a PowerShell script
44
+ # @return [String] the script as single-line base64
45
+ # @api private
25
46
  def encode_command(script)
26
47
  encoded_script = script.encode("UTF-16LE", "UTF-8")
27
48
  Base64.strict_encode64(encoded_script)
28
49
  end
29
50
 
51
+ # The processor architecture Windows reports for this process.
52
+ #
53
+ # `PROCESSOR_ARCHITEW6432` is only set for a 32-bit process running under
54
+ # WOW64, where it holds the *machine's* architecture while
55
+ # `PROCESSOR_ARCHITECTURE` holds the emulated one -- so it wins when
56
+ # present.
57
+ #
58
+ # @return [String,nil] e.g. `"AMD64"`, or nil off Windows
59
+ # @api private
60
+ def os_architecture
61
+ ENV["PROCESSOR_ARCHITEW6432"] || ENV["PROCESSOR_ARCHITECTURE"]
62
+ end
63
+
64
+ # Whether the running Ruby is a 64-bit build, determined from the size of
65
+ # a packed pointer.
66
+ #
67
+ # @return [true,false] whether this Ruby is 64-bit
68
+ # @api private
69
+ def ruby_64bit?
70
+ ["foo"].pack("p").size != 4
71
+ end
72
+
73
+ # Whether both the OS and the running Ruby are 64-bit.
74
+ #
75
+ # @return [true,false] whether this is a 64-bit Ruby on a 64-bit Windows
76
+ # @api private
30
77
  def is_64bit?
31
- os_arch = ENV["PROCESSOR_ARCHITEW6432"] || ENV["PROCESSOR_ARCHITECTURE"]
32
- ruby_arch = ["foo"].pack("p").size == 4 ? 32 : 64
33
- os_arch == "AMD64" && ruby_arch == 64
78
+ os_architecture == "AMD64" && ruby_64bit?
34
79
  end
35
80
 
81
+ # Whether both the OS and the running Ruby are 32-bit.
82
+ #
83
+ # Note this is deliberately *not* the negation of {#is_64bit?}: a 32-bit
84
+ # Ruby on a 64-bit Windows (the WOW64 case) is neither.
85
+ #
86
+ # @return [true,false] whether this is a 32-bit Ruby on a 32-bit Windows
87
+ # @api private
36
88
  def is_32bit?
37
- os_arch = ENV["PROCESSOR_ARCHITEW6432"] || ENV["PROCESSOR_ARCHITECTURE"]
38
- ruby_arch = ["foo"].pack("p").size == 4 ? 32 : 64
39
- os_arch != "AMD64" && ruby_arch == 32
89
+ os_architecture != "AMD64" && !ruby_64bit?
40
90
  end
41
91
 
92
+ # Path to a PowerShell that matches the machine's architecture.
93
+ #
94
+ # When Ruby and Windows agree on bitness, the real `System32` PowerShell
95
+ # is correct. When they disagree -- a 32-bit Ruby on 64-bit Windows --
96
+ # `System32` would be silently redirected by WOW64 to the 32-bit
97
+ # PowerShell, which cannot see the Hyper-V cmdlets; `Sysnative` is the
98
+ # alias that reaches the 64-bit one.
99
+ #
100
+ # @return [String] absolute path to powershell.exe
101
+ # @api private
42
102
  def powershell_64_bit
43
103
  if is_64bit? || is_32bit?
44
104
  'c:\windows\system32\windowspowershell\v1.0\powershell.exe'
@@ -47,6 +107,12 @@ module Kitchen
47
107
  end
48
108
  end
49
109
 
110
+ # Builds a full `powershell.exe` command line that dot-sources the gem's
111
+ # `support/hyperv.ps1` helper functions and then runs `script`.
112
+ #
113
+ # @param script [String] a PowerShell script
114
+ # @return [String] a command line ready to hand to a shell
115
+ # @api private
50
116
  def wrap_command(script)
51
117
  base_script_path = File.join(File.dirname(__FILE__), "/../../../support/hyperv.ps1")
52
118
  debug("Loading functions from #{base_script_path}")
@@ -60,6 +126,7 @@ module Kitchen
60
126
  #
61
127
  # @param cmd [String] command to run locally
62
128
  # @param options [Hash] options hash
129
+ # @return [Hash,nil] the parsed JSON the script emitted, if any
63
130
  # @see Kitchen::ShellOut.run_command
64
131
  # @api private
65
132
  def run_ps(cmd, options = {})
@@ -70,6 +137,13 @@ module Kitchen
70
137
  execute_command wrapped_command, options
71
138
  end
72
139
 
140
+ # Runs a command locally and parses its output as JSON.
141
+ #
142
+ # @param cmd [String] command to run locally
143
+ # @param options [Hash] options passed through to `Mixlib::ShellOut`
144
+ # @return [Hash,Array,nil] the parsed output, or nil if there was none
145
+ # @raise [RuntimeError] if the command exited non-zero
146
+ # @api private
73
147
  def execute_command(cmd, options = {})
74
148
  debug("#Local Command BEGIN (#{cmd})")
75
149
  sh = Mixlib::ShellOut.new(cmd, options)
@@ -81,10 +155,21 @@ module Kitchen
81
155
  JSON.parse(stdout) if stdout.length > 2
82
156
  end
83
157
 
158
+ # Strips the interactive prompt lines PowerShell interleaves with real
159
+ # output, which would otherwise make the result unparseable as JSON.
160
+ #
161
+ # @param stdout [String] raw standard output
162
+ # @return [String] output with `PS ...>` lines removed
163
+ # @api private
84
164
  def sanitize_stdout(stdout)
85
165
  stdout.split("\n").select { |s| !s.start_with?("PS") }.join("\n")
86
166
  end
87
167
 
168
+ # Asks Hyper-V for the virtual switch new VMs should be attached to.
169
+ #
170
+ # @return [String] the name of the switch
171
+ # @raise [RuntimeError] if no usable switch could be determined
172
+ # @api private
88
173
  def hyperv_switch
89
174
  default_switch_object = run_ps hyperv_default_switch_ps
90
175
  if default_switch_object.nil? ||
@@ -96,6 +181,11 @@ module Kitchen
96
181
  default_switch_object["Name"]
97
182
  end
98
183
 
184
+ # The PowerShell that {#hyperv_switch} runs. Honours
185
+ # `KITCHEN_HYPERV_SWITCH`; without it, `Get-DefaultVMSwitch` picks one.
186
+ #
187
+ # @return [String] a PowerShell script emitting a JSON switch object
188
+ # @api private
99
189
  def hyperv_default_switch_ps
100
190
  <<-VMSWITCH
101
191
  Get-DefaultVMSwitch #{ENV["KITCHEN_HYPERV_SWITCH"]} | ConvertTo-Json
@@ -104,6 +194,11 @@ module Kitchen
104
194
 
105
195
  private
106
196
 
197
+ # Renders a Ruby Array as a PowerShell array literal.
198
+ #
199
+ # @param list [Array,nil] the values to render
200
+ # @return [String] e.g. `@("a", "b")`, or `@()` when empty
201
+ # @api private
107
202
  def ruby_array_to_ps_array(list)
108
203
  return "@()" if list.nil? || list.empty?
109
204
 
@@ -80,6 +80,8 @@ module Kitchen
80
80
 
81
81
  default_config :synced_folders, []
82
82
 
83
+ default_config :env, []
84
+
83
85
  default_config :use_cached_chef_client, false
84
86
 
85
87
  default_config :vagrant_binary, "vagrant"
@@ -125,6 +127,11 @@ module Kitchen
125
127
  info("Vagrant instance #{instance.to_str} created.")
126
128
  end
127
129
 
130
+ # The box this Instance should use when the user has not named one.
131
+ #
132
+ # Platforms the Bento project builds are mapped onto their `bento/`
133
+ # box; anything else is assumed to name a box directly.
134
+ #
128
135
  # @return [String,nil] the Vagrant box for this Instance
129
136
  def default_box
130
137
  if bento_box?(instance.platform.name)
@@ -134,6 +141,11 @@ module Kitchen
134
141
  end
135
142
  end
136
143
 
144
+ # The box URL this Instance should use when the user has not named one.
145
+ #
146
+ # Always nil: modern Vagrant resolves boxes through Vagrant Cloud, so an
147
+ # explicit URL is only needed for privately hosted boxes.
148
+ #
137
149
  # @return [String,nil] the Vagrant box URL for this Instance
138
150
  def default_box_url
139
151
  nil
@@ -155,6 +167,12 @@ module Kitchen
155
167
  state.delete(:hostname)
156
168
  end
157
169
 
170
+ # Packages a created instance into a redistributable `.box` file in the
171
+ # current working directory, then destroys the instance.
172
+ #
173
+ # @param state [Hash] mutable instance state
174
+ # @raise [UserError] if the instance has not been created
175
+ # @raise [ActionFailed] if the action could not be completed
158
176
  def package(state)
159
177
  if state[:hostname].nil?
160
178
  raise UserError, "Vagrant instance not created!"
@@ -214,9 +232,11 @@ module Kitchen
214
232
  instance.transport.name.downcase =~ /win_?rm/
215
233
  end
216
234
 
217
- # Setting up the `cache_directory` to store omnibus packages in cache
218
- # and share a local folder to that directory so that we don't pull them
219
- # down every single time
235
+ # The guest-side directory that the host's omnibus package cache should
236
+ # be shared into, so repeated converges do not re-download packages.
237
+ #
238
+ # @return [String,false] the guest path, or false if caching does not
239
+ # apply to this box and provider combination
220
240
  def cache_directory
221
241
  if enable_cache?
222
242
  config[:cache_directory]
@@ -227,7 +247,10 @@ module Kitchen
227
247
 
228
248
  protected
229
249
 
250
+ # Where users are pointed when Vagrant is missing or too old.
230
251
  WEBSITE = "https://developer.hashicorp.com/vagrant/install".freeze
252
+
253
+ # The oldest Vagrant this driver supports.
231
254
  MIN_VER = "2.4.0".freeze
232
255
 
233
256
  class << self
@@ -236,10 +259,11 @@ module Kitchen
236
259
  attr_accessor :vagrant_version
237
260
  end
238
261
 
239
- # Retuns whether or not a platform name could have a correcponding Bento
262
+ # Returns whether or not a platform name could have a corresponding Bento
240
263
  # box produced by the Bento project.
241
264
  # (https://github.com/chef/bento).
242
265
  #
266
+ # @param name [String] a Test Kitchen platform name
243
267
  # @return [TrueClass,FalseClass] whether or not the name could be a Bento
244
268
  # box
245
269
  # @api private
@@ -248,8 +272,13 @@ module Kitchen
248
272
  end
249
273
 
250
274
  # Returns whether or not the we expect the box to work with shared folders
251
- # by matching against a whitelist of bento boxes
252
- # @return [TrueClass,FalseClass] whether or not the box shoud work with
275
+ # by matching against a whitelist of bento boxes.
276
+ #
277
+ # Providers without usable shared folder support are excluded outright,
278
+ # whatever the box.
279
+ #
280
+ # @param box [String] the Vagrant box name
281
+ # @return [TrueClass,FalseClass] whether or not the box should work with
253
282
  # shared folders
254
283
  # @api private
255
284
  def safe_share?(box)
@@ -259,7 +288,11 @@ module Kitchen
259
288
  end
260
289
 
261
290
  # Return true if we found the criteria to enable the cache_directory
262
- # functionality
291
+ # functionality.
292
+ #
293
+ # @return [TrueClass,FalseClass] whether the package cache should be
294
+ # shared into the guest
295
+ # @api private
263
296
  def enable_cache?
264
297
  return false unless config[:cache_directory]
265
298
  return true if safe_share?(config[:box])
@@ -269,8 +302,10 @@ module Kitchen
269
302
  false
270
303
  end
271
304
 
272
- # Renders and writes out a Vagrantfile dedicated to this instance.
305
+ # Renders and writes out a Vagrantfile dedicated to this instance. A
306
+ # no-op if this action has already written one.
273
307
  #
308
+ # @return [void]
274
309
  # @api private
275
310
  def create_vagrantfile
276
311
  return if @vagrantfile_created
@@ -286,6 +321,7 @@ module Kitchen
286
321
  # Logs the Vagrantfile's contents to the debug log level.
287
322
  #
288
323
  # @param vagrantfile [String] path to the Vagrantfile
324
+ # @return [void]
289
325
  # @api private
290
326
  def debug_vagrantfile(vagrantfile)
291
327
  return unless logger.debug?
@@ -295,8 +331,10 @@ module Kitchen
295
331
  debug("------------")
296
332
  end
297
333
 
298
- # Setup path for CA cert
334
+ # Expands `:box_download_ca_cert` relative to the kitchen root, so a
335
+ # `.kitchen.yml` can refer to a cert alongside itself.
299
336
  #
337
+ # @return [void]
300
338
  # @api private
301
339
  def finalize_ca_cert!
302
340
  unless config[:box_download_ca_cert].nil?
@@ -306,9 +344,14 @@ module Kitchen
306
344
  end
307
345
  end
308
346
 
309
- # Create vagrant command to update box to the latest version
347
+ # Replaces a truthy `:box_auto_update` with the `vagrant box update`
348
+ # command line that {#run_box_auto_update} should run. A falsey value is
349
+ # left alone so that `box_auto_update: false` stays disabled.
350
+ #
351
+ # @return [void]
352
+ # @api private
310
353
  def finalize_box_auto_update!
311
- return if config[:box_auto_update].nil?
354
+ return unless config[:box_auto_update]
312
355
 
313
356
  cmd = "#{config[:vagrant_binary]} box update --box #{config[:box]}"
314
357
  cmd += " --architecture #{config[:box_arch]}" if config[:box_arch]
@@ -317,9 +360,14 @@ module Kitchen
317
360
  config[:box_auto_update] = cmd
318
361
  end
319
362
 
320
- # Create vagrant command to remove older versions of the box
363
+ # Replaces a truthy `:box_auto_prune` with the `vagrant box prune`
364
+ # command line that {#run_box_auto_prune} should run. A falsey value is
365
+ # left alone so that `box_auto_prune: false` stays disabled.
366
+ #
367
+ # @return [void]
368
+ # @api private
321
369
  def finalize_box_auto_prune!
322
- return if config[:box_auto_prune].nil?
370
+ return unless config[:box_auto_prune]
323
371
 
324
372
  cmd = "#{config[:vagrant_binary]} box prune --force --keep-active-boxes --name #{config[:box]}"
325
373
  cmd += " --provider #{config[:provider]}" if config[:provider]
@@ -336,8 +384,45 @@ module Kitchen
336
384
  .gsub("{{vagrant_root}}", vagrant_root)
337
385
  end
338
386
 
339
- # Replaces an `%{instance_name}` tokens in the synced folder items.
387
+ # Formats synced folder options for use in the Vagrantfile.
388
+ # Accepts either a Hash (converts to Ruby hash syntax) or a String (returns as-is).
389
+ # Supports SMB options like smb_username, smb_password, etc.
390
+ #
391
+ # @param options [Hash, String, nil] synced folder options
392
+ # @return [String] formatted options string for Vagrantfile
393
+ # @api private
394
+ def format_synced_folder_options(options)
395
+ return "nil" if options.nil?
396
+ return options if options.is_a?(String)
397
+
398
+ # Convert Hash to Ruby hash literal syntax
399
+ if options.is_a?(Hash)
400
+ options.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
401
+ else
402
+ options.to_s
403
+ end
404
+ end
405
+
406
+ # Formats network options for use in the Vagrantfile.
407
+ #
408
+ # Accepts either a Hash (rendered as Ruby keyword syntax) or a String
409
+ # (passed through as-is, which is what {#finalize_network!} produces).
410
+ #
411
+ # @param options [Hash,String,#to_s] network options
412
+ # @return [String] formatted options string for Vagrantfile
413
+ # @api private
414
+ def format_network_options(options)
415
+ return options if options.is_a?(String)
416
+ return options.map { |k, v| "#{k}: #{v.inspect}" }.join(", ") if options.is_a?(Hash)
417
+
418
+ options.to_s
419
+ end
420
+
421
+ # Normalises every `:synced_folders` entry: expands the host path
422
+ # against the kitchen root, substitutes `%{instance_name}` tokens, and
423
+ # formats the options for the template.
340
424
  #
425
+ # @return [void]
341
426
  # @api private
342
427
  def finalize_synced_folders!
343
428
  config[:synced_folders] = config[:synced_folders]
@@ -348,15 +433,17 @@ module Kitchen
348
433
  config[:kitchen_root]
349
434
  ),
350
435
  destination.gsub("%{instance_name}", instance.name),
351
- options || "nil",
436
+ format_synced_folder_options(options),
352
437
  ]
353
438
  end
354
439
  add_extra_synced_folders!
355
440
  end
356
441
 
357
- # We would like to sync a local folder to the instance so we can
358
- # take advantage of the packages that we might have in cache,
359
- # therefore we wont download a package we already have
442
+ # Shares the host's omnibus package cache into the guest so a converge
443
+ # does not re-download packages it already has.
444
+ #
445
+ # @return [void]
446
+ # @api private
360
447
  def add_extra_synced_folders!
361
448
  if cache_directory
362
449
  FileUtils.mkdir_p(local_kitchen_cache)
@@ -368,9 +455,12 @@ module Kitchen
368
455
  end
369
456
  end
370
457
 
371
- # Truncates the length of `:vm_hostname` to 12 characters for
372
- # Windows-based operating systems.
458
+ # Truncates an over-long `:vm_hostname` on Windows guests, where the
459
+ # NetBIOS name is capped at 15 characters. The final character of the
460
+ # original is kept as a suffix so that names which differ only in their
461
+ # tail do not collapse onto each other.
373
462
  #
463
+ # @return [void]
374
464
  # @api private
375
465
  def finalize_vm_hostname!
376
466
  string = config[:vm_hostname]
@@ -380,17 +470,20 @@ module Kitchen
380
470
  end
381
471
  end
382
472
 
383
- # If Hyper-V and no network configuration
384
- # check KITCHEN_HYPERV_SWITCH and fallback to helper method
385
- # to select the best switch
473
+ # Gives a Hyper-V instance a default `public_network` bridged onto the
474
+ # switch named by `KITCHEN_HYPERV_SWITCH`, or whichever switch
475
+ # {HypervHelpers#hyperv_switch} considers best. Instances that already
476
+ # configure a network, and every other provider, are left alone.
477
+ #
478
+ # @return [void]
386
479
  # @api private
387
480
  def finalize_network!
388
- if config[:provider] == "hyperv" && config[:network].empty?
389
- config[:network].push([
390
- "public_network",
391
- "bridge: \"#{hyperv_switch}\"",
392
- ])
393
- end
481
+ return unless config[:provider] == "hyperv" && config[:network].empty?
482
+
483
+ # Deliberately a new Array rather than a push: `default_config` hands
484
+ # every instance the *same* Array object, so mutating it in place would
485
+ # leak this network into every other instance in the process.
486
+ config[:network] = [["public_network", %{bridge: "#{hyperv_switch}"}]]
394
487
  end
395
488
 
396
489
  # Renders the Vagrantfile ERb template.
@@ -433,6 +526,9 @@ module Kitchen
433
526
  # any bundler environment should we detect one. Otherwise, subcommands
434
527
  # will inherit our bundled environment.
435
528
  # @see https://github.com/test-kitchen/kitchen-vagrant/issues/190
529
+ # @param cmd [String] command to run locally
530
+ # @param options [Hash] options hash
531
+ # @return [String] the standard output of the command
436
532
  # @see Kitchen::ShellOut#run_command
437
533
  # rubocop:disable Metrics/CyclomaticComplexity
438
534
  def run_command(cmd, options = {})
@@ -458,10 +554,10 @@ module Kitchen
458
554
  # is passed to a windows process with a PATH, Vagrant's batch installer
459
555
  # (https://github.com/mitchellh/vagrant-installers/blob/master/substrate
460
556
  # /modules/vagrant_installer/templates/windows_vagrant.bat.erb)
461
- # does not efectively prepend the vagrant ruby path in a persistent
557
+ # does not effectively prepend the vagrant ruby path in a persistent
462
558
  # manner which causes vagrant to use the same ruby as test-kitchen and
463
559
  # then the environment is essentially corrupted leading to many errors
464
- # and dispair
560
+ # and despair
465
561
  unless windows_host?
466
562
  gem_home = ENV["GEM_HOME"]
467
563
  if gem_home && (env["PATH"] || ENV["PATH"])
@@ -475,8 +571,11 @@ module Kitchen
475
571
  end
476
572
  # rubocop:enable Metrics/CyclomaticComplexity
477
573
 
478
- # Check if a newer version of the vagrant box is available and warn the user
574
+ # Check if a newer version of the vagrant box is available and warn the
575
+ # user. Skipped when `:box_auto_update` is on, since the update happens
576
+ # regardless.
479
577
  #
578
+ # @return [void]
480
579
  # @api private
481
580
  def check_box_outdated
482
581
  # Skip if box_auto_update is enabled (they'll get the update anyway)
@@ -498,6 +597,7 @@ module Kitchen
498
597
  # Parse vagrant box outdated output and warn if a new version is available
499
598
  #
500
599
  # @param output [String] output from vagrant box outdated command
600
+ # @return [void]
501
601
  # @api private
502
602
  def warn_if_outdated(output)
503
603
  return unless box_is_outdated?(output)
@@ -528,15 +628,20 @@ module Kitchen
528
628
  output_downcase.include?("newer version of the box")
529
629
  end
530
630
 
631
+ # Characters that may legitimately appear inside a box version. Vagrant
632
+ # quotes versions in prose ("version '202401.31.0'.") and separates them
633
+ # with punctuation in tabular output ("Current: 1.2.3, Latest: 4.5.6"),
634
+ # so the version has to end on an alphanumeric to avoid swallowing the
635
+ # trailing quote, comma or full stop.
636
+ VERSION_PATTERN = /v?(\w[\w.+-]*\w|\w)/
637
+
531
638
  # Extract current version from vagrant box outdated output
532
639
  #
533
640
  # @param output [String] output from vagrant box outdated command
534
641
  # @return [String, nil] current version or nil if not found
535
642
  # @api private
536
643
  def extract_current_version(output)
537
- match = output.match(/Current:\s+v?(\S+)/i) ||
538
- output.match(/currently have version\s+'?v?([^'.\s]+)/i)
539
- match ? match[1] : nil
644
+ extract_version(output, /Current:\s+/i, /currently have version\s+'?/i)
540
645
  end
541
646
 
542
647
  # Extract latest version from vagrant box outdated output
@@ -545,12 +650,32 @@ module Kitchen
545
650
  # @return [String, nil] latest version or nil if not found
546
651
  # @api private
547
652
  def extract_latest_version(output)
548
- match = output.match(/Latest:\s+v?(\S+)/i) ||
549
- output.match(/latest is version\s+'?v?([^'.\s]+)/i)
550
- match ? match[1] : nil
653
+ extract_version(output, /Latest:\s+/i, /latest is version\s+'?/i)
551
654
  end
552
655
 
553
- # Tell vagrant to update vagrant box to latest version
656
+ # Finds the first version number that follows any of the given prefixes.
657
+ #
658
+ # @param output [String] output from vagrant box outdated command
659
+ # @param prefixes [Array<Regexp>] prefixes to look for, in priority order
660
+ # @return [String, nil] the version, or nil if no prefix matched
661
+ # @api private
662
+ def extract_version(output, *prefixes)
663
+ prefixes.each do |prefix|
664
+ match = output.match(/#{prefix.source}#{VERSION_PATTERN.source}/i)
665
+ return match[1] if match
666
+ end
667
+ nil
668
+ end
669
+
670
+ # Runs the `vagrant box update` command built by
671
+ # {#finalize_box_auto_update!}, if any.
672
+ #
673
+ # A box that has never been downloaded cannot be updated; that specific
674
+ # failure is expected on a first run and is swallowed.
675
+ #
676
+ # @return [void]
677
+ # @raise [Kitchen::ShellOut::ShellCommandFailed] for any other failure
678
+ # @api private
554
679
  def run_box_auto_update
555
680
  if config[:box_auto_update]
556
681
  begin
@@ -563,15 +688,21 @@ module Kitchen
563
688
  end
564
689
  end
565
690
 
566
- # Tell vagrant to remove older vagrant boxes
691
+ # Runs the `vagrant box prune` command built by
692
+ # {#finalize_box_auto_prune!}, if any.
693
+ #
694
+ # @return [void]
695
+ # @api private
567
696
  def run_box_auto_prune
568
697
  if config[:box_auto_prune]
569
698
  run(config[:box_auto_prune])
570
699
  end
571
700
  end
572
701
 
573
- # Runs a local command before `vagrant up` has been called.
702
+ # Runs `:pre_create_command`, if set, from the kitchen root -- before
703
+ # `vagrant up`, so it can prepare anything the Vagrantfile depends on.
574
704
  #
705
+ # @return [void]
575
706
  # @api private
576
707
  def run_pre_create_command
577
708
  if config[:pre_create_command]
@@ -582,6 +713,8 @@ module Kitchen
582
713
  # Runs a local command without streaming the stdout to the logger.
583
714
  #
584
715
  # @param cmd [String] command to run locally
716
+ # @param options [Hash] options hash
717
+ # @return [String] the standard output of the command
585
718
  # @api private
586
719
  def run_silently(cmd, options = {})
587
720
  merged = {
@@ -592,6 +725,7 @@ module Kitchen
592
725
 
593
726
  # Runs the `vagrant up` command locally.
594
727
  #
728
+ # @return [void]
595
729
  # @api private
596
730
  def run_vagrant_up
597
731
  cmd = "#{config[:vagrant_binary]} up"
@@ -600,9 +734,11 @@ module Kitchen
600
734
  run(cmd)
601
735
  end
602
736
 
603
- # Updates any state after creation.
737
+ # Records the connection details Vagrant reports for the new machine
738
+ # into the instance state, so the transport can reach it.
604
739
  #
605
740
  # @param state [Hash] mutable instance state
741
+ # @return [void]
606
742
  # @api private
607
743
  def update_state(state)
608
744
  hash = winrm_transport? ? vagrant_config(:winrm) : vagrant_config(:ssh)
@@ -17,9 +17,10 @@
17
17
 
18
18
  module Kitchen
19
19
 
20
+ # Test Kitchen driver plugins.
20
21
  module Driver
21
22
 
22
23
  # Version string for Vagrant Kitchen driver
23
- VAGRANT_VERSION = "2.2.0".freeze
24
+ VAGRANT_VERSION = "2.3.0".freeze
24
25
  end
25
26
  end
@@ -85,7 +85,7 @@ Vagrant.configure("2") do |c|
85
85
  <% end %>
86
86
 
87
87
  <% Array(config[:network]).each do |opts| %>
88
- c.vm.network(:<%= opts[0] %>, <%= opts[1].map { |k, v| "#{k}: #{v.inspect}" }.join(', ') %>)
88
+ c.vm.network(:<%= opts[0] %>, <%= format_network_options(opts[1]) %>)
89
89
  <% end %>
90
90
 
91
91
  c.vm.synced_folder ".", "/vagrant", disabled: true
@@ -97,6 +97,8 @@ Vagrant.configure("2") do |c|
97
97
  <% case config[:provider]
98
98
  when "virtualbox" %>
99
99
  p.name = "kitchen-<%= File.basename(config[:kitchen_root]) %>-<%= instance.name %>-<%= SecureRandom.uuid %>"
100
+ <% when "tart" %>
101
+ p.name = "kitchen-<%= File.basename(config[:kitchen_root]) %>-<%= instance.name %>"
100
102
  <% end %>
101
103
 
102
104
  <% case config[:provider]
@@ -253,4 +255,14 @@ Vagrant.configure("2") do |c|
253
255
  <% end %>
254
256
  <% end %>
255
257
  end
258
+
259
+ <% if config[:env] && !config[:env].empty? %>
260
+ # Set environment variables
261
+ <% config[:env].each do |env_var| %>
262
+ c.vm.provision "shell", inline: <<-SHELL
263
+ echo 'export <%= env_var.gsub("'", "'\\\\''") %>' >> /etc/profile.d/kitchen.sh
264
+ SHELL
265
+ <% end %>
266
+ c.vm.provision "shell", inline: "chmod +x /etc/profile.d/kitchen.sh", run: "once"
267
+ <% end %>
256
268
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-vagrant
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fletcher Nichol
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-09 00:00:00.000000000 Z
11
+ date: 2026-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: test-kitchen
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -19,7 +33,7 @@ dependencies:
19
33
  version: '1.4'
20
34
  - - "<"
21
35
  - !ruby/object:Gem::Version
22
- version: '4'
36
+ version: '5'
23
37
  type: :runtime
24
38
  prerelease: false
25
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +43,7 @@ dependencies:
29
43
  version: '1.4'
30
44
  - - "<"
31
45
  - !ruby/object:Gem::Version
32
- version: '4'
46
+ version: '5'
33
47
  description: Kitchen::Driver::Vagrant - A HashiCorp Vagrant Driver for Test Kitchen.
34
48
  email:
35
49
  - fnichol@nichol.ca
@@ -55,7 +69,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
69
  requirements:
56
70
  - - ">="
57
71
  - !ruby/object:Gem::Version
58
- version: '3.0'
72
+ version: '3.1'
59
73
  required_rubygems_version: !ruby/object:Gem::Requirement
60
74
  requirements:
61
75
  - - ">="