ovirt-engine-sdk 4.0.6 → 4.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.adoc +16 -0
- data/README.adoc +6 -6
- data/ext/ovirtsdk4c/extconf.rb +10 -0
- data/ext/ovirtsdk4c/ov_http_client.c +56 -9
- data/lib/ovirtsdk4/connection.rb +78 -70
- data/lib/ovirtsdk4/probe.rb +49 -25
- data/lib/ovirtsdk4/reader.rb +21 -29
- data/lib/ovirtsdk4/readers.rb +192 -192
- data/lib/ovirtsdk4/service.rb +7 -19
- data/lib/ovirtsdk4/services.rb +1435 -1266
- data/lib/ovirtsdk4/type.rb +3 -8
- data/lib/ovirtsdk4/types.rb +1803 -1803
- data/lib/ovirtsdk4/version.rb +1 -1
- data/lib/ovirtsdk4/writer.rb +13 -26
- data/lib/ovirtsdk4/writers.rb +193 -193
- metadata +45 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 941838d3336897908d87d082d471f5aa63bc1302
|
4
|
+
data.tar.gz: 07eee0003584d83b43dbaf5f11c5b8dc7ce593e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28756c56277fad2ed7b439ab0a12d61719e15d4636172dbb21fcf086d10afcc6777e40f05f2f437f6d1c4e510f26b524fa714775393ba31b3388d78ba4525b27
|
7
|
+
data.tar.gz: a094e808b259fd31d37d1bf8849f08e871dad46a863cd6817e2ac7249d92c45c3061f9bf0c2b56cb6d749e06ce689a31a676fc536cc06b722fcf66e9743d9eeb
|
data/CHANGES.adoc
CHANGED
@@ -2,6 +2,22 @@
|
|
2
2
|
|
3
3
|
This document describes the relevant changes between releases of the SDK.
|
4
4
|
|
5
|
+
== 4.0.7 / Jan 12 2017
|
6
|
+
|
7
|
+
New features:
|
8
|
+
|
9
|
+
* Add HTTP proxy support.
|
10
|
+
|
11
|
+
* Require Ruby 2.
|
12
|
+
|
13
|
+
Bug fixes:
|
14
|
+
|
15
|
+
* Fix writing `id` attribute for enum values
|
16
|
+
https://bugzilla.redhat.com/1408839[#1408839].
|
17
|
+
|
18
|
+
* Enable installation in Vagrant with embedded Ruby
|
19
|
+
https://bugzilla.redhat.com/1411594[#1411594].
|
20
|
+
|
5
21
|
== 4.0.6 / Oct 18 2016
|
6
22
|
|
7
23
|
Update to model 4.0.37.
|
data/README.adoc
CHANGED
@@ -23,12 +23,12 @@ and gives you access to the root of the tree of services of the API:
|
|
23
23
|
require 'ovirtsdk4'
|
24
24
|
|
25
25
|
# Create a connection to the server:
|
26
|
-
connection = OvirtSDK4::Connection.new(
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
|
26
|
+
connection = OvirtSDK4::Connection.new(
|
27
|
+
url: 'https://engine.example.com/ovirt-engine/api',
|
28
|
+
username: 'admin@internal',
|
29
|
+
password: '...',
|
30
|
+
ca_file: 'ca.pem'
|
31
|
+
)
|
32
32
|
|
33
33
|
# Get the reference to the system service:
|
34
34
|
system_service = connection.system_service
|
data/ext/ovirtsdk4c/extconf.rb
CHANGED
@@ -30,5 +30,15 @@ unless pkg_config('libcurl')
|
|
30
30
|
raise 'The "libcurl" package isn\'t available.'
|
31
31
|
end
|
32
32
|
|
33
|
+
# When installing the SDK as a plugin in Vagrant there is an issue with
|
34
|
+
# some versions of Vagrant that embed "libxml2" and "libcurl", but using
|
35
|
+
# an incorrect directory. To avoid that we need to explicitly fix the
|
36
|
+
# Vagrant path.
|
37
|
+
def fix_vagrant_prefix(flags)
|
38
|
+
flags.gsub!('/vagrant-substrate/staging', '/opt/vagrant')
|
39
|
+
end
|
40
|
+
fix_vagrant_prefix($CPPFLAGS)
|
41
|
+
fix_vagrant_prefix($LDFLAGS)
|
42
|
+
|
33
43
|
# Create the Makefile:
|
34
44
|
create_makefile 'ovirtsdk4c'
|
@@ -41,6 +41,9 @@ static VALUE LOG_SYMBOL;
|
|
41
41
|
static VALUE PASSWORD_SYMBOL;
|
42
42
|
static VALUE TIMEOUT_SYMBOL;
|
43
43
|
static VALUE USERNAME_SYMBOL;
|
44
|
+
static VALUE PROXY_URL_SYMBOL;
|
45
|
+
static VALUE PROXY_USERNAME_SYMBOL;
|
46
|
+
static VALUE PROXY_PASSWORD_SYMBOL;
|
44
47
|
|
45
48
|
/* Method identifiers: */
|
46
49
|
static ID DEBUG_ID;
|
@@ -342,6 +345,9 @@ static VALUE ov_http_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
342
345
|
bool debug;
|
343
346
|
bool insecure;
|
344
347
|
char* ca_file;
|
348
|
+
char* proxy_password;
|
349
|
+
char* proxy_url;
|
350
|
+
char* proxy_username;
|
345
351
|
int timeout;
|
346
352
|
ov_http_client_object* object;
|
347
353
|
|
@@ -416,6 +422,36 @@ static VALUE ov_http_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
416
422
|
compress = RTEST(opt);
|
417
423
|
}
|
418
424
|
|
425
|
+
/* Get the value of the 'proxy_url' parameter: */
|
426
|
+
opt = rb_hash_aref(opts, PROXY_URL_SYMBOL);
|
427
|
+
if (NIL_P(opt)) {
|
428
|
+
proxy_url = NULL;
|
429
|
+
}
|
430
|
+
else {
|
431
|
+
Check_Type(opt, T_STRING);
|
432
|
+
proxy_url = StringValueCStr(opt);
|
433
|
+
}
|
434
|
+
|
435
|
+
/* Get the value of the 'proxy_username' parameter: */
|
436
|
+
opt = rb_hash_aref(opts, PROXY_USERNAME_SYMBOL);
|
437
|
+
if (NIL_P(opt)) {
|
438
|
+
proxy_username = NULL;
|
439
|
+
}
|
440
|
+
else {
|
441
|
+
Check_Type(opt, T_STRING);
|
442
|
+
proxy_username = StringValueCStr(opt);
|
443
|
+
}
|
444
|
+
|
445
|
+
/* Get the value of the 'proxy_password' parameter: */
|
446
|
+
opt = rb_hash_aref(opts, PROXY_PASSWORD_SYMBOL);
|
447
|
+
if (NIL_P(opt)) {
|
448
|
+
proxy_password = NULL;
|
449
|
+
}
|
450
|
+
else {
|
451
|
+
Check_Type(opt, T_STRING);
|
452
|
+
proxy_password = StringValueCStr(opt);
|
453
|
+
}
|
454
|
+
|
419
455
|
/* Create the libcurl object: */
|
420
456
|
object->curl = curl_easy_init();
|
421
457
|
if (object->curl == NULL) {
|
@@ -431,7 +467,6 @@ static VALUE ov_http_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
431
467
|
curl_easy_setopt(object->curl, CURLOPT_CAINFO, ca_file);
|
432
468
|
}
|
433
469
|
|
434
|
-
|
435
470
|
/* Configure the timeout: */
|
436
471
|
curl_easy_setopt(object->curl, CURLOPT_TIMEOUT, timeout);
|
437
472
|
|
@@ -447,6 +482,15 @@ static VALUE ov_http_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
447
482
|
curl_easy_setopt(object->curl, CURLOPT_DEBUGFUNCTION, ov_http_client_debug_function);
|
448
483
|
}
|
449
484
|
|
485
|
+
/* Configure the proxy: */
|
486
|
+
if (proxy_url != NULL) {
|
487
|
+
curl_easy_setopt(object->curl, CURLOPT_PROXY, proxy_url);
|
488
|
+
if (proxy_username != NULL && proxy_password != NULL) {
|
489
|
+
curl_easy_setopt(object->curl, CURLOPT_PROXYUSERNAME, proxy_username);
|
490
|
+
curl_easy_setopt(object->curl, CURLOPT_PROXYPASSWORD, proxy_password);
|
491
|
+
}
|
492
|
+
}
|
493
|
+
|
450
494
|
/* Configure callbacks: */
|
451
495
|
curl_easy_setopt(object->curl, CURLOPT_READFUNCTION, ov_http_client_read_function);
|
452
496
|
curl_easy_setopt(object->curl, CURLOPT_WRITEFUNCTION, ov_http_client_write_function);
|
@@ -653,14 +697,17 @@ void ov_http_client_define(void) {
|
|
653
697
|
rb_define_method(ov_http_client_class, "send", ov_http_client_send, 2);
|
654
698
|
|
655
699
|
/* Define the symbols: */
|
656
|
-
USERNAME_SYMBOL
|
657
|
-
PASSWORD_SYMBOL
|
658
|
-
INSECURE_SYMBOL
|
659
|
-
CA_FILE_SYMBOL
|
660
|
-
DEBUG_SYMBOL
|
661
|
-
LOG_SYMBOL
|
662
|
-
COMPRESS_SYMBOL
|
663
|
-
TIMEOUT_SYMBOL
|
700
|
+
USERNAME_SYMBOL = ID2SYM(rb_intern("username"));
|
701
|
+
PASSWORD_SYMBOL = ID2SYM(rb_intern("password"));
|
702
|
+
INSECURE_SYMBOL = ID2SYM(rb_intern("insecure"));
|
703
|
+
CA_FILE_SYMBOL = ID2SYM(rb_intern("ca_file"));
|
704
|
+
DEBUG_SYMBOL = ID2SYM(rb_intern("debug"));
|
705
|
+
LOG_SYMBOL = ID2SYM(rb_intern("log"));
|
706
|
+
COMPRESS_SYMBOL = ID2SYM(rb_intern("compress"));
|
707
|
+
TIMEOUT_SYMBOL = ID2SYM(rb_intern("timeout"));
|
708
|
+
PROXY_URL_SYMBOL = ID2SYM(rb_intern("proxy_url"));
|
709
|
+
PROXY_USERNAME_SYMBOL = ID2SYM(rb_intern("proxy_username"));
|
710
|
+
PROXY_PASSWORD_SYMBOL = ID2SYM(rb_intern("proxy_password"));
|
664
711
|
|
665
712
|
/* Define the method identifiers: */
|
666
713
|
DEBUG_ID = rb_intern("debug");
|
data/lib/ovirtsdk4/connection.rb
CHANGED
@@ -18,7 +18,6 @@ require 'json'
|
|
18
18
|
require 'uri'
|
19
19
|
|
20
20
|
module OvirtSDK4
|
21
|
-
|
22
21
|
#
|
23
22
|
# This class is responsible for managing an HTTP connection to the engine server. It is intended as the entry
|
24
23
|
# point for the SDK, and it provides access to the `system` service and, from there, to the rest of the services
|
@@ -31,10 +30,10 @@ module OvirtSDK4
|
|
31
30
|
# [source,ruby]
|
32
31
|
# ----
|
33
32
|
# connection = OvirtSDK4::Connection.new(
|
34
|
-
# :
|
35
|
-
# :
|
36
|
-
# :
|
37
|
-
# :
|
33
|
+
# url: 'https://engine.example.com/ovirt-engine/api',
|
34
|
+
# username: 'admin@internal',
|
35
|
+
# password: '...',
|
36
|
+
# ca_file:'/etc/pki/ovirt-engine/ca.pem'
|
38
37
|
# )
|
39
38
|
# ----
|
40
39
|
#
|
@@ -75,6 +74,15 @@ module OvirtSDK4
|
|
75
74
|
# compressed responses. Note that this is a hint for the server, and that it may return uncompressed data even
|
76
75
|
# when this parameter is set to `true`.
|
77
76
|
#
|
77
|
+
# @option opts [String] :proxy_url A string containing the protocol, address and port number of the proxy server
|
78
|
+
# to use to connect to the server. For example, in order to use the HTTP proxy `proxy.example.com` that is
|
79
|
+
# listening on port `3128` the value should be `http://proxy.example.com:3128`. This is optional, and if not
|
80
|
+
# given the connection will go directly to the server specified in the `url` parameter.
|
81
|
+
#
|
82
|
+
# @option opts [String] :proxy_username The name of the user to authenticate to the proxy server.
|
83
|
+
#
|
84
|
+
# @option opts [String] :proxy_password The password of the user to authenticate to the proxy server.
|
85
|
+
#
|
78
86
|
def initialize(opts = {})
|
79
87
|
# Get the values of the parameters and assign default values:
|
80
88
|
@url = opts[:url]
|
@@ -88,15 +96,21 @@ module OvirtSDK4
|
|
88
96
|
@kerberos = opts[:kerberos] || false
|
89
97
|
@timeout = opts[:timeout] || 0
|
90
98
|
@compress = opts[:compress] || false
|
99
|
+
@proxy_url = opts[:proxy_url]
|
100
|
+
@proxy_username = opts[:proxy_username]
|
101
|
+
@proxy_password = opts[:proxy_password]
|
91
102
|
|
92
103
|
# Create the HTTP client:
|
93
104
|
@client = HttpClient.new(
|
94
|
-
:
|
95
|
-
:
|
96
|
-
:
|
97
|
-
:
|
98
|
-
:
|
99
|
-
:
|
105
|
+
insecure: @insecure,
|
106
|
+
ca_file: @ca_file,
|
107
|
+
debug: @debug,
|
108
|
+
log: @log,
|
109
|
+
timeout: @timeout,
|
110
|
+
compress: @compress,
|
111
|
+
proxy_url: @proxy_url,
|
112
|
+
proxy_username: @proxy_username,
|
113
|
+
proxy_password: @proxy_password
|
100
114
|
)
|
101
115
|
end
|
102
116
|
|
@@ -106,7 +120,7 @@ module OvirtSDK4
|
|
106
120
|
# @return [SystemService]
|
107
121
|
#
|
108
122
|
def system_service
|
109
|
-
@system_service ||= SystemService.new(self,
|
123
|
+
@system_service ||= SystemService.new(self, '')
|
110
124
|
end
|
111
125
|
|
112
126
|
#
|
@@ -119,7 +133,7 @@ module OvirtSDK4
|
|
119
133
|
# @raise [Error] If there is no service corresponding to the given path.
|
120
134
|
#
|
121
135
|
def service(path)
|
122
|
-
|
136
|
+
system_service.service(path)
|
123
137
|
end
|
124
138
|
|
125
139
|
#
|
@@ -132,18 +146,14 @@ module OvirtSDK4
|
|
132
146
|
#
|
133
147
|
def send(request)
|
134
148
|
# Add the base URL to the request:
|
135
|
-
|
136
|
-
request.url = @url
|
137
|
-
else
|
138
|
-
request.url = "#{@url}#{request.url}"
|
139
|
-
end
|
149
|
+
request.url = request.url.nil? ? request.url = @url : "#{@url}#{request.url}"
|
140
150
|
|
141
151
|
# Set the headers common to all requests:
|
142
152
|
request.headers.merge!(
|
143
153
|
'User-Agent' => "RubySDK/#{VERSION}",
|
144
154
|
'Version' => '4',
|
145
155
|
'Content-Type' => 'application/xml',
|
146
|
-
'Accept' => 'application/xml'
|
156
|
+
'Accept' => 'application/xml'
|
147
157
|
)
|
148
158
|
|
149
159
|
# Older versions of the engine (before 4.1) required the 'all_content' as an HTTP header instead of a query
|
@@ -151,13 +161,11 @@ module OvirtSDK4
|
|
151
161
|
# included in the request, and add the corresponding header.
|
152
162
|
unless request.query.nil?
|
153
163
|
all_content = request.query['all_content']
|
154
|
-
unless all_content.nil?
|
155
|
-
request.headers['All-Content'] = all_content
|
156
|
-
end
|
164
|
+
request.headers['All-Content'] = all_content unless all_content.nil?
|
157
165
|
end
|
158
166
|
|
159
167
|
# Set the authentication token:
|
160
|
-
@token ||=
|
168
|
+
@token ||= create_access_token
|
161
169
|
request.token = @token
|
162
170
|
|
163
171
|
# Create an empty response:
|
@@ -167,7 +175,7 @@ module OvirtSDK4
|
|
167
175
|
@client.send(request, response)
|
168
176
|
|
169
177
|
# Return the response:
|
170
|
-
|
178
|
+
response
|
171
179
|
end
|
172
180
|
|
173
181
|
#
|
@@ -177,20 +185,18 @@ module OvirtSDK4
|
|
177
185
|
#
|
178
186
|
# @api private
|
179
187
|
#
|
180
|
-
def
|
188
|
+
def create_access_token
|
181
189
|
# Build the URL and parameters required for the request:
|
182
190
|
url, parameters = build_sso_auth_request
|
183
191
|
|
184
|
-
# Send the
|
192
|
+
# Send the request and wait for the request:
|
185
193
|
response = get_sso_response(url, parameters)
|
194
|
+
response = response[0] if response.is_a?(Array)
|
186
195
|
|
187
|
-
if
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
unless response['error'].nil?
|
192
|
-
raise Error.new("Error during SSO authentication: #{response['error_code']}: #{response['error']}")
|
193
|
-
end
|
196
|
+
# Check the response and raise an error if it contains an error code:
|
197
|
+
code = response['error_code']
|
198
|
+
error = response['error']
|
199
|
+
raise Error, "Error during SSO authentication: #{code}: #{error}" if error
|
194
200
|
|
195
201
|
response['access_token']
|
196
202
|
end
|
@@ -204,15 +210,14 @@ module OvirtSDK4
|
|
204
210
|
# Build the URL and parameters required for the request:
|
205
211
|
url, parameters = build_sso_revoke_request
|
206
212
|
|
213
|
+
# Send the request and wait for the response:
|
207
214
|
response = get_sso_response(url, parameters)
|
215
|
+
response = response[0] if response.is_a?(Array)
|
208
216
|
|
209
|
-
if
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
unless response['error'].nil?
|
214
|
-
raise Error.new("Error during SSO revoke: #{response['error_code']}: #{response['error']}")
|
215
|
-
end
|
217
|
+
# Check the response and raise an error if it contains an error code:
|
218
|
+
code = response['error_code']
|
219
|
+
error = response['error']
|
220
|
+
raise Error, "Error during SSO revoke: #{code}: #{error}" if error
|
216
221
|
end
|
217
222
|
|
218
223
|
#
|
@@ -229,14 +234,14 @@ module OvirtSDK4
|
|
229
234
|
def get_sso_response(url, parameters)
|
230
235
|
# Create the request:
|
231
236
|
request = HttpRequest.new(
|
232
|
-
:
|
233
|
-
:
|
234
|
-
:
|
237
|
+
method: :POST,
|
238
|
+
url: url,
|
239
|
+
headers: {
|
235
240
|
'User-Agent' => "RubySDK/#{VERSION}",
|
236
241
|
'Content-Type' => 'application/x-www-form-urlencoded',
|
237
|
-
'Accept' => 'application/json'
|
242
|
+
'Accept' => 'application/json'
|
238
243
|
},
|
239
|
-
:
|
244
|
+
body: URI.encode_www_form(parameters)
|
240
245
|
)
|
241
246
|
|
242
247
|
# Create an empty response:
|
@@ -260,19 +265,17 @@ module OvirtSDK4
|
|
260
265
|
def build_sso_auth_request
|
261
266
|
# Compute the entry point and the parameters:
|
262
267
|
parameters = {
|
263
|
-
:
|
268
|
+
scope: 'ovirt-app-api'
|
264
269
|
}
|
265
270
|
if @kerberos
|
266
271
|
entry_point = 'token-http-auth'
|
267
|
-
parameters
|
268
|
-
:grant_type => 'urn:ovirt:params:oauth:grant-type:http',
|
269
|
-
)
|
272
|
+
parameters[:grant_type] = 'urn:ovirt:params:oauth:grant-type:http'
|
270
273
|
else
|
271
274
|
entry_point = 'token'
|
272
275
|
parameters.merge!(
|
273
|
-
:
|
274
|
-
:
|
275
|
-
:
|
276
|
+
grant_type: 'password',
|
277
|
+
username: @username,
|
278
|
+
password: @password
|
276
279
|
)
|
277
280
|
end
|
278
281
|
|
@@ -296,8 +299,8 @@ module OvirtSDK4
|
|
296
299
|
def build_sso_revoke_request
|
297
300
|
# Compute the parameters:
|
298
301
|
parameters = {
|
299
|
-
:
|
300
|
-
:
|
302
|
+
scope: '',
|
303
|
+
token: @token
|
301
304
|
}
|
302
305
|
|
303
306
|
# Compute the URL:
|
@@ -318,13 +321,11 @@ module OvirtSDK4
|
|
318
321
|
# @return [Boolean]
|
319
322
|
#
|
320
323
|
def test(raise_exception = false)
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
false
|
327
|
-
end
|
324
|
+
system_service.get
|
325
|
+
true
|
326
|
+
rescue StandardError
|
327
|
+
raise if raise_exception
|
328
|
+
false
|
328
329
|
end
|
329
330
|
|
330
331
|
#
|
@@ -336,7 +337,7 @@ module OvirtSDK4
|
|
336
337
|
# @return [String]
|
337
338
|
#
|
338
339
|
def authenticate
|
339
|
-
@token ||=
|
340
|
+
@token ||= create_access_token
|
340
341
|
end
|
341
342
|
|
342
343
|
#
|
@@ -344,10 +345,20 @@ module OvirtSDK4
|
|
344
345
|
#
|
345
346
|
# @return [Boolean]
|
346
347
|
#
|
347
|
-
def
|
348
|
+
def link?(object)
|
348
349
|
!object.href.nil?
|
349
350
|
end
|
350
351
|
|
352
|
+
#
|
353
|
+
# The `link?` method used to be named `is_link?`, and we need to preserve it for backwards compatibility, but try to
|
354
|
+
# avoid using it.
|
355
|
+
#
|
356
|
+
# @return [Boolean]
|
357
|
+
#
|
358
|
+
# @deprecated Please use `link?` instead.
|
359
|
+
#
|
360
|
+
alias is_link? link?
|
361
|
+
|
351
362
|
#
|
352
363
|
# Follows the `href` attribute of the given object, retrieves the target object and returns it.
|
353
364
|
#
|
@@ -358,16 +369,14 @@ module OvirtSDK4
|
|
358
369
|
# Check that the "href" has a value, as it is needed in order to retrieve the representation of the object:
|
359
370
|
href = object.href
|
360
371
|
if href.nil?
|
361
|
-
raise Error
|
372
|
+
raise Error, "Can't follow link because the 'href' attribute does't have a value"
|
362
373
|
end
|
363
374
|
|
364
375
|
# Check that the value of the "href" attribute is compatible with the base URL of the connection:
|
365
376
|
prefix = URI(@url).path
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
if !href.start_with?(prefix)
|
370
|
-
raise Error.new("The URL '#{href}' isn't compatible with the base URL of the connection")
|
377
|
+
prefix += '/' unless prefix.end_with?('/')
|
378
|
+
unless href.start_with?(prefix)
|
379
|
+
raise Error, "The URL '#{href}' isn't compatible with the base URL of the connection"
|
371
380
|
end
|
372
381
|
|
373
382
|
# Remove the prefix from the URL, follow the path to the relevant service and invoke the "get" or "list" method
|
@@ -391,6 +400,5 @@ module OvirtSDK4
|
|
391
400
|
# Close the HTTP client:
|
392
401
|
@client.close if @client
|
393
402
|
end
|
394
|
-
|
395
403
|
end
|
396
404
|
end
|