idcf-ilb 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +93 -0
- data/.travis.yml +9 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/idcf-ilb.gemspec +29 -0
- data/lib/idcf/ilb.rb +13 -0
- data/lib/idcf/ilb/client.rb +180 -0
- data/lib/idcf/ilb/client_extensions.rb +24 -0
- data/lib/idcf/ilb/client_extensions/config.rb +97 -0
- data/lib/idcf/ilb/client_extensions/fwgroup.rb +57 -0
- data/lib/idcf/ilb/client_extensions/job.rb +75 -0
- data/lib/idcf/ilb/client_extensions/limit.rb +24 -0
- data/lib/idcf/ilb/client_extensions/loadbalancer.rb +79 -0
- data/lib/idcf/ilb/client_extensions/log.rb +30 -0
- data/lib/idcf/ilb/client_extensions/network.rb +26 -0
- data/lib/idcf/ilb/client_extensions/server.rb +46 -0
- data/lib/idcf/ilb/client_extensions/sslalgorithm.rb +43 -0
- data/lib/idcf/ilb/client_extensions/sslcert.rb +79 -0
- data/lib/idcf/ilb/client_extensions/sslpolicy.rb +58 -0
- data/lib/idcf/ilb/client_extensions/traffic.rb +35 -0
- data/lib/idcf/ilb/client_extensions/virtualmachine.rb +29 -0
- data/lib/idcf/ilb/errors.rb +10 -0
- data/lib/idcf/ilb/request.rb +97 -0
- data/lib/idcf/ilb/resources.rb +22 -0
- data/lib/idcf/ilb/resources/base.rb +60 -0
- data/lib/idcf/ilb/resources/config.rb +21 -0
- data/lib/idcf/ilb/resources/fwgroup.rb +16 -0
- data/lib/idcf/ilb/resources/job.rb +16 -0
- data/lib/idcf/ilb/resources/limit.rb +13 -0
- data/lib/idcf/ilb/resources/loadbalancer.rb +16 -0
- data/lib/idcf/ilb/resources/log.rb +13 -0
- data/lib/idcf/ilb/resources/network.rb +9 -0
- data/lib/idcf/ilb/resources/nic.rb +13 -0
- data/lib/idcf/ilb/resources/server.rb +9 -0
- data/lib/idcf/ilb/resources/sslalgorithm.rb +21 -0
- data/lib/idcf/ilb/resources/sslcert.rb +16 -0
- data/lib/idcf/ilb/resources/sslpolicy.rb +23 -0
- data/lib/idcf/ilb/resources/traffic.rb +9 -0
- data/lib/idcf/ilb/resources/virtualmachine.rb +13 -0
- data/lib/idcf/ilb/response.rb +84 -0
- data/lib/idcf/ilb/validators.rb +22 -0
- data/lib/idcf/ilb/validators/base.rb +105 -0
- data/lib/idcf/ilb/validators/config.rb +27 -0
- data/lib/idcf/ilb/validators/fwgroup.rb +17 -0
- data/lib/idcf/ilb/validators/job.rb +18 -0
- data/lib/idcf/ilb/validators/limit.rb +17 -0
- data/lib/idcf/ilb/validators/loadbalancer.rb +26 -0
- data/lib/idcf/ilb/validators/log.rb +22 -0
- data/lib/idcf/ilb/validators/network.rb +25 -0
- data/lib/idcf/ilb/validators/server.rb +13 -0
- data/lib/idcf/ilb/validators/sslalgorithm.rb +14 -0
- data/lib/idcf/ilb/validators/sslcert.rb +24 -0
- data/lib/idcf/ilb/validators/sslpolicy.rb +17 -0
- data/lib/idcf/ilb/validators/traffic.rb +14 -0
- data/lib/idcf/ilb/validators/virtualmachine.rb +26 -0
- data/lib/idcf/ilb/version.rb +5 -0
- metadata +232 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
module Idcf
|
2
|
+
module Ilb
|
3
|
+
module ClientExtensions
|
4
|
+
# SDK APIs for sslalgorithm resource
|
5
|
+
module Sslalgorithm
|
6
|
+
# Get list of sslalgorithms
|
7
|
+
#
|
8
|
+
# @param headers [Hash] HTTP request headers
|
9
|
+
# @return [Response] HTTP response object
|
10
|
+
def list_sslalgorithms(headers = {})
|
11
|
+
get!("sslpolicies/algorithms", {}, headers)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Get a sslalgorithm
|
15
|
+
#
|
16
|
+
# @param id [String] ID of target sslalgorithm
|
17
|
+
# @param headers [Hash] HTTP request headers
|
18
|
+
# @return [Response] HTTP response object
|
19
|
+
def get_sslalgorithm(id, headers = {})
|
20
|
+
get!("sslpolicies/algorithms/#{id}", {}, headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get an array of sslalgorithm objects.
|
24
|
+
#
|
25
|
+
# @param headers [Hash] HTTP request headers
|
26
|
+
# @return [Array<Resources::Sslalgorithm>] An array of sslalgorithm objects
|
27
|
+
def sslalgorithms(headers = {})
|
28
|
+
list_sslalgorithms(headers).resources.map do |sslalgorithm|
|
29
|
+
Resources::Sslalgorithm.new(self, sslalgorithm)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get a hash of sslalgorithm id array
|
34
|
+
#
|
35
|
+
# @param headers [Hash] HTTP request headers
|
36
|
+
# @return {"algorithms": [ {"id": <sslalgorithm.id>}, ..*] }
|
37
|
+
def sslalgorithms_ids(headers = {})
|
38
|
+
{ algorithms: sslalgorithms(headers).collect { |a| { id: a.id } } }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Idcf
|
2
|
+
module Ilb
|
3
|
+
module ClientExtensions
|
4
|
+
# SDK APIs for sslcert resource
|
5
|
+
module Sslcert
|
6
|
+
# Create a new sslcert.
|
7
|
+
#
|
8
|
+
# @param attributes [Hash] request attributes
|
9
|
+
# @option attributes [String] :name unique name of sslcert (required)
|
10
|
+
# @param headers [Hash] HTTP request headers
|
11
|
+
# @return [Response] HTTP response object
|
12
|
+
def create_sslcert(attributes, headers = {})
|
13
|
+
fixed_attributes = { type: "system" }
|
14
|
+
attributes.merge!(fixed_attributes)
|
15
|
+
Validators::Sslcert.validate_attributes!(attributes, :create)
|
16
|
+
res = post!("sslcerts", attributes, headers)
|
17
|
+
check_job(res.body["job_id"], headers, ["get_sslcert"])
|
18
|
+
end
|
19
|
+
|
20
|
+
# Upload a sslcert.
|
21
|
+
#
|
22
|
+
# @param attributes [Hash] request attributes
|
23
|
+
# @option attributes [String] :name unique name of sslcert (required)
|
24
|
+
# @option attributes [String] :certificate certificate of sslcert (required)
|
25
|
+
# @option attributes [String] :private_key private_key of sslcert (required)
|
26
|
+
# @option attributes [String] :certificate_chain certificate_chain of sslcert (optional)
|
27
|
+
# @param headers [Hash] HTTP request headers
|
28
|
+
# @return [Response] HTTP response object
|
29
|
+
def upload_sslcert(attributes, headers = {})
|
30
|
+
fixed_attributes = { type: "self" }
|
31
|
+
attributes.merge!(fixed_attributes)
|
32
|
+
Validators::Sslcert.validate_attributes!(attributes, :upload)
|
33
|
+
res = post!("sslcerts", attributes, headers)
|
34
|
+
check_job(res.body["job_id"], headers, ["get_sslcert"])
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get list of existing sslcerts
|
38
|
+
#
|
39
|
+
# @param attributes [Hash] request attributes
|
40
|
+
# @option attributes [String] :name unique name of sslcert (optional)
|
41
|
+
# @param headers [Hash] HTTP request headers
|
42
|
+
# @return [Response] HTTP response object
|
43
|
+
def list_sslcerts(attributes = {}, headers = {})
|
44
|
+
Validators::Sslcert.validate_attributes!(attributes, :list)
|
45
|
+
get!("sslcerts", attributes, headers)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get a sslcert
|
49
|
+
#
|
50
|
+
# @param id [String] ID of target sslcert
|
51
|
+
# @param headers [Hash] HTTP request headers
|
52
|
+
# @return [Response] HTTP response object
|
53
|
+
def get_sslcert(id, headers = {})
|
54
|
+
get!("sslcerts/#{id}", {}, headers)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Delete a sslcert
|
58
|
+
#
|
59
|
+
# @param id [String] ID of target sslcert
|
60
|
+
# @param headers [Hash] HTTP request headers
|
61
|
+
# @return [Boolean] delete success = true
|
62
|
+
def delete_sslcert(id, headers = {})
|
63
|
+
res = delete!("sslcerts/#{id}", {}, headers)
|
64
|
+
check_job(res.body["job_id"], headers)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get an array of existing sslcert objects.
|
68
|
+
#
|
69
|
+
# @param headers [Hash] HTTP request headers
|
70
|
+
# @return [Array<Resources::Sslcert>] An array of sslcert objects
|
71
|
+
def sslcerts(headers = {})
|
72
|
+
list_sslcerts(headers).resources.map do |sslcert|
|
73
|
+
Resources::Sslcert.new(self, sslcert)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Idcf
|
2
|
+
module Ilb
|
3
|
+
module ClientExtensions
|
4
|
+
# SDK APIs for sslpolicy resource
|
5
|
+
module Sslpolicy
|
6
|
+
# Create a new sslpolicy.
|
7
|
+
#
|
8
|
+
# @param attributes [Hash] request attributes
|
9
|
+
# @option attributes [String] :name unique name of sslpolicy (required)
|
10
|
+
# @option attributes [Array] :algorithms array of sslalgorithm IDs (required)
|
11
|
+
# @param headers [Hash] HTTP request headers
|
12
|
+
# @return [Response] HTTP response object
|
13
|
+
def create_sslpolicy(attributes, headers = {})
|
14
|
+
Validators::Sslpolicy.validate_attributes!(attributes, :create)
|
15
|
+
res = post!("sslpolicies", attributes, headers)
|
16
|
+
check_job(res.body["job_id"], headers, ["get_sslpolicy"])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get list of existing sslpolicies
|
20
|
+
#
|
21
|
+
# @param headers [Hash] HTTP request headers
|
22
|
+
# @return [Response] HTTP response object
|
23
|
+
def list_sslpolicies(headers = {})
|
24
|
+
get!("sslpolicies", {}, headers)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get a sslpolicy
|
28
|
+
#
|
29
|
+
# @param id [String] ID of target sslpolicy
|
30
|
+
# @param headers [Hash] HTTP request headers
|
31
|
+
# @return [Response] HTTP response object
|
32
|
+
def get_sslpolicy(id, headers = {})
|
33
|
+
get!("sslpolicies/#{id}", {}, headers)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Delete a sslpolicy
|
37
|
+
#
|
38
|
+
# @param id [String] ID of target sslpolicy
|
39
|
+
# @param headers [Hash] HTTP request headers
|
40
|
+
# @return [Boolean] delete success = true
|
41
|
+
def delete_sslpolicy(id, headers = {})
|
42
|
+
res = delete!("sslpolicies/#{id}", {}, headers)
|
43
|
+
check_job(res.body["job_id"], headers)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get an array of existing sslpolicy objects.
|
47
|
+
#
|
48
|
+
# @param headers [Hash] HTTP request headers
|
49
|
+
# @return [Array<Resources::Sslpolicy>] An array of sslpolicy objects
|
50
|
+
def sslpolicies(headers = {})
|
51
|
+
list_sslpolicies(headers).resources.map do |sslpolicy|
|
52
|
+
Resources::Sslpolicy.new(self, sslpolicy)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Idcf
|
2
|
+
module Ilb
|
3
|
+
module ClientExtensions
|
4
|
+
# SDK APIs for traffic resource
|
5
|
+
module Traffic
|
6
|
+
# Get list of traffics by account
|
7
|
+
#
|
8
|
+
# @param attributes [Hash] request attributes
|
9
|
+
# @option attributes [String] :from (optional)
|
10
|
+
# @option attributes [String] :to (optional)
|
11
|
+
# @option attributes [String] :unit (optional)
|
12
|
+
# @param headers [Hash] HTTP request headers
|
13
|
+
# @return [Response] HTTP response object
|
14
|
+
def list_traffics_by_account(attributes = {}, headers = {})
|
15
|
+
Validators::Traffic.validate_attributes!(attributes, :list)
|
16
|
+
get!("traffics", attributes, headers)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get traffic by loadbalancer
|
20
|
+
#
|
21
|
+
# @param loadbalancer_id [String] ID of target loadbalancer
|
22
|
+
# @param attributes [Hash] request attributes
|
23
|
+
# @option attributes [String] :from (optional)
|
24
|
+
# @option attributes [String] :to (optional)
|
25
|
+
# @option attributes [String] :unit (optional) (MB|GB|TB)
|
26
|
+
# @param headers [Hash] HTTP request headers
|
27
|
+
# @return [Response] HTTP response object
|
28
|
+
def get_traffic_by_loadbalancer(loadbalancer_id, attributes = {}, headers = {})
|
29
|
+
Validators::Traffic.validate_attributes!(attributes, :list)
|
30
|
+
get!("traffics/loadbalancers/#{loadbalancer_id}", attributes, headers)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Idcf
|
2
|
+
module Ilb
|
3
|
+
module ClientExtensions
|
4
|
+
# SDK APIs for virtualmachine resource
|
5
|
+
module Virtualmachine
|
6
|
+
# Get list of virtualmachines
|
7
|
+
#
|
8
|
+
# @param attributes [Hash] request attributes
|
9
|
+
# @option attributes [String] :name virtualmachine name (optional)
|
10
|
+
# @param headers [Hash] HTTP request headers
|
11
|
+
# @return [Response] HTTP response object
|
12
|
+
def list_virtualmachines(attributes = {}, headers = {})
|
13
|
+
Validators::Virtualmachine.validate_attributes!(attributes, :list)
|
14
|
+
get!("virtualmachines", attributes, headers)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get an array of existing virtualmachine objects.
|
18
|
+
#
|
19
|
+
# @param headers [Hash] HTTP request headers
|
20
|
+
# @return [Array<Resources::Virtualmachine>] An array of virtualmachine objects
|
21
|
+
def virtualmachines(headers = {})
|
22
|
+
list_virtualmachines(headers).resources.map do |virtualmachine|
|
23
|
+
Resources::Virtualmachine.new(self, virtualmachine)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Idcf
|
2
|
+
module Ilb
|
3
|
+
Error = Class.new(StandardError)
|
4
|
+
ApiError = Class.new(Error)
|
5
|
+
InvalidAttributeName = Class.new(Error)
|
6
|
+
InvalidAttributeType = Class.new(Error)
|
7
|
+
MissingAttribute = Class.new(Error)
|
8
|
+
UnnecessaryAttribute = Class.new(Error)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "openssl"
|
3
|
+
|
4
|
+
module Idcf
|
5
|
+
module Ilb
|
6
|
+
# - Send HTTP request
|
7
|
+
# - Generate signatures
|
8
|
+
# - Set default expires
|
9
|
+
class Request
|
10
|
+
attr_reader :client, :method, :resource
|
11
|
+
attr_reader :parameters
|
12
|
+
|
13
|
+
# @private
|
14
|
+
def initialize(client, method, resource, parameters, headers)
|
15
|
+
@client = client
|
16
|
+
@method = method
|
17
|
+
@resource = resource
|
18
|
+
@parameters = parameters
|
19
|
+
@headers = headers
|
20
|
+
end
|
21
|
+
|
22
|
+
# @private
|
23
|
+
def send
|
24
|
+
Response.new(
|
25
|
+
client.connection.send(
|
26
|
+
method,
|
27
|
+
path,
|
28
|
+
parameters,
|
29
|
+
headers
|
30
|
+
)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @private
|
35
|
+
def headers
|
36
|
+
auth_headers.merge(
|
37
|
+
@headers.reject { |key, _| key.to_s == "X-IDCF-Expires" }
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def api_key
|
44
|
+
client.api_key
|
45
|
+
end
|
46
|
+
|
47
|
+
def auth_headers
|
48
|
+
{
|
49
|
+
"X-IDCF-Signature" => signature,
|
50
|
+
"X-IDCF-APIKEY" => api_key,
|
51
|
+
"X-IDCF-Expires" => expires
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def body
|
56
|
+
if method == :post || method == :put || method == :patch
|
57
|
+
parameters.to_json
|
58
|
+
else
|
59
|
+
""
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def expires
|
64
|
+
@expires ||=
|
65
|
+
(@headers["X-IDCF-Expires"] || @headers[:"X-IDCF-Expires"] || Time.now.to_i + 600).to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def path
|
69
|
+
"#{client.endpoint}/#{resource}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def secret_key
|
73
|
+
client.secret_key
|
74
|
+
end
|
75
|
+
|
76
|
+
def signature
|
77
|
+
Base64.encode64(
|
78
|
+
OpenSSL::HMAC.digest(
|
79
|
+
OpenSSL::Digest::SHA256.new,
|
80
|
+
secret_key,
|
81
|
+
signature_seed
|
82
|
+
)
|
83
|
+
).chomp
|
84
|
+
end
|
85
|
+
|
86
|
+
def signature_seed
|
87
|
+
[
|
88
|
+
method.to_s.upcase,
|
89
|
+
path,
|
90
|
+
api_key,
|
91
|
+
expires,
|
92
|
+
""
|
93
|
+
].join("\n")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Idcf
|
2
|
+
module Ilb
|
3
|
+
# ILB resources module
|
4
|
+
module Resources
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require "idcf/ilb/resources/base"
|
10
|
+
require "idcf/ilb/resources/loadbalancer"
|
11
|
+
require "idcf/ilb/resources/config"
|
12
|
+
require "idcf/ilb/resources/server"
|
13
|
+
require "idcf/ilb/resources/job"
|
14
|
+
require "idcf/ilb/resources/sslcert"
|
15
|
+
require "idcf/ilb/resources/sslalgorithm"
|
16
|
+
require "idcf/ilb/resources/sslpolicy"
|
17
|
+
require "idcf/ilb/resources/fwgroup"
|
18
|
+
require "idcf/ilb/resources/network"
|
19
|
+
require "idcf/ilb/resources/virtualmachine"
|
20
|
+
require "idcf/ilb/resources/limit"
|
21
|
+
require "idcf/ilb/resources/log"
|
22
|
+
require "idcf/ilb/resources/traffic"
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "active_support/core_ext/string/inflections"
|
2
|
+
|
3
|
+
module Idcf
|
4
|
+
module Ilb
|
5
|
+
module Resources
|
6
|
+
# Base resource class
|
7
|
+
class Base
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# @private
|
12
|
+
def inherited(subclass)
|
13
|
+
subclass.send(:generate_readers)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @private
|
17
|
+
def class_name
|
18
|
+
to_s.gsub(/^#<Class|>$/, "").split(/::/).last
|
19
|
+
end
|
20
|
+
|
21
|
+
# @private
|
22
|
+
def generate_readers
|
23
|
+
validator_class.valid_attributes.each do |name, properties|
|
24
|
+
next if properties[:reader] == false
|
25
|
+
attr_reader name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
def validator_class
|
31
|
+
"Idcf::Ilb::Validators::#{class_name}".constantize
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Constructor
|
36
|
+
#
|
37
|
+
# @param client [Client] client object
|
38
|
+
# @param attributes [Hash] attributes for resource
|
39
|
+
def initialize(client, attributes = {})
|
40
|
+
@client = client
|
41
|
+
self.attributes = attributes
|
42
|
+
end
|
43
|
+
|
44
|
+
# Inspect this class
|
45
|
+
def inspect
|
46
|
+
"#<#{self.class}:0x%014x @name=#{name} @id=#{id}>" % [object_id]
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def attributes=(attributes)
|
52
|
+
self.class.validator_class.validate_attributes!(attributes)
|
53
|
+
attributes.each do |name, value|
|
54
|
+
instance_variable_set(:"@#{name}", value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Idcf
|
2
|
+
module Ilb
|
3
|
+
module Resources
|
4
|
+
# Config resource class
|
5
|
+
class Config < Base
|
6
|
+
# Refresh this config
|
7
|
+
#
|
8
|
+
# @return [Config] self object
|
9
|
+
def refresh
|
10
|
+
self.attributes = client.get_config(id).body
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
o_id = object_id
|
16
|
+
"#<#{self.class}:0x%014x @protocol=#{frontend_protocol} @port=#{port} @id=#{id}>" % [o_id]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|