vmware-vra 1.0.0.rc1

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