kitchen-vra 3.3.4 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fafa7c0d38316e0caed88baf634bd75e30f096bfe9798edcd1a9e3d7a5c994ff
4
- data.tar.gz: 7a50b7b1e4189297c2b739f4ea5eac816550b0097129709e2e99d4f53c1d870e
3
+ metadata.gz: 3077884f3c8f5e434889905e6c4d47e06ecf308fe810948c79d644e5f13086bd
4
+ data.tar.gz: addc3a87013a8834a75392625e026b5efdf1cfaff8009e0eaa8e690236b0be84
5
5
  SHA512:
6
- metadata.gz: ad976260af42bb96af58b18b9cd3837f5eda92c0b98b8e86d29676e76862cb1d9c3d4c3dbd604fb09018415622a53f4796d1f79673b357215023dff53854076b
7
- data.tar.gz: 0b9fb222821945220304a7d04bffea5b80f98adbf156d43aef3b09aa2d5f75e43619eae90793146984a11e99e07d1f9b9425752aa783f647351d6dca6f0426b1
6
+ metadata.gz: 2d6521702e1cc486f91d1aa2c0d1c17fdd1feaece55a8b634aaea65950282bde3b327e4bb79e7c256a6683ba8128b36ef5b59852b2c1f0de763d64dc4aa359a2
7
+ data.tar.gz: 190fa3d7bbfc3fd0db0220934390545f9b28cd4565126648d0503459de10035962f795b69275599409671aa15d2f5d31a018074f7b59d4a092f83437b027d8cd
data/.gitignore CHANGED
@@ -2,7 +2,6 @@
2
2
  /.yardoc
3
3
  /Gemfile.lock
4
4
  /_yardoc/
5
- /coverage/
6
5
  /doc/
7
6
  /pkg/
8
7
  /spec/reports/
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "3.3.4"
2
+ ".": "3.4.1"
3
3
  }
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --color
data/.rubocop.yml CHANGED
@@ -6,4 +6,3 @@ AllCops:
6
6
  TargetRubyVersion: 3.1
7
7
  Exclude:
8
8
  - "vendor/**/*"
9
- - "spec/**/*"
data/.yardopts CHANGED
@@ -8,4 +8,4 @@ lib/**/*.rb
8
8
  -
9
9
  CHANGELOG.md
10
10
  CONTRIBUTING.md
11
- LICENSE.txt
11
+ LICENSE
data/CHANGELOG.md CHANGED
@@ -10,6 +10,26 @@
10
10
  * Docs: rewrite README for new users and split contributor docs ([#80](https://github.com/test-kitchen/kitchen-vra/pull/80)) ([d9ebc45](https://github.com/test-kitchen/kitchen-vra/commit/d9ebc45))
11
11
  * Standardize renovate config and remove dependabot ([#81](https://github.com/test-kitchen/kitchen-vra/pull/81)) ([be552bd](https://github.com/test-kitchen/kitchen-vra/commit/be552bd))
12
12
 
13
+ ## [3.4.1](https://github.com/test-kitchen/kitchen-vra/compare/v3.4.0...v3.4.1) (2026-08-30)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * stop prompting for credentials on every run with cache_credentials ([#91](https://github.com/test-kitchen/kitchen-vra/issues/91)) ([e1c185f](https://github.com/test-kitchen/kitchen-vra/commit/e1c185f8b38700abd98824cae67f01a33d36b710))
19
+
20
+ ## [3.4.0](https://github.com/test-kitchen/kitchen-vra/compare/v3.3.4...v3.4.0) (2026-08-24)
21
+
22
+
23
+ ### Features
24
+
25
+ * implement the driver status hook ([#88](https://github.com/test-kitchen/kitchen-vra/issues/88)) ([933e254](https://github.com/test-kitchen/kitchen-vra/commit/933e25445d1a231490ef977cbbfd9644aa16022d))
26
+
27
+
28
+ ### Bug Fixes
29
+
30
+ * constrain the test-kitchen dependency ([#86](https://github.com/test-kitchen/kitchen-vra/issues/86)) ([ac5f32d](https://github.com/test-kitchen/kitchen-vra/commit/ac5f32d1dd5eb132c91a3fda58ee41f778148245))
31
+ * drop the unused rack runtime dependency ([#89](https://github.com/test-kitchen/kitchen-vra/issues/89)) ([b68f1fd](https://github.com/test-kitchen/kitchen-vra/commit/b68f1fdd4dfc6d1a0c37320f8b099b5d0ca19c8a))
32
+
13
33
  ## [3.3.4](https://github.com/test-kitchen/kitchen-vra/compare/v3.3.3...v3.3.4) (2026-08-23)
14
34
 
15
35
 
data/Gemfile CHANGED
@@ -10,10 +10,6 @@ group :test do
10
10
  gem "webmock"
11
11
  end
12
12
 
13
- group :debug do
14
- gem "pry"
15
- end
16
-
17
13
  group :docs do
18
14
  gem "yard"
19
15
  end
data/kitchen-vra.gemspec CHANGED
@@ -20,9 +20,8 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.required_ruby_version = ">= 3.1"
22
22
 
23
- spec.add_dependency "test-kitchen"
23
+ spec.add_dependency "test-kitchen", ">= 3.0", "< 5"
24
24
  spec.add_dependency "vmware-vra", "~> 3.0", ">= 3.2.0" # 3.0 required for vRA 8.x
25
25
  spec.add_dependency "highline"
26
- spec.add_dependency "rack", ">= 1.6", "< 4.0"
27
26
  spec.add_dependency "ffi-yajl", ">= 2.2.3", "< 3.1.0"
28
27
  end
@@ -25,6 +25,7 @@ require "base64" unless defined?(Base64)
25
25
  require "digest" unless defined?(Digest)
26
26
  require "fileutils" unless defined?(FileUtils)
27
27
  require "vra"
28
+ require "time" unless defined?(Time.now.iso8601)
28
29
  require_relative "vra_version"
29
30
 
30
31
  module Kitchen
@@ -46,6 +47,11 @@ module Kitchen
46
47
  kitchen_driver_api_version 2
47
48
  plugin_version Kitchen::Driver::VRA_VERSION
48
49
 
50
+ # Deployment statuses vRA reports for a deployment that is up.
51
+ #
52
+ # @return [Array<String>]
53
+ LIVE_STATUSES = %w{CREATE_SUCCESSFUL}.freeze
54
+
49
55
  # Location of the credential cache, relative to the working directory.
50
56
  CREDENTIALS_CACHE_FILE = ".kitchen/cached_vra"
51
57
 
@@ -314,6 +320,41 @@ module Kitchen
314
320
  end
315
321
  end
316
322
 
323
+ # Reports what vRA currently thinks of the deployment.
324
+ #
325
+ # @param state [Hash] instance state naming the deployment
326
+ # @return [Hash] a Test Kitchen status hash, or the base implementation's
327
+ # answer when there is no deployment or vRA does not know it
328
+ def status(state)
329
+ return super unless state[:deployment_id]
330
+
331
+ deployment = lookup_deployment(state[:deployment_id])
332
+ return super unless deployment
333
+
334
+ deployment_status = deployment.status.to_s
335
+ {
336
+ live: LIVE_STATUSES.include?(deployment_status),
337
+ state: deployment_status,
338
+ source: "driver",
339
+ resource_id: state[:deployment_id],
340
+ message: "vRA reports the deployment as #{deployment_status}",
341
+ checked_at: Time.now.utc.iso8601,
342
+ }
343
+ end
344
+
345
+ # Looks a deployment up without turning an unreachable vRA into a
346
+ # failure. A deployment that has already been destroyed answers with
347
+ # NotFound, which is a real answer rather than an error.
348
+ #
349
+ # @param deployment_id [String] the vRA deployment ID
350
+ # @return [Vra::Deployment, nil] the deployment, or nil when vRA does not
351
+ # know it or cannot be reached
352
+ def lookup_deployment(deployment_id)
353
+ vra_client.deployments.by_id(deployment_id)
354
+ rescue ::StandardError
355
+ nil
356
+ end
357
+
317
358
  # Destroys the vRA deployment, if one exists.
318
359
  #
319
360
  # A deployment that vRA no longer knows about, or that offers no destroy
@@ -342,8 +383,10 @@ module Kitchen
342
383
  wait_for_request(destroy_request)
343
384
  info("Destroy request complete.")
344
385
 
345
- File.delete(".kitchen/cached_vra") if File.exist?(".kitchen/cached_vra")
346
- info("Removed cached file")
386
+ return unless File.exist?(CREDENTIALS_CACHE_FILE)
387
+
388
+ File.delete(CREDENTIALS_CACHE_FILE)
389
+ info("Removed the cached credentials in #{CREDENTIALS_CACHE_FILE}.")
347
390
  end
348
391
 
349
392
  # Builds the vRA catalog request for the configured blueprint.
@@ -390,21 +433,43 @@ module Kitchen
390
433
 
391
434
  # The vRA API client, built from the resolved credentials.
392
435
  #
393
- # On any failure the credentials are re-prompted, on the assumption that
394
- # what failed was authentication.
436
+ # Credentials are resolved once, when the client is first built. A
437
+ # failure to build it is assumed to be an authentication problem, so the
438
+ # credentials are asked for again and the client built a second time. A
439
+ # second failure is raised rather than swallowed.
395
440
  #
396
441
  # @return [Vra::Client]
442
+ # @raise [ArgumentError] if the credentials are still not usable after
443
+ # re-prompting
397
444
  def vra_client
398
- check_config config[:cache_credentials]
399
- @client ||= ::Vra::Client.new(
400
- base_url: config[:base_url],
401
- username: config[:username],
402
- password: config[:password],
403
- domain: config[:domain],
404
- verify_ssl: config[:verify_ssl]
405
- )
406
- rescue => _e
407
- check_config true
445
+ @client ||= build_client
446
+ end
447
+
448
+ # Resolves the credentials and builds the client, asking for the
449
+ # credentials a second time if the first attempt fails.
450
+ #
451
+ # @return [Vra::Client]
452
+ # @raise [ArgumentError] if the second attempt fails as well
453
+ # @api private
454
+ def build_client
455
+ retried = false
456
+
457
+ begin
458
+ check_config(retried)
459
+ ::Vra::Client.new(
460
+ base_url: config[:base_url],
461
+ username: config[:username],
462
+ password: config[:password],
463
+ domain: config[:domain],
464
+ verify_ssl: config[:verify_ssl]
465
+ )
466
+ rescue => e
467
+ raise if retried
468
+
469
+ warn("Unable to build a vRA client: #{e.message}")
470
+ retried = true
471
+ retry
472
+ end
408
473
  end
409
474
 
410
475
  # Polls a vRA request until it completes or times out.
@@ -416,8 +481,6 @@ module Kitchen
416
481
  # @return [void]
417
482
  # @raise [Timeout::Error] if the request does not complete in time
418
483
  def wait_for_request(request)
419
- # config = check_config config
420
-
421
484
  last_status = ""
422
485
  wait_time = config[:request_timeout]
423
486
  sleep_time = config[:request_refresh_rate]
@@ -23,6 +23,6 @@ module Kitchen
23
23
  # Test Kitchen's driver plugins.
24
24
  module Driver
25
25
  # The version of the kitchen-vra gem.
26
- VRA_VERSION = "3.3.4"
26
+ VRA_VERSION = "3.4.1"
27
27
  end
28
28
  end
data/spec/spec_helper.rb CHANGED
@@ -19,13 +19,51 @@
19
19
  #
20
20
 
21
21
  require "webmock/rspec"
22
+ require "tmpdir"
23
+ require "fileutils"
22
24
 
25
+ require "kitchen/driver/vra"
26
+ require "kitchen/provisioner/dummy"
27
+ require "kitchen/transport/dummy"
28
+ require "kitchen/verifier/dummy"
29
+
30
+ Dir[File.join(__dir__, "support", "**", "*.rb")].sort.each { |file| require file }
31
+
32
+ # These specs never reach a vRA appliance. Everything the driver does is turn
33
+ # kitchen.yml configuration into a vRA catalog request, and turn vRA's answers
34
+ # back into Test Kitchen state, so an outbound request from a spec is a mistake
35
+ # rather than a slow test.
23
36
  WebMock.disable_net_connect!(allow_localhost: true)
24
37
 
25
- if ENV["COVERAGE"]
26
- require "simplecov"
27
- SimpleCov.profiles.define "gem" do
28
- command_name "Specs"
38
+ RSpec.configure do |config|
39
+ config.expect_with :rspec do |expectations|
40
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
29
41
  end
30
- SimpleCov.start "gem"
42
+
43
+ config.mock_with :rspec do |mocks|
44
+ # Fail if an example stubs a method the real object does not have. Almost
45
+ # every driver method is a call into the vmware-vra gem, so without this a
46
+ # rename there leaves the specs stubbing a method that no longer exists,
47
+ # passing while the driver is broken.
48
+ mocks.verify_partial_doubles = true
49
+ end
50
+
51
+ config.shared_context_metadata_behavior = :apply_to_host_groups
52
+
53
+ # No `describe` on bare objects and no `should` -- the RSpec 2 DSL, kept
54
+ # alive only by monkey patches on Object and BasicObject.
55
+ config.disable_monkey_patching!
56
+
57
+ # Surface deprecations as failures rather than warnings that scroll past.
58
+ config.raise_errors_for_deprecations!
59
+
60
+ config.include VraApi
61
+
62
+ config.filter_run_when_matching :focus
63
+
64
+ # Run specs in random order to surface order dependencies. To debug one, fix
65
+ # the order by passing the seed printed after each run:
66
+ # --seed 1234
67
+ config.order = :random
68
+ Kernel.srand config.seed
31
69
  end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ # Stand-ins for the vmware-vra objects the driver talks to.
18
+ #
19
+ # The driver is a thin layer over the vmware-vra gem: nearly every method
20
+ # either asks a Vra object something or hands it a request. A bare `double`
21
+ # answers whatever it is told to answer, so a suite built on them keeps passing
22
+ # after vmware-vra renames a method -- which is exactly the change most likely
23
+ # to break the driver. Everything here is a verifying double checked against
24
+ # the real class instead, so the specs fail when the gem moves underneath them.
25
+ #
26
+ # Each helper takes a hash of overrides so an example can say only what it
27
+ # cares about:
28
+ #
29
+ # vra_deployment(status: "CREATE_FAILED", failed?: true)
30
+ module VraApi
31
+ # A VM inside a deployment, as returned by Vra::Deployment#resources.
32
+ #
33
+ # @param overrides [Hash] stubs to replace the defaults with
34
+ # @return [InstanceDouble<Vra::Resource>]
35
+ def vra_resource(overrides = {})
36
+ instance_double(
37
+ Vra::Resource,
38
+ {
39
+ id: "res-1",
40
+ deployment_id: "dep-1",
41
+ name: "test-server",
42
+ ip_address: "1.2.3.4",
43
+ vm?: true,
44
+ }.merge(overrides)
45
+ )
46
+ end
47
+
48
+ # A deployment. This is also what Vra::DeploymentRequest#submit hands back,
49
+ # which is why the driver waits on it and then asks it for its resources.
50
+ #
51
+ # @param overrides [Hash] stubs to replace the defaults with
52
+ # @return [InstanceDouble<Vra::Deployment>]
53
+ def vra_deployment(overrides = {})
54
+ instance_double(
55
+ Vra::Deployment,
56
+ {
57
+ id: "dep-1",
58
+ name: "test-instance",
59
+ status: "CREATE_SUCCESSFUL",
60
+ failed?: false,
61
+ completed?: true,
62
+ completion_details: nil,
63
+ refresh: nil,
64
+ resources: [vra_resource],
65
+ }.merge(overrides)
66
+ )
67
+ end
68
+
69
+ # A request against a deployment, such as the destroy action.
70
+ #
71
+ # @param overrides [Hash] stubs to replace the defaults with
72
+ # @return [InstanceDouble<Vra::Request>]
73
+ def vra_request(overrides = {})
74
+ instance_double(
75
+ Vra::Request,
76
+ {
77
+ id: "req-1",
78
+ status: "SUCCESSFUL",
79
+ completed?: true,
80
+ refresh: nil,
81
+ details: nil,
82
+ }.merge(overrides)
83
+ )
84
+ end
85
+
86
+ # A catalog item, as returned by Vra::Catalog#fetch_catalog_items.
87
+ #
88
+ # @param overrides [Hash] stubs to replace the defaults with
89
+ # @return [InstanceDouble<Vra::CatalogItem>]
90
+ def vra_catalog_item(overrides = {})
91
+ instance_double(
92
+ Vra::CatalogItem,
93
+ { id: "cat-1", name: "Ubuntu Server" }.merge(overrides)
94
+ )
95
+ end
96
+
97
+ # An unsubmitted catalog request.
98
+ #
99
+ # @param overrides [Hash] stubs to replace the defaults with
100
+ # @return [InstanceDouble<Vra::DeploymentRequest>]
101
+ def vra_deployment_request(overrides = {})
102
+ instance_double(
103
+ Vra::DeploymentRequest,
104
+ { submit: vra_deployment, set_parameters: nil }.merge(overrides)
105
+ )
106
+ end
107
+
108
+ # A vRA API client, wired to a catalog and a deployments collection.
109
+ #
110
+ # @param catalog [InstanceDouble<Vra::Catalog>, nil]
111
+ # @param deployments [InstanceDouble<Vra::Deployments>, nil]
112
+ # @return [InstanceDouble<Vra::Client>]
113
+ def vra_client(catalog: nil, deployments: nil)
114
+ instance_double(
115
+ Vra::Client,
116
+ catalog: catalog || instance_double(Vra::Catalog),
117
+ deployments: deployments || instance_double(Vra::Deployments)
118
+ )
119
+ end
120
+ end