vmware-vra 1.0.0.rc1

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/lib/vra.rb ADDED
@@ -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/catalog'
20
+ require 'vra/catalog_item'
21
+ require 'vra/catalog_request'
22
+ require 'vra/client'
23
+ require 'vra/exceptions'
24
+ require 'vra/request'
25
+ require 'vra/request_parameters'
26
+ require 'vra/requests'
27
+ require 'vra/resource'
28
+ require 'vra/resources'
29
+ require 'vra/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,82 @@
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 'ffi_yajl'
20
+
21
+ module Vra
22
+ class CatalogItem
23
+ attr_reader :id, :client
24
+ def initialize(client, opts)
25
+ @client = client
26
+ @id = opts[:id]
27
+ @catalog_item_data = opts[:data]
28
+
29
+ if @id.nil? && @catalog_item_data.nil?
30
+ raise ArgumentError, 'must supply an id or a catalog item data hash'
31
+ end
32
+
33
+ if ! @id.nil? && ! @catalog_item_data.nil?
34
+ raise ArgumentError, 'must supply an id OR a catalog item data hash, not both'
35
+ end
36
+
37
+ if @catalog_item_data.nil?
38
+ fetch_catalog_item
39
+ else
40
+ @id = @catalog_item_data['id']
41
+ end
42
+ end
43
+
44
+ def fetch_catalog_item
45
+ @catalog_item_data = FFI_Yajl::Parser.parse(client.http_get!("/catalog-service/api/consumer/catalogItems/#{id}"))
46
+ rescue Vra::Exception::HTTPNotFound
47
+ raise Vra::Exception::NotFound, "catalog ID #{id} does not exist"
48
+ end
49
+
50
+ def name
51
+ @catalog_item_data['name']
52
+ end
53
+
54
+ def description
55
+ @catalog_item_data['description']
56
+ end
57
+
58
+ def status
59
+ @catalog_item_data['status']
60
+ end
61
+
62
+ def tenant_id
63
+ @catalog_item_data['organization']['tenantRef']
64
+ end
65
+
66
+ def tenant_name
67
+ @catalog_item_data['organization']['tenantLabel']
68
+ end
69
+
70
+ def subtenant_id
71
+ @catalog_item_data['organization']['subtenantRef']
72
+ end
73
+
74
+ def subtenant_name
75
+ @catalog_item_data['organization']['subtenantLabel']
76
+ end
77
+
78
+ def blueprint_id
79
+ @catalog_item_data['providerBinding']['bindingId']
80
+ end
81
+ end
82
+ 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
data/lib/vra/client.rb ADDED
@@ -0,0 +1,207 @@
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 'ffi_yajl'
20
+ require 'rest-client'
21
+
22
+ module Vra
23
+ # rubocop:disable ClassLength
24
+ class Client
25
+ attr_accessor :bearer_token
26
+
27
+ def initialize(opts)
28
+ @base_url = opts[:base_url]
29
+ @username = opts[:username]
30
+ @password = opts[:password]
31
+ @tenant = opts[:tenant]
32
+ @verify_ssl = opts.fetch(:verify_ssl, true)
33
+ @bearer_token = nil
34
+
35
+ validate_client_options!
36
+ end
37
+
38
+ #########################
39
+ #
40
+ # methods to other classes
41
+ #
42
+
43
+ def catalog
44
+ Vra::Catalog.new(self)
45
+ end
46
+
47
+ def requests(*args)
48
+ Vra::Requests.new(self, *args)
49
+ end
50
+
51
+ def resources(*args)
52
+ Vra::Resources.new(self, *args)
53
+ end
54
+
55
+ #########################
56
+ #
57
+ # client methods
58
+ #
59
+
60
+ def bearer_token_request_body
61
+ {
62
+ 'username' => @username,
63
+ 'password' => @password,
64
+ 'tenant' => @tenant
65
+ }
66
+ end
67
+
68
+ def request_headers
69
+ headers = {}
70
+ headers['Accept'] = 'application/json'
71
+ headers['Content-Type'] = 'application/json'
72
+ headers['Authorization'] = "Bearer #{@bearer_token}" unless @bearer_token.nil?
73
+ headers
74
+ end
75
+
76
+ def authorize!
77
+ generate_bearer_token unless authorized?
78
+
79
+ raise Vra::Exception::Unauthorized, 'Unable to authorize against vRA' unless authorized?
80
+ end
81
+
82
+ def authorized?
83
+ return false if @bearer_token.nil?
84
+
85
+ response = http_head("/identity/api/tokens/#{@bearer_token}", :skip_auth)
86
+ if response.code == 204
87
+ true
88
+ else
89
+ false
90
+ end
91
+ end
92
+
93
+ def generate_bearer_token
94
+ @bearer_token = nil
95
+ validate_client_options!
96
+
97
+ response = http_post('/identity/api/tokens', bearer_token_request_body.to_json, :skip_auth)
98
+ if response.code != 200
99
+ raise Vra::Exception::Unauthorized, "Unable to get bearer token: #{response.body}"
100
+ end
101
+
102
+ @bearer_token = FFI_Yajl::Parser.parse(response.body)['id']
103
+ end
104
+
105
+ def full_url(path)
106
+ "#{@base_url}#{path}"
107
+ end
108
+
109
+ def http_head(path, skip_auth=nil)
110
+ authorize! unless skip_auth
111
+
112
+ response = RestClient::Request.execute(method: :head,
113
+ url: full_url(path),
114
+ headers: request_headers,
115
+ verify_ssl: @verify_ssl)
116
+ rescue => e
117
+ raise Vra::Exception::HTTPError, "head #{path} failed: #{e.class}: #{e.message}"
118
+ else
119
+ response
120
+ end
121
+
122
+ def http_get(path, skip_auth=nil)
123
+ authorize! unless skip_auth
124
+
125
+ response = RestClient::Request.execute(method: :get,
126
+ url: full_url(path),
127
+ headers: request_headers,
128
+ verify_ssl: @verify_ssl)
129
+ rescue => e
130
+ raise_http_exception(e, path)
131
+ else
132
+ response
133
+ end
134
+
135
+ def http_get!(path)
136
+ response = http_get(path)
137
+ response.body
138
+ end
139
+
140
+ def http_get_paginated_array!(path, limit=20)
141
+ items = []
142
+ page = 1
143
+ base_path = path + "?limit=#{limit}"
144
+
145
+ loop do
146
+ response = FFI_Yajl::Parser.parse(http_get!("#{base_path}&page=#{page}"))
147
+ items += response['content']
148
+
149
+ break if page >= response['metadata']['totalPages']
150
+ page += 1
151
+ end
152
+
153
+ items
154
+ end
155
+
156
+ def http_post(path, payload, skip_auth=nil)
157
+ authorize! unless skip_auth
158
+
159
+ response = RestClient::Request.execute(method: :post,
160
+ url: full_url(path),
161
+ headers: request_headers,
162
+ payload: payload,
163
+ verify_ssl: @verify_ssl)
164
+ rescue => e
165
+ raise_http_exception(e, path)
166
+ else
167
+ response
168
+ end
169
+
170
+ def http_post!(path, payload)
171
+ response = http_post(path, payload)
172
+ response.body
173
+ end
174
+
175
+ def raise_http_exception(caught_exception, path)
176
+ raise unless caught_exception.respond_to?(:http_code)
177
+
178
+ if caught_exception.http_code == 404
179
+ klass = Vra::Exception::HTTPNotFound
180
+ else
181
+ klass = Vra::Exception::HTTPError
182
+ end
183
+
184
+ exception = klass.new(code: caught_exception.http_code,
185
+ body: caught_exception.response,
186
+ klass: caught_exception.class,
187
+ path: path)
188
+
189
+ message = exception.errors.empty? ? caught_exception.message : exception.errors.join(', ')
190
+ raise exception, message
191
+ end
192
+
193
+ def validate_client_options!
194
+ raise ArgumentError, 'Username and password are required' if @username.nil? || @password.nil?
195
+ raise ArgumentError, 'A tenant is required' if @tenant.nil?
196
+ raise ArgumentError, 'A base URL is required' if @base_url.nil?
197
+ raise ArgumentError, "Base URL #{@base_url} is not a valid URI." unless valid_uri?(@base_url)
198
+ end
199
+
200
+ def valid_uri?(uri)
201
+ uri = URI.parse(uri)
202
+ uri.is_a?(URI::HTTP)
203
+ rescue URI::InvalidURIError
204
+ false
205
+ end
206
+ end
207
+ end