aws-sdk-core 3.171.1 → 3.234.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/CHANGELOG.md +787 -0
- data/VERSION +1 -1
- data/lib/aws-defaults/default_configuration.rb +5 -6
- data/lib/aws-defaults.rb +4 -1
- data/lib/aws-sdk-core/arn.rb +1 -3
- data/lib/aws-sdk-core/assume_role_credentials.rb +21 -13
- data/lib/aws-sdk-core/assume_role_web_identity_credentials.rb +16 -9
- data/lib/aws-sdk-core/binary/decode_handler.rb +3 -9
- data/lib/aws-sdk-core/binary/encode_handler.rb +1 -1
- data/lib/aws-sdk-core/binary/event_builder.rb +34 -37
- data/lib/aws-sdk-core/binary/event_stream_decoder.rb +1 -0
- data/lib/aws-sdk-core/binary/event_stream_encoder.rb +4 -3
- data/lib/aws-sdk-core/cbor/decoder.rb +308 -0
- data/lib/aws-sdk-core/cbor/encoder.rb +243 -0
- data/lib/aws-sdk-core/cbor.rb +53 -0
- data/lib/aws-sdk-core/client_side_monitoring.rb +9 -0
- data/lib/aws-sdk-core/client_stubs.rb +33 -55
- data/lib/aws-sdk-core/credential_provider.rb +8 -1
- data/lib/aws-sdk-core/credential_provider_chain.rb +74 -25
- data/lib/aws-sdk-core/credentials.rb +19 -6
- data/lib/aws-sdk-core/ec2_metadata.rb +1 -1
- data/lib/aws-sdk-core/ecs_credentials.rb +92 -24
- data/lib/aws-sdk-core/endpoints/endpoint.rb +3 -1
- data/lib/aws-sdk-core/endpoints/matchers.rb +21 -19
- data/lib/aws-sdk-core/endpoints.rb +106 -22
- data/lib/aws-sdk-core/error_handler.rb +46 -0
- data/lib/aws-sdk-core/errors.rb +14 -5
- data/lib/aws-sdk-core/event_emitter.rb +1 -17
- data/lib/aws-sdk-core/ini_parser.rb +7 -0
- data/lib/aws-sdk-core/instance_profile_credentials.rb +168 -155
- data/lib/aws-sdk-core/json/builder.rb +8 -1
- data/lib/aws-sdk-core/json/error_handler.rb +29 -13
- data/lib/aws-sdk-core/json/handler.rb +13 -6
- data/lib/aws-sdk-core/json/json_engine.rb +3 -1
- data/lib/aws-sdk-core/json/oj_engine.rb +7 -1
- data/lib/aws-sdk-core/json/parser.rb +33 -3
- data/lib/aws-sdk-core/json.rb +43 -14
- data/lib/aws-sdk-core/log/formatter.rb +6 -0
- data/lib/aws-sdk-core/log/param_filter.rb +2 -2
- data/lib/aws-sdk-core/log/param_formatter.rb +7 -3
- data/lib/aws-sdk-core/log.rb +10 -0
- data/lib/aws-sdk-core/lru_cache.rb +75 -0
- data/lib/aws-sdk-core/pageable_response.rb +3 -1
- data/lib/aws-sdk-core/param_validator.rb +9 -4
- data/lib/aws-sdk-core/plugins/bearer_authorization.rb +2 -0
- data/lib/aws-sdk-core/plugins/checksum_algorithm.rb +348 -169
- data/lib/aws-sdk-core/plugins/client_metrics_plugin.rb +1 -1
- data/lib/aws-sdk-core/plugins/client_metrics_send_plugin.rb +14 -2
- data/lib/aws-sdk-core/plugins/credentials_configuration.rb +78 -56
- data/lib/aws-sdk-core/plugins/endpoint_pattern.rb +40 -32
- data/lib/aws-sdk-core/plugins/global_configuration.rb +8 -9
- data/lib/aws-sdk-core/plugins/http_checksum.rb +3 -8
- data/lib/aws-sdk-core/plugins/invocation_id.rb +1 -11
- data/lib/aws-sdk-core/plugins/logging.rb +2 -0
- data/lib/aws-sdk-core/plugins/protocols/api_gateway.rb +3 -1
- data/lib/aws-sdk-core/plugins/protocols/ec2.rb +2 -24
- data/lib/aws-sdk-core/plugins/protocols/json_rpc.rb +6 -8
- data/lib/aws-sdk-core/plugins/protocols/query.rb +4 -2
- data/lib/aws-sdk-core/plugins/protocols/rest_json.rb +3 -15
- data/lib/aws-sdk-core/plugins/protocols/rest_xml.rb +3 -0
- data/lib/aws-sdk-core/plugins/protocols/rpc_v2.rb +17 -0
- data/lib/aws-sdk-core/plugins/regional_endpoint.rb +162 -37
- data/lib/aws-sdk-core/plugins/request_compression.rb +226 -0
- data/lib/aws-sdk-core/plugins/retry_errors.rb +12 -3
- data/lib/aws-sdk-core/plugins/sign.rb +55 -33
- data/lib/aws-sdk-core/plugins/signature_v2.rb +2 -1
- data/lib/aws-sdk-core/plugins/signature_v4.rb +2 -1
- data/lib/aws-sdk-core/plugins/stub_responses.rb +59 -9
- data/lib/aws-sdk-core/plugins/telemetry.rb +75 -0
- data/lib/aws-sdk-core/plugins/transfer_encoding.rb +16 -9
- data/lib/aws-sdk-core/plugins/user_agent.rb +192 -14
- data/lib/aws-sdk-core/plugins.rb +39 -0
- data/lib/aws-sdk-core/process_credentials.rb +48 -29
- data/lib/aws-sdk-core/query/ec2_handler.rb +27 -0
- data/lib/aws-sdk-core/query/ec2_param_builder.rb +5 -7
- data/lib/aws-sdk-core/query/handler.rb +4 -4
- data/lib/aws-sdk-core/query/param_builder.rb +2 -2
- data/lib/aws-sdk-core/query.rb +2 -1
- data/lib/aws-sdk-core/refreshing_credentials.rb +20 -23
- data/lib/aws-sdk-core/resources.rb +8 -0
- data/lib/aws-sdk-core/rest/content_type_handler.rb +60 -0
- data/lib/aws-sdk-core/rest/handler.rb +3 -4
- data/lib/aws-sdk-core/rest/request/body.rb +32 -5
- data/lib/aws-sdk-core/rest/request/endpoint.rb +24 -4
- data/lib/aws-sdk-core/rest/request/headers.rb +15 -7
- data/lib/aws-sdk-core/rest/request/querystring_builder.rb +62 -36
- data/lib/aws-sdk-core/rest/response/body.rb +15 -1
- data/lib/aws-sdk-core/rest/response/header_list_parser.rb +79 -0
- data/lib/aws-sdk-core/rest/response/headers.rb +8 -3
- data/lib/aws-sdk-core/rest.rb +1 -0
- data/lib/aws-sdk-core/rpc_v2/builder.rb +62 -0
- data/lib/aws-sdk-core/rpc_v2/cbor_engine.rb +18 -0
- data/lib/aws-sdk-core/rpc_v2/content_type_handler.rb +47 -0
- data/lib/aws-sdk-core/rpc_v2/error_handler.rb +95 -0
- data/lib/aws-sdk-core/rpc_v2/handler.rb +79 -0
- data/lib/aws-sdk-core/rpc_v2/parser.rb +98 -0
- data/lib/aws-sdk-core/rpc_v2.rb +69 -0
- data/lib/aws-sdk-core/shared_config.rb +135 -39
- data/lib/aws-sdk-core/shared_credentials.rb +1 -7
- data/lib/aws-sdk-core/sso_credentials.rb +6 -3
- data/lib/aws-sdk-core/static_token_provider.rb +1 -2
- data/lib/aws-sdk-core/stubbing/protocols/ec2.rb +12 -11
- data/lib/aws-sdk-core/stubbing/protocols/json.rb +11 -10
- data/lib/aws-sdk-core/stubbing/protocols/query.rb +7 -6
- data/lib/aws-sdk-core/stubbing/protocols/rest.rb +2 -1
- data/lib/aws-sdk-core/stubbing/protocols/rest_json.rb +9 -8
- data/lib/aws-sdk-core/stubbing/protocols/rest_xml.rb +6 -5
- data/lib/aws-sdk-core/stubbing/protocols/rpc_v2.rb +39 -0
- data/lib/aws-sdk-core/stubbing/stub_data.rb +11 -0
- data/lib/aws-sdk-core/stubbing.rb +22 -0
- data/lib/aws-sdk-core/telemetry/base.rb +177 -0
- data/lib/aws-sdk-core/telemetry/no_op.rb +70 -0
- data/lib/aws-sdk-core/telemetry/otel.rb +235 -0
- data/lib/aws-sdk-core/telemetry/span_kind.rb +22 -0
- data/lib/aws-sdk-core/telemetry/span_status.rb +59 -0
- data/lib/aws-sdk-core/telemetry.rb +78 -0
- data/lib/aws-sdk-core/token.rb +3 -3
- data/lib/aws-sdk-core/token_provider.rb +4 -0
- data/lib/aws-sdk-core/token_provider_chain.rb +2 -6
- data/lib/aws-sdk-core/util.rb +41 -1
- data/lib/aws-sdk-core/waiters/poller.rb +12 -5
- data/lib/aws-sdk-core/xml/builder.rb +17 -9
- data/lib/aws-sdk-core/xml/error_handler.rb +35 -43
- data/lib/aws-sdk-core/xml/parser/frame.rb +4 -20
- data/lib/aws-sdk-core/xml/parser/stack.rb +2 -0
- data/lib/aws-sdk-core/xml/parser.rb +2 -6
- data/lib/aws-sdk-core.rb +82 -107
- data/lib/aws-sdk-sso/client.rb +205 -92
- data/lib/aws-sdk-sso/client_api.rb +7 -0
- data/lib/aws-sdk-sso/endpoint_parameters.rb +9 -6
- data/lib/aws-sdk-sso/endpoint_provider.rb +30 -28
- data/lib/aws-sdk-sso/endpoints.rb +3 -54
- data/lib/aws-sdk-sso/plugins/endpoints.rb +23 -22
- data/lib/aws-sdk-sso/types.rb +1 -0
- data/lib/aws-sdk-sso.rb +15 -11
- data/lib/aws-sdk-ssooidc/client.rb +625 -125
- data/lib/aws-sdk-ssooidc/client_api.rb +94 -1
- data/lib/aws-sdk-ssooidc/endpoint_parameters.rb +9 -6
- data/lib/aws-sdk-ssooidc/endpoint_provider.rb +30 -28
- data/lib/aws-sdk-ssooidc/endpoints.rb +3 -40
- data/lib/aws-sdk-ssooidc/errors.rb +62 -0
- data/lib/aws-sdk-ssooidc/plugins/endpoints.rb +23 -20
- data/lib/aws-sdk-ssooidc/types.rb +419 -53
- data/lib/aws-sdk-ssooidc.rb +15 -11
- data/lib/aws-sdk-sts/client.rb +526 -243
- data/lib/aws-sdk-sts/client_api.rb +48 -9
- data/lib/aws-sdk-sts/customizations.rb +5 -2
- data/lib/aws-sdk-sts/endpoint_parameters.rb +10 -9
- data/lib/aws-sdk-sts/endpoint_provider.rb +82 -84
- data/lib/aws-sdk-sts/endpoints.rb +3 -118
- data/lib/aws-sdk-sts/errors.rb +15 -0
- data/lib/aws-sdk-sts/plugins/endpoints.rb +23 -30
- data/lib/aws-sdk-sts/presigner.rb +3 -7
- data/lib/aws-sdk-sts/types.rb +217 -36
- data/lib/aws-sdk-sts.rb +15 -11
- data/lib/seahorse/client/async_base.rb +4 -5
- data/lib/seahorse/client/async_response.rb +19 -0
- data/lib/seahorse/client/base.rb +18 -21
- data/lib/seahorse/client/configuration.rb +0 -4
- data/lib/seahorse/client/h2/connection.rb +18 -28
- data/lib/seahorse/client/h2/handler.rb +14 -3
- data/lib/seahorse/client/handler.rb +1 -1
- data/lib/seahorse/client/http/response.rb +1 -1
- data/lib/seahorse/client/net_http/connection_pool.rb +15 -12
- data/lib/seahorse/client/net_http/handler.rb +21 -9
- data/lib/seahorse/client/net_http/patches.rb +1 -4
- data/lib/seahorse/client/networking_error.rb +1 -1
- data/lib/seahorse/client/plugin.rb +9 -0
- data/lib/seahorse/client/plugins/endpoint.rb +0 -1
- data/lib/seahorse/client/plugins/h2.rb +4 -4
- data/lib/seahorse/client/plugins/net_http.rb +57 -16
- data/lib/seahorse/client/plugins/request_callback.rb +31 -0
- data/lib/seahorse/client/request_context.rb +9 -2
- data/lib/seahorse/client/response.rb +8 -0
- data/lib/seahorse/model/operation.rb +3 -0
- data/lib/seahorse/model/shapes.rb +2 -2
- data/lib/seahorse/util.rb +2 -1
- data/sig/aws-sdk-core/async_client_stubs.rbs +21 -0
- data/sig/aws-sdk-core/client_stubs.rbs +10 -0
- data/sig/aws-sdk-core/errors.rbs +22 -0
- data/sig/aws-sdk-core/resources/collection.rbs +21 -0
- data/sig/aws-sdk-core/structure.rbs +4 -0
- data/sig/aws-sdk-core/telemetry/base.rbs +46 -0
- data/sig/aws-sdk-core/telemetry/otel.rbs +22 -0
- data/sig/aws-sdk-core/telemetry/span_kind.rbs +15 -0
- data/sig/aws-sdk-core/telemetry/span_status.rbs +24 -0
- data/sig/aws-sdk-core/waiters/errors.rbs +20 -0
- data/sig/aws-sdk-core.rbs +7 -0
- data/sig/seahorse/client/async_base.rbs +18 -0
- data/sig/seahorse/client/base.rbs +25 -0
- data/sig/seahorse/client/handler_builder.rbs +16 -0
- data/sig/seahorse/client/response.rbs +61 -0
- metadata +106 -23
- /data/lib/aws-sdk-core/xml/parser/{engines/libxml.rb → libxml_engine.rb} +0 -0
- /data/lib/aws-sdk-core/xml/parser/{engines/nokogiri.rb → nokogiri_engine.rb} +0 -0
- /data/lib/aws-sdk-core/xml/parser/{engines/oga.rb → oga_engine.rb} +0 -0
- /data/lib/aws-sdk-core/xml/parser/{engines/ox.rb → ox_engine.rb} +0 -0
- /data/lib/aws-sdk-core/xml/parser/{engines/rexml.rb → rexml_engine.rb} +0 -0
|
@@ -13,10 +13,6 @@ module Aws
|
|
|
13
13
|
option(:sigv4_region)
|
|
14
14
|
option(:unsigned_operations, default: [])
|
|
15
15
|
|
|
16
|
-
supported_auth_types = %w[sigv4 bearer none]
|
|
17
|
-
supported_auth_types += ['sigv4a'] if Aws::Sigv4::Signer.use_crt?
|
|
18
|
-
SUPPORTED_AUTH_TYPES = supported_auth_types.freeze
|
|
19
|
-
|
|
20
16
|
def add_handlers(handlers, cfg)
|
|
21
17
|
operations = cfg.api.operation_names - cfg.unsigned_operations
|
|
22
18
|
handlers.add(Handler, step: :sign, operations: operations)
|
|
@@ -24,12 +20,16 @@ module Aws
|
|
|
24
20
|
|
|
25
21
|
# @api private
|
|
26
22
|
# Return a signer with the `sign(context)` method
|
|
27
|
-
def self.signer_for(auth_scheme, config,
|
|
23
|
+
def self.signer_for(auth_scheme, config, sigv4_region_override = nil, sigv4_credentials_override = nil)
|
|
28
24
|
case auth_scheme['name']
|
|
29
|
-
when 'sigv4', 'sigv4a'
|
|
30
|
-
|
|
25
|
+
when 'sigv4', 'sigv4a', 'sigv4-s3express'
|
|
26
|
+
sigv4_overrides = {
|
|
27
|
+
region: sigv4_region_override,
|
|
28
|
+
credentials: sigv4_credentials_override
|
|
29
|
+
}
|
|
30
|
+
SignatureV4.new(auth_scheme, config, sigv4_overrides)
|
|
31
31
|
when 'bearer'
|
|
32
|
-
Bearer.new
|
|
32
|
+
Bearer.new(config)
|
|
33
33
|
else
|
|
34
34
|
NullSigner.new
|
|
35
35
|
end
|
|
@@ -42,15 +42,27 @@ module Aws
|
|
|
42
42
|
signer = Sign.signer_for(
|
|
43
43
|
context[:auth_scheme],
|
|
44
44
|
context.config,
|
|
45
|
-
context[:sigv4_region]
|
|
45
|
+
context[:sigv4_region],
|
|
46
|
+
context[:sigv4_credentials]
|
|
46
47
|
)
|
|
47
48
|
signer.sign(context)
|
|
48
49
|
end
|
|
49
|
-
@handler.call(context)
|
|
50
|
+
with_metrics(signer) { @handler.call(context) }
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
private
|
|
53
54
|
|
|
55
|
+
def with_metrics(signer, &block)
|
|
56
|
+
case signer
|
|
57
|
+
when SignatureV4
|
|
58
|
+
Aws::Plugins::UserAgent.metric(*signer.credentials.metrics, &block)
|
|
59
|
+
when Bearer
|
|
60
|
+
Aws::Plugins::UserAgent.metric(*signer.token_provider.metrics, &block)
|
|
61
|
+
else
|
|
62
|
+
block.call
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
54
66
|
def v2_signing?(config)
|
|
55
67
|
# 's3' is legacy signing, 'v4' is default
|
|
56
68
|
config.respond_to?(:signature_version) &&
|
|
@@ -60,21 +72,19 @@ module Aws
|
|
|
60
72
|
|
|
61
73
|
# @api private
|
|
62
74
|
class Bearer
|
|
63
|
-
def initialize
|
|
75
|
+
def initialize(config)
|
|
76
|
+
@token_provider = config.token_provider
|
|
64
77
|
end
|
|
65
78
|
|
|
79
|
+
attr_reader :token_provider
|
|
80
|
+
|
|
66
81
|
def sign(context)
|
|
67
82
|
if context.http_request.endpoint.scheme != 'https'
|
|
68
|
-
raise ArgumentError,
|
|
69
|
-
'Unable to use bearer authorization on non https endpoint.'
|
|
83
|
+
raise ArgumentError, 'Unable to use bearer authorization on non https endpoint.'
|
|
70
84
|
end
|
|
85
|
+
raise Errors::MissingBearerTokenError unless @token_provider && @token_provider.set?
|
|
71
86
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
raise Errors::MissingBearerTokenError unless token_provider&.set?
|
|
75
|
-
|
|
76
|
-
context.http_request.headers['Authorization'] =
|
|
77
|
-
"Bearer #{token_provider.token.token}"
|
|
87
|
+
context.http_request.headers['Authorization'] = "Bearer #{@token_provider.token.token}"
|
|
78
88
|
end
|
|
79
89
|
|
|
80
90
|
def presign_url(*args)
|
|
@@ -88,33 +98,33 @@ module Aws
|
|
|
88
98
|
|
|
89
99
|
# @api private
|
|
90
100
|
class SignatureV4
|
|
91
|
-
def initialize(auth_scheme, config,
|
|
101
|
+
def initialize(auth_scheme, config, sigv4_overrides = {})
|
|
92
102
|
scheme_name = auth_scheme['name']
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
raise ArgumentError,
|
|
96
|
-
"Expected sigv4 or sigv4a auth scheme, got #{scheme_name}"
|
|
103
|
+
unless %w[sigv4 sigv4a sigv4-s3express].include?(scheme_name)
|
|
104
|
+
raise ArgumentError, "Expected sigv4, sigv4a, or sigv4-s3express auth scheme, got #{scheme_name}"
|
|
97
105
|
end
|
|
98
|
-
|
|
99
106
|
region = if scheme_name == 'sigv4a'
|
|
100
|
-
auth_scheme['signingRegionSet'].
|
|
107
|
+
auth_scheme['signingRegionSet'].join(',')
|
|
101
108
|
else
|
|
102
109
|
auth_scheme['signingRegion']
|
|
103
110
|
end
|
|
104
111
|
begin
|
|
105
|
-
@signer = Aws::Sigv4::Signer.new(
|
|
112
|
+
@signer = config.sigv4_signer || Aws::Sigv4::Signer.new(
|
|
106
113
|
service: config.sigv4_name || auth_scheme['signingName'],
|
|
107
|
-
region:
|
|
108
|
-
credentials_provider: config.credentials,
|
|
114
|
+
region: sigv4_overrides[:region] || config.sigv4_region || region,
|
|
115
|
+
credentials_provider: sigv4_overrides[:credentials] || config.credentials,
|
|
109
116
|
signing_algorithm: scheme_name.to_sym,
|
|
110
|
-
uri_escape_path:
|
|
111
|
-
|
|
117
|
+
uri_escape_path: !auth_scheme['disableDoubleEncoding'],
|
|
118
|
+
normalize_path: !auth_scheme['disableNormalizePath'],
|
|
119
|
+
unsigned_headers: %w[content-length user-agent x-amzn-trace-id expect transfer-encoding connection]
|
|
112
120
|
)
|
|
113
121
|
rescue Aws::Sigv4::Errors::MissingCredentialsError
|
|
114
122
|
raise Aws::Errors::MissingCredentialsError
|
|
115
123
|
end
|
|
116
124
|
end
|
|
117
125
|
|
|
126
|
+
attr_reader :signer
|
|
127
|
+
|
|
118
128
|
def sign(context)
|
|
119
129
|
req = context.http_request
|
|
120
130
|
|
|
@@ -150,15 +160,27 @@ module Aws
|
|
|
150
160
|
@signer.sign_event(*args)
|
|
151
161
|
end
|
|
152
162
|
|
|
163
|
+
def credentials
|
|
164
|
+
@signer.credentials_provider
|
|
165
|
+
end
|
|
166
|
+
|
|
153
167
|
private
|
|
154
168
|
|
|
155
169
|
def apply_authtype(context, req)
|
|
156
|
-
|
|
157
|
-
|
|
170
|
+
# only used for event streaming at input
|
|
171
|
+
if context[:input_event_emitter]
|
|
172
|
+
req.headers['X-Amz-Content-Sha256'] = 'STREAMING-AWS4-HMAC-SHA256-EVENTS'
|
|
173
|
+
elsif unsigned_payload?(context, req)
|
|
158
174
|
req.headers['X-Amz-Content-Sha256'] ||= 'UNSIGNED-PAYLOAD'
|
|
159
175
|
end
|
|
160
176
|
end
|
|
161
177
|
|
|
178
|
+
def unsigned_payload?(context, req)
|
|
179
|
+
(context.operation['unsignedPayload'] ||
|
|
180
|
+
context.operation['authtype'] == 'v4-unsigned-body') &&
|
|
181
|
+
req.endpoint.scheme == 'https'
|
|
182
|
+
end
|
|
183
|
+
|
|
162
184
|
def reset_signature(req)
|
|
163
185
|
# in case this request is being re-signed
|
|
164
186
|
req.headers.delete('Authorization')
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Aws
|
|
4
4
|
module Plugins
|
|
5
5
|
# @api private
|
|
6
|
-
#
|
|
6
|
+
# Deprecated - does not look at new traits like `auth` and `unsignedPayload`
|
|
7
|
+
# Necessary to exist after endpoints 2.0 for old service clients + new core
|
|
7
8
|
class SignatureV2 < Seahorse::Client::Plugin
|
|
8
9
|
|
|
9
10
|
option(:v2_signer) do |cfg|
|
|
@@ -5,7 +5,8 @@ require 'aws-sigv4'
|
|
|
5
5
|
module Aws
|
|
6
6
|
module Plugins
|
|
7
7
|
# @api private
|
|
8
|
-
#
|
|
8
|
+
# Deprecated - does not look at new traits like `auth` and `unsignedPayload`
|
|
9
|
+
# Necessary to exist after endpoints 2.0 for old service clients + new core
|
|
9
10
|
class SignatureV4 < Seahorse::Client::Plugin
|
|
10
11
|
|
|
11
12
|
V4_AUTH = %w[v4 v4-unsigned-payload v4-unsigned-body]
|
|
@@ -8,6 +8,7 @@ module Aws
|
|
|
8
8
|
option(:stub_responses,
|
|
9
9
|
default: false,
|
|
10
10
|
doc_type: 'Boolean',
|
|
11
|
+
rbs_type: 'untyped',
|
|
11
12
|
docstring: <<-DOCS)
|
|
12
13
|
Causes the client to return stubbed responses. By default
|
|
13
14
|
fake responses are generated and returned. You can specify
|
|
@@ -28,8 +29,22 @@ requests are made, and retries are disabled.
|
|
|
28
29
|
end
|
|
29
30
|
end
|
|
30
31
|
|
|
32
|
+
option(:token_provider) do |config|
|
|
33
|
+
if config.stub_responses
|
|
34
|
+
StaticTokenProvider.new('stubbed-token')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
option(:stubs) { {} }
|
|
39
|
+
option(:stubs_mutex) { Mutex.new }
|
|
40
|
+
option(:api_requests) { [] }
|
|
41
|
+
option(:api_requests_mutex) { Mutex.new }
|
|
42
|
+
|
|
31
43
|
def add_handlers(handlers, config)
|
|
32
|
-
|
|
44
|
+
return unless config.stub_responses
|
|
45
|
+
|
|
46
|
+
handlers.add(ApiRequestsHandler)
|
|
47
|
+
handlers.add(StubbingHandler, step: :send)
|
|
33
48
|
end
|
|
34
49
|
|
|
35
50
|
def after_initialize(client)
|
|
@@ -45,20 +60,43 @@ requests are made, and retries are disabled.
|
|
|
45
60
|
end
|
|
46
61
|
end
|
|
47
62
|
|
|
48
|
-
class
|
|
63
|
+
class ApiRequestsHandler < Seahorse::Client::Handler
|
|
64
|
+
def call(context)
|
|
65
|
+
context.config.api_requests_mutex.synchronize do
|
|
66
|
+
context.config.api_requests << {
|
|
67
|
+
operation_name: context.operation_name,
|
|
68
|
+
params: context.params,
|
|
69
|
+
context: context
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
@handler.call(context)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
49
75
|
|
|
76
|
+
class StubbingHandler < Seahorse::Client::Handler
|
|
50
77
|
def call(context)
|
|
51
|
-
|
|
78
|
+
span_wrapper(context) do
|
|
79
|
+
stub_responses(context)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def stub_responses(context)
|
|
52
86
|
resp = Seahorse::Client::Response.new(context: context)
|
|
53
87
|
async_mode = context.client.is_a? Seahorse::Client::AsyncBase
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
stub = context.client.next_stub(context)
|
|
89
|
+
stub[:mutex].synchronize { apply_stub(stub, resp, async_mode) }
|
|
90
|
+
|
|
91
|
+
if async_mode
|
|
92
|
+
Seahorse::Client::AsyncResponse.new(
|
|
93
|
+
context: context,
|
|
94
|
+
stream: context[:input_event_stream_handler].event_emitter.stream,
|
|
95
|
+
sync_queue: Queue.new
|
|
96
|
+
)
|
|
56
97
|
else
|
|
57
|
-
|
|
98
|
+
resp
|
|
58
99
|
end
|
|
59
|
-
|
|
60
|
-
async_mode ? Seahorse::Client::AsyncResponse.new(
|
|
61
|
-
context: context, stream: context[:input_event_stream_handler].event_emitter.stream, sync_queue: Queue.new) : resp
|
|
62
100
|
end
|
|
63
101
|
|
|
64
102
|
def apply_stub(stub, response, async_mode = false)
|
|
@@ -98,6 +136,18 @@ requests are made, and retries are disabled.
|
|
|
98
136
|
http_resp.signal_done
|
|
99
137
|
end
|
|
100
138
|
|
|
139
|
+
def span_wrapper(context, &block)
|
|
140
|
+
context.tracer.in_span(
|
|
141
|
+
'Handler.StubResponses',
|
|
142
|
+
attributes: Aws::Telemetry.http_request_attrs(context)
|
|
143
|
+
) do |span|
|
|
144
|
+
block.call.tap do
|
|
145
|
+
span.add_attributes(
|
|
146
|
+
Aws::Telemetry.http_response_attrs(context)
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
101
151
|
end
|
|
102
152
|
end
|
|
103
153
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module Plugins
|
|
5
|
+
# @api private
|
|
6
|
+
class Telemetry < Seahorse::Client::Plugin
|
|
7
|
+
option(
|
|
8
|
+
:telemetry_provider,
|
|
9
|
+
default: Aws::Telemetry::NoOpTelemetryProvider,
|
|
10
|
+
doc_type: Aws::Telemetry::TelemetryProviderBase,
|
|
11
|
+
rbs_type: Aws::Telemetry::TelemetryProviderBase,
|
|
12
|
+
docstring: <<-DOCS) do |_cfg|
|
|
13
|
+
Allows you to provide a telemetry provider, which is used to
|
|
14
|
+
emit telemetry data. By default, uses `NoOpTelemetryProvider` which
|
|
15
|
+
will not record or emit any telemetry data. The SDK supports the
|
|
16
|
+
following telemetry providers:
|
|
17
|
+
|
|
18
|
+
* OpenTelemetry (OTel) - To use the OTel provider, install and require the
|
|
19
|
+
`opentelemetry-sdk` gem and then, pass in an instance of a
|
|
20
|
+
`Aws::Telemetry::OTelProvider` for telemetry provider.
|
|
21
|
+
DOCS
|
|
22
|
+
Aws::Telemetry::NoOpTelemetryProvider.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def after_initialize(client)
|
|
26
|
+
validate_telemetry_provider(client.config)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def validate_telemetry_provider(config)
|
|
30
|
+
unless config.telemetry_provider.is_a?(Aws::Telemetry::TelemetryProviderBase)
|
|
31
|
+
raise ArgumentError,
|
|
32
|
+
'Must provide a telemetry provider for the '\
|
|
33
|
+
'`telemetry_provider` configuration option.'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Handler < Seahorse::Client::Handler
|
|
38
|
+
def call(context)
|
|
39
|
+
span_wrapper(context) { @handler.call(context) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def span_wrapper(context, &block)
|
|
45
|
+
service_id = service_id(context)
|
|
46
|
+
attributes = {
|
|
47
|
+
'rpc.system' => 'aws-api',
|
|
48
|
+
'rpc.service' => service_id,
|
|
49
|
+
'rpc.method' => context.operation.name,
|
|
50
|
+
'code.function' => context.operation_name.to_s,
|
|
51
|
+
'code.namespace' => 'Aws::Plugins::Telemetry'
|
|
52
|
+
}
|
|
53
|
+
context.tracer.in_span(
|
|
54
|
+
parent_span_name(context, service_id),
|
|
55
|
+
attributes: attributes,
|
|
56
|
+
kind: Aws::Telemetry::SpanKind::CLIENT,
|
|
57
|
+
&block
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def service_id(context)
|
|
62
|
+
context.config.api.metadata['serviceId'] ||
|
|
63
|
+
context.config.api.metadata['serviceAbbreviation'] ||
|
|
64
|
+
context.config.api.metadata['serviceFullName']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parent_span_name(context, service_id)
|
|
68
|
+
"#{service_id}.#{context.operation.name}".delete(' ')
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
handler(Handler, step: :initialize, priority: 99)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -5,7 +5,8 @@ module Aws
|
|
|
5
5
|
|
|
6
6
|
# For Streaming Input Operations, when `requiresLength` is enabled
|
|
7
7
|
# checking whether `Content-Length` header can be set,
|
|
8
|
-
# for `v4-unsigned-body` operations,
|
|
8
|
+
# for `unsignedPayload` and `v4-unsigned-body` operations,
|
|
9
|
+
# set `Transfer-Encoding` header.
|
|
9
10
|
class TransferEncoding < Seahorse::Client::Plugin
|
|
10
11
|
|
|
11
12
|
# @api private
|
|
@@ -16,8 +17,8 @@ module Aws
|
|
|
16
17
|
unless context.http_request.body.respond_to?(:size)
|
|
17
18
|
if requires_length?(context.operation.input)
|
|
18
19
|
# if size of the IO is not available but required
|
|
19
|
-
raise Aws::Errors::MissingContentLength
|
|
20
|
-
elsif context.operation
|
|
20
|
+
raise Aws::Errors::MissingContentLength
|
|
21
|
+
elsif unsigned_payload?(context.operation)
|
|
21
22
|
context.http_request.headers['Transfer-Encoding'] = 'chunked'
|
|
22
23
|
end
|
|
23
24
|
end
|
|
@@ -29,18 +30,24 @@ module Aws
|
|
|
29
30
|
private
|
|
30
31
|
|
|
31
32
|
def streaming?(ref)
|
|
32
|
-
if payload = ref[:payload_member]
|
|
33
|
-
payload[
|
|
34
|
-
payload.shape["streaming"]
|
|
33
|
+
if (payload = ref[:payload_member])
|
|
34
|
+
payload['streaming'] || payload.shape['streaming']
|
|
35
35
|
else
|
|
36
36
|
false
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
def unsigned_payload?(operation)
|
|
41
|
+
operation['unsignedPayload'] ||
|
|
42
|
+
operation['authtype'] == 'v4-unsigned-body'
|
|
43
|
+
end
|
|
44
|
+
|
|
40
45
|
def requires_length?(ref)
|
|
41
|
-
payload = ref[:payload_member]
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
if (payload = ref[:payload_member])
|
|
47
|
+
payload['requiresLength'] || payload.shape['requiresLength']
|
|
48
|
+
else
|
|
49
|
+
false
|
|
50
|
+
end
|
|
44
51
|
end
|
|
45
52
|
|
|
46
53
|
end
|
|
@@ -4,7 +4,93 @@ module Aws
|
|
|
4
4
|
module Plugins
|
|
5
5
|
# @api private
|
|
6
6
|
class UserAgent < Seahorse::Client::Plugin
|
|
7
|
+
METRICS = Aws::Json.load(<<-METRICS)
|
|
8
|
+
{
|
|
9
|
+
"RESOURCE_MODEL": "A",
|
|
10
|
+
"WAITER": "B",
|
|
11
|
+
"PAGINATOR": "C",
|
|
12
|
+
"RETRY_MODE_LEGACY": "D",
|
|
13
|
+
"RETRY_MODE_STANDARD": "E",
|
|
14
|
+
"RETRY_MODE_ADAPTIVE": "F",
|
|
15
|
+
"S3_TRANSFER": "G",
|
|
16
|
+
"S3_CRYPTO_V1N": "H",
|
|
17
|
+
"S3_CRYPTO_V2": "I",
|
|
18
|
+
"S3_EXPRESS_BUCKET": "J",
|
|
19
|
+
"S3_ACCESS_GRANTS": "K",
|
|
20
|
+
"GZIP_REQUEST_COMPRESSION": "L",
|
|
21
|
+
"PROTOCOL_RPC_V2_CBOR": "M",
|
|
22
|
+
"ENDPOINT_OVERRIDE": "N",
|
|
23
|
+
"ACCOUNT_ID_ENDPOINT": "O",
|
|
24
|
+
"ACCOUNT_ID_MODE_PREFERRED": "P",
|
|
25
|
+
"ACCOUNT_ID_MODE_DISABLED": "Q",
|
|
26
|
+
"ACCOUNT_ID_MODE_REQUIRED": "R",
|
|
27
|
+
"SIGV4A_SIGNING": "S",
|
|
28
|
+
"RESOLVED_ACCOUNT_ID": "T",
|
|
29
|
+
"FLEXIBLE_CHECKSUMS_REQ_CRC32" : "U",
|
|
30
|
+
"FLEXIBLE_CHECKSUMS_REQ_CRC32C" : "V",
|
|
31
|
+
"FLEXIBLE_CHECKSUMS_REQ_CRC64" : "W",
|
|
32
|
+
"FLEXIBLE_CHECKSUMS_REQ_SHA1" : "X",
|
|
33
|
+
"FLEXIBLE_CHECKSUMS_REQ_SHA256" : "Y",
|
|
34
|
+
"FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED" : "Z",
|
|
35
|
+
"FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED" : "a",
|
|
36
|
+
"FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED" : "b",
|
|
37
|
+
"FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED" : "c",
|
|
38
|
+
"DDB_MAPPER": "d",
|
|
39
|
+
"CREDENTIALS_CODE" : "e",
|
|
40
|
+
"CREDENTIALS_ENV_VARS" : "g",
|
|
41
|
+
"CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN" : "h",
|
|
42
|
+
"CREDENTIALS_STS_ASSUME_ROLE" : "i",
|
|
43
|
+
"CREDENTIALS_STS_ASSUME_ROLE_WEB_ID" : "k",
|
|
44
|
+
"CREDENTIALS_PROFILE" : "n",
|
|
45
|
+
"CREDENTIALS_PROFILE_SOURCE_PROFILE" : "o",
|
|
46
|
+
"CREDENTIALS_PROFILE_NAMED_PROVIDER" : "p",
|
|
47
|
+
"CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN" : "q",
|
|
48
|
+
"CREDENTIALS_PROFILE_SSO" : "r",
|
|
49
|
+
"CREDENTIALS_SSO" : "s",
|
|
50
|
+
"CREDENTIALS_PROFILE_SSO_LEGACY" : "t",
|
|
51
|
+
"CREDENTIALS_SSO_LEGACY" : "u",
|
|
52
|
+
"CREDENTIALS_PROFILE_PROCESS" : "v",
|
|
53
|
+
"CREDENTIALS_PROCESS" : "w",
|
|
54
|
+
"CREDENTIALS_HTTP" : "z",
|
|
55
|
+
"CREDENTIALS_IMDS" : "0",
|
|
56
|
+
"SSO_LOGIN_DEVICE" : "1",
|
|
57
|
+
"SSO_LOGIN_AUTH" : "2",
|
|
58
|
+
"BEARER_SERVICE_ENV_VARS": "3"
|
|
59
|
+
}
|
|
60
|
+
METRICS
|
|
61
|
+
|
|
62
|
+
# @api private
|
|
7
63
|
option(:user_agent_suffix)
|
|
64
|
+
# @api private
|
|
65
|
+
option(:user_agent_frameworks, default: [])
|
|
66
|
+
|
|
67
|
+
option(
|
|
68
|
+
:sdk_ua_app_id,
|
|
69
|
+
doc_type: 'String',
|
|
70
|
+
docstring: <<-DOCS) do |cfg|
|
|
71
|
+
A unique and opaque application ID that is appended to the
|
|
72
|
+
User-Agent header as app/sdk_ua_app_id. It should have a
|
|
73
|
+
maximum length of 50. This variable is sourced from environment
|
|
74
|
+
variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.
|
|
75
|
+
DOCS
|
|
76
|
+
app_id = ENV['AWS_SDK_UA_APP_ID']
|
|
77
|
+
app_id ||= Aws.shared_config.sdk_ua_app_id(profile: cfg.profile)
|
|
78
|
+
app_id
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Deprecated - must exist for old service gems
|
|
82
|
+
def self.feature(_feature, &block)
|
|
83
|
+
block.call
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.metric(*metrics, &block)
|
|
87
|
+
Thread.current[:aws_sdk_core_user_agent_metric] ||= []
|
|
88
|
+
metrics = metrics.map { |metric| METRICS[metric] }.compact
|
|
89
|
+
Thread.current[:aws_sdk_core_user_agent_metric].concat(metrics)
|
|
90
|
+
block.call
|
|
91
|
+
ensure
|
|
92
|
+
Thread.current[:aws_sdk_core_user_agent_metric].pop(metrics.size)
|
|
93
|
+
end
|
|
8
94
|
|
|
9
95
|
# @api private
|
|
10
96
|
class Handler < Seahorse::Client::Handler
|
|
@@ -14,33 +100,125 @@ module Aws
|
|
|
14
100
|
end
|
|
15
101
|
|
|
16
102
|
def set_user_agent(context)
|
|
17
|
-
|
|
103
|
+
context.http_request.headers['User-Agent'] = UserAgent.new(context).to_s
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class UserAgent
|
|
107
|
+
def initialize(context)
|
|
108
|
+
@context = context
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def to_s
|
|
112
|
+
ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
|
|
113
|
+
ua += ' ua/2.1'
|
|
114
|
+
if (api_m = api_metadata)
|
|
115
|
+
ua += " #{api_m}"
|
|
116
|
+
end
|
|
117
|
+
ua += " #{os_metadata}"
|
|
118
|
+
ua += " #{language_metadata}"
|
|
119
|
+
if (env_m = env_metadata)
|
|
120
|
+
ua += " #{env_m}"
|
|
121
|
+
end
|
|
122
|
+
if (app_id_m = app_id_metadata)
|
|
123
|
+
ua += " #{app_id_m}"
|
|
124
|
+
end
|
|
125
|
+
if (framework_m = framework_metadata)
|
|
126
|
+
ua += " #{framework_m}"
|
|
127
|
+
end
|
|
128
|
+
if (metric_m = metric_metadata)
|
|
129
|
+
ua += " #{metric_m}"
|
|
130
|
+
end
|
|
131
|
+
if @context.config.user_agent_suffix
|
|
132
|
+
ua += " #{@context.config.user_agent_suffix}"
|
|
133
|
+
end
|
|
134
|
+
ua.strip
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
18
138
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
139
|
+
# Used to be gem_name/gem_version
|
|
140
|
+
def api_metadata
|
|
141
|
+
service_id = @context.config.api.metadata['serviceId']
|
|
142
|
+
return unless service_id
|
|
143
|
+
|
|
144
|
+
service_id = service_id.gsub(' ', '_').downcase
|
|
145
|
+
gem_version = @context[:gem_version]
|
|
146
|
+
"api/#{service_id}##{gem_version}"
|
|
23
147
|
end
|
|
24
148
|
|
|
25
|
-
|
|
149
|
+
# Used to be RUBY_PLATFORM
|
|
150
|
+
def os_metadata
|
|
151
|
+
os =
|
|
152
|
+
case RbConfig::CONFIG['host_os']
|
|
153
|
+
when /mac|darwin/
|
|
154
|
+
'macos'
|
|
155
|
+
when /linux|cygwin/
|
|
156
|
+
'linux'
|
|
157
|
+
when /mingw|mswin/
|
|
158
|
+
'windows'
|
|
159
|
+
else
|
|
160
|
+
'other'
|
|
161
|
+
end
|
|
162
|
+
metadata = "os/#{os}"
|
|
163
|
+
local_version = Gem::Platform.local.version
|
|
164
|
+
metadata += "##{local_version}" if local_version
|
|
165
|
+
metadata += " md/#{RbConfig::CONFIG['host_cpu']}"
|
|
166
|
+
end
|
|
26
167
|
|
|
27
|
-
|
|
28
|
-
|
|
168
|
+
# Used to be RUBY_ENGINE/RUBY_VERSION
|
|
169
|
+
def language_metadata
|
|
170
|
+
"lang/#{RUBY_ENGINE}##{RUBY_ENGINE_VERSION} md/#{RUBY_VERSION}"
|
|
29
171
|
end
|
|
30
172
|
|
|
31
|
-
|
|
32
|
-
|
|
173
|
+
def env_metadata
|
|
174
|
+
return unless (execution_env = ENV['AWS_EXECUTION_ENV'])
|
|
175
|
+
|
|
176
|
+
"exec-env/#{execution_env}"
|
|
33
177
|
end
|
|
34
178
|
|
|
35
|
-
|
|
36
|
-
|
|
179
|
+
def app_id_metadata
|
|
180
|
+
return unless (app_id = @context.config.sdk_ua_app_id)
|
|
181
|
+
|
|
182
|
+
# Sanitize and only allow these characters
|
|
183
|
+
app_id = app_id.gsub(/[^!#$%&'*+\-.^_`|~0-9A-Za-z]/, '-')
|
|
184
|
+
"app/#{app_id}"
|
|
37
185
|
end
|
|
38
186
|
|
|
39
|
-
|
|
187
|
+
def framework_metadata
|
|
188
|
+
if (frameworks_cfg = @context.config.user_agent_frameworks).empty?
|
|
189
|
+
return
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Frameworks may be aws-record, aws-sdk-rails, etc.
|
|
193
|
+
regex = /gems\/(?<name>#{frameworks_cfg.join('|')})-(?<version>\d+\.\d+\.\d+)/.freeze
|
|
194
|
+
frameworks = {}
|
|
195
|
+
Kernel.caller.each do |line|
|
|
196
|
+
match = line.match(regex)
|
|
197
|
+
next unless match
|
|
198
|
+
|
|
199
|
+
frameworks[match[:name]] = match[:version]
|
|
200
|
+
end
|
|
201
|
+
frameworks.map { |n, v| "lib/#{n}##{v}" }.join(' ')
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def metric_metadata
|
|
205
|
+
if Thread.current[:aws_sdk_core_user_agent_metric].nil? ||
|
|
206
|
+
Thread.current[:aws_sdk_core_user_agent_metric].empty?
|
|
207
|
+
return
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
metrics = Thread.current[:aws_sdk_core_user_agent_metric].join(',')
|
|
211
|
+
# Metric metadata is limited to 1024 bytes
|
|
212
|
+
return "m/#{metrics}" if metrics.bytesize <= 1024
|
|
213
|
+
|
|
214
|
+
# Removes the last unfinished metric
|
|
215
|
+
"m/#{metrics[0...metrics[0..1024].rindex(',')]}"
|
|
216
|
+
end
|
|
40
217
|
end
|
|
41
218
|
end
|
|
42
219
|
|
|
43
|
-
|
|
220
|
+
# Priority set to 5 in order to add user agent as late as possible after signing
|
|
221
|
+
handler(Handler, step: :sign, priority: 5)
|
|
44
222
|
end
|
|
45
223
|
end
|
|
46
224
|
end
|