vra-restapi 1.5.2
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 +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +14 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +202 -0
- data/README.md +220 -0
- data/Rakefile +8 -0
- data/lib/vra-restapi.rb +29 -0
- data/lib/vra-restapi/catalog.rb +41 -0
- data/lib/vra-restapi/catalog_item.rb +81 -0
- data/lib/vra-restapi/catalog_request.rb +112 -0
- data/lib/vra-restapi/client.rb +223 -0
- data/lib/vra-restapi/exceptions.rb +61 -0
- data/lib/vra-restapi/request.rb +97 -0
- data/lib/vra-restapi/request_parameters.rb +67 -0
- data/lib/vra-restapi/requests.rb +40 -0
- data/lib/vra-restapi/resource.rb +234 -0
- data/lib/vra-restapi/resources.rb +42 -0
- data/lib/vra-restapi/version.rb +21 -0
- data/spec/catalog_item_spec.rb +98 -0
- data/spec/catalog_request_spec.rb +124 -0
- data/spec/catalog_spec.rb +124 -0
- data/spec/client_spec.rb +481 -0
- data/spec/fixtures/resource/non_vm_resource.json +18 -0
- data/spec/fixtures/resource/vm_resource.json +251 -0
- data/spec/fixtures/resource/vm_resource_no_operations.json +101 -0
- data/spec/request_spec.rb +145 -0
- data/spec/requests_spec.rb +59 -0
- data/spec/resource_spec.rb +394 -0
- data/spec/resources_spec.rb +59 -0
- data/spec/spec_helper.rb +22 -0
- data/vra-restapi.gemspec +30 -0
- metadata +201 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module Vra
|
20
|
+
class Resources
|
21
|
+
attr_reader :client
|
22
|
+
|
23
|
+
def initialize(client)
|
24
|
+
@client = client
|
25
|
+
end
|
26
|
+
|
27
|
+
def all_resources
|
28
|
+
resources = []
|
29
|
+
|
30
|
+
items = client.http_get_paginated_array!('/catalog-service/api/consumer/resources')
|
31
|
+
items.each do |item|
|
32
|
+
resources << Vra::Resource.new(client, data: item)
|
33
|
+
end
|
34
|
+
|
35
|
+
resources
|
36
|
+
end
|
37
|
+
|
38
|
+
def by_id(id)
|
39
|
+
Vra::Resource.new(client, id: id)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module Vra
|
20
|
+
VERSION = '1.5.2'.freeze
|
21
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'spec_helper'
|
20
|
+
|
21
|
+
describe Vra::CatalogRequest do
|
22
|
+
let(:client) do
|
23
|
+
Vra::Client.new(username: 'user@corp.local',
|
24
|
+
password: 'password',
|
25
|
+
tenant: 'tenant',
|
26
|
+
base_url: 'https://vra.corp.local')
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:catalog_id) { '9e98042e-5443-4082-afd5-ab5a32939bbc' }
|
30
|
+
|
31
|
+
let(:catalog_item_payload) do
|
32
|
+
{
|
33
|
+
'@type' => 'CatalogItem',
|
34
|
+
'id' => '9e98042e-5443-4082-afd5-ab5a32939bbc',
|
35
|
+
'version' => 2,
|
36
|
+
'name' => 'CentOS 6.6',
|
37
|
+
'description' => 'Blueprint for deploying a CentOS Linux development server',
|
38
|
+
'status' => 'PUBLISHED',
|
39
|
+
'statusName' => 'Published',
|
40
|
+
'organization' => {
|
41
|
+
'tenantRef' => 'vsphere.local',
|
42
|
+
'tenantLabel' => 'vsphere.local',
|
43
|
+
'subtenantRef' => '962ab3f9-858c-4483-a49f-fa97392c314b',
|
44
|
+
'subtenantLabel' => 'catalog_subtenant'
|
45
|
+
},
|
46
|
+
'providerBinding' => {
|
47
|
+
'bindingId' => '33af5413-4f20-4b3b-8268-32edad434dfb',
|
48
|
+
'providerRef' => {
|
49
|
+
'id' => 'c3b2bc30-47b0-454f-b57d-df02a7356fe6',
|
50
|
+
'label' => 'iaas-service'
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#initialize' do
|
57
|
+
it 'raises an error if no ID or catalog item data have been provided' do
|
58
|
+
expect { Vra::CatalogItem.new }.to raise_error(ArgumentError)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'raises an error if an ID and catalog item data have both been provided' do
|
62
|
+
expect { Vra::CatalogItem.new(id: 123, data: 'foo') }.to raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when an ID is provided' do
|
66
|
+
it 'fetches the catalog_item record' do
|
67
|
+
catalog_item = Vra::CatalogItem.allocate
|
68
|
+
expect(catalog_item).to receive(:fetch_catalog_item)
|
69
|
+
catalog_item.send(:initialize, client, id: catalog_id)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when catalog item data is provided' do
|
74
|
+
it 'populates the ID correctly' do
|
75
|
+
catalog_item = Vra::Resource.new(client, data: catalog_item_payload)
|
76
|
+
expect(catalog_item.id).to eq catalog_id
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#fetch_catalog_item' do
|
82
|
+
context 'when the catalog item exists' do
|
83
|
+
let(:response) { double('response', code: 200, body: catalog_item_payload.to_json) }
|
84
|
+
|
85
|
+
it 'calls http_get against the catalog_service' do
|
86
|
+
expect(client).to receive(:http_get).with('/catalog-service/api/consumer/catalogItems/catalog-12345').and_return(response)
|
87
|
+
Vra::CatalogItem.new(client, id: 'catalog-12345')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when the catalog item does not exist' do
|
92
|
+
it 'raises an exception' do
|
93
|
+
allow(client).to receive(:http_get).with('/catalog-service/api/consumer/catalogItems/catalog-12345').and_raise(Vra::Exception::HTTPNotFound)
|
94
|
+
expect { Vra::CatalogItem.new(client, id: 'catalog-12345') }.to raise_error(Vra::Exception::NotFound)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'spec_helper'
|
20
|
+
|
21
|
+
describe Vra::CatalogRequest do
|
22
|
+
before(:each) do
|
23
|
+
catalog_item = double('catalog_item')
|
24
|
+
allow(catalog_item).to receive(:blueprint_id).and_return('catalog_blueprint')
|
25
|
+
allow(catalog_item).to receive(:tenant_id).and_return('catalog_tenant')
|
26
|
+
allow(catalog_item).to receive(:subtenant_id).and_return('catalog_subtenant')
|
27
|
+
allow(Vra::CatalogItem).to receive(:new).and_return(catalog_item)
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:client) do
|
31
|
+
Vra::Client.new(username: 'user@corp.local',
|
32
|
+
password: 'password',
|
33
|
+
tenant: 'tenant',
|
34
|
+
base_url: 'https://vra.corp.local')
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when no subtenant ID is provided' do
|
38
|
+
let(:request) do
|
39
|
+
client.catalog.request('catalog-12345',
|
40
|
+
cpus: 2,
|
41
|
+
memory: 1024,
|
42
|
+
lease_days: 15,
|
43
|
+
requested_for: 'tester@corp.local',
|
44
|
+
notes: 'test notes')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'uses the subtenant ID from the catalog item' do
|
48
|
+
expect(request.subtenant_id).to eq 'catalog_subtenant'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when subtenant is provided, and all shared tests' do
|
53
|
+
let(:request) do
|
54
|
+
client.catalog.request('catalog-12345',
|
55
|
+
cpus: 2,
|
56
|
+
memory: 1024,
|
57
|
+
lease_days: 15,
|
58
|
+
requested_for: 'tester@corp.local',
|
59
|
+
notes: 'test notes',
|
60
|
+
subtenant_id: 'user_subtenant')
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#initialize' do
|
64
|
+
it 'sets the appropriate instance vars' do
|
65
|
+
expect(request.catalog_id).to eq 'catalog-12345'
|
66
|
+
expect(request.cpus).to eq 2
|
67
|
+
expect(request.memory).to eq 1024
|
68
|
+
expect(request.lease_days).to eq 15
|
69
|
+
expect(request.requested_for).to eq 'tester@corp.local'
|
70
|
+
expect(request.notes).to eq 'test notes'
|
71
|
+
expect(request.subtenant_id).to eq 'user_subtenant'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#validate_params!' do
|
76
|
+
context 'when all required params are provided' do
|
77
|
+
it 'does not raise an exception' do
|
78
|
+
expect { request.validate_params! }.to_not raise_error
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when a required parameter is not provided' do
|
83
|
+
it 'raises an exception' do
|
84
|
+
request.cpus = nil
|
85
|
+
expect { request.validate_params! }.to raise_error(ArgumentError)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#request_payload' do
|
91
|
+
it 'properly handles additional parameters' do
|
92
|
+
request.set_parameter('param1', 'string', 'my string')
|
93
|
+
request.set_parameter('param2', 'integer', '2468')
|
94
|
+
|
95
|
+
payload = request.request_payload
|
96
|
+
param1 = payload['requestData']['entries'].find { |x| x['key'] == 'param1' }
|
97
|
+
param2 = payload['requestData']['entries'].find { |x| x['key'] == 'param2' }
|
98
|
+
|
99
|
+
expect(param1).to be_a(Hash)
|
100
|
+
expect(param2).to be_a(Hash)
|
101
|
+
expect(param1['value']['value']).to eq 'my string'
|
102
|
+
expect(param2['value']['value']).to eq 2468
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#submit' do
|
107
|
+
before do
|
108
|
+
allow(request).to receive(:request_payload).and_return({})
|
109
|
+
response = double('response', code: 200, headers: { location: '/requests/request-12345' })
|
110
|
+
allow(client).to receive(:http_post).with('/catalog-service/api/consumer/requests', '{}').and_return(response)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'calls http_post' do
|
114
|
+
expect(client).to receive(:http_post).with('/catalog-service/api/consumer/requests', '{}')
|
115
|
+
|
116
|
+
request.submit
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'returns a Vra::Request object' do
|
120
|
+
expect(request.submit).to be_an_instance_of(Vra::Request)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'spec_helper'
|
20
|
+
|
21
|
+
describe Vra::Catalog do
|
22
|
+
let(:client) do
|
23
|
+
Vra::Client.new(username: 'user@corp.local',
|
24
|
+
password: 'password',
|
25
|
+
tenant: 'tenant',
|
26
|
+
base_url: 'https://vra.corp.local')
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:catalog_item) do
|
30
|
+
{
|
31
|
+
'@type' => 'CatalogItem',
|
32
|
+
'id' => 'a9cd6148-6e0b-4a80-ac47-f5255c52b43d',
|
33
|
+
'version' => 2,
|
34
|
+
'name' => 'CentOS 6.6',
|
35
|
+
'description' => 'Blueprint for deploying a CentOS Linux development server',
|
36
|
+
'status' => 'PUBLISHED',
|
37
|
+
'statusName' => 'Published',
|
38
|
+
'organization' => {
|
39
|
+
'tenantRef' => 'vsphere.local',
|
40
|
+
'tenantLabel' => 'vsphere.local',
|
41
|
+
'subtenantRef' => nil,
|
42
|
+
'subtenantLabel' => nil
|
43
|
+
},
|
44
|
+
'providerBinding' => {
|
45
|
+
'bindingId' => '33af5413-4f20-4b3b-8268-32edad434dfb',
|
46
|
+
'providerRef' => {
|
47
|
+
'id' => 'c3b2bc30-47b0-454f-b57d-df02a7356fe6',
|
48
|
+
'label' => 'iaas-service'
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:entitled_catalog_item) do
|
55
|
+
{
|
56
|
+
'@type' => 'ConsumerEntitledCatalogItem',
|
57
|
+
'catalogItem' => {
|
58
|
+
'id' => 'd29efd6b-3cd6-4f8d-b1d8-da4ddd4e52b1',
|
59
|
+
'version' => 2,
|
60
|
+
'name' => 'WindowsServer2012',
|
61
|
+
'description' => 'Windows Server 2012 with the latest updates and patches.',
|
62
|
+
'status' => 'PUBLISHED',
|
63
|
+
'statusName' => 'Published',
|
64
|
+
'organization' => {
|
65
|
+
'tenantRef' => 'vsphere.local',
|
66
|
+
'tenantLabel' => 'vsphere.local',
|
67
|
+
'subtenantRef' => nil,
|
68
|
+
'subtenantLabel' => nil
|
69
|
+
},
|
70
|
+
'providerBinding' => {
|
71
|
+
'bindingId' => '59fd02a1-acca-4918-9d3d-2298d310caef',
|
72
|
+
'providerRef' => {
|
73
|
+
'id' => 'c3b2bc30-47b0-454f-b57d-df02a7356fe6',
|
74
|
+
'label' => 'iaas-service'
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#all_items' do
|
82
|
+
it 'calls the catalogItems endpoint' do
|
83
|
+
expect(client).to receive(:http_get_paginated_array!).with('/catalog-service/api/consumer/catalogItems')
|
84
|
+
.and_return([ catalog_item ])
|
85
|
+
|
86
|
+
client.catalog.all_items
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'returns a Vra::CatalogItem object' do
|
90
|
+
allow(client).to receive(:http_get_paginated_array!).with('/catalog-service/api/consumer/catalogItems')
|
91
|
+
.and_return([ catalog_item ])
|
92
|
+
|
93
|
+
items = client.catalog.all_items
|
94
|
+
|
95
|
+
expect(items.first).to be_an_instance_of(Vra::CatalogItem)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#entitled_items' do
|
100
|
+
it 'calls the entitledCatalogItems endpoint' do
|
101
|
+
expect(client).to receive(:http_get_paginated_array!).with('/catalog-service/api/consumer/entitledCatalogItems')
|
102
|
+
.and_return([ entitled_catalog_item ])
|
103
|
+
|
104
|
+
client.catalog.entitled_items
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'returns a Vra::CatalogItem object' do
|
108
|
+
allow(client).to receive(:http_get_paginated_array!).with('/catalog-service/api/consumer/entitledCatalogItems')
|
109
|
+
.and_return([ entitled_catalog_item ])
|
110
|
+
|
111
|
+
items = client.catalog.entitled_items
|
112
|
+
|
113
|
+
expect(items.first).to be_an_instance_of(Vra::CatalogItem)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '#request' do
|
118
|
+
it 'returns a new Vra::CatalogRequest object' do
|
119
|
+
allow(Vra::CatalogItem).to receive(:new)
|
120
|
+
request = client.catalog.request('blueprint-1', cpus: 2)
|
121
|
+
expect(request).to be_an_instance_of(Vra::CatalogRequest)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,481 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'spec_helper'
|
20
|
+
|
21
|
+
describe Vra::Client do
|
22
|
+
let(:client) do
|
23
|
+
Vra::Client.new(username: 'user@corp.local',
|
24
|
+
password: 'password',
|
25
|
+
tenant: 'tenant',
|
26
|
+
base_url: 'https://vra.corp.local')
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#initialize' do
|
30
|
+
it 'calls validate_client_options!' do
|
31
|
+
client = Vra::Client.allocate
|
32
|
+
expect(client).to receive(:validate_client_options!)
|
33
|
+
client.send(:initialize, username: 'user@corp.local',
|
34
|
+
password: 'password',
|
35
|
+
tenant: 'tenant',
|
36
|
+
base_url: 'https://vra.corp.local')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#bearer_token_request_body' do
|
41
|
+
it 'gets the correct password from the PasswordMasker object' do
|
42
|
+
expect(client.bearer_token_request_body['password']).to eq('password')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#request_headers' do
|
47
|
+
context 'when bearer token exists' do
|
48
|
+
it 'has an Authorization header' do
|
49
|
+
client.bearer_token = '12345'
|
50
|
+
expect(client.request_headers.key?('Authorization')).to be true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when bearer token does not exist' do
|
55
|
+
it 'has an Authorization header' do
|
56
|
+
expect(client.request_headers.key?('Authorization')).to be false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#authorize!' do
|
62
|
+
context 'when a token is not authorized or no token exists' do
|
63
|
+
it 'generates a token successfully' do
|
64
|
+
allow(client).to receive(:authorized?).twice.and_return(false, true)
|
65
|
+
expect(client).to receive(:generate_bearer_token)
|
66
|
+
|
67
|
+
client.authorize!
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'raises an exception if token generation fails' do
|
71
|
+
allow(client).to receive(:authorized?).and_return(false)
|
72
|
+
allow(client).to receive(:generate_bearer_token).and_raise(Vra::Exception::Unauthorized)
|
73
|
+
|
74
|
+
expect { client.authorize! }.to raise_error(Vra::Exception::Unauthorized)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'raises an exception if a generated token is unauthorized' do
|
78
|
+
allow(client).to receive(:authorized).twice.and_return(false, false)
|
79
|
+
allow(client).to receive(:generate_bearer_token)
|
80
|
+
|
81
|
+
expect { client.authorize! }.to raise_error(Vra::Exception::Unauthorized)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when a token is authorized' do
|
86
|
+
it 'does not generate a new token' do
|
87
|
+
allow(client).to receive(:authorized?).and_return(true)
|
88
|
+
expect(client).to_not receive(:generate_bearer_token)
|
89
|
+
|
90
|
+
client.authorize!
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#authorized?' do
|
96
|
+
context 'when token does not exist' do
|
97
|
+
it 'returns false' do
|
98
|
+
expect(client.authorized?).to be false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when token exists' do
|
103
|
+
before(:each) do
|
104
|
+
client.bearer_token = '12345'
|
105
|
+
end
|
106
|
+
|
107
|
+
url = '/identity/api/tokens/12345'
|
108
|
+
|
109
|
+
it 'returns true if the token validates successfully' do
|
110
|
+
response = double('response')
|
111
|
+
allow(response).to receive(:code).and_return(204)
|
112
|
+
allow(client).to receive(:http_head).with(url, :skip_auth).and_return(response)
|
113
|
+
|
114
|
+
expect(client.authorized?).to be true
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'returns false if the token validates unsuccessfully' do
|
118
|
+
response = double('response')
|
119
|
+
allow(response).to receive(:code).and_return(500)
|
120
|
+
allow(client).to receive(:http_head).with(url, :skip_auth).and_return(response)
|
121
|
+
|
122
|
+
expect(client.authorized?).to be false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '#generate_bearer_token' do
|
128
|
+
payload = {
|
129
|
+
'username' => 'user@corp.local',
|
130
|
+
'password' => 'password',
|
131
|
+
'tenant' => 'tenant'
|
132
|
+
}.to_json
|
133
|
+
|
134
|
+
it 'posts to the tokens API endpoint' do
|
135
|
+
response = double('response')
|
136
|
+
allow(response).to receive(:code).and_return(200)
|
137
|
+
allow(response).to receive(:body).and_return('{"id":"12345"}')
|
138
|
+
expect(client).to receive(:http_post).with('/identity/api/tokens',
|
139
|
+
payload,
|
140
|
+
:skip_auth).and_return(response)
|
141
|
+
|
142
|
+
client.generate_bearer_token
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'when token is generated successfully' do
|
146
|
+
it 'sets the token' do
|
147
|
+
response = double('response')
|
148
|
+
allow(response).to receive(:code).and_return(200)
|
149
|
+
allow(response).to receive(:body).and_return('{"id":"12345"}')
|
150
|
+
allow(client).to receive(:http_post).with('/identity/api/tokens',
|
151
|
+
payload,
|
152
|
+
:skip_auth).and_return(response)
|
153
|
+
|
154
|
+
client.generate_bearer_token
|
155
|
+
|
156
|
+
expect(client.bearer_token).to eq '12345'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'when token is not generated successfully' do
|
161
|
+
it 'raises an exception' do
|
162
|
+
response = double('response')
|
163
|
+
allow(response).to receive(:code).and_return(500)
|
164
|
+
allow(response).to receive(:body).and_return('error string')
|
165
|
+
allow(client).to receive(:http_post).with('/identity/api/tokens',
|
166
|
+
payload,
|
167
|
+
:skip_auth)
|
168
|
+
.and_return(response)
|
169
|
+
|
170
|
+
expect { client.generate_bearer_token }.to raise_error(Vra::Exception::Unauthorized)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe '#full_url' do
|
176
|
+
it 'returns a properly formatted url' do
|
177
|
+
expect(client.full_url('/mypath')).to eq 'https://vra.corp.local/mypath'
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe '#http_head' do
|
182
|
+
context 'when skip_auth is nil' do
|
183
|
+
it 'authorizes before proceeding' do
|
184
|
+
response = double('response')
|
185
|
+
allow(RestClient::Request).to receive(:execute).and_return(response)
|
186
|
+
expect(client).to receive(:authorize!)
|
187
|
+
|
188
|
+
client.http_head('/test')
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'when skip_auth is not nil' do
|
193
|
+
it 'does not authorize before proceeding' do
|
194
|
+
response = double('response')
|
195
|
+
allow(RestClient::Request).to receive(:execute).and_return(response)
|
196
|
+
expect(client).to_not receive(:authorize!)
|
197
|
+
|
198
|
+
client.http_head('/test', :skip_auth)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'calls RestClient::Request#execute' do
|
203
|
+
response = double('response')
|
204
|
+
path = '/test'
|
205
|
+
full_url = 'https://vra.corp.local/test'
|
206
|
+
headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
|
207
|
+
verify_ssl = true
|
208
|
+
|
209
|
+
allow(client).to receive(:authorize!)
|
210
|
+
expect(RestClient::Request).to receive(:execute).with(method: :head,
|
211
|
+
url: full_url,
|
212
|
+
headers: headers,
|
213
|
+
verify_ssl: verify_ssl)
|
214
|
+
.and_return(response)
|
215
|
+
|
216
|
+
client.http_head(path)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe '#http_get' do
|
221
|
+
context 'when skip_auth is nil' do
|
222
|
+
it 'authorizes before proceeding' do
|
223
|
+
response = double('response')
|
224
|
+
allow(RestClient::Request).to receive(:execute).and_return(response)
|
225
|
+
expect(client).to receive(:authorize!)
|
226
|
+
|
227
|
+
client.http_get('/test')
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context 'when skip_auth is not nil' do
|
232
|
+
it 'does not authorize before proceeding' do
|
233
|
+
response = double('response')
|
234
|
+
allow(RestClient::Request).to receive(:execute).and_return(response)
|
235
|
+
expect(client).to_not receive(:authorize!)
|
236
|
+
|
237
|
+
client.http_get('/test', :skip_auth)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'calls RestClient::Request#execute' do
|
242
|
+
response = double('response')
|
243
|
+
path = '/test'
|
244
|
+
full_url = 'https://vra.corp.local/test'
|
245
|
+
headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
|
246
|
+
verify_ssl = true
|
247
|
+
|
248
|
+
allow(client).to receive(:authorize!)
|
249
|
+
expect(RestClient::Request).to receive(:execute).with(method: :get,
|
250
|
+
url: full_url,
|
251
|
+
headers: headers,
|
252
|
+
verify_ssl: verify_ssl)
|
253
|
+
.and_return(response)
|
254
|
+
|
255
|
+
client.http_get(path)
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'calls raise_http_exception upon a RestClient error' do
|
259
|
+
allow(client).to receive(:authorize!)
|
260
|
+
allow(RestClient::Request).to receive(:execute).and_raise(RestClient::ResourceNotFound)
|
261
|
+
expect(client).to receive(:raise_http_exception)
|
262
|
+
|
263
|
+
client.http_get('/404')
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
describe '#http_get!' do
|
268
|
+
it 'returns the response body' do
|
269
|
+
response = double('response', body: 'body text')
|
270
|
+
allow(client).to receive(:http_get).with('/test').and_return(response)
|
271
|
+
|
272
|
+
expect(client.http_get!('/test')).to eq 'body text'
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe '#http_get_paginated_array!' do
|
277
|
+
it 'allows a limit override' do
|
278
|
+
client.page_size = 10
|
279
|
+
expect(client).to receive(:http_get!)
|
280
|
+
.with('/test?limit=10&page=1')
|
281
|
+
.and_return({ 'content' => [], 'metadata' => { 'totalPages' => 1 } }.to_json)
|
282
|
+
|
283
|
+
client.http_get_paginated_array!('/test')
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'only calls http_get! once when total pages is 0 (no items)' do
|
287
|
+
expect(client).to receive(:http_get!)
|
288
|
+
.once
|
289
|
+
.with('/test?limit=20&page=1')
|
290
|
+
.and_return({ 'content' => [], 'metadata' => { 'totalPages' => 0 } }.to_json)
|
291
|
+
|
292
|
+
client.http_get_paginated_array!('/test')
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'only calls http_get! once when total pages is 1' do
|
296
|
+
expect(client).to receive(:http_get!)
|
297
|
+
.once
|
298
|
+
.with('/test?limit=20&page=1')
|
299
|
+
.and_return({ 'content' => [], 'metadata' => { 'totalPages' => 1 } }.to_json)
|
300
|
+
|
301
|
+
client.http_get_paginated_array!('/test')
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'calls http_get! 3 times if there are 3 pages of response' do
|
305
|
+
expect(client).to receive(:http_get!)
|
306
|
+
.with('/test?limit=20&page=1')
|
307
|
+
.and_return({ 'content' => [], 'metadata' => { 'totalPages' => 3 } }.to_json)
|
308
|
+
expect(client).to receive(:http_get!)
|
309
|
+
.with('/test?limit=20&page=2')
|
310
|
+
.and_return({ 'content' => [], 'metadata' => { 'totalPages' => 3 } }.to_json)
|
311
|
+
expect(client).to receive(:http_get!)
|
312
|
+
.with('/test?limit=20&page=3')
|
313
|
+
.and_return({ 'content' => [], 'metadata' => { 'totalPages' => 3 } }.to_json)
|
314
|
+
|
315
|
+
client.http_get_paginated_array!('/test')
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'raises an exception if duplicate items are returned by the API' do
|
319
|
+
allow(client).to receive(:http_get!)
|
320
|
+
.with('/test?limit=20&page=1')
|
321
|
+
.and_return({ 'content' => [ 1, 2, 3, 1 ], 'metadata' => { 'totalPages' => 1 } }.to_json)
|
322
|
+
|
323
|
+
expect { client.http_get_paginated_array!('/test') }.to raise_error(Vra::Exception::DuplicateItemsDetected)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
describe '#http_post' do
|
328
|
+
context 'when skip_auth is nil' do
|
329
|
+
it 'authorizes before proceeding' do
|
330
|
+
response = double('response')
|
331
|
+
allow(RestClient::Request).to receive(:execute).and_return(response)
|
332
|
+
expect(client).to receive(:authorize!)
|
333
|
+
|
334
|
+
client.http_post('/test', 'some payload')
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
context 'when skip_auth is not nil' do
|
339
|
+
it 'does not authorize before proceeding' do
|
340
|
+
response = double('response')
|
341
|
+
allow(RestClient::Request).to receive(:execute).and_return(response)
|
342
|
+
expect(client).to_not receive(:authorize!)
|
343
|
+
|
344
|
+
client.http_post('/test', 'some payload', :skip_auth)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'calls RestClient::Request#execute' do
|
349
|
+
response = double('response')
|
350
|
+
path = '/test'
|
351
|
+
full_url = 'https://vra.corp.local/test'
|
352
|
+
headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
|
353
|
+
payload = 'some payload'
|
354
|
+
verify_ssl = true
|
355
|
+
|
356
|
+
allow(client).to receive(:authorize!)
|
357
|
+
expect(RestClient::Request).to receive(:execute).with(method: :post,
|
358
|
+
url: full_url,
|
359
|
+
headers: headers,
|
360
|
+
payload: payload,
|
361
|
+
verify_ssl: verify_ssl)
|
362
|
+
.and_return(response)
|
363
|
+
|
364
|
+
client.http_post(path, payload)
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'calls raise_http_exception upon a RestClient error' do
|
368
|
+
allow(client).to receive(:authorize!)
|
369
|
+
allow(RestClient::Request).to receive(:execute).and_raise(RestClient::ResourceNotFound)
|
370
|
+
expect(client).to receive(:raise_http_exception)
|
371
|
+
|
372
|
+
client.http_post('/404', 'test payload')
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
describe '#http_post!' do
|
377
|
+
it 'returns the response body' do
|
378
|
+
response = double('response', body: 'body text')
|
379
|
+
allow(client).to receive(:http_post).with('/test', 'test payload').and_return(response)
|
380
|
+
|
381
|
+
expect(client.http_post!('/test', 'test payload')).to eq 'body text'
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
describe '#raise_http_exception' do
|
386
|
+
context 'when a 404 is received' do
|
387
|
+
let(:exception) do
|
388
|
+
double('RestClient::ResourceNotFound',
|
389
|
+
http_code: 404,
|
390
|
+
message: 'Not Found',
|
391
|
+
response: '404 Not Found')
|
392
|
+
end
|
393
|
+
|
394
|
+
it 'raises a Vra::Exception::HTTPNotFound exception' do
|
395
|
+
expect { client.raise_http_exception(exception, '/test') }.to raise_error(Vra::Exception::HTTPNotFound)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
context 'when an unspecified http error is received' do
|
400
|
+
let(:exception) do
|
401
|
+
double('RestClient::BadRequest',
|
402
|
+
http_code: 400,
|
403
|
+
message: 'Bad Request',
|
404
|
+
response: '400 Bad Request')
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'raises a Vra::Exception::HTTPError exception' do
|
408
|
+
expect { client.raise_http_exception(exception, '/test') }.to raise_error(Vra::Exception::HTTPError)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
describe '#validate_client_options!' do
|
414
|
+
context 'when all required options are supplied' do
|
415
|
+
it 'does not raise an exception' do
|
416
|
+
expect { client.validate_client_options! }.not_to raise_error
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
context 'when username is missing' do
|
421
|
+
let(:client) do
|
422
|
+
Vra::Client.new(password: 'password',
|
423
|
+
tenant: 'tenant',
|
424
|
+
base_url: 'https://vra.corp.local')
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'raises an exception' do
|
428
|
+
expect { client.validate_client_options! }.to raise_error(ArgumentError)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
context 'when password is missing' do
|
433
|
+
let(:client) do
|
434
|
+
Vra::Client.new(username: 'username',
|
435
|
+
tenant: 'tenant',
|
436
|
+
base_url: 'https://vra.corp.local')
|
437
|
+
end
|
438
|
+
|
439
|
+
it 'raises an exception' do
|
440
|
+
expect { client.validate_client_options! }.to raise_error(ArgumentError)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
context 'when tenant is missing' do
|
445
|
+
let(:client) do
|
446
|
+
Vra::Client.new(username: 'username',
|
447
|
+
password: 'password',
|
448
|
+
base_url: 'https://vra.corp.local')
|
449
|
+
end
|
450
|
+
|
451
|
+
it 'raises an exception' do
|
452
|
+
expect { client.validate_client_options! }.to raise_error(ArgumentError)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
context 'when base URL is missing' do
|
457
|
+
let(:client) do
|
458
|
+
Vra::Client.new(username: 'username',
|
459
|
+
password: 'password',
|
460
|
+
tenant: 'tenant')
|
461
|
+
end
|
462
|
+
|
463
|
+
it 'raises an exception' do
|
464
|
+
expect { client.validate_client_options! }.to raise_error(ArgumentError)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
context 'when base URL is not a valid HTTP URL' do
|
469
|
+
let(:client) do
|
470
|
+
Vra::Client.new(username: 'username',
|
471
|
+
password: 'password',
|
472
|
+
tenant: 'tenant',
|
473
|
+
base_url: 'something-that-is-not-a-HTTP-URI')
|
474
|
+
end
|
475
|
+
|
476
|
+
it 'raises an exception' do
|
477
|
+
expect { client.validate_client_options! }.to raise_error(ArgumentError)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|