kitchen-vra 3.4.0 → 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 +4 -4
- data/.gitignore +0 -1
- data/.release-please-manifest.json +1 -1
- data/.rspec +2 -0
- data/.rubocop.yml +0 -1
- data/.yardopts +1 -1
- data/CHANGELOG.md +7 -0
- data/lib/kitchen/driver/vra.rb +38 -16
- data/lib/kitchen/driver/vra_version.rb +1 -1
- data/spec/spec_helper.rb +43 -5
- data/spec/support/vra_api.rb +120 -0
- data/spec/vra_spec.rb +457 -191
- metadata +5 -3
- /data/{LICENSE.txt → LICENSE} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3077884f3c8f5e434889905e6c4d47e06ecf308fe810948c79d644e5f13086bd
|
|
4
|
+
data.tar.gz: addc3a87013a8834a75392625e026b5efdf1cfaff8009e0eaa8e690236b0be84
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2d6521702e1cc486f91d1aa2c0d1c17fdd1feaece55a8b634aaea65950282bde3b327e4bb79e7c256a6683ba8128b36ef5b59852b2c1f0de763d64dc4aa359a2
|
|
7
|
+
data.tar.gz: 190fa3d7bbfc3fd0db0220934390545f9b28cd4565126648d0503459de10035962f795b69275599409671aa15d2f5d31a018074f7b59d4a092f83437b027d8cd
|
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.rubocop.yml
CHANGED
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
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
|
+
|
|
13
20
|
## [3.4.0](https://github.com/test-kitchen/kitchen-vra/compare/v3.3.4...v3.4.0) (2026-08-24)
|
|
14
21
|
|
|
15
22
|
|
data/lib/kitchen/driver/vra.rb
CHANGED
|
@@ -383,8 +383,10 @@ module Kitchen
|
|
|
383
383
|
wait_for_request(destroy_request)
|
|
384
384
|
info("Destroy request complete.")
|
|
385
385
|
|
|
386
|
-
|
|
387
|
-
|
|
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}.")
|
|
388
390
|
end
|
|
389
391
|
|
|
390
392
|
# Builds the vRA catalog request for the configured blueprint.
|
|
@@ -431,21 +433,43 @@ module Kitchen
|
|
|
431
433
|
|
|
432
434
|
# The vRA API client, built from the resolved credentials.
|
|
433
435
|
#
|
|
434
|
-
#
|
|
435
|
-
#
|
|
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.
|
|
436
440
|
#
|
|
437
441
|
# @return [Vra::Client]
|
|
442
|
+
# @raise [ArgumentError] if the credentials are still not usable after
|
|
443
|
+
# re-prompting
|
|
438
444
|
def vra_client
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
449
473
|
end
|
|
450
474
|
|
|
451
475
|
# Polls a vRA request until it completes or times out.
|
|
@@ -457,8 +481,6 @@ module Kitchen
|
|
|
457
481
|
# @return [void]
|
|
458
482
|
# @raise [Timeout::Error] if the request does not complete in time
|
|
459
483
|
def wait_for_request(request)
|
|
460
|
-
# config = check_config config
|
|
461
|
-
|
|
462
484
|
last_status = ""
|
|
463
485
|
wait_time = config[:request_timeout]
|
|
464
486
|
sleep_time = config[:request_refresh_rate]
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
data/spec/vra_spec.rb
CHANGED
|
@@ -18,43 +18,35 @@
|
|
|
18
18
|
# limitations under the License.
|
|
19
19
|
#
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
require "tmpdir"
|
|
23
|
-
require "fileutils"
|
|
24
|
-
require "kitchen/driver/vra"
|
|
25
|
-
require "kitchen/provisioner/dummy"
|
|
26
|
-
require "kitchen/transport/dummy"
|
|
27
|
-
require "kitchen/verifier/dummy"
|
|
28
|
-
|
|
29
|
-
describe Kitchen::Driver::Vra do
|
|
21
|
+
RSpec.describe Kitchen::Driver::Vra do
|
|
30
22
|
let(:logged_output) { StringIO.new }
|
|
31
23
|
let(:logger) { Logger.new(logged_output) }
|
|
32
24
|
let(:platform) { Kitchen::Platform.new(name: "fake_platform") }
|
|
33
25
|
let(:transport) { Kitchen::Transport::Dummy.new }
|
|
34
|
-
let(:driver) {
|
|
26
|
+
let(:driver) { described_class.new(config) }
|
|
35
27
|
|
|
36
28
|
let(:config) do
|
|
37
29
|
{
|
|
38
|
-
base_url:
|
|
39
|
-
username:
|
|
40
|
-
password:
|
|
41
|
-
domain:
|
|
42
|
-
project_id:
|
|
43
|
-
image_mapping:
|
|
44
|
-
flavor_mapping:
|
|
45
|
-
verify_ssl:
|
|
46
|
-
use_dns:
|
|
30
|
+
base_url: "https://vra.corp.local",
|
|
31
|
+
username: "myuser",
|
|
32
|
+
password: "mypassword",
|
|
33
|
+
domain: "mytenant.corp.com",
|
|
34
|
+
project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
|
|
35
|
+
image_mapping: "VRA-nc-lnx-ce8.4-Docker",
|
|
36
|
+
flavor_mapping: "Small",
|
|
37
|
+
verify_ssl: true,
|
|
38
|
+
use_dns: false,
|
|
47
39
|
deployment_name: "test-instance",
|
|
48
|
-
catalog_id:
|
|
40
|
+
catalog_id: "1536b95d-68fe-3c68-9417-46ed24bee3ef",
|
|
49
41
|
}
|
|
50
42
|
end
|
|
51
43
|
|
|
52
44
|
let(:instance) do
|
|
53
45
|
instance_double(Kitchen::Instance,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
logger: logger,
|
|
47
|
+
transport: transport,
|
|
48
|
+
platform: platform,
|
|
49
|
+
to_str: "instance_str")
|
|
58
50
|
end
|
|
59
51
|
|
|
60
52
|
before do
|
|
@@ -71,6 +63,54 @@ describe Kitchen::Driver::Vra do
|
|
|
71
63
|
end
|
|
72
64
|
end
|
|
73
65
|
|
|
66
|
+
describe "configuration defaults" do
|
|
67
|
+
let(:config) do
|
|
68
|
+
{
|
|
69
|
+
base_url: "https://vra.corp.local",
|
|
70
|
+
domain: "mytenant.corp.com",
|
|
71
|
+
project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
|
|
72
|
+
image_mapping: "VRA-nc-lnx-ce8.4-Docker",
|
|
73
|
+
flavor_mapping: "Small",
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "names the deployment after the platform under test" do
|
|
78
|
+
expect(driver[:deployment_name]).to eq("fake_platform")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "connects by IP address rather than by name" do
|
|
82
|
+
expect(driver[:use_dns]).to be false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "verifies the appliance certificate" do
|
|
86
|
+
expect(driver[:verify_ssl]).to be true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe "the private key" do
|
|
90
|
+
let(:id_rsa) { File.expand_path("~/.ssh/id_rsa") }
|
|
91
|
+
let(:id_dsa) { File.expand_path("~/.ssh/id_dsa") }
|
|
92
|
+
|
|
93
|
+
before { allow(File).to receive(:exist?).and_return(false) }
|
|
94
|
+
|
|
95
|
+
it "prefers an RSA key" do
|
|
96
|
+
allow(File).to receive(:exist?).with(id_rsa).and_return(true)
|
|
97
|
+
allow(File).to receive(:exist?).with(id_dsa).and_return(true)
|
|
98
|
+
|
|
99
|
+
expect(driver[:private_key_path]).to eq(id_rsa)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "falls back to a DSA key" do
|
|
103
|
+
allow(File).to receive(:exist?).with(id_dsa).and_return(true)
|
|
104
|
+
|
|
105
|
+
expect(driver[:private_key_path]).to eq(id_dsa)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "is left unset when neither key exists" do
|
|
109
|
+
expect(driver[:private_key_path]).to be_nil
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
74
114
|
describe "#create" do
|
|
75
115
|
context "when the server is already created" do
|
|
76
116
|
let(:state) { { deployment_id: "48959518-6a0d-46e1-8415-3749696b65f4" } }
|
|
@@ -81,14 +121,8 @@ describe Kitchen::Driver::Vra do
|
|
|
81
121
|
end
|
|
82
122
|
end
|
|
83
123
|
|
|
84
|
-
let(:state)
|
|
85
|
-
let(:resource)
|
|
86
|
-
double("server1",
|
|
87
|
-
deployment_id: "e8706351-cf4c-4c12-acb7-c90cc683b22c",
|
|
88
|
-
name: "test-server",
|
|
89
|
-
ip_address: "1.2.3.4",
|
|
90
|
-
vm?: true)
|
|
91
|
-
end
|
|
124
|
+
let(:state) { {} }
|
|
125
|
+
let(:resource) { vra_resource(deployment_id: "e8706351-cf4c-4c12-acb7-c90cc683b22c") }
|
|
92
126
|
|
|
93
127
|
before do
|
|
94
128
|
allow(driver).to receive(:request_server).and_return(resource)
|
|
@@ -115,22 +149,34 @@ describe Kitchen::Driver::Vra do
|
|
|
115
149
|
expect(driver).to receive(:wait_for_server)
|
|
116
150
|
driver.create(state)
|
|
117
151
|
end
|
|
152
|
+
|
|
153
|
+
context "when a private key is configured" do
|
|
154
|
+
let(:config) { super().merge(private_key_path: "/home/me/.ssh/id_vra") }
|
|
155
|
+
|
|
156
|
+
it "tells the transport which key to connect with" do
|
|
157
|
+
driver.create(state)
|
|
158
|
+
expect(state[:ssh_key]).to eq("/home/me/.ssh/id_vra")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context "when no private key was found" do
|
|
163
|
+
let(:config) { super().merge(private_key_path: nil) }
|
|
164
|
+
|
|
165
|
+
it "leaves the transport to work out how to connect" do
|
|
166
|
+
driver.create(state)
|
|
167
|
+
expect(state).not_to have_key(:ssh_key)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
118
170
|
end
|
|
119
171
|
|
|
120
172
|
describe "#hostname_for" do
|
|
121
|
-
let(:server)
|
|
122
|
-
double("server",
|
|
123
|
-
deployment_id: "test_id",
|
|
124
|
-
name: "test_hostname",
|
|
125
|
-
ip_address: "1.2.3.4",
|
|
126
|
-
vm?: true)
|
|
127
|
-
end
|
|
173
|
+
let(:server) { vra_resource(deployment_id: "test_id", name: "test_hostname") }
|
|
128
174
|
|
|
129
175
|
context "when use_dns is true and dns_suffix is defined" do
|
|
130
176
|
let(:config) do
|
|
131
177
|
{
|
|
132
|
-
use_dns:
|
|
133
|
-
dns_suffix:
|
|
178
|
+
use_dns: true,
|
|
179
|
+
dns_suffix: "my.com",
|
|
134
180
|
}
|
|
135
181
|
end
|
|
136
182
|
|
|
@@ -143,20 +189,23 @@ describe Kitchen::Driver::Vra do
|
|
|
143
189
|
let(:config) { { use_dns: true } }
|
|
144
190
|
|
|
145
191
|
it "raises an exception if the server name is nil" do
|
|
146
|
-
|
|
147
|
-
expect { driver.hostname_for(server) }.to raise_error(RuntimeError)
|
|
192
|
+
expect { driver.hostname_for(vra_resource(name: nil)) }.to raise_error(RuntimeError)
|
|
148
193
|
end
|
|
149
194
|
|
|
150
195
|
it "returns the server name" do
|
|
151
196
|
expect(driver.hostname_for(server)).to eq("test_hostname")
|
|
152
197
|
end
|
|
198
|
+
|
|
199
|
+
it "does not go looking for an IP address" do
|
|
200
|
+
expect(server).not_to receive(:ip_address)
|
|
201
|
+
driver.hostname_for(server)
|
|
202
|
+
end
|
|
153
203
|
end
|
|
154
204
|
|
|
155
205
|
context "when use_dns is false" do
|
|
156
206
|
it "falls back to the server name if no IP address exists" do
|
|
157
|
-
allow(server).to receive(:ip_address).and_return(nil)
|
|
158
207
|
expect(driver).to receive(:warn)
|
|
159
|
-
expect(driver.hostname_for(
|
|
208
|
+
expect(driver.hostname_for(vra_resource(ip_address: nil))).to eq("test-server")
|
|
160
209
|
end
|
|
161
210
|
|
|
162
211
|
it "returns the IP address if it exists" do
|
|
@@ -166,47 +215,32 @@ describe Kitchen::Driver::Vra do
|
|
|
166
215
|
end
|
|
167
216
|
|
|
168
217
|
describe "#request_server" do
|
|
169
|
-
let(:
|
|
170
|
-
let(:catalog_request)
|
|
171
|
-
let(:resource1)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
name: "server1",
|
|
175
|
-
ip_address: "1.2.3.4",
|
|
176
|
-
vm?: true)
|
|
177
|
-
end
|
|
178
|
-
let(:resource2) do
|
|
179
|
-
double("server2",
|
|
180
|
-
id: "9e2364cf-7af4-4b85-93fd-1f03ee2ac865",
|
|
181
|
-
name: "server2",
|
|
182
|
-
ip_address: "4.3.2.1",
|
|
183
|
-
vm?: true)
|
|
184
|
-
end
|
|
185
|
-
let(:resources) { [resource1] }
|
|
218
|
+
let(:deployment) { vra_deployment(id: "74e26af9-2d2f-4889-a472-95dbcedb70b8", resources: resources) }
|
|
219
|
+
let(:catalog_request) { vra_deployment_request(submit: deployment) }
|
|
220
|
+
let(:resource1) { vra_resource(id: "e8706351-cf4c-4c12-acb7-c90cc683b22c", name: "server1") }
|
|
221
|
+
let(:resource2) { vra_resource(id: "9e2364cf-7af4-4b85-93fd-1f03ee2ac865", name: "server2") }
|
|
222
|
+
let(:resources) { [resource1] }
|
|
186
223
|
|
|
187
224
|
before do
|
|
188
225
|
allow(driver).to receive(:catalog_request).and_return(catalog_request)
|
|
189
|
-
allow(
|
|
190
|
-
allow(submitted_request).to receive(:id).and_return("74e26af9-2d2f-4889-a472-95dbcedb70b8")
|
|
191
|
-
allow(submitted_request).to receive(:resources).and_return(resources)
|
|
192
|
-
allow(submitted_request).to receive(:failed?).and_return(false)
|
|
193
|
-
allow(driver).to receive(:wait_for_request).with(submitted_request)
|
|
226
|
+
allow(driver).to receive(:wait_for_request).with(deployment)
|
|
194
227
|
end
|
|
195
228
|
|
|
196
229
|
it "submits a catalog request" do
|
|
197
|
-
expect(
|
|
230
|
+
expect(catalog_request).to receive(:submit).and_return(deployment)
|
|
198
231
|
driver.request_server
|
|
199
232
|
end
|
|
200
233
|
|
|
201
234
|
it "waits for the request to complete" do
|
|
202
|
-
expect(driver).to receive(:wait_for_request).with(
|
|
235
|
+
expect(driver).to receive(:wait_for_request).with(deployment)
|
|
203
236
|
driver.request_server
|
|
204
237
|
end
|
|
205
238
|
|
|
206
239
|
it "raises an exception if the request failed" do
|
|
207
|
-
allow(
|
|
208
|
-
allow(
|
|
209
|
-
|
|
240
|
+
allow(deployment).to receive(:failed?).and_return(true)
|
|
241
|
+
allow(deployment).to receive(:completion_details).and_return("it failed")
|
|
242
|
+
|
|
243
|
+
expect { driver.request_server }.to raise_error(RuntimeError, /it failed/)
|
|
210
244
|
end
|
|
211
245
|
|
|
212
246
|
describe "getting the server from the request" do
|
|
@@ -217,16 +251,26 @@ describe Kitchen::Driver::Vra do
|
|
|
217
251
|
end
|
|
218
252
|
|
|
219
253
|
context "when multiple servers are returned" do
|
|
254
|
+
let(:resources) { [resource1, resource2] }
|
|
255
|
+
|
|
220
256
|
it "raises an exception" do
|
|
221
|
-
|
|
222
|
-
expect { driver.request_server }.to raise_error(RuntimeError)
|
|
257
|
+
expect { driver.request_server }.to raise_error(RuntimeError, /more than one server/)
|
|
223
258
|
end
|
|
224
259
|
end
|
|
225
260
|
|
|
226
261
|
context "when no servers are returned" do
|
|
262
|
+
let(:resources) { [] }
|
|
263
|
+
|
|
227
264
|
it "raises an exception" do
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
expect { driver.request_server }.to raise_error(RuntimeError, /did not create any servers/)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
context "when the deployment also contains things that are not VMs" do
|
|
270
|
+
let(:resources) { [vra_resource(id: "net-1", vm?: false), resource1] }
|
|
271
|
+
|
|
272
|
+
it "picks out the one VM" do
|
|
273
|
+
expect(driver.request_server).to eq(resource1)
|
|
230
274
|
end
|
|
231
275
|
end
|
|
232
276
|
end
|
|
@@ -234,18 +278,25 @@ describe Kitchen::Driver::Vra do
|
|
|
234
278
|
it "returns the the single server resource object" do
|
|
235
279
|
expect(driver.request_server).to eq(resource1)
|
|
236
280
|
end
|
|
281
|
+
|
|
282
|
+
context "when unique_name is set" do
|
|
283
|
+
let(:config) { super().merge(unique_name: true) }
|
|
284
|
+
|
|
285
|
+
it "reports the name vRA will give the deployment" do
|
|
286
|
+
allow(driver).to receive(:info)
|
|
287
|
+
|
|
288
|
+
driver.request_server
|
|
289
|
+
|
|
290
|
+
expect(driver).to have_received(:info)
|
|
291
|
+
.with("Deployment name is deployment_74e26af9-2d2f-4889-a472-95dbcedb70b8")
|
|
292
|
+
end
|
|
293
|
+
end
|
|
237
294
|
end
|
|
238
295
|
|
|
239
296
|
describe "#wait_for_server" do
|
|
240
297
|
let(:connection) { instance.transport.connection(state) }
|
|
241
298
|
let(:state) { {} }
|
|
242
|
-
let(:resource1)
|
|
243
|
-
double("server1",
|
|
244
|
-
id: "test_id",
|
|
245
|
-
name: "server1",
|
|
246
|
-
ip_address: "1.2.3.4",
|
|
247
|
-
vm?: true)
|
|
248
|
-
end
|
|
299
|
+
let(:resource1) { vra_resource(id: "test_id", name: "server1") }
|
|
249
300
|
|
|
250
301
|
before do
|
|
251
302
|
allow(transport).to receive(:connection).and_return(connection)
|
|
@@ -331,10 +382,10 @@ describe Kitchen::Driver::Vra do
|
|
|
331
382
|
end
|
|
332
383
|
|
|
333
384
|
describe "#status" do
|
|
334
|
-
let(:deployments) {
|
|
335
|
-
let(:
|
|
385
|
+
let(:deployments) { instance_double(Vra::Deployments) }
|
|
386
|
+
let(:client) { vra_client(deployments: deployments) }
|
|
336
387
|
|
|
337
|
-
before { allow(driver).to receive(:vra_client).and_return(
|
|
388
|
+
before { allow(driver).to receive(:vra_client).and_return(client) }
|
|
338
389
|
|
|
339
390
|
it "reports an unknown status when state names no deployment" do
|
|
340
391
|
expect(driver.status({})).to include(live: nil, state: "unknown")
|
|
@@ -348,8 +399,7 @@ describe Kitchen::Driver::Vra do
|
|
|
348
399
|
end
|
|
349
400
|
|
|
350
401
|
it "reports a successfully created deployment as live" do
|
|
351
|
-
allow(deployments).to receive(:by_id).with("dep-1")
|
|
352
|
-
.and_return(double(status: "CREATE_SUCCESSFUL"))
|
|
402
|
+
allow(deployments).to receive(:by_id).with("dep-1").and_return(vra_deployment)
|
|
353
403
|
|
|
354
404
|
expect(driver.status(deployment_id: "dep-1")).to include(
|
|
355
405
|
live: true, state: "CREATE_SUCCESSFUL", source: "driver",
|
|
@@ -358,24 +408,21 @@ describe Kitchen::Driver::Vra do
|
|
|
358
408
|
end
|
|
359
409
|
|
|
360
410
|
it "stamps when the check happened" do
|
|
361
|
-
allow(deployments).to receive(:by_id)
|
|
362
|
-
.and_return(double(status: "CREATE_SUCCESSFUL"))
|
|
411
|
+
allow(deployments).to receive(:by_id).and_return(vra_deployment)
|
|
363
412
|
|
|
364
413
|
expect(driver.status(deployment_id: "dep-1")[:checked_at])
|
|
365
414
|
.to match(/\A\d{4}-\d{2}-\d{2}T/)
|
|
366
415
|
end
|
|
367
416
|
|
|
368
417
|
it "reports a deployment still building as not live" do
|
|
369
|
-
allow(deployments).to receive(:by_id)
|
|
370
|
-
.and_return(double(status: "CREATE_INPROGRESS"))
|
|
418
|
+
allow(deployments).to receive(:by_id).and_return(vra_deployment(status: "CREATE_INPROGRESS"))
|
|
371
419
|
|
|
372
420
|
expect(driver.status(deployment_id: "dep-1"))
|
|
373
421
|
.to include(live: false, state: "CREATE_INPROGRESS")
|
|
374
422
|
end
|
|
375
423
|
|
|
376
424
|
it "names a failed deployment rather than reporting a bare false" do
|
|
377
|
-
allow(deployments).to receive(:by_id)
|
|
378
|
-
.and_return(double(status: "CREATE_FAILED"))
|
|
425
|
+
allow(deployments).to receive(:by_id).and_return(vra_deployment(status: "CREATE_FAILED", failed?: true))
|
|
379
426
|
|
|
380
427
|
expect(driver.status(deployment_id: "dep-1"))
|
|
381
428
|
.to include(live: false, state: "CREATE_FAILED")
|
|
@@ -389,44 +436,36 @@ describe Kitchen::Driver::Vra do
|
|
|
389
436
|
end
|
|
390
437
|
|
|
391
438
|
describe "#destroy" do
|
|
392
|
-
let(:deployment_id)
|
|
439
|
+
let(:deployment_id) { "8c1a833a-5844-4100-b58c-9cab3543c958" }
|
|
393
440
|
let(:state) { { deployment_id: deployment_id } }
|
|
394
|
-
let(:
|
|
395
|
-
let(:
|
|
396
|
-
let(:destroy_request) {
|
|
397
|
-
let(:
|
|
398
|
-
double("server1",
|
|
399
|
-
deployment_id: deployment_id,
|
|
400
|
-
name: "server1",
|
|
401
|
-
ip_address: "5.6.7.8",
|
|
402
|
-
vm?: true)
|
|
403
|
-
end
|
|
441
|
+
let(:deployments) { instance_double(Vra::Deployments) }
|
|
442
|
+
let(:client) { vra_client(deployments: deployments) }
|
|
443
|
+
let(:destroy_request) { vra_request(id: "6da65982-7c33-4e6e-b346-fdf4bcbf01ab") }
|
|
444
|
+
let(:deployment) { vra_deployment(id: deployment_id, destroy: destroy_request) }
|
|
404
445
|
|
|
405
446
|
before do
|
|
406
|
-
allow(driver).to receive(:vra_client).and_return(
|
|
447
|
+
allow(driver).to receive(:vra_client).and_return(client)
|
|
407
448
|
allow(driver).to receive(:wait_for_request).with(destroy_request)
|
|
408
|
-
allow(
|
|
409
|
-
allow(deployments).to receive(:by_id).and_return(resource)
|
|
410
|
-
allow(resource).to receive(:destroy).and_return(destroy_request)
|
|
411
|
-
allow(destroy_request).to receive(:id).and_return("6da65982-7c33-4e6e-b346-fdf4bcbf01ab")
|
|
449
|
+
allow(deployments).to receive(:by_id).and_return(deployment)
|
|
412
450
|
end
|
|
413
451
|
|
|
414
452
|
context "when the resource is not created" do
|
|
415
453
|
let(:state) { {} }
|
|
454
|
+
|
|
416
455
|
it "does not look up the resource if no resource ID exists" do
|
|
417
|
-
expect(
|
|
456
|
+
expect(deployments).not_to receive(:by_id)
|
|
418
457
|
driver.destroy(state)
|
|
419
458
|
end
|
|
420
459
|
end
|
|
421
460
|
|
|
422
461
|
it "looks up the resource record" do
|
|
423
|
-
expect(
|
|
462
|
+
expect(deployments).to receive(:by_id).with(deployment_id).and_return(deployment)
|
|
424
463
|
driver.destroy(state)
|
|
425
464
|
end
|
|
426
465
|
|
|
427
466
|
context "when the resource record cannot be found" do
|
|
428
467
|
it "does not raise an exception" do
|
|
429
|
-
allow(
|
|
468
|
+
allow(deployments).to receive(:by_id).with(deployment_id).and_raise(Vra::Exception::NotFound)
|
|
430
469
|
expect { driver.destroy(state) }.not_to raise_error
|
|
431
470
|
end
|
|
432
471
|
end
|
|
@@ -434,13 +473,13 @@ describe Kitchen::Driver::Vra do
|
|
|
434
473
|
describe "creating the destroy request" do
|
|
435
474
|
context "when the destroy method or server is not found" do
|
|
436
475
|
it "does not raise an exception" do
|
|
437
|
-
allow(
|
|
476
|
+
allow(deployment).to receive(:destroy).and_raise(Vra::Exception::NotFound)
|
|
438
477
|
expect { driver.destroy(state) }.not_to raise_error
|
|
439
478
|
end
|
|
440
479
|
end
|
|
441
480
|
|
|
442
481
|
it "calls #destroy on the server" do
|
|
443
|
-
expect(
|
|
482
|
+
expect(deployment).to receive(:destroy).and_return(destroy_request)
|
|
444
483
|
driver.destroy(state)
|
|
445
484
|
end
|
|
446
485
|
end
|
|
@@ -449,81 +488,201 @@ describe Kitchen::Driver::Vra do
|
|
|
449
488
|
expect(driver).to receive(:wait_for_request).with(destroy_request)
|
|
450
489
|
driver.destroy(state)
|
|
451
490
|
end
|
|
452
|
-
end
|
|
453
491
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
492
|
+
describe "the cached credentials" do
|
|
493
|
+
let(:cache_dir) { Dir.mktmpdir("kitchen-vra-spec") }
|
|
494
|
+
|
|
495
|
+
around { |example| Dir.chdir(cache_dir) { example.run } }
|
|
496
|
+
after { FileUtils.remove_entry(cache_dir) }
|
|
497
|
+
|
|
498
|
+
it "removes the cache file left behind by a previous run" do
|
|
499
|
+
FileUtils.mkdir_p(".kitchen")
|
|
500
|
+
File.write(".kitchen/cached_vra", "cached")
|
|
501
|
+
|
|
502
|
+
driver.destroy(state)
|
|
503
|
+
|
|
504
|
+
expect(File.exist?(".kitchen/cached_vra")).to be false
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
it "does not claim to have removed a cache file that was never written" do
|
|
508
|
+
expect(driver).not_to receive(:info).with(/Removed.*cached/i)
|
|
509
|
+
|
|
510
|
+
driver.destroy(state)
|
|
464
511
|
end
|
|
465
512
|
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
describe "#catalog_request" do
|
|
516
|
+
let(:deployment_request) { vra_deployment_request }
|
|
517
|
+
let(:catalog) { instance_double(Vra::Catalog, request: deployment_request) }
|
|
518
|
+
let(:client) { vra_client(catalog: catalog) }
|
|
519
|
+
|
|
520
|
+
before { allow(driver).to receive(:vra_client).and_return(client) }
|
|
466
521
|
|
|
467
522
|
it "creates a catalog_request" do
|
|
468
|
-
expect(
|
|
523
|
+
expect(catalog).to receive(:request).and_return(deployment_request)
|
|
469
524
|
driver.catalog_request
|
|
470
525
|
end
|
|
471
526
|
|
|
472
|
-
|
|
527
|
+
it "asks for the configured image, flavor, project and version" do
|
|
528
|
+
expect(catalog).to receive(:request).with(
|
|
529
|
+
"1536b95d-68fe-3c68-9417-46ed24bee3ef",
|
|
530
|
+
hash_including(
|
|
531
|
+
image_mapping: "VRA-nc-lnx-ce8.4-Docker",
|
|
532
|
+
flavor_mapping: "Small",
|
|
533
|
+
project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
|
|
534
|
+
version: nil
|
|
535
|
+
)
|
|
536
|
+
).and_return(deployment_request)
|
|
537
|
+
|
|
538
|
+
driver.catalog_request
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
it "names the deployment" do
|
|
542
|
+
expect(catalog).to receive(:request)
|
|
543
|
+
.with(anything, hash_including(name: "test-instance"))
|
|
544
|
+
.and_return(deployment_request)
|
|
545
|
+
|
|
546
|
+
driver.catalog_request
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
context "when unique_name is set" do
|
|
550
|
+
let(:config) { super().merge(unique_name: true) }
|
|
551
|
+
|
|
552
|
+
it "leaves vRA to name the deployment after the request" do
|
|
553
|
+
expect(catalog).to receive(:request)
|
|
554
|
+
.with(anything, hash_excluding(:name))
|
|
555
|
+
.and_return(deployment_request)
|
|
556
|
+
|
|
557
|
+
driver.catalog_request
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
context "when the catalog item is named rather than identified" do
|
|
473
562
|
let(:config) do
|
|
474
|
-
{
|
|
475
|
-
|
|
476
|
-
username: "myuser",
|
|
477
|
-
password: "mypassword",
|
|
478
|
-
domain: "domain.corp.com",
|
|
479
|
-
verify_ssl: true,
|
|
480
|
-
project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
|
|
481
|
-
image_mapping: "VRA-nc-lnx-ce8.4-Docker",
|
|
482
|
-
flavor_mapping: "Small",
|
|
483
|
-
catalog_id: "2kabc423-af89-56fc-990a-82fab34ed12",
|
|
484
|
-
}
|
|
563
|
+
super().reject { |key, _value| key == :catalog_id }
|
|
564
|
+
.merge(catalog_name: "Ubuntu Server")
|
|
485
565
|
end
|
|
486
566
|
|
|
567
|
+
it "looks the name up and requests the ID it resolves to" do
|
|
568
|
+
allow(catalog).to receive(:fetch_catalog_items).with("Ubuntu Server")
|
|
569
|
+
.and_return([vra_catalog_item(id: "cat-9")])
|
|
570
|
+
expect(catalog).to receive(:request).with("cat-9", anything).and_return(deployment_request)
|
|
571
|
+
|
|
572
|
+
driver.catalog_request
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
context "when the name matches nothing in the catalog" do
|
|
576
|
+
before do
|
|
577
|
+
allow(catalog).to receive(:fetch_catalog_items).and_return([])
|
|
578
|
+
allow(driver).to receive(:error)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
it "fails rather than requesting an unknown catalog item" do
|
|
582
|
+
expect { driver.catalog_request }
|
|
583
|
+
.to raise_error(Kitchen::InstanceFailure, /without a valid catalog/)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
it "says which name it could not resolve" do
|
|
587
|
+
expect { driver.catalog_request }.to raise_error(Kitchen::InstanceFailure)
|
|
588
|
+
|
|
589
|
+
expect(driver).to have_received(:error).with(/Ubuntu Server/)
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
context "when neither a catalog ID nor a catalog name is given" do
|
|
595
|
+
let(:config) { super().reject { |key, _value| key == :catalog_id } }
|
|
596
|
+
|
|
597
|
+
it "fails before talking to vRA" do
|
|
598
|
+
expect(catalog).not_to receive(:request)
|
|
599
|
+
expect { driver.catalog_request }.to raise_error(Kitchen::InstanceFailure)
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
context "when option parameters are not supplied" do
|
|
487
604
|
it "does not attempt to set params on the catalog_request" do
|
|
488
|
-
expect(
|
|
605
|
+
expect(deployment_request).not_to receive(:set_parameters)
|
|
489
606
|
driver.catalog_request
|
|
490
607
|
end
|
|
491
608
|
end
|
|
492
609
|
|
|
493
610
|
context "when extra parameters are set" do
|
|
494
611
|
let(:config) do
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
|
|
502
|
-
image_mapping: "VRA-nc-lnx-ce8.4-Docker",
|
|
503
|
-
flavor_mapping: "Small",
|
|
504
|
-
catalog_id: "2kabc423-af89-56fc-990a-82fab34ed12",
|
|
505
|
-
extra_parameters: { "key1" => { type: "string", value: "value1" },
|
|
506
|
-
"key2" => { type: "integer", value: 123 } },
|
|
507
|
-
}
|
|
612
|
+
super().merge(
|
|
613
|
+
extra_parameters: {
|
|
614
|
+
"key1" => { type: "string", value: "value1" },
|
|
615
|
+
"key2" => { type: "integer", value: 123 },
|
|
616
|
+
}
|
|
617
|
+
)
|
|
508
618
|
end
|
|
509
619
|
|
|
510
620
|
it "sets extra parameters" do
|
|
511
|
-
expect(
|
|
512
|
-
expect(
|
|
621
|
+
expect(deployment_request).to receive(:set_parameters).with("key1", { type: "string", value: "value1" })
|
|
622
|
+
expect(deployment_request).to receive(:set_parameters).with("key2", { type: "integer", value: 123 })
|
|
513
623
|
driver.catalog_request
|
|
514
624
|
end
|
|
515
625
|
end
|
|
516
626
|
end
|
|
517
627
|
|
|
518
628
|
describe "#vra_client" do
|
|
629
|
+
let(:client) { vra_client }
|
|
630
|
+
|
|
519
631
|
it "sets up a client object" do
|
|
520
632
|
expect(Vra::Client).to receive(:new).with(base_url: config[:base_url],
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
633
|
+
username: config[:username],
|
|
634
|
+
password: config[:password],
|
|
635
|
+
domain: config[:domain],
|
|
636
|
+
verify_ssl: config[:verify_ssl])
|
|
525
637
|
driver.vra_client
|
|
526
638
|
end
|
|
639
|
+
|
|
640
|
+
it "builds the client once and hands the same one back" do
|
|
641
|
+
expect(Vra::Client).to receive(:new).once.and_return(client)
|
|
642
|
+
|
|
643
|
+
expect(driver.vra_client).to eq(client)
|
|
644
|
+
expect(driver.vra_client).to eq(client)
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
context "when cache_credentials is enabled" do
|
|
648
|
+
let(:config) { super().merge(cache_credentials: true) }
|
|
649
|
+
|
|
650
|
+
before { allow(driver).to receive(:c_save) }
|
|
651
|
+
|
|
652
|
+
it "does not prompt for credentials it already has" do
|
|
653
|
+
allow(Vra::Client).to receive(:new).and_return(client)
|
|
654
|
+
expect(driver).not_to receive(:ask)
|
|
655
|
+
|
|
656
|
+
driver.vra_client
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
context "when the client cannot be built" do
|
|
661
|
+
before do
|
|
662
|
+
allow(driver).to receive(:warn)
|
|
663
|
+
allow(driver).to receive(:check_config)
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
it "re-prompts for the credentials and tries once more" do
|
|
667
|
+
attempts = 0
|
|
668
|
+
allow(Vra::Client).to receive(:new) do
|
|
669
|
+
attempts += 1
|
|
670
|
+
raise ArgumentError, "Username and password are required" if attempts == 1
|
|
671
|
+
|
|
672
|
+
client
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
expect(driver.vra_client).to eq(client)
|
|
676
|
+
expect(driver).to have_received(:check_config).with(true)
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
it "raises rather than handing back something that is not a client" do
|
|
680
|
+
allow(Vra::Client).to receive(:new)
|
|
681
|
+
.and_raise(ArgumentError, "Username and password are required")
|
|
682
|
+
|
|
683
|
+
expect { driver.vra_client }.to raise_error(ArgumentError)
|
|
684
|
+
end
|
|
685
|
+
end
|
|
527
686
|
end
|
|
528
687
|
|
|
529
688
|
describe "#wait_for_request" do
|
|
@@ -534,21 +693,42 @@ describe Kitchen::Driver::Vra do
|
|
|
534
693
|
|
|
535
694
|
context "when the requests completes normally, 3 loops" do
|
|
536
695
|
it "only refreshes the request 3 times" do
|
|
537
|
-
request =
|
|
538
|
-
allow(request).to receive(:
|
|
539
|
-
allow(request).to receive(:completed?).exactly(3).times.and_return(false, false, true)
|
|
540
|
-
expect(request).to receive(:refresh).exactly(3).times
|
|
696
|
+
request = vra_request(status: nil)
|
|
697
|
+
allow(request).to receive(:completed?).and_return(false, false, true)
|
|
541
698
|
|
|
542
699
|
driver.wait_for_request(request)
|
|
700
|
+
|
|
701
|
+
expect(request).to have_received(:refresh).exactly(3).times
|
|
543
702
|
end
|
|
544
703
|
end
|
|
545
704
|
|
|
546
705
|
context "when the request is completed on the first loop" do
|
|
547
706
|
it "only refreshes the request 1 time" do
|
|
548
|
-
request =
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
707
|
+
request = vra_request(status: nil)
|
|
708
|
+
|
|
709
|
+
driver.wait_for_request(request)
|
|
710
|
+
|
|
711
|
+
expect(request).to have_received(:refresh).once
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
it "reports a status once, not on every poll" do
|
|
716
|
+
request = vra_request(status: "IN_PROGRESS")
|
|
717
|
+
allow(request).to receive(:completed?).and_return(false, false, true)
|
|
718
|
+
allow(driver).to receive(:info)
|
|
719
|
+
|
|
720
|
+
driver.wait_for_request(request)
|
|
721
|
+
|
|
722
|
+
expect(driver).to have_received(:info).with("Current request status: IN_PROGRESS").once
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
context "with a slower refresh rate configured" do
|
|
726
|
+
let(:config) { super().merge(request_refresh_rate: 10) }
|
|
727
|
+
|
|
728
|
+
it "waits that long between polls" do
|
|
729
|
+
request = vra_request(status: nil)
|
|
730
|
+
allow(request).to receive(:completed?).and_return(false, true)
|
|
731
|
+
expect(driver).to receive(:sleep).with(10)
|
|
552
732
|
|
|
553
733
|
driver.wait_for_request(request)
|
|
554
734
|
end
|
|
@@ -556,16 +736,19 @@ describe Kitchen::Driver::Vra do
|
|
|
556
736
|
|
|
557
737
|
context "when the timeout is exceeded" do
|
|
558
738
|
it "prints a warning and exits" do
|
|
559
|
-
request = double("request")
|
|
560
739
|
allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
|
|
561
|
-
|
|
740
|
+
allow(driver).to receive(:error)
|
|
741
|
+
|
|
742
|
+
expect { driver.wait_for_request(vra_request) }.to raise_error(Timeout::Error)
|
|
743
|
+
expect(driver).to have_received(:error).with(/did not complete in 600 seconds/)
|
|
562
744
|
end
|
|
563
745
|
end
|
|
564
746
|
|
|
565
747
|
context "when a non-timeout exception is raised" do
|
|
566
748
|
it "raises the original exception" do
|
|
567
|
-
request =
|
|
749
|
+
request = vra_request
|
|
568
750
|
allow(request).to receive(:refresh).and_raise(RuntimeError)
|
|
751
|
+
|
|
569
752
|
expect { driver.wait_for_request(request) }.to raise_error(RuntimeError)
|
|
570
753
|
end
|
|
571
754
|
end
|
|
@@ -583,6 +766,13 @@ describe Kitchen::Driver::Vra do
|
|
|
583
766
|
|
|
584
767
|
after { FileUtils.remove_entry(cache_dir) }
|
|
585
768
|
|
|
769
|
+
# A second driver reading the cache the first one wrote, which is what the
|
|
770
|
+
# next `kitchen` run is.
|
|
771
|
+
def reader(overrides = {})
|
|
772
|
+
described_class.new(config.merge(overrides).reject { |k, _v| %i{username password}.include?(k) })
|
|
773
|
+
.tap { |driver| allow(driver).to receive(:instance).and_return(instance) }
|
|
774
|
+
end
|
|
775
|
+
|
|
586
776
|
describe "#c_save" do
|
|
587
777
|
it "creates the cache file" do
|
|
588
778
|
driver.c_save
|
|
@@ -602,18 +792,29 @@ describe Kitchen::Driver::Vra do
|
|
|
602
792
|
driver.c_save
|
|
603
793
|
expect(File.read(cache_file)).not_to include("mypassword")
|
|
604
794
|
end
|
|
795
|
+
|
|
796
|
+
it "does not write the username in the clear either" do
|
|
797
|
+
driver.c_save
|
|
798
|
+
expect(File.read(cache_file)).not_to include("myuser")
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
it "reports a cache it could not write rather than failing the run" do
|
|
802
|
+
allow(FileUtils).to receive(:mkdir_p).and_raise(Errno::EACCES)
|
|
803
|
+
expect(driver).to receive(:warn).with(/Unable to save credentials/)
|
|
804
|
+
|
|
805
|
+
expect { driver.c_save }.not_to raise_error
|
|
806
|
+
end
|
|
605
807
|
end
|
|
606
808
|
|
|
607
809
|
describe "#c_load" do
|
|
608
810
|
it "restores credentials written by #c_save" do
|
|
609
811
|
driver.c_save
|
|
610
812
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
reader.c_load
|
|
813
|
+
loader = reader
|
|
814
|
+
loader.c_load
|
|
614
815
|
|
|
615
|
-
expect(
|
|
616
|
-
expect(
|
|
816
|
+
expect(loader[:username]).to eq("myuser")
|
|
817
|
+
expect(loader[:password]).to eq("mypassword")
|
|
617
818
|
end
|
|
618
819
|
|
|
619
820
|
it "does nothing when no cache file exists" do
|
|
@@ -624,9 +825,7 @@ describe Kitchen::Driver::Vra do
|
|
|
624
825
|
it "refuses a cache written against a different base_url" do
|
|
625
826
|
driver.c_save
|
|
626
827
|
|
|
627
|
-
other =
|
|
628
|
-
.reject { |k, _v| %i{username password}.include?(k) })
|
|
629
|
-
allow(other).to receive(:instance).and_return(instance)
|
|
828
|
+
other = reader(base_url: "https://other.corp.local")
|
|
630
829
|
expect(other).to receive(:warn).with(/Failed to load cached credentials/)
|
|
631
830
|
other.c_load
|
|
632
831
|
|
|
@@ -638,30 +837,97 @@ describe Kitchen::Driver::Vra do
|
|
|
638
837
|
driver.c_save
|
|
639
838
|
File.write(cache_file, File.read(cache_file).succ)
|
|
640
839
|
|
|
641
|
-
loader =
|
|
642
|
-
allow(loader).to receive(:instance).and_return(instance)
|
|
840
|
+
loader = reader
|
|
643
841
|
expect(loader).to receive(:warn).with(/Failed to load cached credentials/)
|
|
644
842
|
loader.c_load
|
|
645
843
|
|
|
646
844
|
expect(loader[:username]).to be_nil
|
|
647
845
|
end
|
|
846
|
+
|
|
847
|
+
it "refuses a cache written in a layout it does not recognize" do
|
|
848
|
+
FileUtils.mkdir_p(File.dirname(cache_file))
|
|
849
|
+
File.write(cache_file, "v0:whatever")
|
|
850
|
+
|
|
851
|
+
loader = reader
|
|
852
|
+
expect(loader).to receive(:warn).with(/unrecognized cache format/)
|
|
853
|
+
loader.c_load
|
|
854
|
+
|
|
855
|
+
expect(loader[:username]).to be_nil
|
|
856
|
+
end
|
|
648
857
|
end
|
|
649
858
|
|
|
650
859
|
describe "#check_config" do
|
|
651
|
-
it "
|
|
652
|
-
driver.
|
|
860
|
+
it "keeps the credentials it was configured with" do
|
|
861
|
+
allow(driver).to receive(:ask)
|
|
862
|
+
|
|
863
|
+
driver.check_config
|
|
864
|
+
|
|
865
|
+
expect(driver[:username]).to eq("myuser")
|
|
866
|
+
expect(driver[:password]).to eq("mypassword")
|
|
867
|
+
expect(driver).not_to have_received(:ask)
|
|
868
|
+
end
|
|
653
869
|
|
|
654
|
-
|
|
655
|
-
allow(
|
|
656
|
-
allow(ENV).to receive(:[]).and_call_original
|
|
657
|
-
allow(ENV).to receive(:[]).with("VRA_USER_NAME").and_return(nil)
|
|
658
|
-
allow(ENV).to receive(:[]).with("VRA_USER_PASSWORD").and_return(nil)
|
|
659
|
-
expect(reader).not_to receive(:ask)
|
|
870
|
+
it "asks again when told to, even though it has credentials" do
|
|
871
|
+
allow(driver).to receive(:ask).and_return("newuser", "newpass")
|
|
660
872
|
|
|
661
|
-
|
|
873
|
+
driver.check_config(true)
|
|
662
874
|
|
|
663
|
-
expect(
|
|
664
|
-
expect(
|
|
875
|
+
expect(driver[:username]).to eq("newuser")
|
|
876
|
+
expect(driver[:password]).to eq("newpass")
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
it "does not write a cache file unless asked to" do
|
|
880
|
+
driver.check_config
|
|
881
|
+
|
|
882
|
+
expect(File.exist?(cache_file)).to be false
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
context "when cache_credentials is enabled" do
|
|
886
|
+
let(:config) { super().merge(cache_credentials: true) }
|
|
887
|
+
|
|
888
|
+
it "writes the credentials out for the next run" do
|
|
889
|
+
driver.check_config
|
|
890
|
+
|
|
891
|
+
expect(File.exist?(cache_file)).to be true
|
|
892
|
+
end
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
context "when the config names no credentials" do
|
|
896
|
+
it "reads them from the environment" do
|
|
897
|
+
stub_const("ENV", ENV.to_hash.merge("VRA_USER_NAME" => "envuser",
|
|
898
|
+
"VRA_USER_PASSWORD" => "envpass"))
|
|
899
|
+
loader = reader
|
|
900
|
+
expect(loader).not_to receive(:ask)
|
|
901
|
+
|
|
902
|
+
loader.check_config
|
|
903
|
+
|
|
904
|
+
expect(loader[:username]).to eq("envuser")
|
|
905
|
+
expect(loader[:password]).to eq("envpass")
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
it "uses the cache instead of prompting" do
|
|
909
|
+
driver.c_save
|
|
910
|
+
|
|
911
|
+
stub_const("ENV", ENV.to_hash.reject { |key, _v| key.start_with?("VRA_USER_") })
|
|
912
|
+
loader = reader
|
|
913
|
+
expect(loader).not_to receive(:ask)
|
|
914
|
+
|
|
915
|
+
loader.check_config
|
|
916
|
+
|
|
917
|
+
expect(loader[:username]).to eq("myuser")
|
|
918
|
+
expect(loader[:password]).to eq("mypassword")
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
it "prompts as a last resort" do
|
|
922
|
+
stub_const("ENV", ENV.to_hash.reject { |key, _v| key.start_with?("VRA_USER_") })
|
|
923
|
+
loader = reader
|
|
924
|
+
allow(loader).to receive(:ask).and_return("askeduser", "askedpass")
|
|
925
|
+
|
|
926
|
+
loader.check_config
|
|
927
|
+
|
|
928
|
+
expect(loader[:username]).to eq("askeduser")
|
|
929
|
+
expect(loader[:password]).to eq("askedpass")
|
|
930
|
+
end
|
|
665
931
|
end
|
|
666
932
|
end
|
|
667
933
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-vra
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.4.
|
|
4
|
+
version: 3.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chef Community Tools Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: test-kitchen
|
|
@@ -97,13 +97,14 @@ files:
|
|
|
97
97
|
- ".gitignore"
|
|
98
98
|
- ".markdownlint.yaml"
|
|
99
99
|
- ".release-please-manifest.json"
|
|
100
|
+
- ".rspec"
|
|
100
101
|
- ".rubocop.yml"
|
|
101
102
|
- ".yamllint"
|
|
102
103
|
- ".yardopts"
|
|
103
104
|
- CHANGELOG.md
|
|
104
105
|
- CONTRIBUTING.md
|
|
105
106
|
- Gemfile
|
|
106
|
-
- LICENSE
|
|
107
|
+
- LICENSE
|
|
107
108
|
- README.md
|
|
108
109
|
- Rakefile
|
|
109
110
|
- kitchen-vra.gemspec
|
|
@@ -112,6 +113,7 @@ files:
|
|
|
112
113
|
- release-please-config.json
|
|
113
114
|
- renovate.json
|
|
114
115
|
- spec/spec_helper.rb
|
|
116
|
+
- spec/support/vra_api.rb
|
|
115
117
|
- spec/vra_spec.rb
|
|
116
118
|
homepage: https://github.com/test-kitchen/kitchen-vra
|
|
117
119
|
licenses:
|
/data/{LICENSE.txt → LICENSE}
RENAMED
|
File without changes
|