vra-restapi 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
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