ovirt-engine-sdk 4.0.1 → 4.4.1

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