Skip to content

Commit 83d2934

Browse files
authored
User Agent 2.0 (#2854)
1 parent f1c4a66 commit 83d2934

File tree

29 files changed

+492
-98
lines changed

29 files changed

+492
-98
lines changed

build_tools/aws-sdk-code-generator/lib/aws-sdk-code-generator/resource_batch_action_code.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ def build
1515
parts << 'batch_enum.each do |batch|'
1616
parts << initialize_params
1717
parts << apply_params_per_batch
18-
parts << " batch[0].client.#{client_method}(params)"
18+
parts << " Aws::Plugins::UserAgent.feature('resource') do"
19+
parts << " batch[0].client.#{client_method}(params)"
20+
parts << ' end'
1921
parts << 'end'
2022
parts << 'nil'
2123
parts.join("\n").rstrip

build_tools/aws-sdk-code-generator/lib/aws-sdk-code-generator/resource_client_request.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ def build(options)
1515
parts = []
1616
parts << request_options(params) if merge
1717
parts << assignment(options)
18-
parts << "@client."
18+
parts << "Aws::Plugins::UserAgent.feature('resource') do\n"
19+
parts << " @client."
1920
parts << operation_name(request)
2021
parts << arguments(merge, params, streaming)
22+
parts << "\nend"
2123
parts.join
2224
end
2325

build_tools/aws-sdk-code-generator/lib/aws-sdk-code-generator/resource_waiter.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,12 @@ def wait_call(waiter)
5252
args = ResourceClientRequestParams.new(
5353
params: waiter['params']
5454
).to_s.strip
55-
if waiter['path']
56-
"resp = waiter.wait(params.merge(#{args}))"
57-
else
58-
"waiter.wait(params.merge(#{args}))"
59-
end
55+
parts = []
56+
parts << 'resp = ' if waiter['path']
57+
parts << "Aws::Plugins::UserAgent.feature('resource') do\n"
58+
parts << " waiter.wait(params.merge(#{args}))"
59+
parts << "\nend"
60+
parts.join
6061
end
6162

6263
def constructor_args(resource, waiter)

build_tools/aws-sdk-code-generator/spec/spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def generate_service(module_names, options = {})
9696
apig_opts = shared_opts.merge({
9797
api: model_path(:service, api_dir),
9898
default_endpoint: "https://foobar.us-west-2.amazonaws.com/test",
99+
remove_plugins: ['Aws::Plugins::UserAgent']
99100
})
100101

101102
svc_opts = options[:custom] ? apig_opts : service_opts

build_tools/aws-sdk-code-generator/templates/resource_class.mustache

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,9 @@ module {{module_name}}
213213
:retry
214214
end
215215
end
216-
Aws::Waiters::Waiter.new(options).wait({})
216+
Aws::Plugins::UserAgent.feature('resource') do
217+
Aws::Waiters::Waiter.new(options).wait({})
218+
end
217219
end
218220
{{/wait_until}}
219221
{{#actions?}}

build_tools/services.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class ServiceEnumerator
1010
MANIFEST_PATH = File.expand_path('../../services.json', __FILE__)
1111

1212
# Minimum `aws-sdk-core` version for new gem builds
13-
MINIMUM_CORE_VERSION = "3.165.0"
13+
MINIMUM_CORE_VERSION = "3.174.0"
1414

1515
EVENTSTREAM_PLUGIN = "Aws::Plugins::EventStreamConfiguration"
1616

gems/aws-sdk-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Unreleased Changes
22
------------------
33

4+
* Feature - Improve User-Agent metrics tracking.
5+
46
3.173.1 (2023-05-24)
57
------------------
68

gems/aws-sdk-core/lib/aws-sdk-core/pageable_response.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ def each(&block)
201201
def next_response(params)
202202
params = next_page_params(params)
203203
request = context.client.build_request(context.operation_name, params)
204-
request.send_request
204+
Aws::Plugins::UserAgent.feature('paginator') do
205+
request.send_request
206+
end
205207
end
206208

207209
def next_page_params(params)

gems/aws-sdk-core/lib/aws-sdk-core/plugins/user_agent.rb

Lines changed: 117 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,31 @@ module Aws
44
module Plugins
55
# @api private
66
class UserAgent < Seahorse::Client::Plugin
7+
# @api private
78
option(:user_agent_suffix)
9+
# @api private
10+
option(:user_agent_frameworks, default: [])
11+
12+
option(
13+
:sdk_ua_app_id,
14+
doc_type: 'String',
15+
docstring: <<-DOCS) do |cfg|
16+
A unique and opaque application ID that is appended to the
17+
User-Agent header as app/<sdk_ua_app_id>. It should have a
18+
maximum length of 50.
19+
DOCS
20+
app_id = ENV['AWS_SDK_UA_APP_ID']
21+
app_id ||= Aws.shared_config.sdk_ua_app_id(profile: cfg.profile)
22+
app_id
23+
end
24+
25+
def self.feature(feature, &block)
26+
Thread.current[:aws_sdk_core_user_agent_feature] ||= []
27+
Thread.current[:aws_sdk_core_user_agent_feature] << "ft/#{feature}"
28+
block.call
29+
ensure
30+
Thread.current[:aws_sdk_core_user_agent_feature].pop
31+
end
832

933
# @api private
1034
class Handler < Seahorse::Client::Handler
@@ -14,33 +38,112 @@ def call(context)
1438
end
1539

1640
def set_user_agent(context)
17-
ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
41+
context.http_request.headers['User-Agent'] = UserAgent.new(context).to_s
42+
end
43+
44+
class UserAgent
45+
def initialize(context)
46+
@context = context
47+
end
48+
49+
def to_s
50+
ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
51+
ua += ' ua/2.0'
52+
ua += " #{api_metadata}" if api_metadata
53+
ua += " #{os_metadata}"
54+
ua += " #{language_metadata}"
55+
ua += " #{env_metadata}" if env_metadata
56+
ua += " #{config_metadata}" if config_metadata
57+
ua += " #{app_id}" if app_id
58+
ua += " #{feature_metadata}" if feature_metadata
59+
ua += " #{framework_metadata}" if framework_metadata
60+
if @context.config.user_agent_suffix
61+
ua += " #{@context.config.user_agent_suffix}"
62+
end
63+
ua.strip
64+
end
65+
66+
private
1867

19-
begin
20-
ua += " #{RUBY_ENGINE}/#{RUBY_VERSION}"
21-
rescue
22-
ua += " RUBY_ENGINE_NA/#{RUBY_VERSION}"
68+
# Used to be gem_name/gem_version
69+
def api_metadata
70+
service_id = @context.config.api.metadata['serviceId']
71+
return unless service_id
72+
73+
service_id = service_id.gsub(' ', '_').downcase
74+
gem_version = @context[:gem_version]
75+
"api/#{service_id}##{gem_version}"
76+
end
77+
78+
# Used to be RUBY_PLATFORM
79+
def os_metadata
80+
os =
81+
case RbConfig::CONFIG['host_os']
82+
when /mac|darwin/
83+
'macos'
84+
when /linux|cygwin/
85+
'linux'
86+
when /mingw|mswin/
87+
'windows'
88+
else
89+
'other'
90+
end
91+
metadata = "os/#{os}"
92+
local_version = Gem::Platform.local.version
93+
metadata += "##{local_version}" if local_version
94+
metadata += " md/#{RbConfig::CONFIG['host_cpu']}"
95+
metadata
2396
end
2497

25-
ua += " #{RUBY_PLATFORM}"
98+
# Used to be RUBY_ENGINE/RUBY_VERSION
99+
def language_metadata
100+
"lang/#{RUBY_ENGINE}##{RUBY_ENGINE_VERSION} md/#{RUBY_VERSION}"
101+
end
102+
103+
def env_metadata
104+
return unless (execution_env = ENV['AWS_EXECUTION_ENV'])
105+
106+
"exec-env/#{execution_env}"
107+
end
26108

27-
if context[:gem_name] && context[:gem_version]
28-
ua += " #{context[:gem_name]}/#{context[:gem_version]}"
109+
def config_metadata
110+
"cfg/retry-mode##{@context.config.retry_mode}"
29111
end
30112

31-
if (execution_env = ENV['AWS_EXECUTION_ENV'])
32-
ua += " exec-env/#{execution_env}"
113+
def app_id
114+
return unless (app_id = @context.config.sdk_ua_app_id)
115+
116+
# Sanitize and only allow these characters
117+
app_id = app_id.gsub(/[^!#$%&'*+\-.^_`|~0-9A-Za-z]/, '-')
118+
"app/#{app_id}"
33119
end
34120

35-
if context.config.user_agent_suffix
36-
ua += " #{context.config.user_agent_suffix}"
121+
def feature_metadata
122+
return unless Thread.current[:aws_sdk_core_user_agent_feature]
123+
124+
Thread.current[:aws_sdk_core_user_agent_feature].join(' ')
37125
end
38126

39-
context.http_request.headers['User-Agent'] = ua.strip
127+
def framework_metadata
128+
if (frameworks_cfg = @context.config.user_agent_frameworks).empty?
129+
return
130+
end
131+
132+
# Frameworks may be aws-record, aws-sdk-rails, etc.
133+
regex = /gems\/(?<name>#{frameworks_cfg.join('|')})-(?<version>\d+\.\d+\.\d+)/.freeze
134+
frameworks = {}
135+
Kernel.caller.each do |line|
136+
match = line.match(regex)
137+
next unless match
138+
139+
frameworks[match[:name]] = match[:version]
140+
end
141+
frameworks.map { |n, v| "lib/#{n}##{v}" }.join(' ')
142+
end
40143
end
41144
end
42145

43-
handler(Handler)
146+
handler(Handler, priority: 1)
44147
end
45148
end
46149
end

gems/aws-sdk-core/lib/aws-sdk-core/shared_config.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ def self.config_reader(*attrs)
197197
:s3_use_arn_region,
198198
:s3_us_east_1_regional_endpoint,
199199
:s3_disable_multiregion_access_points,
200-
:defaults_mode
200+
:defaults_mode,
201+
:sdk_ua_app_id
201202
)
202203

203204
private

0 commit comments

Comments
 (0)