fog-aws 0.5.0 → 0.6.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 +4 -4
- data/fog-aws.gemspec +1 -0
- data/lib/fog/aws.rb +18 -6
- data/lib/fog/aws/errors.rb +26 -5
- data/lib/fog/aws/lambda.rb +195 -0
- data/lib/fog/aws/parsers/lambda/base.rb +39 -0
- data/lib/fog/aws/requests/lambda/add_permission.rb +100 -0
- data/lib/fog/aws/requests/lambda/create_event_source_mapping.rb +94 -0
- data/lib/fog/aws/requests/lambda/create_function.rb +146 -0
- data/lib/fog/aws/requests/lambda/delete_event_source_mapping.rb +46 -0
- data/lib/fog/aws/requests/lambda/delete_function.rb +43 -0
- data/lib/fog/aws/requests/lambda/get_event_source_mapping.rb +54 -0
- data/lib/fog/aws/requests/lambda/get_function.rb +74 -0
- data/lib/fog/aws/requests/lambda/get_function_configuration.rb +44 -0
- data/lib/fog/aws/requests/lambda/get_policy.rb +52 -0
- data/lib/fog/aws/requests/lambda/invoke.rb +85 -0
- data/lib/fog/aws/requests/lambda/list_event_source_mappings.rb +69 -0
- data/lib/fog/aws/requests/lambda/list_functions.rb +39 -0
- data/lib/fog/aws/requests/lambda/remove_permission.rb +56 -0
- data/lib/fog/aws/requests/lambda/update_event_source_mapping.rb +86 -0
- data/lib/fog/aws/requests/lambda/update_function_code.rb +75 -0
- data/lib/fog/aws/requests/lambda/update_function_configuration.rb +84 -0
- data/lib/fog/aws/version.rb +1 -1
- data/tests/requests/lambda/function_sample_1.js +9 -0
- data/tests/requests/lambda/function_sample_2.js +9 -0
- data/tests/requests/lambda/function_tests.rb +460 -0
- data/tests/requests/lambda/helper.rb +81 -0
- metadata +38 -2
@@ -0,0 +1,94 @@
|
|
1
|
+
module Fog
|
2
|
+
module AWS
|
3
|
+
class Lambda
|
4
|
+
class Real
|
5
|
+
# Identifies a stream as an event source for a Lambda function.
|
6
|
+
# http://docs.aws.amazon.com/lambda/latest/dg/API_CreateEventSourceMapping.html
|
7
|
+
# ==== Parameters
|
8
|
+
# * BatchSize <~Integer> - largest number of records that AWS Lambda will retrieve from your event source at the time of invoking your function.
|
9
|
+
# * Enabled <~Boolean> - indicates whether AWS Lambda should begin polling the event source.
|
10
|
+
# * EventSourceArn <~String> - Amazon Resource Name (ARN) of the stream that is the event source
|
11
|
+
# * FunctionName <~String> - Lambda function to invoke when AWS Lambda detects an event on the stream.
|
12
|
+
# * StartingPosition <~String> - position in the stream where AWS Lambda should start reading.
|
13
|
+
# ==== Returns
|
14
|
+
# * response<~Excon::Response>:
|
15
|
+
# * body<~Hash>:
|
16
|
+
# * 'BatchSize' <~Integer> - largest number of records that AWS Lambda will retrieve from your event source at the time of invoking your function.
|
17
|
+
# * 'EventSourceArn' <~String> - Amazon Resource Name (ARN) of the stream that is the source of events.
|
18
|
+
# * 'FunctionArn' <~String> - Lambda function to invoke when AWS Lambda detects an event on the stream.
|
19
|
+
# * 'LastModified' <~Time> - UTC time string indicating the last time the event mapping was updated.
|
20
|
+
# * 'LastProcessingResult' <~String> - result of the last AWS Lambda invocation of your Lambda function.
|
21
|
+
# * 'State' <~String> - state of the event source mapping.
|
22
|
+
# * 'StateTransitionReason' <~String> - reason the event source mapping is in its current state.
|
23
|
+
# * 'UUID' <~String> - AWS Lambda assigned opaque identifier for the mapping.
|
24
|
+
def create_event_source_mapping(params={})
|
25
|
+
enabled = params.delete('Enabled')
|
26
|
+
batch_size = params.delete('BatchSize')
|
27
|
+
event_source_arn = params.delete('EventSourceArn')
|
28
|
+
function_name = params.delete('FunctionName')
|
29
|
+
starting_pos = params.delete('StartingPosition')
|
30
|
+
|
31
|
+
data = {
|
32
|
+
'EventSourceArn' => event_source_arn,
|
33
|
+
'FunctionName' => function_name,
|
34
|
+
'StartingPosition' => starting_pos
|
35
|
+
}
|
36
|
+
data.merge!('BatchSize' => batch_size) if batch_size
|
37
|
+
data.merge!('Enabled' => enabled) if !enabled.nil?
|
38
|
+
|
39
|
+
request({
|
40
|
+
:method => 'POST',
|
41
|
+
:path => '/event-source-mappings/',
|
42
|
+
:expects => 202,
|
43
|
+
:body => Fog::JSON.encode(data)
|
44
|
+
}.merge(params))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Mock
|
49
|
+
def create_event_source_mapping(params={})
|
50
|
+
enabled = params.delete('Enabled') || false
|
51
|
+
batch_size = params.delete('BatchSize') || 100
|
52
|
+
event_source_arn = params.delete('EventSourceArn')
|
53
|
+
function_name = params.delete('FunctionName')
|
54
|
+
starting_pos = params.delete('StartingPosition')
|
55
|
+
|
56
|
+
function = self.get_function_configuration('FunctionName' => function_name).body
|
57
|
+
|
58
|
+
unless event_source_arn
|
59
|
+
message = "ValidationException => "
|
60
|
+
message << "'eventSourceArn' cannot be blank"
|
61
|
+
raise Fog::AWS::Lambda::Error, message
|
62
|
+
end
|
63
|
+
|
64
|
+
unless starting_pos
|
65
|
+
message = "ValidationException => "
|
66
|
+
message << "'startingPosition' cannot be blank"
|
67
|
+
raise Fog::AWS::Lambda::Error, message
|
68
|
+
end
|
69
|
+
|
70
|
+
event_source_mapping_id = UUID.uuid
|
71
|
+
event_source_mapping = {
|
72
|
+
'BatchSize' => batch_size,
|
73
|
+
'EventSourceArn' => event_source_arn,
|
74
|
+
'FunctionArn' => function['FunctionArn'],
|
75
|
+
'LastModified' => Time.now.to_f,
|
76
|
+
'LastProcessingResult' => 'No records processed',
|
77
|
+
'State' => 'Creating',
|
78
|
+
'StateTransitionReason' => 'User action',
|
79
|
+
'UUID' => event_source_mapping_id
|
80
|
+
}
|
81
|
+
|
82
|
+
self.data[:event_source_mappings].merge!(
|
83
|
+
event_source_mapping_id => event_source_mapping
|
84
|
+
)
|
85
|
+
|
86
|
+
response = Excon::Response.new
|
87
|
+
response.body = event_source_mapping
|
88
|
+
response.status = 202
|
89
|
+
response
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Fog
|
2
|
+
module AWS
|
3
|
+
class Lambda
|
4
|
+
class Real
|
5
|
+
require 'fog/aws/parsers/lambda/base'
|
6
|
+
|
7
|
+
# Creates a new Lambda function.
|
8
|
+
# http://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html
|
9
|
+
# ==== Parameters
|
10
|
+
# * Code <~Hash> - code for the Lambda function.
|
11
|
+
# * Description <~String> - short, user-defined function description.
|
12
|
+
# * FunctionName <~String> - name you want to assign to the function you are uploading.
|
13
|
+
# * Handler <~String> - function within your code that Lambda calls to begin execution.
|
14
|
+
# * MemorySize <~Integer> - amount of memory, in MB, your Lambda function is given.
|
15
|
+
# * Role <~String> - ARN of the IAM role that Lambda assumes when it executes your function to access any other AWS resources.
|
16
|
+
# * Runtime <~String> - runtime environment for the Lambda function you are uploading.
|
17
|
+
# * Timeout <~Integer> - function execution time at which Lambda should terminate the function.
|
18
|
+
# ==== Returns
|
19
|
+
# * response<~Excon::Response>:
|
20
|
+
# * body<~Hash>:
|
21
|
+
# * 'CodeSize' <~Integer> - size, in bytes, of the function .zip file you uploaded.
|
22
|
+
# * 'Description' <~String> - user-provided description.
|
23
|
+
# * 'FunctionArn' <~String> - Amazon Resource Name (ARN) assigned to the function.
|
24
|
+
# * 'FunctionName' <~String> - name of the function.
|
25
|
+
# * 'Handler' <~String> - function Lambda calls to begin executing your function.
|
26
|
+
# * 'LastModified' <~Time> - timestamp of the last time you updated the function.
|
27
|
+
# * 'MemorySize' <~Integer> - memory size, in MB, you configured for the function.
|
28
|
+
# * 'Role' <~String> - ARN of the IAM role that Lambda assumes when it executes your function to access any other AWS resources.
|
29
|
+
# * 'Runtime' <~String> - runtime environment for the Lambda function.
|
30
|
+
# * 'Timeout' <~Integer> - function execution time at which Lambda should terminate the function.
|
31
|
+
def create_function(params={})
|
32
|
+
runtime = params.delete('Runtime') || 'nodejs'
|
33
|
+
code = params.delete('Code')
|
34
|
+
function_name = params.delete('FunctionName')
|
35
|
+
handler = params.delete('Handler')
|
36
|
+
role = params.delete('Role')
|
37
|
+
|
38
|
+
data = {
|
39
|
+
'Runtime' => runtime,
|
40
|
+
'Code' => code,
|
41
|
+
'FunctionName' => function_name,
|
42
|
+
'Handler' => handler,
|
43
|
+
'Role' => role
|
44
|
+
}
|
45
|
+
|
46
|
+
description = params.delete('Description')
|
47
|
+
data.merge!('Description' => description) if description
|
48
|
+
|
49
|
+
memory_size = params.delete('MemorySize')
|
50
|
+
data.merge!('MemorySize' => memory_size) if memory_size
|
51
|
+
|
52
|
+
timeout = params.delete('Timeout')
|
53
|
+
data.merge!('Timeout' => timeout) if timeout
|
54
|
+
|
55
|
+
request({
|
56
|
+
:method => 'POST',
|
57
|
+
:path => '/functions',
|
58
|
+
:expects => 201,
|
59
|
+
:body => Fog::JSON.encode(data),
|
60
|
+
:parser => Fog::AWS::Parsers::Lambda::Base.new
|
61
|
+
}.merge(params))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Mock
|
66
|
+
def create_function(params={})
|
67
|
+
response = Excon::Response.new
|
68
|
+
|
69
|
+
runtime = params.delete('Runtime') || 'nodejs'
|
70
|
+
if !%w(nodejs java8).include?(runtime)
|
71
|
+
message = 'ValidationException: Runtime must be nodejs or java8.'
|
72
|
+
raise Fog::AWS::Lambda::Error, message
|
73
|
+
end
|
74
|
+
|
75
|
+
unless code = params.delete('Code')
|
76
|
+
message = 'ValidationException: Code cannot be blank.'
|
77
|
+
raise Fog::AWS::Lambda::Error, message
|
78
|
+
end
|
79
|
+
|
80
|
+
unless function_name = params.delete('FunctionName')
|
81
|
+
message = 'ValidationException: Function name cannot be blank.'
|
82
|
+
raise Fog::AWS::Lambda::Error, message
|
83
|
+
end
|
84
|
+
|
85
|
+
unless handler = params.delete('Handler')
|
86
|
+
message = 'ValidationException: Handler cannot be blank.'
|
87
|
+
raise Fog::AWS::Lambda::Error, message
|
88
|
+
end
|
89
|
+
|
90
|
+
unless role = params.delete('Role')
|
91
|
+
message = 'ValidationException: Role cannot be blank.'
|
92
|
+
raise Fog::AWS::Lambda::Error, message
|
93
|
+
end
|
94
|
+
|
95
|
+
code_size = if code.has_key?('ZipFile')
|
96
|
+
Base64.decode64(code['ZipFile']).length
|
97
|
+
else
|
98
|
+
Fog::Mock.random_numbers(5).to_i
|
99
|
+
end
|
100
|
+
|
101
|
+
description = params.delete('Description')
|
102
|
+
|
103
|
+
function = {}
|
104
|
+
begin
|
105
|
+
opts = { 'FunctionName' => function_name }
|
106
|
+
function = self.get_function_configuration(opts).body
|
107
|
+
rescue Fog::AWS::Lambda::Error => e
|
108
|
+
# ignore: if the function doesn't exist we are OK.
|
109
|
+
end
|
110
|
+
|
111
|
+
if !function.empty?
|
112
|
+
message = "ResourceConflictException => "
|
113
|
+
message << "Function already exist: #{function_name}"
|
114
|
+
raise Fog::AWS::Lambda::Error, message
|
115
|
+
end
|
116
|
+
|
117
|
+
function_path = "function:#{function_name}"
|
118
|
+
function_arn = Fog::AWS::Mock.arn(
|
119
|
+
'lambda',
|
120
|
+
self.account_id,
|
121
|
+
function_path,
|
122
|
+
self.region
|
123
|
+
)
|
124
|
+
|
125
|
+
function = {
|
126
|
+
'CodeSize' => code_size,
|
127
|
+
'FunctionArn' => function_arn,
|
128
|
+
'FunctionName' => function_name,
|
129
|
+
'Handler' => handler,
|
130
|
+
'LastModified' => Time.now.utc,
|
131
|
+
'MemorySize' => params.delete('MemorySize') || 128,
|
132
|
+
'Timeout' => params.delete('Timeout') || 3,
|
133
|
+
'Role' => role,
|
134
|
+
'Runtime' => runtime
|
135
|
+
}
|
136
|
+
function['Description'] = description if description
|
137
|
+
|
138
|
+
self.data[:functions][function_arn] = function
|
139
|
+
response.body = function
|
140
|
+
response.status = 200
|
141
|
+
response
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Fog
|
2
|
+
module AWS
|
3
|
+
class Lambda
|
4
|
+
class Real
|
5
|
+
|
6
|
+
# Removes an event source mapping.
|
7
|
+
# http://docs.aws.amazon.com/lambda/latest/dg/API_DeleteEventSourceMapping.html
|
8
|
+
# ==== Parameters
|
9
|
+
# * UUID <~String> - event source mapping ID.
|
10
|
+
# ==== Returns
|
11
|
+
# * response<~Excon::Response>:
|
12
|
+
# * body<~String>:
|
13
|
+
def delete_event_source_mapping(params={})
|
14
|
+
mapping_id = params.delete('UUID')
|
15
|
+
request({
|
16
|
+
:method => 'DELETE',
|
17
|
+
:path => "/event-source-mappings/#{mapping_id}",
|
18
|
+
:expects => 202
|
19
|
+
}.merge(params))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Mock
|
24
|
+
def delete_event_source_mapping(params={})
|
25
|
+
mapping = self.get_event_source_mapping(params).body
|
26
|
+
|
27
|
+
unless mapping
|
28
|
+
message = "ResourceNotFoundException => "
|
29
|
+
message << "The resource you requested does not exist."
|
30
|
+
raise Fog::AWS::Lambda::Error, message
|
31
|
+
end
|
32
|
+
|
33
|
+
mapping_id = mapping['UUID']
|
34
|
+
self.data[:event_source_mappings].delete(mapping_id)
|
35
|
+
|
36
|
+
mapping['State'] = 'Deleting'
|
37
|
+
|
38
|
+
response = Excon::Response.new
|
39
|
+
response.status = 202
|
40
|
+
response.body = mapping
|
41
|
+
response
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Fog
|
2
|
+
module AWS
|
3
|
+
class Lambda
|
4
|
+
class Real
|
5
|
+
|
6
|
+
# Deletes the specified Lambda function code and configuration.
|
7
|
+
# http://docs.aws.amazon.com/lambda/latest/dg/API_DeleteFunction.html
|
8
|
+
# ==== Parameters
|
9
|
+
# * FunctionName <~String> - Lambda function to delete.
|
10
|
+
# ==== Returns
|
11
|
+
# * response<~Excon::Response>:
|
12
|
+
# * body<~String>:
|
13
|
+
def delete_function(params={})
|
14
|
+
function_name = params.delete('FunctionName')
|
15
|
+
request({
|
16
|
+
:method => 'DELETE',
|
17
|
+
:path => "/functions/#{function_name}",
|
18
|
+
:expects => 204
|
19
|
+
}.merge(params))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Mock
|
24
|
+
def delete_function(params={})
|
25
|
+
response = Excon::Response.new
|
26
|
+
response.status = 204
|
27
|
+
response.body = ''
|
28
|
+
|
29
|
+
function = self.get_function_configuration(params).body
|
30
|
+
function_id = function['FunctionArn']
|
31
|
+
|
32
|
+
self.data[:functions].delete function_id
|
33
|
+
self.data[:permissions].delete function_id
|
34
|
+
self.data[:event_source_mappings].delete_if do |m,f|
|
35
|
+
f['FunctionArn'].eql?(function_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
response
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Fog
|
2
|
+
module AWS
|
3
|
+
class Lambda
|
4
|
+
class Real
|
5
|
+
# Returns configuration information for the specified event source mapping.
|
6
|
+
# http://docs.aws.amazon.com/lambda/latest/dg/API_GetEventSourceMapping.html
|
7
|
+
# ==== Parameters
|
8
|
+
# * UUID <~String> - AWS Lambda assigned ID of the event source mapping.
|
9
|
+
# ==== Returns
|
10
|
+
# * response<~Excon::Response>:
|
11
|
+
# * body<~Hash>:
|
12
|
+
# * 'BatchSize' <~Integer> - largest number of records that AWS Lambda will retrieve from your event source at the time of invoking your function.
|
13
|
+
# * 'EventSourceArn' <~String> - Amazon Resource Name (ARN) of the stream that is the source of events.
|
14
|
+
# * 'FunctionArn' <~String> - Lambda function to invoke when AWS Lambda detects an event on the stream.
|
15
|
+
# * 'LastModified' <~Time> - UTC time string indicating the last time the event mapping was updated.
|
16
|
+
# * 'LastProcessingResult' <~String> - result of the last AWS Lambda invocation of your Lambda function.
|
17
|
+
# * 'State' <~String> - state of the event source mapping.
|
18
|
+
# * 'StateTransitionReason' <~String> - reason the event source mapping is in its current state.
|
19
|
+
# * 'UUID' <~String> - AWS Lambda assigned opaque identifier for the mapping.
|
20
|
+
# * 'Code' <~Hash> - object for the Lambda function location.
|
21
|
+
# * 'Configuration' <~Hash> - function metadata description.
|
22
|
+
def get_event_source_mapping(params={})
|
23
|
+
mapping_id = params.delete('UUID')
|
24
|
+
request({
|
25
|
+
:method => 'GET',
|
26
|
+
:path => "/event-source-mappings/#{mapping_id}"
|
27
|
+
}.merge(params))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Mock
|
32
|
+
def get_event_source_mapping(params={})
|
33
|
+
mapping_id = params.delete('UUID')
|
34
|
+
|
35
|
+
unless mapping = self.data[:event_source_mappings][mapping_id]
|
36
|
+
message = 'ResourceNotFoundException => '
|
37
|
+
message << 'The resource you requested does not exist.'
|
38
|
+
raise Fog::AWS::Lambda::Error, message
|
39
|
+
end
|
40
|
+
|
41
|
+
if mapping['State'].eql?('Creating')
|
42
|
+
mapping['LastProcessingResult'] = 'OK'
|
43
|
+
mapping['State'] = 'Enabled'
|
44
|
+
end
|
45
|
+
|
46
|
+
response = Excon::Response.new
|
47
|
+
response.status = 200
|
48
|
+
response.body = mapping
|
49
|
+
response
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Fog
|
2
|
+
module AWS
|
3
|
+
class Lambda
|
4
|
+
class Real
|
5
|
+
require 'fog/aws/parsers/lambda/base'
|
6
|
+
|
7
|
+
# Returns the configuration information of the Lambda function.
|
8
|
+
# http://docs.aws.amazon.com/lambda/latest/dg/API_GetFunction.html
|
9
|
+
# ==== Parameters
|
10
|
+
# * FunctionName <~String> - Lambda function name.
|
11
|
+
# ==== Returns
|
12
|
+
# * response<~Excon::Response>:
|
13
|
+
# * body<~Hash>:
|
14
|
+
# * 'Code' <~Hash> - object for the Lambda function location.
|
15
|
+
# * 'Configuration' <~Hash> - function metadata description.
|
16
|
+
def get_function(params={})
|
17
|
+
function_name = params.delete('FunctionName')
|
18
|
+
request({
|
19
|
+
:method => 'GET',
|
20
|
+
:path => "/functions/#{function_name}/versions/HEAD",
|
21
|
+
:parser => Fog::AWS::Parsers::Lambda::Base.new
|
22
|
+
}.merge(params))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Mock
|
27
|
+
def get_function(params={})
|
28
|
+
response = Excon::Response.new
|
29
|
+
response.status = 200
|
30
|
+
response.body = ''
|
31
|
+
|
32
|
+
unless function_id = params.delete('FunctionName')
|
33
|
+
raise Fog::AWS::Lambda::Error, 'Function name cannot be blank.'
|
34
|
+
end
|
35
|
+
|
36
|
+
if function_id.match(/^arn:aws:lambda:.+:function:.+/)
|
37
|
+
function = self.data[:functions][function_id]
|
38
|
+
else
|
39
|
+
search_function = Hash[
|
40
|
+
self.data[:functions].select do |f,v|
|
41
|
+
v['FunctionName'].eql?(function_id)
|
42
|
+
end
|
43
|
+
]
|
44
|
+
function = search_function.values.first
|
45
|
+
end
|
46
|
+
|
47
|
+
msg = 'The resource you requested does not exist.'
|
48
|
+
raise Fog::AWS::Lambda::Error, msg if (function.nil? || function.empty?)
|
49
|
+
|
50
|
+
location = "https://awslambda-#{self.region}-tasks.s3-#{self.region}"
|
51
|
+
location << ".amazonaws.com/snapshot/#{self.account_id}/"
|
52
|
+
location << "#{function['FunctionName']}-#{UUID.uuid}"
|
53
|
+
location << '?x-amz-security-token='
|
54
|
+
location << Fog::Mock.random_base64(718)
|
55
|
+
location << "&AWSAccessKeyId=#{self.aws_access_key_id}"
|
56
|
+
location << "&Expires=#{Time.now.to_i + 60*10}"
|
57
|
+
location << '&Signature='
|
58
|
+
location << Fog::Mock.random_base64(28)
|
59
|
+
|
60
|
+
body = {
|
61
|
+
'Code' => {
|
62
|
+
'Location' => location,
|
63
|
+
'RepositoryType' => 'S3'
|
64
|
+
},
|
65
|
+
'Configuration' => function
|
66
|
+
}
|
67
|
+
response.body = body
|
68
|
+
|
69
|
+
response
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|