ovirt-engine-sdk 4.0.1 → 4.4.1

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.
@@ -1,548 +0,0 @@
1
- #
2
- # Copyright (c) 2015-2016 Red Hat, Inc.
3
- #
4
- # Licensed under the Apache License, Version 2.0 (the "License");
5
- # you may not use this file except in compliance with the License.
6
- # You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- # Unless required by applicable law or agreed to in writing, software
11
- # distributed under the License is distributed on an "AS IS" BASIS,
12
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- # See the License for the specific language governing permissions and
14
- # limitations under the License.
15
- #
16
-
17
- require 'curb'
18
- require 'json'
19
- require 'uri'
20
-
21
- module OvirtSDK4
22
-
23
- #
24
- # This class represents an HTTP request.
25
- #
26
- # @api private
27
- #
28
- class Request
29
- attr_accessor :method
30
- attr_accessor :path
31
- attr_accessor :query
32
- attr_accessor :headers
33
- attr_accessor :body
34
-
35
- #
36
- # Creates a new HTTP request.
37
- #
38
- def initialize(opts = {})
39
- self.method = opts[:method] || :GET
40
- self.path = opts[:path] || ''
41
- self.headers = opts[:headers] || {}
42
- self.query = opts[:query] || {}
43
- self.body = opts[:body]
44
- end
45
-
46
- end
47
-
48
- #
49
- # This class represents an HTTP response.
50
- #
51
- # @api private
52
- #
53
- class Response
54
- attr_accessor :body
55
- attr_accessor :code
56
- attr_accessor :headers
57
- attr_accessor :message
58
-
59
- #
60
- # Creates a new HTTP response.
61
- #
62
- def initialize(opts = {})
63
- self.body = opts[:body]
64
- self.code = opts[:code]
65
- self.headers = opts[:headers]
66
- self.message = opts[:message]
67
- end
68
- end
69
-
70
- #
71
- # This class is responsible for managing an HTTP connection to the engine server. It is intended as the entry
72
- # point for the SDK, and it provides access to the `system` service and, from there, to the rest of the services
73
- # provided by the API.
74
- #
75
- class Connection
76
-
77
- #
78
- # Creates a new connection to the API server.
79
- #
80
- # [source,ruby]
81
- # ----
82
- # connection = OvirtSDK4::Connection.new(
83
- # :url => 'https://engine.example.com/ovirt-engine/api',
84
- # :username => 'admin@internal',
85
- # :password => '...',
86
- # :ca_file => '/etc/pki/ovirt-engine/ca.pem',
87
- # )
88
- # ----
89
- #
90
- # @param opts [Hash] The options used to create the connection.
91
- #
92
- # @option opts [String] :url A string containing the base URL of the server, usually something like
93
- # `\https://server.example.com/ovirt-engine/api`.
94
- #
95
- # @option opts [String] :user The name of the user, something like `admin@internal`.
96
- #
97
- # @option opts [String] :password The password of the user.
98
- #
99
- # @options opts [String] :token The token used to authenticate. Optionally the caller can explicitly provide
100
- # the token, instead of the user name and password. If the token isn't provided then it will be automatically
101
- # created.
102
- #
103
- # @option opts [Boolean] :insecure (false) A boolean flag that indicates if the server TLS certificate and host
104
- # name should be checked.
105
- #
106
- # @option opts [String] :ca_file The name of a PEM file containing the trusted CA certificates. The certificate
107
- # presented by the server will be verified using these CA certificates. If not set then the system wide CA
108
- # certificates store is used.
109
- #
110
- # @option opts [Boolean] :debug (false) A boolean flag indicating if debug output should be generated. If the
111
- # values is `true` and the `log` parameter isn't `nil` then the data sent to and received from the server will be
112
- # written to the log. Be aware that user names and passwords will also be written, so handle with care.
113
- #
114
- # @option opts [Logger] :log The logger where the log messages will be written.
115
- #
116
- # @option opts [Boolean] :kerberos (false) A boolean flag indicating if Kerberos uthentication should be used
117
- # instead of the default basic authentication.
118
- #
119
- # @option opts [Integer] :timeout (0) The maximun total time to wait for the response, in seconds. A value of zero
120
- # (the default) means wait for ever. If the timeout expires before the response is received an exception will be
121
- # raised.
122
- #
123
- # @option opts [Boolean] :compress (false) A boolean flag indicating if the SDK should ask the server to send
124
- # compressed responses. Note that this is a hint for the server, and that it may return uncompressed data even
125
- # when this parameter is set to `true`.
126
- #
127
- def initialize(opts = {})
128
- # Get the values of the parameters and assign default values:
129
- @url = opts[:url]
130
- @username = opts[:username]
131
- @password = opts[:password]
132
- @token = opts[:token]
133
- @insecure = opts[:insecure] || false
134
- @ca_file = opts[:ca_file]
135
- @debug = opts[:debug] || false
136
- @log = opts[:log]
137
- @kerberos = opts[:kerberos] || false
138
- @timeout = opts[:timeout] || 0
139
- @compress = opts[:compress] || false
140
-
141
- # Check mandatory parameters:
142
- if url.nil?
143
- raise ArgumentError.new("The 'url' parameter is mandatory.")
144
- end
145
-
146
- # Save the URL:
147
- @url = URI(@url)
148
-
149
- # Create the cURL handle:
150
- @curl = Curl::Easy.new
151
-
152
- # Configure TLS parameters:
153
- if @url.scheme == 'https'
154
- if @insecure
155
- @curl.ssl_verify_peer = false
156
- @curl.ssl_verify_host = false
157
- elsif !@ca_file.nil?
158
- raise ArgumentError.new("The CA file '#{@ca_file}' doesn't exist.") unless ::File.file?(@ca_file)
159
- @curl.cacert = @ca_file
160
- end
161
- end
162
-
163
- # Configure the timeout:
164
- @curl.timeout = @timeout
165
-
166
- # Configure compression of responses (setting the value to a zero length string means accepting all the
167
- # compression types that libcurl supports):
168
- if @compress
169
- @curl.encoding = ''
170
- end
171
-
172
- # Configure debug mode:
173
- if @debug && @log
174
- @curl.verbose = true
175
- @curl.on_debug do |_, data|
176
- lines = data.gsub("\r\n", "\n").strip.split("\n")
177
- lines.each do |line|
178
- @log.debug(line)
179
- end
180
- end
181
- end
182
-
183
- end
184
-
185
- #
186
- # Returns the base URL of this connection.
187
- #
188
- # @return [String]
189
- #
190
- def url
191
- return @url
192
- end
193
-
194
- #
195
- # Returns a reference to the root of the services tree.
196
- #
197
- # @return [SystemService]
198
- #
199
- def system_service
200
- @system_service ||= SystemService.new(self, "")
201
- return @system_service
202
- end
203
-
204
- #
205
- # Returns a reference to the service corresponding to the given path. For example, if the `path` parameter
206
- # is `vms/123/diskattachments` then it will return a reference to the service that manages the disk
207
- # attachments for the virtual machine with identifier `123`.
208
- #
209
- # @param path [String] The path of the service, for example `vms/123/diskattachments`.
210
- # @return [Service]
211
- # @raise [Error] If there is no service corresponding to the given path.
212
- #
213
- def service(path)
214
- return system_service.service(path)
215
- end
216
-
217
- #
218
- # Sends an HTTP request and waits for the response.
219
- #
220
- # @param request [Request] The Request object containing the details of the HTTP request to send.
221
- # @return [Response] A request object containing the details of the HTTP response received.
222
- #
223
- # @api private
224
- #
225
- def send(request)
226
- # Check if we already have an SSO access token:
227
- @token ||= get_access_token
228
-
229
- # Build the URL:
230
- @curl.url = build_url({
231
- :path => request.path,
232
- :query => request.query,
233
- })
234
-
235
- # Add headers, avoiding those that have no value:
236
- @curl.headers.clear
237
- @curl.headers.merge!(request.headers)
238
- @curl.headers['User-Agent'] = "RubySDK/#{VERSION}"
239
- @curl.headers['Version'] = '4'
240
- @curl.headers['Content-Type'] = 'application/xml'
241
- @curl.headers['Accept'] = 'application/xml'
242
- @curl.headers['Authorization'] = "Bearer #{@token}"
243
-
244
- # Clear any data that may be in the buffers:
245
- @curl.post_body = nil
246
-
247
- # Send the request and wait for the response:
248
- case request.method
249
- when :DELETE
250
- @curl.http_delete
251
- when :GET
252
- @curl.http_get
253
- when :PUT
254
- @curl.http_put(request.body)
255
- when :HEAD
256
- @curl.http_head
257
- when :POST
258
- @curl.http_post(request.body)
259
- end
260
-
261
- # Return the response:
262
- response = Response.new
263
- response.body = @curl.body_str
264
- response.code = @curl.response_code
265
- return response
266
- end
267
-
268
- #
269
- # Obtains the access token from SSO to be used for bearer authentication.
270
- #
271
- # @return [String] The access token.
272
- #
273
- # @api private
274
- #
275
- def get_access_token
276
- # Build the URL and parameters required for the request:
277
- url, parameters = build_sso_auth_request
278
-
279
- # Send the response and wait for the request:
280
- response = get_sso_response(url, parameters)
281
-
282
- if response.is_a?(Array)
283
- response = response[0]
284
- end
285
-
286
- unless response['error'].nil?
287
- raise Error.new("Error during SSO authentication #{response['error_code']}: #{response['error']}")
288
- end
289
-
290
- response['access_token']
291
- end
292
-
293
- #
294
- # Revoke the SSO access token.
295
- #
296
- # @api private
297
- #
298
- def revoke_access_token
299
- # Build the URL and parameters required for the request:
300
- url, parameters = build_sso_revoke_request
301
-
302
- response = get_sso_response(url, parameters)
303
-
304
- if response.is_a?(Array)
305
- response = response[0]
306
- end
307
-
308
- unless response['error'].nil?
309
- raise Error.new("Error during SSO revoke #{response['error_code']}: #{response['error']}")
310
- end
311
- end
312
-
313
- #
314
- # Execute a get request to the SSO server and return the response.
315
- #
316
- # @param url [String] The URL of the SSO server.
317
- #
318
- # @param parameters [Hash] The parameters to send to the SSO server.
319
- #
320
- # @return [Hash] The JSON response.
321
- #
322
- # @api private
323
- #
324
- def get_sso_response(url, parameters)
325
- # Create the cURL handle for SSO:
326
- sso_curl = Curl::Easy.new
327
-
328
- # Configure the timeout:
329
- sso_curl.timeout = @timeout
330
-
331
- # Configure debug mode:
332
- if @debug && @log
333
- sso_curl.verbose = true
334
- sso_curl.on_debug do |_, data|
335
- lines = data.gsub("\r\n", "\n").strip.split("\n")
336
- lines.each do |line|
337
- @log.debug(line)
338
- end
339
- end
340
- end
341
-
342
- begin
343
- # Configure TLS parameters:
344
- if url.scheme == 'https'
345
- if @insecure
346
- sso_curl.ssl_verify_peer = false
347
- sso_curl.ssl_verify_host = false
348
- elsif !@ca_file.nil?
349
- raise ArgumentError.new("The CA file \"#{@ca_file}\" doesn't exist.") unless ::File.file?(@ca_file)
350
- sso_curl.cacert = @ca_file
351
- end
352
- end
353
-
354
- # Configure authentication:
355
- sso_curl.http_auth_types = @kerberos ? :gssnegotiate : 0
356
-
357
- # Build the SSO access_token request url:
358
- sso_curl.url = url.to_s
359
-
360
- # Add headers:
361
- sso_curl.headers['User-Agent'] = "RubySDK/#{VERSION}"
362
- sso_curl.headers['Content-Type'] = 'application/x-www-form-urlencoded'
363
- sso_curl.headers['Accept'] = 'application/json'
364
-
365
- # Request access token:
366
- body = URI.encode_www_form(parameters)
367
- sso_curl.http_post(body)
368
-
369
- # Parse and return the JSON response:
370
- body = sso_curl.body_str
371
- return JSON.parse(body)
372
- ensure
373
- sso_curl.close
374
- end
375
- end
376
-
377
- #
378
- # Builds a the URL and parameters to acquire the access token from SSO.
379
- #
380
- # @return [Array] An array containing two elements, the first is the URL of the SSO service and the second is a hash
381
- # containing the parameters required to perform authentication.
382
- #
383
- # @api private
384
- #
385
- def build_sso_auth_request
386
- # Compute the entry point and the parameters:
387
- parameters = {
388
- :scope => 'ovirt-app-api',
389
- }
390
- if @kerberos
391
- entry_point = 'token-http-auth'
392
- parameters.merge!(
393
- :grant_type => 'urn:ovirt:params:oauth:grant-type:http',
394
- )
395
- else
396
- entry_point = 'token'
397
- parameters.merge!(
398
- :grant_type => 'password',
399
- :username => @username,
400
- :password => @password,
401
- )
402
- end
403
-
404
- # Compute the URL:
405
- url = URI(@url.to_s)
406
- url.path = "/ovirt-engine/sso/oauth/#{entry_point}"
407
-
408
- # Return the pair containing the URL and the parameters:
409
- [url, parameters]
410
- end
411
-
412
- #
413
- # Builds a the URL and parameters to revoke the SSO access token
414
- #
415
- # @return [Array] An array containing two elements, the first is the URL of the SSO service and the second is a hash
416
- # containing the parameters required to perform the revoke.
417
- #
418
- # @api private
419
- #
420
- def build_sso_revoke_request
421
- # Compute the parameters:
422
- parameters = {
423
- :scope => '',
424
- :token => @token,
425
- }
426
-
427
- # Compute the URL:
428
- url = URI(@url.to_s)
429
- url.path = '/ovirt-engine/services/sso-logout'
430
-
431
- # Return the pair containing the URL and the parameters:
432
- [url, parameters]
433
- end
434
-
435
- #
436
- # Tests the connectivity with the server. If connectivity works correctly it returns `true`. If there is any
437
- # connectivity problem it will either return `false` or raise an exception if the `raise_exception` parameter is
438
- # `true`.
439
- #
440
- # @param raise_exception [Boolean]
441
- # @return [Boolean]
442
- #
443
- def test(raise_exception = false)
444
- begin
445
- system_service.get
446
- return true
447
- rescue Exception
448
- raise if raise_exception
449
- return false
450
- end
451
- end
452
-
453
- #
454
- # Performs the authentication process and returns the authentication token. Usually there is no need to
455
- # call this method, as authentication is performed automatically when needed. But in some situations it
456
- # may be useful to perform authentication explicitly, and then use the obtained token to create other
457
- # connections, using the `token` parameter of the constructor instead of the user name and password.
458
- #
459
- # @return [String]
460
- #
461
- def authenticate
462
- @token ||= get_access_token
463
- end
464
-
465
- #
466
- # Indicates if the given object is a link. An object is a link if it has an `href` attribute.
467
- #
468
- # @return [Boolean]
469
- #
470
- def is_link?(object)
471
- return !object.href.nil?
472
- end
473
-
474
- #
475
- # Follows the `href` attribute of the given object, retrieves the target object and returns it.
476
- #
477
- # @param object [Type] The object containing the `href` attribute.
478
- # @raise [Error] If the `href` attribute has no value, or the link can't be followed.
479
- #
480
- def follow_link(object)
481
- # Check that the "href" has a value, as it is needed in order to retrieve the representation of the object:
482
- href = object.href
483
- if href.nil?
484
- raise Error.new("Can't follow link because the 'href' attribute does't have a value")
485
- end
486
-
487
- # Check that the value of the "href" attribute is compatible with the base URL of the connection:
488
- prefix = @url.path
489
- if !prefix.end_with?('/')
490
- prefix += '/'
491
- end
492
- if !href.start_with?(prefix)
493
- raise Error.new("The URL '#{href}' isn't compatible with the base URL of the connection")
494
- end
495
-
496
- # Remove the prefix from the URL, follow the path to the relevant service and invoke the "get" or "list" method
497
- # to retrieve its representation:
498
- path = href[prefix.length..-1]
499
- service = service(path)
500
- if object.is_a?(Array)
501
- service.list
502
- else
503
- service.get
504
- end
505
- end
506
-
507
- #
508
- # Releases the resources used by this connection.
509
- #
510
- def close
511
- # Revoke the SSO access token:
512
- revoke_access_token unless @token.nil?
513
-
514
- # Release resources used by the cURL handle:
515
- @curl.close
516
- end
517
-
518
- #
519
- # Builds a request URL from a path, and the set of query parameters.
520
- #
521
- # @params opts [Hash] The options used to build the URL.
522
- #
523
- # @option opts [String] :path The path that will be added to the base URL. The default is an empty string.
524
- #
525
- # @option opts [Hash<String, String>] :query ({}) A hash containing the query parameters to add to the URL. The
526
- # keys of the hash should be strings containing the names of the parameters, and the values should be strings
527
- # containing the values.
528
- #
529
- # @return [String] The URL.
530
- #
531
- # @api private
532
- #
533
- def build_url(opts = {})
534
- # Get the values of the parameters and assign default values:
535
- path = opts[:path] || ''
536
- query = opts[:query] || {}
537
-
538
- # Add the path and the parameters:
539
- url = @url.to_s + path
540
- if not query.empty?
541
- url = url + '?' + URI.encode_www_form(query)
542
- end
543
- return url
544
- end
545
-
546
- end
547
-
548
- end