kitchen-vagrant 2.2.1 → 2.4.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 +4 -4
- data/lib/kitchen/driver/helpers.rb +101 -6
- data/lib/kitchen/driver/vagrant.rb +289 -41
- data/lib/kitchen/driver/vagrant_version.rb +2 -1
- data/templates/Vagrantfile.erb +3 -1
- metadata +19 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb41469b88a11508fe064bd6b04389da5cdb617ef027e5a633f47a1c88478be0
|
|
4
|
+
data.tar.gz: d4e9d511d64c4bff5a21121f2e2e9bcad8cd453efe7981de9a2c87597bb5f758
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8332c2ee9115ff0c69684420767948b5ad620051b4e99b5ac763e4b08636fe81caa4678ba9ccbbb5e662f6495438499c1fdafd9e7c127738c4219980d61f09f2
|
|
7
|
+
data.tar.gz: 163808d47b023456f260985e2180e7de2f46d0b0201729d828bb328d2f16d89c381b4eb7b43191e42a8d4645c14b2e39d6e630409e0847dd01c0e4497235af73
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
require "erb" unless defined?(Erb)
|
|
19
19
|
require "fileutils" unless defined?(FileUtils)
|
|
20
20
|
require "rubygems/version"
|
|
21
|
+
# :nocov:
|
|
22
|
+
# Chefstyle's Chef/Ruby/UnlessDefinedRequire wants this guard, but the guard's
|
|
23
|
+
# other branch is unreachable: kitchen has already loaded "time" by here. The
|
|
24
|
+
# repo enforces a branch-coverage floor, so exclude the line rather than let
|
|
25
|
+
# the two rules fight.
|
|
26
|
+
require "time" unless defined?(Time.now.iso8601)
|
|
27
|
+
# :nocov:
|
|
21
28
|
|
|
22
29
|
require "kitchen"
|
|
23
30
|
require_relative "vagrant_version"
|
|
@@ -31,6 +38,10 @@ module Kitchen
|
|
|
31
38
|
#
|
|
32
39
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
33
40
|
class Vagrant < Kitchen::Driver::Base
|
|
41
|
+
# Machine states Vagrant reports for a box that is up and reachable.
|
|
42
|
+
#
|
|
43
|
+
# @return [Array<String>]
|
|
44
|
+
LIVE_STATES = %w{running}.freeze
|
|
34
45
|
|
|
35
46
|
include ShellOut
|
|
36
47
|
include Kitchen::Driver::HypervHelpers
|
|
@@ -127,6 +138,11 @@ module Kitchen
|
|
|
127
138
|
info("Vagrant instance #{instance.to_str} created.")
|
|
128
139
|
end
|
|
129
140
|
|
|
141
|
+
# The box this Instance should use when the user has not named one.
|
|
142
|
+
#
|
|
143
|
+
# Platforms the Bento project builds are mapped onto their `bento/`
|
|
144
|
+
# box; anything else is assumed to name a box directly.
|
|
145
|
+
#
|
|
130
146
|
# @return [String,nil] the Vagrant box for this Instance
|
|
131
147
|
def default_box
|
|
132
148
|
if bento_box?(instance.platform.name)
|
|
@@ -136,6 +152,11 @@ module Kitchen
|
|
|
136
152
|
end
|
|
137
153
|
end
|
|
138
154
|
|
|
155
|
+
# The box URL this Instance should use when the user has not named one.
|
|
156
|
+
#
|
|
157
|
+
# Always nil: modern Vagrant resolves boxes through Vagrant Cloud, so an
|
|
158
|
+
# explicit URL is only needed for privately hosted boxes.
|
|
159
|
+
#
|
|
139
160
|
# @return [String,nil] the Vagrant box URL for this Instance
|
|
140
161
|
def default_box_url
|
|
141
162
|
nil
|
|
@@ -157,6 +178,12 @@ module Kitchen
|
|
|
157
178
|
state.delete(:hostname)
|
|
158
179
|
end
|
|
159
180
|
|
|
181
|
+
# Packages a created instance into a redistributable `.box` file in the
|
|
182
|
+
# current working directory, then destroys the instance.
|
|
183
|
+
#
|
|
184
|
+
# @param state [Hash] mutable instance state
|
|
185
|
+
# @raise [UserError] if the instance has not been created
|
|
186
|
+
# @raise [ActionFailed] if the action could not be completed
|
|
160
187
|
def package(state)
|
|
161
188
|
if state[:hostname].nil?
|
|
162
189
|
raise UserError, "Vagrant instance not created!"
|
|
@@ -172,6 +199,27 @@ module Kitchen
|
|
|
172
199
|
destroy(state)
|
|
173
200
|
end
|
|
174
201
|
|
|
202
|
+
# Reports what Vagrant currently thinks of the machine.
|
|
203
|
+
#
|
|
204
|
+
# @param state [Hash] instance state naming the machine
|
|
205
|
+
# @return [Hash] a Test Kitchen status hash, or the base implementation's
|
|
206
|
+
# answer when there is nothing to ask Vagrant about
|
|
207
|
+
def status(state)
|
|
208
|
+
return super unless state[:hostname]
|
|
209
|
+
|
|
210
|
+
machine_state = vagrant_machine_state
|
|
211
|
+
return super unless machine_state
|
|
212
|
+
|
|
213
|
+
{
|
|
214
|
+
live: LIVE_STATES.include?(machine_state),
|
|
215
|
+
state: machine_state,
|
|
216
|
+
source: "driver",
|
|
217
|
+
resource_id: instance.name,
|
|
218
|
+
message: "Vagrant reports the machine as #{machine_state}",
|
|
219
|
+
checked_at: Time.now.utc.iso8601,
|
|
220
|
+
}
|
|
221
|
+
end
|
|
222
|
+
|
|
175
223
|
# A lifecycle method that should be invoked when the object is about
|
|
176
224
|
# ready to be used. A reference to an Instance is required as
|
|
177
225
|
# configuration dependant data may be access through an Instance. This
|
|
@@ -209,6 +257,23 @@ module Kitchen
|
|
|
209
257
|
end
|
|
210
258
|
end
|
|
211
259
|
|
|
260
|
+
# Checks the host-side things a Vagrant run needs, reporting rather than
|
|
261
|
+
# raising so `kitchen doctor` can list every problem at once.
|
|
262
|
+
#
|
|
263
|
+
# {#verify_dependencies} already raises on an old or absent Vagrant, but
|
|
264
|
+
# it only runs on the actions that need it. This repeats the version rule
|
|
265
|
+
# as a report and adds the file and folder checks that nothing validates
|
|
266
|
+
# today.
|
|
267
|
+
#
|
|
268
|
+
# @param state [Hash] mutable instance and driver state
|
|
269
|
+
# @return [Boolean] true when a problem was reported
|
|
270
|
+
def doctor(state) # rubocop:disable Lint/UnusedMethodArgument
|
|
271
|
+
problems = vagrant_problems + template_problems + synced_folder_problems
|
|
272
|
+
|
|
273
|
+
problems.each { |problem| warn(problem) }
|
|
274
|
+
!problems.empty?
|
|
275
|
+
end
|
|
276
|
+
|
|
212
277
|
# @return [TrueClass,FalseClass] whether or not the transport's name
|
|
213
278
|
# implies a WinRM-based transport
|
|
214
279
|
# @api private
|
|
@@ -216,9 +281,11 @@ module Kitchen
|
|
|
216
281
|
instance.transport.name.downcase =~ /win_?rm/
|
|
217
282
|
end
|
|
218
283
|
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
#
|
|
284
|
+
# The guest-side directory that the host's omnibus package cache should
|
|
285
|
+
# be shared into, so repeated converges do not re-download packages.
|
|
286
|
+
#
|
|
287
|
+
# @return [String,false] the guest path, or false if caching does not
|
|
288
|
+
# apply to this box and provider combination
|
|
222
289
|
def cache_directory
|
|
223
290
|
if enable_cache?
|
|
224
291
|
config[:cache_directory]
|
|
@@ -229,7 +296,10 @@ module Kitchen
|
|
|
229
296
|
|
|
230
297
|
protected
|
|
231
298
|
|
|
299
|
+
# Where users are pointed when Vagrant is missing or too old.
|
|
232
300
|
WEBSITE = "https://developer.hashicorp.com/vagrant/install".freeze
|
|
301
|
+
|
|
302
|
+
# The oldest Vagrant this driver supports.
|
|
233
303
|
MIN_VER = "2.4.0".freeze
|
|
234
304
|
|
|
235
305
|
class << self
|
|
@@ -238,10 +308,11 @@ module Kitchen
|
|
|
238
308
|
attr_accessor :vagrant_version
|
|
239
309
|
end
|
|
240
310
|
|
|
241
|
-
#
|
|
311
|
+
# Returns whether or not a platform name could have a corresponding Bento
|
|
242
312
|
# box produced by the Bento project.
|
|
243
313
|
# (https://github.com/chef/bento).
|
|
244
314
|
#
|
|
315
|
+
# @param name [String] a Test Kitchen platform name
|
|
245
316
|
# @return [TrueClass,FalseClass] whether or not the name could be a Bento
|
|
246
317
|
# box
|
|
247
318
|
# @api private
|
|
@@ -250,8 +321,13 @@ module Kitchen
|
|
|
250
321
|
end
|
|
251
322
|
|
|
252
323
|
# Returns whether or not the we expect the box to work with shared folders
|
|
253
|
-
# by matching against a whitelist of bento boxes
|
|
254
|
-
#
|
|
324
|
+
# by matching against a whitelist of bento boxes.
|
|
325
|
+
#
|
|
326
|
+
# Providers without usable shared folder support are excluded outright,
|
|
327
|
+
# whatever the box.
|
|
328
|
+
#
|
|
329
|
+
# @param box [String] the Vagrant box name
|
|
330
|
+
# @return [TrueClass,FalseClass] whether or not the box should work with
|
|
255
331
|
# shared folders
|
|
256
332
|
# @api private
|
|
257
333
|
def safe_share?(box)
|
|
@@ -261,7 +337,11 @@ module Kitchen
|
|
|
261
337
|
end
|
|
262
338
|
|
|
263
339
|
# Return true if we found the criteria to enable the cache_directory
|
|
264
|
-
# functionality
|
|
340
|
+
# functionality.
|
|
341
|
+
#
|
|
342
|
+
# @return [TrueClass,FalseClass] whether the package cache should be
|
|
343
|
+
# shared into the guest
|
|
344
|
+
# @api private
|
|
265
345
|
def enable_cache?
|
|
266
346
|
return false unless config[:cache_directory]
|
|
267
347
|
return true if safe_share?(config[:box])
|
|
@@ -271,8 +351,10 @@ module Kitchen
|
|
|
271
351
|
false
|
|
272
352
|
end
|
|
273
353
|
|
|
274
|
-
# Renders and writes out a Vagrantfile dedicated to this instance.
|
|
354
|
+
# Renders and writes out a Vagrantfile dedicated to this instance. A
|
|
355
|
+
# no-op if this action has already written one.
|
|
275
356
|
#
|
|
357
|
+
# @return [void]
|
|
276
358
|
# @api private
|
|
277
359
|
def create_vagrantfile
|
|
278
360
|
return if @vagrantfile_created
|
|
@@ -288,6 +370,7 @@ module Kitchen
|
|
|
288
370
|
# Logs the Vagrantfile's contents to the debug log level.
|
|
289
371
|
#
|
|
290
372
|
# @param vagrantfile [String] path to the Vagrantfile
|
|
373
|
+
# @return [void]
|
|
291
374
|
# @api private
|
|
292
375
|
def debug_vagrantfile(vagrantfile)
|
|
293
376
|
return unless logger.debug?
|
|
@@ -297,8 +380,10 @@ module Kitchen
|
|
|
297
380
|
debug("------------")
|
|
298
381
|
end
|
|
299
382
|
|
|
300
|
-
#
|
|
383
|
+
# Expands `:box_download_ca_cert` relative to the kitchen root, so a
|
|
384
|
+
# `.kitchen.yml` can refer to a cert alongside itself.
|
|
301
385
|
#
|
|
386
|
+
# @return [void]
|
|
302
387
|
# @api private
|
|
303
388
|
def finalize_ca_cert!
|
|
304
389
|
unless config[:box_download_ca_cert].nil?
|
|
@@ -308,9 +393,14 @@ module Kitchen
|
|
|
308
393
|
end
|
|
309
394
|
end
|
|
310
395
|
|
|
311
|
-
#
|
|
396
|
+
# Replaces a truthy `:box_auto_update` with the `vagrant box update`
|
|
397
|
+
# command line that {#run_box_auto_update} should run. A falsey value is
|
|
398
|
+
# left alone so that `box_auto_update: false` stays disabled.
|
|
399
|
+
#
|
|
400
|
+
# @return [void]
|
|
401
|
+
# @api private
|
|
312
402
|
def finalize_box_auto_update!
|
|
313
|
-
return
|
|
403
|
+
return unless config[:box_auto_update]
|
|
314
404
|
|
|
315
405
|
cmd = "#{config[:vagrant_binary]} box update --box #{config[:box]}"
|
|
316
406
|
cmd += " --architecture #{config[:box_arch]}" if config[:box_arch]
|
|
@@ -319,9 +409,14 @@ module Kitchen
|
|
|
319
409
|
config[:box_auto_update] = cmd
|
|
320
410
|
end
|
|
321
411
|
|
|
322
|
-
#
|
|
412
|
+
# Replaces a truthy `:box_auto_prune` with the `vagrant box prune`
|
|
413
|
+
# command line that {#run_box_auto_prune} should run. A falsey value is
|
|
414
|
+
# left alone so that `box_auto_prune: false` stays disabled.
|
|
415
|
+
#
|
|
416
|
+
# @return [void]
|
|
417
|
+
# @api private
|
|
323
418
|
def finalize_box_auto_prune!
|
|
324
|
-
return
|
|
419
|
+
return unless config[:box_auto_prune]
|
|
325
420
|
|
|
326
421
|
cmd = "#{config[:vagrant_binary]} box prune --force --keep-active-boxes --name #{config[:box]}"
|
|
327
422
|
cmd += " --provider #{config[:provider]}" if config[:provider]
|
|
@@ -357,8 +452,26 @@ module Kitchen
|
|
|
357
452
|
end
|
|
358
453
|
end
|
|
359
454
|
|
|
360
|
-
#
|
|
455
|
+
# Formats network options for use in the Vagrantfile.
|
|
456
|
+
#
|
|
457
|
+
# Accepts either a Hash (rendered as Ruby keyword syntax) or a String
|
|
458
|
+
# (passed through as-is, which is what {#finalize_network!} produces).
|
|
459
|
+
#
|
|
460
|
+
# @param options [Hash,String,#to_s] network options
|
|
461
|
+
# @return [String] formatted options string for Vagrantfile
|
|
462
|
+
# @api private
|
|
463
|
+
def format_network_options(options)
|
|
464
|
+
return options if options.is_a?(String)
|
|
465
|
+
return options.map { |k, v| "#{k}: #{v.inspect}" }.join(", ") if options.is_a?(Hash)
|
|
466
|
+
|
|
467
|
+
options.to_s
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# Normalises every `:synced_folders` entry: expands the host path
|
|
471
|
+
# against the kitchen root, substitutes `%{instance_name}` tokens, and
|
|
472
|
+
# formats the options for the template.
|
|
361
473
|
#
|
|
474
|
+
# @return [void]
|
|
362
475
|
# @api private
|
|
363
476
|
def finalize_synced_folders!
|
|
364
477
|
config[:synced_folders] = config[:synced_folders]
|
|
@@ -375,9 +488,11 @@ module Kitchen
|
|
|
375
488
|
add_extra_synced_folders!
|
|
376
489
|
end
|
|
377
490
|
|
|
378
|
-
#
|
|
379
|
-
#
|
|
380
|
-
#
|
|
491
|
+
# Shares the host's omnibus package cache into the guest so a converge
|
|
492
|
+
# does not re-download packages it already has.
|
|
493
|
+
#
|
|
494
|
+
# @return [void]
|
|
495
|
+
# @api private
|
|
381
496
|
def add_extra_synced_folders!
|
|
382
497
|
if cache_directory
|
|
383
498
|
FileUtils.mkdir_p(local_kitchen_cache)
|
|
@@ -389,9 +504,12 @@ module Kitchen
|
|
|
389
504
|
end
|
|
390
505
|
end
|
|
391
506
|
|
|
392
|
-
# Truncates
|
|
393
|
-
#
|
|
507
|
+
# Truncates an over-long `:vm_hostname` on Windows guests, where the
|
|
508
|
+
# NetBIOS name is capped at 15 characters. The final character of the
|
|
509
|
+
# original is kept as a suffix so that names which differ only in their
|
|
510
|
+
# tail do not collapse onto each other.
|
|
394
511
|
#
|
|
512
|
+
# @return [void]
|
|
395
513
|
# @api private
|
|
396
514
|
def finalize_vm_hostname!
|
|
397
515
|
string = config[:vm_hostname]
|
|
@@ -401,17 +519,20 @@ module Kitchen
|
|
|
401
519
|
end
|
|
402
520
|
end
|
|
403
521
|
|
|
404
|
-
#
|
|
405
|
-
#
|
|
406
|
-
#
|
|
522
|
+
# Gives a Hyper-V instance a default `public_network` bridged onto the
|
|
523
|
+
# switch named by `KITCHEN_HYPERV_SWITCH`, or whichever switch
|
|
524
|
+
# {HypervHelpers#hyperv_switch} considers best. Instances that already
|
|
525
|
+
# configure a network, and every other provider, are left alone.
|
|
526
|
+
#
|
|
527
|
+
# @return [void]
|
|
407
528
|
# @api private
|
|
408
529
|
def finalize_network!
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
530
|
+
return unless config[:provider] == "hyperv" && config[:network].empty?
|
|
531
|
+
|
|
532
|
+
# Deliberately a new Array rather than a push: `default_config` hands
|
|
533
|
+
# every instance the *same* Array object, so mutating it in place would
|
|
534
|
+
# leak this network into every other instance in the process.
|
|
535
|
+
config[:network] = [["public_network", %{bridge: "#{hyperv_switch}"}]]
|
|
415
536
|
end
|
|
416
537
|
|
|
417
538
|
# Renders the Vagrantfile ERb template.
|
|
@@ -431,6 +552,90 @@ module Kitchen
|
|
|
431
552
|
end
|
|
432
553
|
end
|
|
433
554
|
|
|
555
|
+
# Asks Vagrant for the machine state.
|
|
556
|
+
#
|
|
557
|
+
# `--machine-readable` is parsed rather than the human output because the
|
|
558
|
+
# human wording is localised and reworded between Vagrant releases, while
|
|
559
|
+
# the machine format is a stable comma-separated
|
|
560
|
+
# `timestamp,target,type,data` and the `state` row carries the raw value
|
|
561
|
+
# (`running`, `poweroff`, `not_created`, ...).
|
|
562
|
+
#
|
|
563
|
+
# @return [String, nil] the machine state, or nil when there is no
|
|
564
|
+
# Vagrantfile to ask about, the command fails, or no state row is found
|
|
565
|
+
# @api private
|
|
566
|
+
def vagrant_machine_state
|
|
567
|
+
return nil unless File.exist?(File.join(vagrant_root, "Vagrantfile"))
|
|
568
|
+
|
|
569
|
+
output = run("#{config[:vagrant_binary]} status --machine-readable")
|
|
570
|
+
parse_machine_state(output)
|
|
571
|
+
rescue ::StandardError => e
|
|
572
|
+
debug("Could not read the Vagrant machine state: #{e.message}")
|
|
573
|
+
nil
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# Pulls the `state` row out of Vagrant's machine-readable output.
|
|
577
|
+
#
|
|
578
|
+
# @param output [String] the raw `--machine-readable` output
|
|
579
|
+
# @return [String, nil] the state value, or nil when no state row is present
|
|
580
|
+
# @api private
|
|
581
|
+
def parse_machine_state(output)
|
|
582
|
+
output.to_s.each_line do |line|
|
|
583
|
+
fields = line.strip.split(",")
|
|
584
|
+
return fields[3] if fields[2] == "state" && fields[3]
|
|
585
|
+
end
|
|
586
|
+
nil
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Vagrant itself: present, and new enough.
|
|
590
|
+
#
|
|
591
|
+
# @return [Array<String>] a problem, or an empty array
|
|
592
|
+
def vagrant_problems
|
|
593
|
+
found = vagrant_version
|
|
594
|
+
if Gem::Version.new(found) < Gem::Version.new(MIN_VER.dup)
|
|
595
|
+
return ["Vagrant #{found} is older than the #{MIN_VER} this driver " \
|
|
596
|
+
"needs. Upgrade from #{WEBSITE}."]
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
info("vagrant #{found} found at #{config[:vagrant_binary]}")
|
|
600
|
+
[]
|
|
601
|
+
rescue UserError => e
|
|
602
|
+
[e.message]
|
|
603
|
+
rescue ::StandardError => e
|
|
604
|
+
["Could not run #{config[:vagrant_binary]} --version: #{e.message}"]
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# The Vagrantfile template, and any extra Vagrantfiles spliced into it.
|
|
608
|
+
# Both are `expand_path_for` settings, so a wrong relative path becomes
|
|
609
|
+
# an absolute path that simply is not there.
|
|
610
|
+
#
|
|
611
|
+
# @return [Array<String>] one problem per missing file
|
|
612
|
+
def template_problems
|
|
613
|
+
problems = []
|
|
614
|
+
|
|
615
|
+
unless File.exist?(config[:vagrantfile_erb].to_s)
|
|
616
|
+
problems << "vagrantfile_erb #{config[:vagrantfile_erb]} does not exist."
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
Array(config[:vagrantfiles]).each do |path|
|
|
620
|
+
problems << "vagrantfiles entry #{path} does not exist." unless File.exist?(path.to_s)
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
problems
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# Host paths in `synced_folders`. Vagrant creates a missing host path
|
|
627
|
+
# silently for some providers and fails for others, so an absent source
|
|
628
|
+
# is worth naming either way.
|
|
629
|
+
#
|
|
630
|
+
# @return [Array<String>] one problem per missing host path
|
|
631
|
+
def synced_folder_problems
|
|
632
|
+
Array(config[:synced_folders]).filter_map do |source, destination, _options|
|
|
633
|
+
next if source.nil? || File.exist?(source.to_s)
|
|
634
|
+
|
|
635
|
+
"synced_folders source #{source} (mounted at #{destination}) does not exist."
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
434
639
|
# Convenience method to run a command locally.
|
|
435
640
|
#
|
|
436
641
|
# @param cmd [String] command to run locally
|
|
@@ -454,6 +659,9 @@ module Kitchen
|
|
|
454
659
|
# any bundler environment should we detect one. Otherwise, subcommands
|
|
455
660
|
# will inherit our bundled environment.
|
|
456
661
|
# @see https://github.com/test-kitchen/kitchen-vagrant/issues/190
|
|
662
|
+
# @param cmd [String] command to run locally
|
|
663
|
+
# @param options [Hash] options hash
|
|
664
|
+
# @return [String] the standard output of the command
|
|
457
665
|
# @see Kitchen::ShellOut#run_command
|
|
458
666
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
459
667
|
def run_command(cmd, options = {})
|
|
@@ -479,10 +687,10 @@ module Kitchen
|
|
|
479
687
|
# is passed to a windows process with a PATH, Vagrant's batch installer
|
|
480
688
|
# (https://github.com/mitchellh/vagrant-installers/blob/master/substrate
|
|
481
689
|
# /modules/vagrant_installer/templates/windows_vagrant.bat.erb)
|
|
482
|
-
# does not
|
|
690
|
+
# does not effectively prepend the vagrant ruby path in a persistent
|
|
483
691
|
# manner which causes vagrant to use the same ruby as test-kitchen and
|
|
484
692
|
# then the environment is essentially corrupted leading to many errors
|
|
485
|
-
# and
|
|
693
|
+
# and despair
|
|
486
694
|
unless windows_host?
|
|
487
695
|
gem_home = ENV["GEM_HOME"]
|
|
488
696
|
if gem_home && (env["PATH"] || ENV["PATH"])
|
|
@@ -496,8 +704,11 @@ module Kitchen
|
|
|
496
704
|
end
|
|
497
705
|
# rubocop:enable Metrics/CyclomaticComplexity
|
|
498
706
|
|
|
499
|
-
# Check if a newer version of the vagrant box is available and warn the
|
|
707
|
+
# Check if a newer version of the vagrant box is available and warn the
|
|
708
|
+
# user. Skipped when `:box_auto_update` is on, since the update happens
|
|
709
|
+
# regardless.
|
|
500
710
|
#
|
|
711
|
+
# @return [void]
|
|
501
712
|
# @api private
|
|
502
713
|
def check_box_outdated
|
|
503
714
|
# Skip if box_auto_update is enabled (they'll get the update anyway)
|
|
@@ -519,6 +730,7 @@ module Kitchen
|
|
|
519
730
|
# Parse vagrant box outdated output and warn if a new version is available
|
|
520
731
|
#
|
|
521
732
|
# @param output [String] output from vagrant box outdated command
|
|
733
|
+
# @return [void]
|
|
522
734
|
# @api private
|
|
523
735
|
def warn_if_outdated(output)
|
|
524
736
|
return unless box_is_outdated?(output)
|
|
@@ -549,15 +761,20 @@ module Kitchen
|
|
|
549
761
|
output_downcase.include?("newer version of the box")
|
|
550
762
|
end
|
|
551
763
|
|
|
764
|
+
# Characters that may legitimately appear inside a box version. Vagrant
|
|
765
|
+
# quotes versions in prose ("version '202401.31.0'.") and separates them
|
|
766
|
+
# with punctuation in tabular output ("Current: 1.2.3, Latest: 4.5.6"),
|
|
767
|
+
# so the version has to end on an alphanumeric to avoid swallowing the
|
|
768
|
+
# trailing quote, comma or full stop.
|
|
769
|
+
VERSION_PATTERN = /v?(\w[\w.+-]*\w|\w)/
|
|
770
|
+
|
|
552
771
|
# Extract current version from vagrant box outdated output
|
|
553
772
|
#
|
|
554
773
|
# @param output [String] output from vagrant box outdated command
|
|
555
774
|
# @return [String, nil] current version or nil if not found
|
|
556
775
|
# @api private
|
|
557
776
|
def extract_current_version(output)
|
|
558
|
-
|
|
559
|
-
output.match(/currently have version\s+'?v?([^'.\s]+)/i)
|
|
560
|
-
match ? match[1] : nil
|
|
777
|
+
extract_version(output, /Current:\s+/i, /currently have version\s+'?/i)
|
|
561
778
|
end
|
|
562
779
|
|
|
563
780
|
# Extract latest version from vagrant box outdated output
|
|
@@ -566,12 +783,32 @@ module Kitchen
|
|
|
566
783
|
# @return [String, nil] latest version or nil if not found
|
|
567
784
|
# @api private
|
|
568
785
|
def extract_latest_version(output)
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
786
|
+
extract_version(output, /Latest:\s+/i, /latest is version\s+'?/i)
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
# Finds the first version number that follows any of the given prefixes.
|
|
790
|
+
#
|
|
791
|
+
# @param output [String] output from vagrant box outdated command
|
|
792
|
+
# @param prefixes [Array<Regexp>] prefixes to look for, in priority order
|
|
793
|
+
# @return [String, nil] the version, or nil if no prefix matched
|
|
794
|
+
# @api private
|
|
795
|
+
def extract_version(output, *prefixes)
|
|
796
|
+
prefixes.each do |prefix|
|
|
797
|
+
match = output.match(/#{prefix.source}#{VERSION_PATTERN.source}/i)
|
|
798
|
+
return match[1] if match
|
|
799
|
+
end
|
|
800
|
+
nil
|
|
572
801
|
end
|
|
573
802
|
|
|
574
|
-
#
|
|
803
|
+
# Runs the `vagrant box update` command built by
|
|
804
|
+
# {#finalize_box_auto_update!}, if any.
|
|
805
|
+
#
|
|
806
|
+
# A box that has never been downloaded cannot be updated; that specific
|
|
807
|
+
# failure is expected on a first run and is swallowed.
|
|
808
|
+
#
|
|
809
|
+
# @return [void]
|
|
810
|
+
# @raise [Kitchen::ShellOut::ShellCommandFailed] for any other failure
|
|
811
|
+
# @api private
|
|
575
812
|
def run_box_auto_update
|
|
576
813
|
if config[:box_auto_update]
|
|
577
814
|
begin
|
|
@@ -584,15 +821,21 @@ module Kitchen
|
|
|
584
821
|
end
|
|
585
822
|
end
|
|
586
823
|
|
|
587
|
-
#
|
|
824
|
+
# Runs the `vagrant box prune` command built by
|
|
825
|
+
# {#finalize_box_auto_prune!}, if any.
|
|
826
|
+
#
|
|
827
|
+
# @return [void]
|
|
828
|
+
# @api private
|
|
588
829
|
def run_box_auto_prune
|
|
589
830
|
if config[:box_auto_prune]
|
|
590
831
|
run(config[:box_auto_prune])
|
|
591
832
|
end
|
|
592
833
|
end
|
|
593
834
|
|
|
594
|
-
# Runs
|
|
835
|
+
# Runs `:pre_create_command`, if set, from the kitchen root -- before
|
|
836
|
+
# `vagrant up`, so it can prepare anything the Vagrantfile depends on.
|
|
595
837
|
#
|
|
838
|
+
# @return [void]
|
|
596
839
|
# @api private
|
|
597
840
|
def run_pre_create_command
|
|
598
841
|
if config[:pre_create_command]
|
|
@@ -603,6 +846,8 @@ module Kitchen
|
|
|
603
846
|
# Runs a local command without streaming the stdout to the logger.
|
|
604
847
|
#
|
|
605
848
|
# @param cmd [String] command to run locally
|
|
849
|
+
# @param options [Hash] options hash
|
|
850
|
+
# @return [String] the standard output of the command
|
|
606
851
|
# @api private
|
|
607
852
|
def run_silently(cmd, options = {})
|
|
608
853
|
merged = {
|
|
@@ -613,6 +858,7 @@ module Kitchen
|
|
|
613
858
|
|
|
614
859
|
# Runs the `vagrant up` command locally.
|
|
615
860
|
#
|
|
861
|
+
# @return [void]
|
|
616
862
|
# @api private
|
|
617
863
|
def run_vagrant_up
|
|
618
864
|
cmd = "#{config[:vagrant_binary]} up"
|
|
@@ -621,9 +867,11 @@ module Kitchen
|
|
|
621
867
|
run(cmd)
|
|
622
868
|
end
|
|
623
869
|
|
|
624
|
-
#
|
|
870
|
+
# Records the connection details Vagrant reports for the new machine
|
|
871
|
+
# into the instance state, so the transport can reach it.
|
|
625
872
|
#
|
|
626
873
|
# @param state [Hash] mutable instance state
|
|
874
|
+
# @return [void]
|
|
627
875
|
# @api private
|
|
628
876
|
def update_state(state)
|
|
629
877
|
hash = winrm_transport? ? vagrant_config(:winrm) : vagrant_config(:ssh)
|
data/templates/Vagrantfile.erb
CHANGED
|
@@ -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]
|
|
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]
|
metadata
CHANGED
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-vagrant
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.4.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: 2026-
|
|
11
|
+
date: 2026-08-24 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
|
|
16
30
|
requirements:
|
|
17
31
|
- - ">="
|
|
18
32
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
33
|
+
version: '3.0'
|
|
20
34
|
- - "<"
|
|
21
35
|
- !ruby/object:Gem::Version
|
|
22
36
|
version: '5'
|
|
@@ -26,7 +40,7 @@ dependencies:
|
|
|
26
40
|
requirements:
|
|
27
41
|
- - ">="
|
|
28
42
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '
|
|
43
|
+
version: '3.0'
|
|
30
44
|
- - "<"
|
|
31
45
|
- !ruby/object:Gem::Version
|
|
32
46
|
version: '5'
|
|
@@ -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.
|
|
72
|
+
version: '3.1'
|
|
59
73
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
74
|
requirements:
|
|
61
75
|
- - ">="
|