aws-partitions 1.0.0
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/lib/aws-partitions.rb +306 -0
- data/lib/aws-partitions/endpoint_provider.rb +115 -0
- data/lib/aws-partitions/partition.rb +95 -0
- data/lib/aws-partitions/partition_list.rb +62 -0
- data/lib/aws-partitions/region.rb +66 -0
- data/lib/aws-partitions/service.rb +74 -0
- data/partitions.json +1628 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bcd64cee1eb0c323f8692ff5a4c7dca11389772b
|
4
|
+
data.tar.gz: 0adcedec22ecafcc3d905eef56a8e44db74ab6d3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e5bf87351f61caedae8f502e12e6697fd30983961307e629ba2829990a95e6e58abd16ab4602a6f602ed9f308ece8779f4ea5313fc92c2bce084e98da8ef77cb
|
7
|
+
data.tar.gz: ecdb63091ac2d9a8023ab1637d95299a4d418d7d2a2c0131c13166129d1eb89b02ed7d5d7a0773a8f0e04696e35477b592c514d964f651f987e358b8dc6a1764
|
@@ -0,0 +1,306 @@
|
|
1
|
+
require_relative 'aws-partitions/endpoint_provider'
|
2
|
+
require_relative 'aws-partitions/partition'
|
3
|
+
require_relative 'aws-partitions/partition_list'
|
4
|
+
require_relative 'aws-partitions/region'
|
5
|
+
require_relative 'aws-partitions/service'
|
6
|
+
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
module Aws
|
10
|
+
|
11
|
+
# A {Partition} is a group of AWS {Region} and {Service} objects. You
|
12
|
+
# can use a partition to determine what services are available in a region,
|
13
|
+
# or what regions a service is available in.
|
14
|
+
#
|
15
|
+
# ## Partitions
|
16
|
+
#
|
17
|
+
# **AWS accounts are scoped to a single partition**. You can get a partition
|
18
|
+
# by name. Valid partition names include:
|
19
|
+
#
|
20
|
+
# * `"aws"` - Public AWS partition
|
21
|
+
# * `"aws-cn"` - AWS China
|
22
|
+
# * `"aws-us-gov"` - AWS GovCloud
|
23
|
+
#
|
24
|
+
# To get a partition by name:
|
25
|
+
#
|
26
|
+
# aws = Aws::Partitions.partition('aws')
|
27
|
+
#
|
28
|
+
# You can also enumerate all partitions:
|
29
|
+
#
|
30
|
+
# Aws::Partitions.each do |partition|
|
31
|
+
# puts partition.name
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# ## Regions
|
35
|
+
#
|
36
|
+
# A {Partition} is divided up into one or more regions. For example, the
|
37
|
+
# "aws" partition contains, "us-east-1", "us-west-1", etc. You can get
|
38
|
+
# a region by name. Calling {Partition#region} will return an instance
|
39
|
+
# of {Region}.
|
40
|
+
#
|
41
|
+
# region = Aws::Partitions.partition('aws').region('us-west-2')
|
42
|
+
# region.name
|
43
|
+
# #=> "us-west-2"
|
44
|
+
#
|
45
|
+
# You can also enumerate all regions within a partition:
|
46
|
+
#
|
47
|
+
# Aws::Partitions.partition('aws').regions.each do |region|
|
48
|
+
# puts region.name
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# Each {Region} object has a name, description and a list of services
|
52
|
+
# available to that region:
|
53
|
+
#
|
54
|
+
# us_west_2 = Aws::Partitions.partition('aws').region('us-west-2')
|
55
|
+
#
|
56
|
+
# us_west_2.name #=> "us-west-2"
|
57
|
+
# us_west_2.description #=> "US West (Oregon)"
|
58
|
+
# us_west_2.partition_name "aws"
|
59
|
+
# us_west_2.services #=> #<Set: {"APIGateway", "AutoScaling", ... }
|
60
|
+
#
|
61
|
+
# To know if a service is available within a region, you can call `#include?`
|
62
|
+
# on the set of service names:
|
63
|
+
#
|
64
|
+
# region.services.include?('DynamoDB') #=> true/false
|
65
|
+
#
|
66
|
+
# The service name should be the service's module name as used by
|
67
|
+
# the AWS SDK for Ruby. To find the complete list of supported
|
68
|
+
# service names, see {Partition#services}.
|
69
|
+
#
|
70
|
+
# Its also possible to enumerate every service for every region in
|
71
|
+
# every partition.
|
72
|
+
#
|
73
|
+
# Aws::Partitions.partitions.each do |partition|
|
74
|
+
# partition.regions.each do |region|
|
75
|
+
# region.services.each do |service_name|
|
76
|
+
# puts "#{partition.name} -> #{region.name} -> #{service_name}"
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# ## Services
|
82
|
+
#
|
83
|
+
# A {Partition} has a list of services available. You can get a
|
84
|
+
# single {Service} by name:
|
85
|
+
#
|
86
|
+
# Aws::Partitions.partition('aws').service('DynamoDB')
|
87
|
+
#
|
88
|
+
# You can also enumerate all services in a partition:
|
89
|
+
#
|
90
|
+
# Aws::Partitions.partition('aws').services.each do |service|
|
91
|
+
# puts service.name
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# Each {Service} object has a name, and information about regions
|
95
|
+
# that service is available in.
|
96
|
+
#
|
97
|
+
# service.name #=> "DynamoDB"
|
98
|
+
# service.partition_name #=> "aws"
|
99
|
+
# service.regions #=> #<Set: {"us-east-1", "us-west-1", ... }
|
100
|
+
#
|
101
|
+
# Some services have multiple regions, and others have a single partition
|
102
|
+
# wide region. For example, {Aws::IAM} has a single region in the "aws"
|
103
|
+
# partition. The {Service#regionalized?} method indicates when this is
|
104
|
+
# the case.
|
105
|
+
#
|
106
|
+
# iam = Aws::Partitions.partition('aws').service('IAM')
|
107
|
+
#
|
108
|
+
# iam.regionalized? #=> false
|
109
|
+
# service.partition_region #=> "aws-global"
|
110
|
+
#
|
111
|
+
# Its also possible to enumerate every region for every service in
|
112
|
+
# every partition.
|
113
|
+
#
|
114
|
+
# Aws::Partitions.partitions.each do |partition|
|
115
|
+
# partition.services.each do |service|
|
116
|
+
# service.regions.each do |region_name|
|
117
|
+
# puts "#{partition.name} -> #{region_name} -> #{service.name}"
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# ## Service Names
|
123
|
+
#
|
124
|
+
# {Service} names are those used by the the AWS SDK for Ruby. They
|
125
|
+
# correspond to the service's module.
|
126
|
+
#
|
127
|
+
module Partitions
|
128
|
+
|
129
|
+
class << self
|
130
|
+
|
131
|
+
include Enumerable
|
132
|
+
|
133
|
+
# @return [Enumerable<Partition>]
|
134
|
+
def each(&block)
|
135
|
+
default_partition_list.each(&block)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Return the partition with the given name. A partition describes
|
139
|
+
# the services and regions available in that partition.
|
140
|
+
#
|
141
|
+
# aws = Aws::Partitions.partition('aws')
|
142
|
+
#
|
143
|
+
# puts "Regions available in the aws partition:\n"
|
144
|
+
# aws.regions.each do |region|
|
145
|
+
# puts region.name
|
146
|
+
# end
|
147
|
+
#
|
148
|
+
# puts "Services available in the aws partition:\n"
|
149
|
+
# aws.services.each do |services|
|
150
|
+
# puts services.name
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# @param [String] name The name of the partition to return.
|
154
|
+
# Valid names include "aws", "aws-cn", and "aws-us-gov".
|
155
|
+
#
|
156
|
+
# @return [Partition]
|
157
|
+
#
|
158
|
+
# @raise [ArgumentError] Raises an `ArgumentError` if a partition is
|
159
|
+
# not found with the given name. The error message contains a list
|
160
|
+
# of valid partition names.
|
161
|
+
def partition(name)
|
162
|
+
default_partition_list.partition(name)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns an array with every partitions. A partition describes
|
166
|
+
# the services and regions available in that partition.
|
167
|
+
#
|
168
|
+
# Aws::Partitions.partitions.each do |partition|
|
169
|
+
#
|
170
|
+
# puts "Regions available in #{partition.name}:\n"
|
171
|
+
# partition.regions.each do |region|
|
172
|
+
# puts region.name
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# puts "Services available in #{partition.name}:\n"
|
176
|
+
# partition.services.each do |service|
|
177
|
+
# puts service.name
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# @return [Enumerable<Partition>] Returns an enumerable of all
|
182
|
+
# known partitions.
|
183
|
+
def partitions
|
184
|
+
default_partition_list
|
185
|
+
end
|
186
|
+
|
187
|
+
# @param [Hash] new_partitions
|
188
|
+
# @api private For internal use only.
|
189
|
+
def add(new_partitions)
|
190
|
+
new_partitions['partitions'].each do |partition|
|
191
|
+
default_partition_list.add_partition(Partition.build(partition))
|
192
|
+
defaults['partitions'] << partition
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# @api private For internal use only.
|
197
|
+
def clear
|
198
|
+
default_partition_list.clear
|
199
|
+
defaults['partitions'].clear
|
200
|
+
end
|
201
|
+
|
202
|
+
# @return [PartitionList]
|
203
|
+
# @api private
|
204
|
+
def default_partition_list
|
205
|
+
@default_partition_list ||= PartitionList.build(defaults)
|
206
|
+
end
|
207
|
+
|
208
|
+
# @return [Hash]
|
209
|
+
# @api private
|
210
|
+
def defaults
|
211
|
+
@defaults ||= begin
|
212
|
+
path = File.expand_path('../../partitions.json', __FILE__)
|
213
|
+
JSON.load(File.read(path))
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# @return [Hash<String,String>] Returns a map of service module names
|
218
|
+
# to their id as used in the endpoints.json document.
|
219
|
+
# @api private For internal use only.
|
220
|
+
def service_ids
|
221
|
+
@service_ids ||= begin
|
222
|
+
# service ids
|
223
|
+
{
|
224
|
+
'ACM' => 'acm',
|
225
|
+
'APIGateway' => 'apigateway',
|
226
|
+
'ApplicationAutoScaling' => 'application-autoscaling',
|
227
|
+
'ApplicationDiscoveryService' => 'discovery',
|
228
|
+
'AutoScaling' => 'autoscaling',
|
229
|
+
'Budgets' => 'budgets',
|
230
|
+
'CloudFormation' => 'cloudformation',
|
231
|
+
'CloudFront' => 'cloudfront',
|
232
|
+
'CloudHSM' => 'cloudhsm',
|
233
|
+
'CloudSearch' => 'cloudsearch',
|
234
|
+
'CloudTrail' => 'cloudtrail',
|
235
|
+
'CloudWatch' => 'monitoring',
|
236
|
+
'CloudWatchEvents' => 'events',
|
237
|
+
'CloudWatchLogs' => 'logs',
|
238
|
+
'CodeCommit' => 'codecommit',
|
239
|
+
'CodeDeploy' => 'codedeploy',
|
240
|
+
'CodePipeline' => 'codepipeline',
|
241
|
+
'CognitoIdentity' => 'cognito-identity',
|
242
|
+
'CognitoIdentityProvider' => 'cognito-idp',
|
243
|
+
'CognitoSync' => 'cognito-sync',
|
244
|
+
'ConfigService' => 'config',
|
245
|
+
'DataPipeline' => 'datapipeline',
|
246
|
+
'DatabaseMigrationService' => 'dms',
|
247
|
+
'DeviceFarm' => 'devicefarm',
|
248
|
+
'DirectConnect' => 'directconnect',
|
249
|
+
'DirectoryService' => 'ds',
|
250
|
+
'DynamoDB' => 'dynamodb',
|
251
|
+
'DynamoDBStreams' => 'streams.dynamodb',
|
252
|
+
'EC2' => 'ec2',
|
253
|
+
'ECR' => 'ecr',
|
254
|
+
'ECS' => 'ecs',
|
255
|
+
'EFS' => 'elasticfilesystem',
|
256
|
+
'EMR' => 'elasticmapreduce',
|
257
|
+
'ElastiCache' => 'elasticache',
|
258
|
+
'ElasticBeanstalk' => 'elasticbeanstalk',
|
259
|
+
'ElasticLoadBalancing' => 'elasticloadbalancing',
|
260
|
+
'ElasticLoadBalancingV2' => 'elasticloadbalancing',
|
261
|
+
'ElasticTranscoder' => 'elastictranscoder',
|
262
|
+
'ElasticsearchService' => 'es',
|
263
|
+
'Firehose' => 'firehose',
|
264
|
+
'GameLift' => 'gamelift',
|
265
|
+
'Glacier' => 'glacier',
|
266
|
+
'IAM' => 'iam',
|
267
|
+
'ImportExport' => 'importexport',
|
268
|
+
'Inspector' => 'inspector',
|
269
|
+
'IoT' => 'iot',
|
270
|
+
'IoTDataPlane' => 'data.iot',
|
271
|
+
'KMS' => 'kms',
|
272
|
+
'Kinesis' => 'kinesis',
|
273
|
+
'KinesisAnalytics' => 'kinesisanalytics',
|
274
|
+
'Lambda' => 'lambda',
|
275
|
+
'LambdaPreview' => 'lambda',
|
276
|
+
'MachineLearning' => 'machinelearning',
|
277
|
+
'MarketplaceCommerceAnalytics' => 'marketplacecommerceanalytics',
|
278
|
+
'MarketplaceMetering' => 'metering.marketplace',
|
279
|
+
'OpsWorks' => 'opsworks',
|
280
|
+
'RDS' => 'rds',
|
281
|
+
'Redshift' => 'redshift',
|
282
|
+
'Route53' => 'route53',
|
283
|
+
'Route53Domains' => 'route53domains',
|
284
|
+
'S3' => 's3',
|
285
|
+
'SES' => 'email',
|
286
|
+
'SMS' => 'sms',
|
287
|
+
'SNS' => 'sns',
|
288
|
+
'SQS' => 'sqs',
|
289
|
+
'SSM' => 'ssm',
|
290
|
+
'STS' => 'sts',
|
291
|
+
'SWF' => 'swf',
|
292
|
+
'ServiceCatalog' => 'servicecatalog',
|
293
|
+
'SimpleDB' => 'sdb',
|
294
|
+
'Snowball' => 'snowball',
|
295
|
+
'StorageGateway' => 'storagegateway',
|
296
|
+
'Support' => 'support',
|
297
|
+
'WAF' => 'waf',
|
298
|
+
'WorkSpaces' => 'workspaces',
|
299
|
+
}
|
300
|
+
# end service ids
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Aws
|
2
|
+
module Partitions
|
3
|
+
# @api private
|
4
|
+
class EndpointProvider
|
5
|
+
|
6
|
+
# Intentionally marked private. The format of the endpoint rules
|
7
|
+
# is an implementation detail.
|
8
|
+
# @api private
|
9
|
+
def initialize(rules)
|
10
|
+
@rules = rules
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [String] region
|
14
|
+
# @param [String] service The endpoint prefix for the service, e.g. "monitoring" for
|
15
|
+
# cloudwatch.
|
16
|
+
# @api private Use the static class methods instead.
|
17
|
+
def resolve(region, service)
|
18
|
+
"https://" + endpoint_for(region, service)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @api private Use the static class methods instead.
|
22
|
+
def signing_region(region, service)
|
23
|
+
get_partition(region).
|
24
|
+
fetch("services", {}).
|
25
|
+
fetch(service, {}).
|
26
|
+
fetch("endpoints", {}).
|
27
|
+
fetch(region, {}).
|
28
|
+
fetch("credentialScope", {}).
|
29
|
+
fetch("region", region)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private Use the static class methods instead.
|
33
|
+
def dns_suffix_for(region)
|
34
|
+
partition = get_partition(region)
|
35
|
+
partition['dnsSuffix']
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def endpoint_for(region, service)
|
41
|
+
partition = get_partition(region)
|
42
|
+
endpoint = default_endpoint(partition, service, region)
|
43
|
+
service_cfg = partition.fetch("services", {}).fetch(service, {})
|
44
|
+
|
45
|
+
# Check for service-level default endpoint.
|
46
|
+
endpoint = service_cfg.fetch("defaults", {}).fetch("hostname", endpoint)
|
47
|
+
|
48
|
+
# Check for global endpoint.
|
49
|
+
if service_cfg["isRegionalized"] == false
|
50
|
+
region = service_cfg.fetch("partitionEndpoint", region)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Check for service/region level endpoint.
|
54
|
+
endpoint = service_cfg.fetch("endpoints", {}).
|
55
|
+
fetch(region, {}).fetch("hostname", endpoint)
|
56
|
+
|
57
|
+
endpoint
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_endpoint(partition, service, region)
|
61
|
+
hostname_template = partition["defaults"]["hostname"]
|
62
|
+
hostname_template.
|
63
|
+
sub('{region}', region).
|
64
|
+
sub('{service}', service).
|
65
|
+
sub('{dnsSuffix}', partition["dnsSuffix"])
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_partition(region)
|
69
|
+
partition_containing_region(region) ||
|
70
|
+
partition_matching_region(region) ||
|
71
|
+
default_partition
|
72
|
+
end
|
73
|
+
|
74
|
+
def partition_containing_region(region)
|
75
|
+
@rules['partitions'].find do |p|
|
76
|
+
p['regions'].key?(region)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def partition_matching_region(region)
|
81
|
+
@rules['partitions'].find do |p|
|
82
|
+
region.match(p["regionRegex"]) ||
|
83
|
+
p['services'].values.find { |svc| svc['endpoints'].key?(region) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def default_partition
|
88
|
+
@rules['partitions'].find { |p| p["partition"] == "aws" } ||
|
89
|
+
@rules['partitions'].first
|
90
|
+
end
|
91
|
+
|
92
|
+
class << self
|
93
|
+
|
94
|
+
def resolve(region, service)
|
95
|
+
default_provider.resolve(region, service)
|
96
|
+
end
|
97
|
+
|
98
|
+
def signing_region(region, service)
|
99
|
+
default_provider.signing_region(region, service)
|
100
|
+
end
|
101
|
+
|
102
|
+
def dns_suffix_for(region)
|
103
|
+
default_provider.dns_suffix_for(region)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def default_provider
|
109
|
+
@default_provider ||= EndpointProvider.new(Partitions.defaults)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Aws
|
2
|
+
module Partitions
|
3
|
+
class Partition
|
4
|
+
|
5
|
+
# @option options [required, String] :name
|
6
|
+
# @option options [required, Hash<String,Region>] :regions
|
7
|
+
# @option options [required, Hash<String,Service>] :services
|
8
|
+
# @api private
|
9
|
+
def initialize(options = {})
|
10
|
+
@name = options[:name]
|
11
|
+
@regions = options[:regions]
|
12
|
+
@services = options[:services]
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String] The partition name, e.g. "aws", "aws-cn", "aws-us-gov".
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
# @param [String] region_name The name of the region, e.g. "us-east-1".
|
19
|
+
# @return [Region]
|
20
|
+
# @raise [ArgumentError] Raises `ArgumentError` for unknown region name.
|
21
|
+
def region(region_name)
|
22
|
+
if @regions.key?(region_name)
|
23
|
+
@regions[region_name]
|
24
|
+
else
|
25
|
+
msg = "invalid region name #{region_name.inspect}; valid region "
|
26
|
+
msg << "names include %s" % [@regions.keys.join(', ')]
|
27
|
+
raise ArgumentError, msg
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Array<Region>]
|
32
|
+
def regions
|
33
|
+
@regions.values
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [String] service_name The service module name.
|
37
|
+
# @return [Service]
|
38
|
+
# @raise [ArgumentError] Raises `ArgumentError` for unknown service name.
|
39
|
+
def service(service_name)
|
40
|
+
if @services.key?(service_name)
|
41
|
+
@services[service_name]
|
42
|
+
else
|
43
|
+
msg = "invalid service name #{service_name.inspect}; valid service "
|
44
|
+
msg << "names include %s" % [@services.keys.join(', ')]
|
45
|
+
raise ArgumentError, msg
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Array<Service>]
|
50
|
+
def services
|
51
|
+
@services.values
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
def build(partition)
|
58
|
+
Partition.new(
|
59
|
+
name: partition['partition'],
|
60
|
+
regions: build_regions(partition),
|
61
|
+
services: build_services(partition),
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# @param [Hash] partition
|
68
|
+
# @return [Hash<String,Region>]
|
69
|
+
def build_regions(partition)
|
70
|
+
partition['regions'].inject({}) do |regions, (region_name, region)|
|
71
|
+
unless region_name == "#{partition['partition']}-global"
|
72
|
+
regions[region_name] = Region.build(region_name, region, partition)
|
73
|
+
end
|
74
|
+
regions
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param [Hash] partition
|
79
|
+
# @return [Hash<String,Service>]
|
80
|
+
def build_services(partition)
|
81
|
+
Partitions.service_ids.inject({}) do |services, (svc_name, svc_id)|
|
82
|
+
if partition['services'].key?(svc_id)
|
83
|
+
svc_data = partition['services'][svc_id]
|
84
|
+
services[svc_name] = Service.build(svc_name, svc_data, partition)
|
85
|
+
else
|
86
|
+
services[svc_name] = Service.build(svc_name, {'endpoints' => {}}, partition)
|
87
|
+
end
|
88
|
+
services
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|