occi-api 4.0.0.alpha.1 → 4.0.0.alpha.2
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/occi-api.rb +0 -1
- data/lib/occi/api/client/{http/authn_utils.rb → authn_utils.rb} +0 -0
- data/lib/occi/api/client/client_base.rb +56 -90
- data/lib/occi/api/client/client_http.rb +13 -3
- data/lib/occi/api/client/http/authn_plugins/base.rb +1 -1
- data/lib/occi/api/client/http/authn_plugins/keystone.rb +52 -9
- data/lib/occi/api/client/http/httparty_fix.rb +8 -32
- data/lib/occi/api/version.rb +1 -1
- data/occi-api.gemspec +1 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/raises_an_error_when_looking_for_a_non-existent_mixin_type.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/returns_nil_when_looking_for_a_non-existent_mixin.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/returns_nil_when_looking_for_a_non-existent_mixin_of_a_specific_type.yml +266 -0
- data/spec/occi/api/client/{http/authn_utils_spec.rb → authn_utils_spec.rb} +1 -1
- data/spec/occi/api/client/client_http_spec.rb +20 -3
- data/spec/occi/api/client/{http/rocci-cred-cert.pem → rocci-cred-cert.pem} +0 -0
- data/spec/occi/api/client/{http/rocci-cred-key-jruby.pem → rocci-cred-key-jruby.pem} +0 -0
- data/spec/occi/api/client/{http/rocci-cred-key.pem → rocci-cred-key.pem} +0 -0
- data/spec/occi/api/client/{http/rocci-cred.p12 → rocci-cred.p12} +0 -0
- metadata +13 -9
data/lib/occi-api.rb
CHANGED
@@ -8,7 +8,6 @@ module Occi::Api; end
|
|
8
8
|
require 'occi/api/version'
|
9
9
|
require 'occi/api/client/client_base'
|
10
10
|
require 'occi/api/client/errors'
|
11
|
-
require 'occi/api/client/http/authn_plugins'
|
12
11
|
require 'occi/api/client/client_http'
|
13
12
|
require 'occi/api/client/client_amqp'
|
14
13
|
require 'occi/api/dsl'
|
File without changes
|
@@ -196,17 +196,15 @@ module Occi
|
|
196
196
|
# @param [String] resource name or resource identifier
|
197
197
|
# @return [Occi::Core::Resource] new resource instance
|
198
198
|
def get_resource(resource_type)
|
199
|
-
|
200
199
|
Occi::Log.debug("Instantiating #{resource_type} ...")
|
201
200
|
|
202
|
-
type_id =
|
203
|
-
if @model.get_by_id resource_type
|
201
|
+
type_id = if @model.get_by_id resource_type
|
204
202
|
# we got a resource type identifier
|
205
|
-
|
203
|
+
resource_type
|
206
204
|
else
|
207
205
|
# we got a resource type name
|
208
206
|
type_ids = @model.kinds.select { |kind| kind.term == resource_type }
|
209
|
-
|
207
|
+
type_ids.first.type_identifier if type_ids.any?
|
210
208
|
end
|
211
209
|
|
212
210
|
raise "Unknown resource type! [#{resource_type}]" unless type_id
|
@@ -320,97 +318,72 @@ module Occi
|
|
320
318
|
# @param [Boolean] should we describe the mixin or return its link?
|
321
319
|
# @return [String, Occi::Collection, nil] link, mixin description or nothing found
|
322
320
|
def find_mixin(name, type = nil, describe = false)
|
323
|
-
|
324
321
|
Occi::Log.debug("Looking for mixin #{name} + #{type} + #{describe}")
|
325
|
-
|
326
|
-
# is type valid?
|
327
322
|
raise "Unknown mixin type! [#{type}]" if type && !@mixins.has_key?(type.to_sym)
|
328
323
|
|
329
324
|
# TODO: extend this code to support multiple matches and regex filters
|
330
325
|
# should we look for links or descriptions?
|
331
|
-
|
332
|
-
# we are looking for descriptions
|
333
|
-
find_mixin_describe name, type
|
334
|
-
else
|
335
|
-
# we are looking for links
|
336
|
-
find_mixin_list name, type
|
337
|
-
end
|
326
|
+
describe ? describe_mixin(name, type) : list_mixin(name, type)
|
338
327
|
end
|
339
328
|
|
340
329
|
# Looks up a mixin using its name and, optionally, a type as well.
|
341
330
|
# Will return mixin's full description.
|
342
331
|
#
|
343
332
|
# @example
|
344
|
-
# client.
|
333
|
+
# client.describe_mixin "debian6"
|
345
334
|
# # => #<Occi::Collection>
|
346
|
-
# client.
|
335
|
+
# client.describe_mixin "debian6", "os_tpl"
|
347
336
|
# # => #<Occi::Collection>
|
348
|
-
# client.
|
337
|
+
# client.describe_mixin "large", "resource_tpl"
|
349
338
|
# # => #<Occi::Collection>
|
350
|
-
# client.
|
339
|
+
# client.describe_mixin "debian6", "resource_tpl" # => nil
|
351
340
|
#
|
352
341
|
# @param [String] name of the mixin
|
353
342
|
# @param [String] type of the mixin
|
354
343
|
# @return [Occi::Collection, nil] mixin description or nothing found
|
355
|
-
def
|
356
|
-
found_ary =
|
357
|
-
|
358
|
-
|
359
|
-
# get the first match from either os_tpls or resource_tpls
|
360
|
-
case type
|
361
|
-
when "os_tpl"
|
362
|
-
found_ary = get_os_templates.select { |mixin| mixin.term == name }
|
363
|
-
when "resource_tpl"
|
364
|
-
found_ary = get_resource_templates.select { |template| template.term == name }
|
365
|
-
else
|
366
|
-
# TODO: should raise an Error?
|
367
|
-
end
|
368
|
-
else
|
369
|
-
# try in os_tpls first
|
370
|
-
found_ary = get_os_templates.select { |os| os.term == name }
|
344
|
+
def describe_mixin(name, type = nil)
|
345
|
+
found_ary = type ? describe_mixin_w_type(name, type) : describe_mixin_wo_type(name)
|
346
|
+
found_ary.any? ? found_ary.first : nil
|
347
|
+
end
|
371
348
|
|
372
|
-
|
349
|
+
#
|
350
|
+
#
|
351
|
+
#
|
352
|
+
def describe_mixin_w_type(name, type)
|
353
|
+
return unless %w( os_tpl resource_tpl ).include? type.to_s
|
354
|
+
send("get_#{type.to_s}s".to_sym).select { |mixin| mixin.term == name }
|
355
|
+
end
|
373
356
|
|
374
|
-
|
375
|
-
|
376
|
-
|
357
|
+
#
|
358
|
+
#
|
359
|
+
#
|
360
|
+
def describe_mixin_wo_type(name)
|
361
|
+
%w( os_tpl resource_tpl ).each do |type|
|
362
|
+
found = send("get_#{type}s".to_sym).select { |mixin| mixin.term == name }
|
363
|
+
return found if found.any?
|
377
364
|
end
|
378
365
|
|
379
|
-
|
366
|
+
[]
|
380
367
|
end
|
381
368
|
|
382
369
|
# Looks up a mixin using its name and, optionally, a type as well.
|
383
370
|
# Will return mixin's full location.
|
384
371
|
#
|
385
372
|
# @example
|
386
|
-
# client.
|
373
|
+
# client.list_mixin "debian6"
|
387
374
|
# # => "http://my.occi.service/occi/infrastructure/os_tpl#debian6"
|
388
|
-
# client.
|
375
|
+
# client.list_mixin "debian6", "os_tpl"
|
389
376
|
# # => "http://my.occi.service/occi/infrastructure/os_tpl#debian6"
|
390
|
-
# client.
|
377
|
+
# client.list_mixin "large", "resource_tpl"
|
391
378
|
# # => "http://my.occi.service/occi/infrastructure/resource_tpl#large"
|
392
|
-
# client.
|
379
|
+
# client.list_mixin "debian6", "resource_tpl" # => nil
|
393
380
|
#
|
394
381
|
# @param [String] name of the mixin
|
395
382
|
# @param [String] type of the mixin
|
396
383
|
# @return [String, nil] link or nothing found
|
397
|
-
def
|
398
|
-
|
399
|
-
mxns =
|
400
|
-
name_rev = "##{name}".reverse
|
401
|
-
|
402
|
-
if type
|
403
|
-
# return the first match with the selected type
|
404
|
-
mxns = @mixins[type.to_sym].select {
|
405
|
-
|mixin| mixin.to_s.reverse.start_with? name_rev
|
406
|
-
}
|
407
|
-
else
|
408
|
-
# there is no type preference, return first global match
|
409
|
-
mxns = @mixins.flatten(2).select {
|
410
|
-
|mixin| mixin.to_s.reverse.start_with? name_rev
|
411
|
-
}
|
412
|
-
end
|
413
|
-
|
384
|
+
def list_mixin(name, type = nil)
|
385
|
+
mxns = type ? @mixins[type.to_sym] : @mixins.flatten(2)
|
386
|
+
mxns = mxns.select { |mixin| mixin.to_s.reverse.start_with? "##{name}".reverse }
|
414
387
|
mxns.any? ? mxns.first : nil
|
415
388
|
end
|
416
389
|
|
@@ -433,18 +406,11 @@ module Occi
|
|
433
406
|
if type
|
434
407
|
# is type valid?
|
435
408
|
raise "Unknown mixin type! #{type}" unless @mixins.has_key? type.to_sym
|
436
|
-
|
437
|
-
# return mixin of the selected type
|
438
409
|
@mixins[type.to_sym]
|
439
410
|
else
|
440
411
|
# we did not get a type, return all mixins
|
441
412
|
mixins = []
|
442
|
-
|
443
|
-
# flatten the hash and remove its keys
|
444
|
-
get_mixin_types.each do |ltype|
|
445
|
-
mixins.concat @mixins[ltype.to_sym]
|
446
|
-
end
|
447
|
-
|
413
|
+
get_mixin_types.each { |ltype| mixins.concat @mixins[ltype.to_sym] }
|
448
414
|
mixins
|
449
415
|
end
|
450
416
|
end
|
@@ -472,7 +438,7 @@ module Occi
|
|
472
438
|
identifiers = []
|
473
439
|
|
474
440
|
get_mixin_types.each do |mixin_type|
|
475
|
-
identifiers <<
|
441
|
+
identifiers << "http://schemas.ogf.org/occi/infrastructure##{mixin_type}"
|
476
442
|
end
|
477
443
|
|
478
444
|
identifiers
|
@@ -487,6 +453,7 @@ module Occi
|
|
487
453
|
def get_os_templates
|
488
454
|
@model.get.mixins.select { |mixin| mixin.related.select { |rel| rel.end_with? 'os_tpl' }.any? }
|
489
455
|
end
|
456
|
+
alias_method :get_os_tpls, :get_os_templates
|
490
457
|
|
491
458
|
# Retrieves available resource_tpls from the model.
|
492
459
|
#
|
@@ -497,6 +464,7 @@ module Occi
|
|
497
464
|
def get_resource_templates
|
498
465
|
@model.get.mixins.select { |mixin| mixin.related.select { |rel| rel.end_with? 'resource_tpl' }.any? }
|
499
466
|
end
|
467
|
+
alias_method :get_resource_tpls, :get_resource_templates
|
500
468
|
|
501
469
|
# Creates a link of a specified kind and binds it to the given resource.
|
502
470
|
#
|
@@ -611,7 +579,7 @@ module Occi
|
|
611
579
|
#
|
612
580
|
# @param [Hash] logger options
|
613
581
|
def set_logger(log_options)
|
614
|
-
|
582
|
+
unless log_options[:logger] && log_options[:logger].kind_of?(Occi::Log)
|
615
583
|
@logger = Occi::Log.new(log_options[:out])
|
616
584
|
@logger.level = log_options[:level]
|
617
585
|
end
|
@@ -626,7 +594,7 @@ module Occi
|
|
626
594
|
# @param [String] endpoint URI in a non-canonical string
|
627
595
|
# @return [String] canonical endpoint URI in a string, with a trailing slash
|
628
596
|
def set_endpoint(endpoint)
|
629
|
-
raise 'Endpoint not a valid URI'
|
597
|
+
raise 'Endpoint not a valid URI' unless (endpoint =~ URI::ABS_URI)
|
630
598
|
@endpoint = endpoint.chomp('/') + '/'
|
631
599
|
end
|
632
600
|
|
@@ -644,7 +612,7 @@ module Occi
|
|
644
612
|
|
645
613
|
@mixins = {
|
646
614
|
:os_tpl => get_os_tpl_mixins_ary,
|
647
|
-
:resource_tpl =>
|
615
|
+
:resource_tpl => get_resource_tpl_mixins_ary
|
648
616
|
}
|
649
617
|
|
650
618
|
@model
|
@@ -654,32 +622,30 @@ module Occi
|
|
654
622
|
#
|
655
623
|
#
|
656
624
|
def get_os_tpl_mixins_ary
|
657
|
-
|
658
|
-
|
659
|
-
get_os_templates.each do |os_tpl|
|
660
|
-
unless os_tpl.nil? || os_tpl.type_identifier.nil?
|
661
|
-
tid = os_tpl.type_identifier.strip
|
662
|
-
os_tpls << tid unless tid.empty?
|
663
|
-
end
|
664
|
-
end
|
625
|
+
get_mixins_ary(:os_tpl)
|
626
|
+
end
|
665
627
|
|
666
|
-
|
628
|
+
#
|
629
|
+
#
|
630
|
+
#
|
631
|
+
def get_resource_tpl_mixins_ary
|
632
|
+
get_mixins_ary(:resource_tpl)
|
667
633
|
end
|
668
634
|
|
669
635
|
#
|
670
636
|
#
|
671
637
|
#
|
672
|
-
def
|
673
|
-
|
638
|
+
def get_mixins_ary(mixin_type)
|
639
|
+
mixins = []
|
640
|
+
|
641
|
+
send("get_#{mixin_type.to_s}s".to_sym).each do |mixin|
|
642
|
+
next if mixin.nil? || mixin.type_identifier.nil?
|
674
643
|
|
675
|
-
|
676
|
-
|
677
|
-
tid = res_tpl.type_identifier.strip
|
678
|
-
res_tpls << tid unless tid.empty?
|
679
|
-
end
|
644
|
+
tid = mixin.type_identifier.strip
|
645
|
+
mixins << tid unless tid.empty?
|
680
646
|
end
|
681
647
|
|
682
|
-
|
648
|
+
mixins
|
683
649
|
end
|
684
650
|
|
685
651
|
end
|
@@ -2,7 +2,8 @@ require 'httparty'
|
|
2
2
|
|
3
3
|
require 'occi/api/client/http/net_http_fix'
|
4
4
|
require 'occi/api/client/http/httparty_fix'
|
5
|
-
require 'occi/api/client/
|
5
|
+
require 'occi/api/client/authn_utils'
|
6
|
+
require 'occi/api/client/http/authn_plugins'
|
6
7
|
|
7
8
|
module Occi
|
8
9
|
module Api
|
@@ -323,7 +324,9 @@ module Occi
|
|
323
324
|
entity_type = Occi::Core::Link if kind.related_to? Occi::Core::Link
|
324
325
|
end
|
325
326
|
|
326
|
-
Occi::
|
327
|
+
entity_type = Occi::Core::Resource unless entity_type
|
328
|
+
|
329
|
+
Occi::Log.debug "Parser call: #{response.content_type} #{path.include?('-/')} #{entity_type} #{response.headers.inspect}"
|
327
330
|
collection = Occi::Parser.parse(response.content_type, response.body, path.include?('-/'), entity_type, response.headers)
|
328
331
|
|
329
332
|
Occi::Log.debug "Parsed collection: empty? #{collection.empty?}"
|
@@ -378,7 +381,13 @@ module Occi
|
|
378
381
|
collection.resources.first.location if collection.resources.first
|
379
382
|
end
|
380
383
|
when 201
|
381
|
-
|
384
|
+
# TODO: OCCI-OS hack, look for header Location instead of uri-list
|
385
|
+
# This should be probably implemented in Occi::Parser.locations
|
386
|
+
if response.header['location']
|
387
|
+
response.header['location']
|
388
|
+
else
|
389
|
+
Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.header).first
|
390
|
+
end
|
382
391
|
else
|
383
392
|
raise "HTTP POST failed! #{response_msg}"
|
384
393
|
end
|
@@ -481,6 +490,7 @@ module Occi
|
|
481
490
|
Occi::Log.debug e.message
|
482
491
|
|
483
492
|
if @authn_plugin.fallbacks.any?
|
493
|
+
# TODO: multiple fallbacks
|
484
494
|
@auth_options[:original_type] = @auth_options[:type]
|
485
495
|
@auth_options[:type] = @authn_plugin.fallbacks.first
|
486
496
|
|
@@ -16,7 +16,7 @@ module Occi::Api::Client
|
|
16
16
|
def setup(options = {}); end
|
17
17
|
|
18
18
|
def authenticate(options = {})
|
19
|
-
response = @env_ref.class.head @env_ref.endpoint
|
19
|
+
response = @env_ref.class.head "#{@env_ref.endpoint}-/"
|
20
20
|
raise ::Occi::Api::Client::Errors::AuthnError, "Authentication failed with code #{response.code.to_s}!" unless response.success?
|
21
21
|
end
|
22
22
|
|
@@ -5,25 +5,50 @@ module Occi::Api::Client
|
|
5
5
|
class Keystone < Base
|
6
6
|
|
7
7
|
def setup(options = {})
|
8
|
-
|
8
|
+
# get Keystone URL if possible, get unscoped token
|
9
|
+
set_keystone_base_url
|
10
|
+
set_auth_token
|
11
|
+
|
12
|
+
# use unscoped token for tenant discovery, get scoped token
|
13
|
+
tenant = get_prefered_tenant
|
14
|
+
set_auth_token(tenant)
|
15
|
+
end
|
16
|
+
|
17
|
+
def authenticate(options = {})
|
18
|
+
# OCCI-OS doesn't support HEAD method!
|
19
|
+
response = @env_ref.class.get "#{@env_ref.endpoint}-/"
|
20
|
+
raise ::Occi::Api::Client::Errors::AuthnError, "Authentication failed with code #{response.code.to_s}!" unless response.success?
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def set_keystone_base_url
|
26
|
+
response = @env_ref.class.head "#{@env_ref.endpoint}-/"
|
9
27
|
Occi::Log.debug response.inspect
|
10
28
|
|
11
29
|
return if response.success?
|
12
30
|
raise ::Occi::Api::Client::Errors::AuthnError, "Keystone AuthN failed with #{response.code.to_s}!" unless response.code == 401
|
13
31
|
|
14
32
|
unless response.headers['www-authenticate'] && response.headers['www-authenticate'].start_with?('Keystone')
|
15
|
-
raise ::Occi::Api::Client::Errors::AuthnError, "Target endpoint is probably not OpenStack!"
|
33
|
+
raise ::Occi::Api::Client::Errors::AuthnError, "Target endpoint is probably not OpenStack, fallback failed!"
|
16
34
|
end
|
17
35
|
|
18
|
-
|
36
|
+
@keystone_url = /^Keystone uri='(.+)'$/.match(response.headers['www-authenticate'])[1]
|
37
|
+
raise ::Occi::Api::Client::Errors::AuthnError, "Unable to get Keystone's URL from the response!" unless @keystone_url
|
19
38
|
|
20
|
-
|
39
|
+
@keystone_url = @keystone_url.chomp('/')
|
40
|
+
end
|
21
41
|
|
42
|
+
def set_auth_token(tenant = nil)
|
22
43
|
headers = @env_ref.class.headers.clone
|
23
44
|
headers['Content-Type'] = "application/json"
|
24
45
|
headers['Accept'] = headers['Content-Type']
|
25
46
|
|
26
|
-
response = @env_ref.class.post(
|
47
|
+
response = @env_ref.class.post(
|
48
|
+
"#{@keystone_url}/v2.0/tokens",
|
49
|
+
:body => get_keystone_req(tenant),
|
50
|
+
:headers => headers
|
51
|
+
)
|
27
52
|
Occi::Log.debug response.inspect
|
28
53
|
|
29
54
|
if response.success?
|
@@ -33,9 +58,7 @@ module Occi::Api::Client
|
|
33
58
|
end
|
34
59
|
end
|
35
60
|
|
36
|
-
|
37
|
-
|
38
|
-
def get_keystone_req(json = true)
|
61
|
+
def get_keystone_req(tenant = nil)
|
39
62
|
if @options[:original_type] == "x509"
|
40
63
|
body = { "auth" => { "voms" => true } }
|
41
64
|
elsif @options[:username] && @options[:password]
|
@@ -51,7 +74,27 @@ module Occi::Api::Client
|
|
51
74
|
raise ::Occi::Api::Client::Errors::AuthnError, "Unable to request a token from Keystone! Chosen AuthN not supported."
|
52
75
|
end
|
53
76
|
|
54
|
-
|
77
|
+
body['auth']['tenantName'] = tenant if tenant && !tenant.empty?
|
78
|
+
body.to_json
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_prefered_tenant(match = nil)
|
82
|
+
headers = @env_ref.class.headers.clone
|
83
|
+
headers['Content-Type'] = "application/json"
|
84
|
+
headers['Accept'] = headers['Content-Type']
|
85
|
+
|
86
|
+
response = @env_ref.class.get(
|
87
|
+
"#{@keystone_url}/v2.0/tenants",
|
88
|
+
:headers => headers
|
89
|
+
)
|
90
|
+
Occi::Log.debug response.inspect
|
91
|
+
|
92
|
+
# TODO: impl match with regexp in case of multiple tenants?
|
93
|
+
raise ::Occi::Api::Client::Errors::AuthnError, "Keystone didn't return any tenants!" unless response['tenants'] && response['tenants'].first
|
94
|
+
tenant = response['tenants'].first['name'] if response.success?
|
95
|
+
raise ::Occi::Api::Client::Errors::AuthnError, "Unable to get a tenant from Keystone!" unless tenant
|
96
|
+
|
97
|
+
tenant
|
55
98
|
end
|
56
99
|
|
57
100
|
end
|
@@ -3,44 +3,20 @@ module HTTParty
|
|
3
3
|
|
4
4
|
private
|
5
5
|
|
6
|
-
|
7
|
-
if http.use_ssl?
|
8
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
9
|
-
|
10
|
-
# Client certificate authentication
|
11
|
-
if options[:pem]
|
12
|
-
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
|
13
|
-
http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
|
14
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
15
|
-
end
|
16
|
-
|
17
|
-
# Set chain of client certificates
|
18
|
-
if options[:ssl_extra_chain_cert]
|
19
|
-
http.extra_chain_cert = []
|
20
|
-
|
21
|
-
options[:ssl_extra_chain_cert].each do |p_ca|
|
22
|
-
http.extra_chain_cert << OpenSSL::X509::Certificate.new(p_ca)
|
23
|
-
end
|
24
|
-
end
|
6
|
+
alias_method :old_attach_ssl_certificates, :attach_ssl_certificates
|
25
7
|
|
26
|
-
|
27
|
-
|
28
|
-
http.ca_file = options[:ssl_ca_file]
|
29
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
30
|
-
end
|
8
|
+
def attach_ssl_certificates(http, options)
|
9
|
+
old_attach_ssl_certificates(http, options)
|
31
10
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
11
|
+
# Set chain of client certificates
|
12
|
+
if options[:ssl_extra_chain_cert]
|
13
|
+
http.extra_chain_cert = []
|
36
14
|
|
37
|
-
|
38
|
-
|
39
|
-
http.ssl_version = options[:ssl_version]
|
15
|
+
options[:ssl_extra_chain_cert].each do |p_ca|
|
16
|
+
http.extra_chain_cert << OpenSSL::X509::Certificate.new(p_ca)
|
40
17
|
end
|
41
18
|
end
|
42
19
|
end
|
43
|
-
|
44
20
|
end
|
45
21
|
|
46
22
|
module ClassMethods
|