File: user_agent.rb

package info (click to toggle)
ruby-aws-sdk-core 3.212.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,232 kB
  • sloc: ruby: 17,533; makefile: 4
file content (193 lines) | stat: -rw-r--r-- 6,036 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# frozen_string_literal: true

module Aws
  module Plugins
    # @api private
    class UserAgent < Seahorse::Client::Plugin
      METRICS = Aws::Json.load(<<-METRICS)
        {
          "RESOURCE_MODEL": "A",
          "WAITER": "B",
          "PAGINATOR": "C",
          "RETRY_MODE_LEGACY": "D",
          "RETRY_MODE_STANDARD": "E",
          "RETRY_MODE_ADAPTIVE": "F",
          "S3_TRANSFER": "G",
          "S3_CRYPTO_V1N": "H",
          "S3_CRYPTO_V2": "I",
          "S3_EXPRESS_BUCKET": "J",
          "S3_ACCESS_GRANTS": "K",
          "GZIP_REQUEST_COMPRESSION": "L",
          "PROTOCOL_RPC_V2_CBOR": "M",
          "ENDPOINT_OVERRIDE": "N",
          "ACCOUNT_ID_ENDPOINT": "O",
          "ACCOUNT_ID_MODE_PREFERRED": "P",
          "ACCOUNT_ID_MODE_DISABLED": "Q",
          "ACCOUNT_ID_MODE_REQUIRED": "R",
          "SIGV4A_SIGNING": "S",
          "RESOLVED_ACCOUNT_ID": "T"
        }
      METRICS

      # @api private
      option(:user_agent_suffix)
      # @api private
      option(:user_agent_frameworks, default: [])

      option(
        :sdk_ua_app_id,
        doc_type: 'String',
        docstring: <<-DOCS) do |cfg|
A unique and opaque application ID that is appended to the
User-Agent header as app/sdk_ua_app_id. It should have a
maximum length of 50. This variable is sourced from environment
variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.
        DOCS
        app_id = ENV['AWS_SDK_UA_APP_ID']
        app_id ||= Aws.shared_config.sdk_ua_app_id(profile: cfg.profile)
        app_id
      end

      # Deprecated - must exist for old service gems
      def self.feature(_feature, &block)
        block.call
      end

      def self.metric(*metrics, &block)
        Thread.current[:aws_sdk_core_user_agent_metric] ||= []
        metrics = metrics.map { |metric| METRICS[metric] }.compact
        Thread.current[:aws_sdk_core_user_agent_metric].concat(metrics)
        block.call
      ensure
        Thread.current[:aws_sdk_core_user_agent_metric].pop(metrics.size)
      end

      # @api private
      class Handler < Seahorse::Client::Handler
        def call(context)
          set_user_agent(context)
          @handler.call(context)
        end

        def set_user_agent(context)
          context.http_request.headers['User-Agent'] = UserAgent.new(context).to_s
        end

        class UserAgent
          def initialize(context)
            @context = context
          end

          def to_s
            ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
            ua += ' ua/2.1'
            if (api_m = api_metadata)
              ua += " #{api_m}"
            end
            ua += " #{os_metadata}"
            ua += " #{language_metadata}"
            if (env_m = env_metadata)
              ua += " #{env_m}"
            end
            if (app_id_m = app_id_metadata)
              ua += " #{app_id_m}"
            end
            if (framework_m = framework_metadata)
              ua += " #{framework_m}"
            end
            if (metric_m = metric_metadata)
              ua += " #{metric_m}"
            end
            if @context.config.user_agent_suffix
              ua += " #{@context.config.user_agent_suffix}"
            end
            ua.strip
          end

          private

          # Used to be gem_name/gem_version
          def api_metadata
            service_id = @context.config.api.metadata['serviceId']
            return unless service_id

            service_id = service_id.gsub(' ', '_').downcase
            gem_version = @context[:gem_version]
            "api/#{service_id}##{gem_version}"
          end

          # Used to be RUBY_PLATFORM
          def os_metadata
            os =
              case RbConfig::CONFIG['host_os']
              when /mac|darwin/
                'macos'
              when /linux|cygwin/
                'linux'
              when /mingw|mswin/
                'windows'
              else
                'other'
              end
            metadata = "os/#{os}"
            local_version = Gem::Platform.local.version
            metadata += "##{local_version}" if local_version
            metadata += " md/#{RbConfig::CONFIG['host_cpu']}"
          end

          # Used to be RUBY_ENGINE/RUBY_VERSION
          def language_metadata
            "lang/#{RUBY_ENGINE}##{RUBY_ENGINE_VERSION} md/#{RUBY_VERSION}"
          end

          def env_metadata
            return unless (execution_env = ENV['AWS_EXECUTION_ENV'])

            "exec-env/#{execution_env}"
          end

          def app_id_metadata
            return unless (app_id = @context.config.sdk_ua_app_id)

            # Sanitize and only allow these characters
            app_id = app_id.gsub(/[^!#$%&'*+\-.^_`|~0-9A-Za-z]/, '-')
            "app/#{app_id}"
          end

          def framework_metadata
            if (frameworks_cfg = @context.config.user_agent_frameworks).empty?
              return
            end

            # Frameworks may be aws-record, aws-sdk-rails, etc.
            regex = /gems\/(?<name>#{frameworks_cfg.join('|')})-(?<version>\d+\.\d+\.\d+)/.freeze
            frameworks = {}
            Kernel.caller.each do |line|
              match = line.match(regex)
              next unless match

              frameworks[match[:name]] = match[:version]
            end
            frameworks.map { |n, v| "lib/#{n}##{v}" }.join(' ')
          end

          def metric_metadata
            if Thread.current[:aws_sdk_core_user_agent_metric].nil? ||
               Thread.current[:aws_sdk_core_user_agent_metric].empty?
              return
            end

            metrics = Thread.current[:aws_sdk_core_user_agent_metric].join(',')
            # Metric metadata is limited to 1024 bytes
            return "m/#{metrics}" if metrics.bytesize <= 1024

            # Removes the last unfinished metric
            "m/#{metrics[0...metrics[0..1024].rindex(',')]}"
          end
        end
      end

      handler(Handler, step: :sign, priority: 97)
    end
  end
end