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.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new(:style)
7
+
8
+ task default: [ :spec, :style ]
@@ -0,0 +1,29 @@
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 'vra-restapi/catalog'
20
+ require 'vra-restapi/catalog_item'
21
+ require 'vra-restapi/catalog_request'
22
+ require 'vra-restapi/client'
23
+ require 'vra-restapi/exceptions'
24
+ require 'vra-restapi/request'
25
+ require 'vra-restapi/request_parameters'
26
+ require 'vra-restapi/requests'
27
+ require 'vra-restapi/resource'
28
+ require 'vra-restapi/resources'
29
+ require 'vra-restapi/version'
@@ -0,0 +1,41 @@
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 Catalog
21
+ attr_reader :client
22
+
23
+ def initialize(client)
24
+ @client = client
25
+ end
26
+
27
+ def all_items
28
+ client.http_get_paginated_array!('/catalog-service/api/consumer/catalogItems')
29
+ .map! { |x| Vra::CatalogItem.new(client, data: x) }
30
+ end
31
+
32
+ def entitled_items
33
+ client.http_get_paginated_array!('/catalog-service/api/consumer/entitledCatalogItems')
34
+ .map! { |x| Vra::CatalogItem.new(client, data: x['catalogItem']) }
35
+ end
36
+
37
+ def request(*args)
38
+ Vra::CatalogRequest.new(@client, *args)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,81 @@
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
+ require 'json'
19
+
20
+ module Vra
21
+ class CatalogItem
22
+ attr_reader :id, :client
23
+ def initialize(client, opts)
24
+ @client = client
25
+ @id = opts[:id]
26
+ @catalog_item_data = opts[:data]
27
+
28
+ if @id.nil? && @catalog_item_data.nil?
29
+ raise ArgumentError, 'must supply an id or a catalog item data hash'
30
+ end
31
+
32
+ if ! @id.nil? && ! @catalog_item_data.nil?
33
+ raise ArgumentError, 'must supply an id OR a catalog item data hash, not both'
34
+ end
35
+
36
+ if @catalog_item_data.nil?
37
+ fetch_catalog_item
38
+ else
39
+ @id = @catalog_item_data['id']
40
+ end
41
+ end
42
+
43
+ def fetch_catalog_item
44
+ @catalog_item_data = JSON.parse(client.http_get!("/catalog-service/api/consumer/catalogItems/#{id}"))
45
+ rescue Vra::Exception::HTTPNotFound
46
+ raise Vra::Exception::NotFound, "catalog ID #{id} does not exist"
47
+ end
48
+
49
+ def name
50
+ @catalog_item_data['name']
51
+ end
52
+
53
+ def description
54
+ @catalog_item_data['description']
55
+ end
56
+
57
+ def status
58
+ @catalog_item_data['status']
59
+ end
60
+
61
+ def tenant_id
62
+ @catalog_item_data['organization']['tenantRef']
63
+ end
64
+
65
+ def tenant_name
66
+ @catalog_item_data['organization']['tenantLabel']
67
+ end
68
+
69
+ def subtenant_id
70
+ @catalog_item_data['organization']['subtenantRef']
71
+ end
72
+
73
+ def subtenant_name
74
+ @catalog_item_data['organization']['subtenantLabel']
75
+ end
76
+
77
+ def blueprint_id
78
+ @catalog_item_data['providerBinding']['bindingId']
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,112 @@
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 CatalogRequest
21
+ attr_reader :catalog_id, :catalog_item, :client, :custom_fields
22
+ attr_writer :subtenant_id
23
+ attr_accessor :cpus, :memory, :requested_for, :lease_days, :notes
24
+
25
+ def initialize(client, catalog_id, opts={})
26
+ @client = client
27
+ @catalog_id = catalog_id
28
+ @cpus = opts[:cpus]
29
+ @memory = opts[:memory]
30
+ @requested_for = opts[:requested_for]
31
+ @lease_days = opts[:lease_days]
32
+ @notes = opts[:notes]
33
+ @subtenant_id = opts[:subtenant_id]
34
+ @additional_params = Vra::RequestParameters.new
35
+
36
+ @catalog_item = Vra::CatalogItem.new(client, id: catalog_id)
37
+ end
38
+
39
+ def set_parameter(key, type, value)
40
+ @additional_params.set(key, type, value)
41
+ end
42
+
43
+ def delete_parameter(key)
44
+ @additional_params.delete(key)
45
+ end
46
+
47
+ def parameters
48
+ @additional_params.all_entries
49
+ end
50
+
51
+ def subtenant_id
52
+ @subtenant_id || catalog_item.subtenant_id
53
+ end
54
+
55
+ def validate_params!
56
+ missing_params = []
57
+ [ :catalog_id, :cpus, :memory, :requested_for, :subtenant_id ].each do |param|
58
+ missing_params << param.to_s if send(param).nil?
59
+ end
60
+
61
+ raise ArgumentError, "Unable to submit request, required param(s) missing => #{missing_params.join(', ')}" unless missing_params.empty?
62
+ end
63
+
64
+ def request_payload
65
+ payload = {
66
+ '@type' => 'CatalogItemRequest',
67
+ 'catalogItemRef' => {
68
+ 'id' => @catalog_id
69
+ },
70
+ 'organization' => {
71
+ 'tenantRef' => catalog_item.tenant_id,
72
+ 'subtenantRef' => subtenant_id
73
+ },
74
+ 'requestedFor' => @requested_for,
75
+ 'state' => 'SUBMITTED',
76
+ 'requestNumber' => 0,
77
+ 'requestData' => {
78
+ 'entries' => [
79
+ Vra::RequestParameter.new('provider-blueprintId', 'string', catalog_item.blueprint_id).to_h,
80
+ Vra::RequestParameter.new('provider-provisioningGroupId', 'string', subtenant_id).to_h,
81
+ Vra::RequestParameter.new('requestedFor', 'string', @requested_for).to_h,
82
+ Vra::RequestParameter.new('provider-VirtualMachine.CPU.Count', 'integer', @cpus).to_h,
83
+ Vra::RequestParameter.new('provider-VirtualMachine.Memory.Size', 'integer', @memory).to_h,
84
+ Vra::RequestParameter.new('provider-VirtualMachine.LeaseDays', 'integer', @lease_days).to_h,
85
+ Vra::RequestParameter.new('provider-__Notes', 'string', @notes).to_h
86
+ ]
87
+ }
88
+ }
89
+
90
+ parameters.each do |entry|
91
+ payload['requestData']['entries'] << entry.to_h
92
+ end
93
+
94
+ payload
95
+ end
96
+
97
+ def submit
98
+ validate_params!
99
+
100
+ begin
101
+ response = client.http_post('/catalog-service/api/consumer/requests', request_payload.to_json)
102
+ rescue Vra::Exception::HTTPError => e
103
+ raise Vra::Exception::RequestError, "Unable to submit request: #{e.errors.join(', ')}"
104
+ rescue
105
+ raise
106
+ end
107
+
108
+ request_id = response.headers[:location].split('/')[-1]
109
+ Vra::Request.new(client, request_id)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,223 @@
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 'json'
20
+ require 'rest-client'
21
+ require 'passwordmasker'
22
+
23
+ module Vra
24
+ # rubocop:disable ClassLength
25
+ class Client
26
+ attr_accessor :page_size
27
+
28
+ def initialize(opts)
29
+ @base_url = opts[:base_url]
30
+ @username = opts[:username]
31
+ @password = PasswordMasker.new(opts[:password])
32
+ @tenant = opts[:tenant]
33
+ @verify_ssl = opts.fetch(:verify_ssl, true)
34
+ @bearer_token = PasswordMasker.new(nil)
35
+ @page_size = opts.fetch(:page_size, 20)
36
+
37
+ validate_client_options!
38
+ end
39
+
40
+ #########################
41
+ #
42
+ # methods to other classes
43
+ #
44
+
45
+ def catalog
46
+ Vra::Catalog.new(self)
47
+ end
48
+
49
+ def requests(*args)
50
+ Vra::Requests.new(self, *args)
51
+ end
52
+
53
+ def resources(*args)
54
+ Vra::Resources.new(self, *args)
55
+ end
56
+
57
+ #########################
58
+ #
59
+ # client methods
60
+ #
61
+
62
+ def bearer_token
63
+ @bearer_token.value
64
+ end
65
+
66
+ def bearer_token=(value)
67
+ @bearer_token.value = value
68
+ end
69
+
70
+ def bearer_token_request_body
71
+ {
72
+ 'username' => @username,
73
+ 'password' => @password.value,
74
+ 'tenant' => @tenant
75
+ }
76
+ end
77
+
78
+ def request_headers
79
+ headers = {}
80
+ headers['Accept'] = 'application/json'
81
+ headers['Content-Type'] = 'application/json'
82
+ headers['Authorization'] = "Bearer #{@bearer_token.value}" unless @bearer_token.value.nil?
83
+ headers
84
+ end
85
+
86
+ def authorize!
87
+ generate_bearer_token unless authorized?
88
+
89
+ raise Vra::Exception::Unauthorized, 'Unable to authorize against vRA' unless authorized?
90
+ end
91
+
92
+ def authorized?
93
+ return false if @bearer_token.value.nil?
94
+
95
+ response = http_head("/identity/api/tokens/#{@bearer_token.value}", :skip_auth)
96
+ if response.code == 204
97
+ true
98
+ else
99
+ false
100
+ end
101
+ end
102
+
103
+ def generate_bearer_token
104
+ @bearer_token.value = nil
105
+ validate_client_options!
106
+
107
+ response = http_post('/identity/api/tokens', bearer_token_request_body.to_json, :skip_auth)
108
+ if response.code != 200
109
+ raise Vra::Exception::Unauthorized, "Unable to get bearer token: #{response.body}"
110
+ end
111
+
112
+ @bearer_token.value = JSON.parse(response.body)['id']
113
+ end
114
+
115
+ def full_url(path)
116
+ "#{@base_url}#{path}"
117
+ end
118
+
119
+ def http_head(path, skip_auth=nil)
120
+ authorize! unless skip_auth
121
+
122
+ response = RestClient::Request.execute(method: :head,
123
+ url: full_url(path),
124
+ headers: request_headers,
125
+ verify_ssl: @verify_ssl)
126
+ rescue => e
127
+ raise Vra::Exception::HTTPError, "head #{path} failed: #{e.class}: #{e.message}"
128
+ else
129
+ response
130
+ end
131
+
132
+ def http_get(path, skip_auth=nil)
133
+ authorize! unless skip_auth
134
+
135
+ response = RestClient::Request.execute(method: :get,
136
+ url: full_url(path),
137
+ headers: request_headers,
138
+ verify_ssl: @verify_ssl)
139
+ rescue => e
140
+ raise_http_exception(e, path)
141
+ else
142
+ response
143
+ end
144
+
145
+ def http_get!(path)
146
+ response = http_get(path)
147
+ response.body
148
+ end
149
+
150
+ def http_get_paginated_array!(path)
151
+ items = []
152
+ page = 1
153
+ base_path = path + "?limit=#{page_size}"
154
+
155
+ loop do
156
+ response = JSON.parse(http_get!("#{base_path}&page=#{page}"))
157
+ items += response['content']
158
+
159
+ break if page >= response['metadata']['totalPages']
160
+ page += 1
161
+ end
162
+
163
+ raise Vra::Exception::DuplicateItemsDetected,
164
+ 'Duplicate items were returned by the vRA API. ' \
165
+ 'Increase your page size to avoid this vRA API bug. ' \
166
+ 'See https://github.com/nwops/vra-restapi#pagination ' \
167
+ 'for more information.' if items.uniq!
168
+
169
+ items
170
+ end
171
+
172
+ def http_post(path, payload, skip_auth=nil)
173
+ authorize! unless skip_auth
174
+
175
+ response = RestClient::Request.execute(method: :post,
176
+ url: full_url(path),
177
+ headers: request_headers,
178
+ payload: payload,
179
+ verify_ssl: @verify_ssl)
180
+ rescue => e
181
+ raise_http_exception(e, path)
182
+ else
183
+ response
184
+ end
185
+
186
+ def http_post!(path, payload)
187
+ response = http_post(path, payload)
188
+ response.body
189
+ end
190
+
191
+ def raise_http_exception(caught_exception, path)
192
+ raise unless caught_exception.respond_to?(:http_code)
193
+
194
+ klass = if caught_exception.http_code == 404
195
+ Vra::Exception::HTTPNotFound
196
+ else
197
+ Vra::Exception::HTTPError
198
+ end
199
+
200
+ exception = klass.new(code: caught_exception.http_code,
201
+ body: caught_exception.response,
202
+ klass: caught_exception.class,
203
+ path: path)
204
+
205
+ message = exception.errors.empty? ? caught_exception.message : exception.errors.join(', ')
206
+ raise exception, message
207
+ end
208
+
209
+ def validate_client_options!
210
+ raise ArgumentError, 'Username and password are required' if @username.nil? || @password.value.nil?
211
+ raise ArgumentError, 'A tenant is required' if @tenant.nil?
212
+ raise ArgumentError, 'A base URL is required' if @base_url.nil?
213
+ raise ArgumentError, "Base URL #{@base_url} is not a valid URI." unless valid_uri?(@base_url)
214
+ end
215
+
216
+ def valid_uri?(uri)
217
+ uri = URI.parse(uri)
218
+ uri.is_a?(URI::HTTP)
219
+ rescue URI::InvalidURIError
220
+ false
221
+ end
222
+ end
223
+ end