diff --git a/build_tools/aws-sdk-code-generator/lib/aws-sdk-code-generator/plugin_list.rb b/build_tools/aws-sdk-code-generator/lib/aws-sdk-code-generator/plugin_list.rb index d0964cd5d06..0e62bcfb686 100644 --- a/build_tools/aws-sdk-code-generator/lib/aws-sdk-code-generator/plugin_list.rb +++ b/build_tools/aws-sdk-code-generator/lib/aws-sdk-code-generator/plugin_list.rb @@ -58,7 +58,8 @@ def default_plugins 'Aws::Plugins::ClientMetricsPlugin' => "#{core_plugins}/client_metrics_plugin.rb", 'Aws::Plugins::ClientMetricsSendPlugin' => "#{core_plugins}/client_metrics_send_plugin.rb", 'Aws::Plugins::TransferEncoding' => "#{core_plugins}/transfer_encoding.rb", - 'Aws::Plugins::HttpChecksum' => "#{core_plugins}/http_checksum.rb" + 'Aws::Plugins::HttpChecksum' => "#{core_plugins}/http_checksum.rb", + 'Aws::Plugins::DefaultsMode' => "#{core_plugins}/defaults_mode.rb" } end diff --git a/build_tools/aws-sdk-code-generator/templates/client_class.mustache b/build_tools/aws-sdk-code-generator/templates/client_class.mustache index 1e8fb7d7fd7..49001ea5fc5 100644 --- a/build_tools/aws-sdk-code-generator/templates/client_class.mustache +++ b/build_tools/aws-sdk-code-generator/templates/client_class.mustache @@ -45,7 +45,7 @@ module {{module_name}} # seconds to wait when opening a HTTP session before raising a # `Timeout::Error`. # - # @option options [Integer] :http_read_timeout (60) The default + # @option options [Float] :http_read_timeout (60) The default # number of seconds to wait for response data. This value can # safely be set per-request on the session. # @@ -61,6 +61,9 @@ module {{module_name}} # disables this behaviour. This value can safely be set per # request on the session. # + # @option options [Float] :ssl_timeout (nil) Sets the SSL timeout + # in seconds. + # # @option options [Boolean] :http_wire_trace (false) When `true`, # HTTP debug output will be sent to the `:logger`. # diff --git a/build_tools/custom_service.rb b/build_tools/custom_service.rb index 4fb90c2d35d..33a4827f65b 100644 --- a/build_tools/custom_service.rb +++ b/build_tools/custom_service.rb @@ -43,7 +43,8 @@ def build 'Aws::Plugins::RegionalEndpoint', 'Aws::Plugins::EndpointDiscovery', 'Aws::Plugins::EndpointPattern', - 'Aws::Plugins::CredentialsConfiguration' + 'Aws::Plugins::CredentialsConfiguration', + 'Aws::Plugins::DefaultsMode' ] ) end diff --git a/build_tools/services.rb b/build_tools/services.rb index 7bf540311ee..40c378fb2f6 100644 --- a/build_tools/services.rb +++ b/build_tools/services.rb @@ -10,7 +10,7 @@ class ServiceEnumerator MANIFEST_PATH = File.expand_path('../../services.json', __FILE__) # Minimum `aws-sdk-core` version for new gem builds - MINIMUM_CORE_VERSION = "3.122.0" + MINIMUM_CORE_VERSION = "3.125.0" EVENTSTREAM_PLUGIN = "Aws::Plugins::EventStreamConfiguration" diff --git a/gems/aws-sdk-core/CHANGELOG.md b/gems/aws-sdk-core/CHANGELOG.md index 19f1e9b52da..98e184f00d3 100644 --- a/gems/aws-sdk-core/CHANGELOG.md +++ b/gems/aws-sdk-core/CHANGELOG.md @@ -1,6 +1,8 @@ Unreleased Changes ------------------ +* Feature - Add `:defaults_mode` configuration - that determines how certain default configuration options are resolved in the SDK. + 3.124.0 (2021-11-30) ------------------ diff --git a/gems/aws-sdk-core/lib/aws-defaults.rb b/gems/aws-sdk-core/lib/aws-defaults.rb new file mode 100644 index 00000000000..94050821ba1 --- /dev/null +++ b/gems/aws-sdk-core/lib/aws-defaults.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative 'aws-defaults/default_configuration' \ No newline at end of file diff --git a/gems/aws-sdk-core/lib/aws-defaults/default_configuration.rb b/gems/aws-sdk-core/lib/aws-defaults/default_configuration.rb new file mode 100644 index 00000000000..6aaaa780a5c --- /dev/null +++ b/gems/aws-sdk-core/lib/aws-defaults/default_configuration.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +require_relative 'defaults_mode_config_resolver' + +module Aws + + # A defaults mode determines how certain default configuration options are resolved in the SDK. + # + # *Note*: For any mode other than `'legacy'` the vended default values might change as best practices may + # evolve. As a result, it is encouraged to perform testing when upgrading the SDK if you are using a mode other than + # `'legacy'`. While the `'legacy'` defaults mode is specific to Ruby, + # other modes are standardized across all of the AWS SDKs. + # + # The defaults mode can be configured: + # + # * Directly on a client via `:defaults_mode` + # + # * On a configuration profile via the "defaults_mode" profile file property. + # + # * Globally via the "AWS_DEFAULTS_MODE" environment variable. + # + # + # @code_generation START - documentation + # The following `:default_mode` values are supported: + # + # * `'standard'` - + # The STANDARD mode provides the latest recommended default values + # that should be safe to run in most scenarios + # + # Note that the default values vended from this mode might change as + # best practices may evolve. As a result, it is encouraged to perform + # tests when upgrading the SDK + # + # * `'in-region'` - + # The IN\_REGION mode builds on the standard mode and includes + # optimization tailored for applications which call AWS services from + # within the same AWS region + # + # Note that the default values vended from this mode might change as + # best practices may evolve. As a result, it is encouraged to perform + # tests when upgrading the SDK + # + # * `'cross-region'` - + # The CROSS\_REGION mode builds on the standard mode and includes + # optimization tailored for applications which call AWS services in a + # different region + # + # Note that the default values vended from this mode might change as + # best practices may evolve. As a result, it is encouraged to perform + # tests when upgrading the SDK + # + # * `'mobile'` - + # The MOBILE mode builds on the standard mode and includes + # optimization tailored for mobile applications + # + # Note that the default values vended from this mode might change as + # best practices may evolve. As a result, it is encouraged to perform + # tests when upgrading the SDK + # + # * `'auto'` - + # The AUTO mode is an experimental mode that builds on the standard + # mode. The SDK will attempt to discover the execution environment to + # determine the appropriate settings automatically. + # + # Note that the auto detection is heuristics-based and does not + # guarantee 100% accuracy. STANDARD mode will be used if the execution + # environment cannot be determined. The auto detection might query + # [EC2 Instance Metadata service][1], which might introduce latency. + # Therefore we recommend choosing an explicit defaults\_mode instead + # if startup latency is critical to your application + # + # * `'legacy'` - + # The LEGACY mode provides default settings that vary per SDK and were + # used prior to establishment of defaults\_mode + # + # Based on the provided mode, the SDK will vend sensible default values + # tailored to the mode for the following settings: + # + # * `:retry_mode` - + # A retry mode specifies how the SDK attempts retries. See [Retry + # Mode][2] + # + # * `:sts_regional_endpoints` - + # Specifies how the SDK determines the AWS service endpoint that it + # uses to talk to the AWS Security Token Service (AWS STS). See + # [Setting STS Regional endpoints][3] + # + # * `:s3_us_east_1_regional_endpoint` - + # Specifies how the SDK determines the AWS service endpoint that it + # uses to talk to the Amazon S3 for the us-east-1 region + # + # * `:http_open_timeout` - + # The amount of time after making an initial connection attempt on a + # socket, where if the client does not receive a completion of the + # connect handshake, the client gives up and fails the operation + # + # * `:ssl_timeout` - + # The maximum amount of time that a TLS handshake is allowed to take + # from the time the CLIENT HELLO message is sent to ethe time the + # client and server have fully negotiated ciphers and exchanged keys + # + # All options above can be configured by users, and the overridden value will take precedence. + # + # [1]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html + # [2]: https://docs.aws.amazon.com/sdkref/latest/guide/setting-global-retry_mode.html + # [3]: https://docs.aws.amazon.com/sdkref/latest/guide/setting-global-sts_regional_endpoints.html + # + # @code_generation END - documentation + module DefaultsModeConfiguration + # @api private + # @code_generation START - configuration + SDK_DEFAULT_CONFIGURATION = + { + "version" => 1, + "base" => { + "retryMode" => "standard", + "stsRegionalEndpoints" => "regional", + "s3UsEast1RegionalEndpoints" => "regional", + "connectTimeoutInMillis" => 1100, + "tlsNegotiationTimeoutInMillis" => 1100 + }, + "modes" => { + "standard" => { + "connectTimeoutInMillis" => { + "override" => 3100 + }, + "tlsNegotiationTimeoutInMillis" => { + "override" => 3100 + } + }, + "in-region" => { + }, + "cross-region" => { + "connectTimeoutInMillis" => { + "override" => 3100 + }, + "tlsNegotiationTimeoutInMillis" => { + "override" => 3100 + } + }, + "mobile" => { + "connectTimeoutInMillis" => { + "override" => 30000 + }, + "tlsNegotiationTimeoutInMillis" => { + "override" => 30000 + } + } + } + } + # @code_generation END - configuration + end +end \ No newline at end of file diff --git a/gems/aws-sdk-core/lib/aws-defaults/defaults_mode_config_resolver.rb b/gems/aws-sdk-core/lib/aws-defaults/defaults_mode_config_resolver.rb new file mode 100644 index 00000000000..df868c2209f --- /dev/null +++ b/gems/aws-sdk-core/lib/aws-defaults/defaults_mode_config_resolver.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module Aws + #@api private + class DefaultsModeConfigResolver + + @@application_region = nil + @@application_region_mutex = Mutex.new + @@imds_client = EC2Metadata.new(retries: 0, http_open_timeout: 0.01) + + # mappings from Ruby SDK configuration names to the + # sdk defaults option names and (optional) scale modifiers + CFG_OPTIONS = { + retry_mode: { name: "retryMode" }, + sts_regional_endpoints: { name: "stsRegionalEndpoints" }, + s3_us_east_1_regional_endpoint: { name: "s3UsEast1RegionalEndpoints" }, + http_open_timeout: { name: "connectTimeoutInMillis", scale: 0.001 }, + http_read_timeout: { name: "timeToFirstByteTimeoutInMillis", scale: 0.001 }, + ssl_timeout: { name: "tlsNegotiationTimeoutInMillis", scale: 0.001 } + }.freeze + + def initialize(sdk_defaults, cfg) + @sdk_defaults = sdk_defaults + @cfg = cfg + @resolved_mode = nil + @mutex = Mutex.new + end + + # option_name should be the symbolized ruby name to resolve + # returns the ruby appropriate value or nil if none are resolved + def resolve(option_name) + return unless (std_option = CFG_OPTIONS[option_name]) + mode = resolved_mode.downcase + + return nil if mode == 'legacy' + + value = resolve_for_mode(std_option[:name], mode) + value = value * std_option[:scale] if value && std_option[:scale] + + value + end + + private + def resolved_mode + @mutex.synchronize do + return @resolved_mode unless @resolved_mode.nil? + + @resolved_mode = @cfg.defaults_mode == 'auto' ? resolve_auto_mode : @cfg.defaults_mode + end + end + + def resolve_auto_mode + return "mobile" if env_mobile? + + region = application_current_region + + if region + @cfg.region == region ? "in-region": "cross-region" + else + # We don't seem to be mobile, and we couldn't determine whether we're running within an AWS region. Fall back to standard. + 'standard' + end + end + + def application_current_region + resolved_region = @@application_region_mutex.synchronize do + return @@application_region unless @@application_region.nil? + + region = nil + if ENV['AWS_EXECUTION_ENV'] + region = ENV['AWS_REGION'] || ENV['AWS_DEFAULT_REGION'] + end + + if region.nil? && ENV['AWS_EC2_METADATA_DISABLED']&.downcase != "true" + begin + region = @@imds_client.get('/latest/meta-data/placement/region') + rescue + # unable to get region, leave it unset + end + end + + # required so that we cache the unknown/nil result + @@application_region = region || :unknown + end + resolved_region == :unknown ? nil : resolved_region + end + + def resolve_for_mode(name, mode) + base_value = @sdk_defaults['base'][name] + mode_value = @sdk_defaults['modes'].fetch(mode, {})[name] + + if mode_value.nil? + return base_value + end + + return mode_value['override'] unless mode_value['override'].nil? + return base_value + mode_value['add'] unless mode_value['add'].nil? + return base_value * mode_value['multiply'] unless mode_value['multiply'].nil? + return base_value + end + + def env_mobile? + false + end + + end +end \ No newline at end of file diff --git a/gems/aws-sdk-core/lib/aws-sdk-core.rb b/gems/aws-sdk-core/lib/aws-sdk-core.rb index 8ab4b20bb60..b58ebf61b58 100644 --- a/gems/aws-sdk-core/lib/aws-sdk-core.rb +++ b/gems/aws-sdk-core/lib/aws-sdk-core.rb @@ -88,6 +88,9 @@ require_relative 'aws-sdk-core/arn_parser' require_relative 'aws-sdk-core/ec2_metadata' +# defaults +require_relative 'aws-defaults' + # plugins # loaded through building STS or SSO .. diff --git a/gems/aws-sdk-core/lib/aws-sdk-core/plugins/defaults_mode.rb b/gems/aws-sdk-core/lib/aws-sdk-core/plugins/defaults_mode.rb new file mode 100644 index 00000000000..9f4f5a99620 --- /dev/null +++ b/gems/aws-sdk-core/lib/aws-sdk-core/plugins/defaults_mode.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Aws + # @api private + module Plugins + # @api private + class DefaultsMode < Seahorse::Client::Plugin + + option(:defaults_mode, + default: 'legacy', + doc_type: String, + docstring: <<-DOCS +See {Aws::DefaultsModeConfiguration} for a list of the +accepted modes and the configuration defaults that are included. + DOCS + ) do |cfg| + resolve_defaults_mode(cfg) + end + + option(:defaults_mode_config_resolver, + doc_type: 'Aws::DefaultsModeConfigResolver') do |cfg| + Aws::DefaultsModeConfigResolver.new( + Aws::DefaultsModeConfiguration::SDK_DEFAULT_CONFIGURATION, cfg) + end + + class << self + private + + def resolve_defaults_mode(cfg) + value = ENV['AWS_DEFAULTS_MODE'] + value ||= Aws.shared_config.defaults_mode( + profile: cfg.profile + ) + value&.downcase || "legacy" + end + end + + end + end +end diff --git a/gems/aws-sdk-core/lib/aws-sdk-core/plugins/retry_errors.rb b/gems/aws-sdk-core/lib/aws-sdk-core/plugins/retry_errors.rb index 912a363e742..a9c3631665a 100644 --- a/gems/aws-sdk-core/lib/aws-sdk-core/plugins/retry_errors.rb +++ b/gems/aws-sdk-core/lib/aws-sdk-core/plugins/retry_errors.rb @@ -163,9 +163,15 @@ class RetryErrors < Seahorse::Client::Plugin option(:clock_skew) { Retries::ClockSkew.new } def self.resolve_retry_mode(cfg) - value = ENV['AWS_RETRY_MODE'] || - Aws.shared_config.retry_mode(profile: cfg.profile) || - 'legacy' + default_mode_value = + if cfg.respond_to?(:defaults_mode_config_resolver) + cfg.defaults_mode_config_resolver.resolve(:retry_mode) + end + + value = ENV['AWS_RETRY_MODE'] || + Aws.shared_config.retry_mode(profile: cfg.profile) || + default_mode_value || + 'legacy' # Raise if provided value is not one of the retry modes if value != 'legacy' && value != 'standard' && value != 'adaptive' raise ArgumentError, diff --git a/gems/aws-sdk-core/lib/aws-sdk-core/shared_config.rb b/gems/aws-sdk-core/lib/aws-sdk-core/shared_config.rb index d6b7df619e8..c82a43cb8a4 100644 --- a/gems/aws-sdk-core/lib/aws-sdk-core/shared_config.rb +++ b/gems/aws-sdk-core/lib/aws-sdk-core/shared_config.rb @@ -178,7 +178,8 @@ def self.config_reader(*attrs) :sts_regional_endpoints, :s3_use_arn_region, :s3_us_east_1_regional_endpoint, - :s3_disable_multiregion_access_points + :s3_disable_multiregion_access_points, + :defaults_mode ) private diff --git a/gems/aws-sdk-core/lib/aws-sdk-sts/client.rb b/gems/aws-sdk-core/lib/aws-sdk-sts/client.rb index 89a8d6b63ce..08b3b925062 100644 --- a/gems/aws-sdk-core/lib/aws-sdk-sts/client.rb +++ b/gems/aws-sdk-core/lib/aws-sdk-sts/client.rb @@ -27,6 +27,7 @@ require 'aws-sdk-core/plugins/client_metrics_send_plugin.rb' require 'aws-sdk-core/plugins/transfer_encoding.rb' require 'aws-sdk-core/plugins/http_checksum.rb' +require 'aws-sdk-core/plugins/defaults_mode.rb' require 'aws-sdk-core/plugins/signature_v4.rb' require 'aws-sdk-core/plugins/protocols/query.rb' require 'aws-sdk-sts/plugins/sts_regional_endpoints.rb' @@ -74,6 +75,7 @@ class Client < Seahorse::Client::Base add_plugin(Aws::Plugins::ClientMetricsSendPlugin) add_plugin(Aws::Plugins::TransferEncoding) add_plugin(Aws::Plugins::HttpChecksum) + add_plugin(Aws::Plugins::DefaultsMode) add_plugin(Aws::Plugins::SignatureV4) add_plugin(Aws::Plugins::Protocols::Query) add_plugin(Aws::STS::Plugins::STSRegionalEndpoints) @@ -177,6 +179,10 @@ class Client < Seahorse::Client::Base # Used only in `standard` and adaptive retry modes. Specifies whether to apply # a clock skew correction and retry requests with skewed client clocks. # + # @option options [String] :defaults_mode ("legacy") + # See {Aws::DefaultsModeConfiguration} for a list of the + # accepted modes and the configuration defaults that are included. + # # @option options [Boolean] :disable_host_prefix_injection (false) # Set to true to disable SDK automatically adding host prefix # to default service endpoint when available. @@ -304,7 +310,7 @@ class Client < Seahorse::Client::Base # seconds to wait when opening a HTTP session before raising a # `Timeout::Error`. # - # @option options [Integer] :http_read_timeout (60) The default + # @option options [Float] :http_read_timeout (60) The default # number of seconds to wait for response data. This value can # safely be set per-request on the session. # @@ -320,6 +326,9 @@ class Client < Seahorse::Client::Base # disables this behaviour. This value can safely be set per # request on the session. # + # @option options [Float] :ssl_timeout (nil) Sets the SSL timeout + # in seconds. + # # @option options [Boolean] :http_wire_trace (false) When `true`, # HTTP debug output will be sent to the `:logger`. # diff --git a/gems/aws-sdk-core/lib/aws-sdk-sts/plugins/sts_regional_endpoints.rb b/gems/aws-sdk-core/lib/aws-sdk-sts/plugins/sts_regional_endpoints.rb index dbb2841b2a5..b0ca55b4c9b 100644 --- a/gems/aws-sdk-core/lib/aws-sdk-sts/plugins/sts_regional_endpoints.rb +++ b/gems/aws-sdk-core/lib/aws-sdk-sts/plugins/sts_regional_endpoints.rb @@ -24,7 +24,11 @@ def self.resolve_sts_regional_endpoints(cfg) env_mode = nil if env_mode == '' cfg_mode = Aws.shared_config.sts_regional_endpoints( profile: cfg.profile) - env_mode || cfg_mode || 'regional' + default_mode_value = + if cfg.respond_to?(:defaults_mode_config_resolver) + cfg.defaults_mode_config_resolver.resolve(:sts_regional_endpoints) + end + env_mode || cfg_mode || default_mode_value || 'regional' end end diff --git a/gems/aws-sdk-core/lib/seahorse/client/net_http/connection_pool.rb b/gems/aws-sdk-core/lib/seahorse/client/net_http/connection_pool.rb index f642dbb1b48..46a3d6534bf 100644 --- a/gems/aws-sdk-core/lib/seahorse/client/net_http/connection_pool.rb +++ b/gems/aws-sdk-core/lib/seahorse/client/net_http/connection_pool.rb @@ -34,6 +34,7 @@ class ConnectionPool ssl_ca_bundle: nil, ssl_ca_directory: nil, ssl_ca_store: nil, + ssl_timeout: nil } # @api private @@ -187,6 +188,9 @@ class << self # disables this behaviour. This value can safely be set per # request on the session yielded by {#session_for}. # + # @option options [Float] :ssl_timeout (nil) Sets the SSL timeout + # in seconds. + # # @option options [Boolean] :http_wire_trace (false) When `true`, # HTTP debug output will be sent to the `:logger`. # @@ -248,6 +252,7 @@ def pool_options options :ssl_ca_bundle => options[:ssl_ca_bundle], :ssl_ca_directory => options[:ssl_ca_directory], :ssl_ca_store => options[:ssl_ca_store], + :ssl_timeout => options[:ssl_timeout] } end @@ -285,6 +290,8 @@ def start_session endpoint if endpoint.scheme == 'https' http.use_ssl = true + http.ssl_timeout = ssl_timeout + if ssl_verify_peer? http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.ca_file = ssl_ca_bundle if ssl_ca_bundle diff --git a/gems/aws-sdk-core/lib/seahorse/client/plugins/net_http.rb b/gems/aws-sdk-core/lib/seahorse/client/plugins/net_http.rb index ea87ba91ac0..93ae591c74a 100644 --- a/gems/aws-sdk-core/lib/seahorse/client/plugins/net_http.rb +++ b/gems/aws-sdk-core/lib/seahorse/client/plugins/net_http.rb @@ -9,9 +9,13 @@ class NetHttp < Plugin option(:http_proxy, default: nil, doc_type: String, docstring: '') - option(:http_open_timeout, default: 15, doc_type: Integer, docstring: '') + option(:http_open_timeout, default: 15, doc_type: Integer, docstring: '') do |cfg| + resolve_http_open_timeout(cfg) + end - option(:http_read_timeout, default: 60, doc_type: Integer, docstring: '') + option(:http_read_timeout, default: 60, doc_type: Integer, docstring: '') do |cfg| + resolve_http_read_timeout(cfg) + end option(:http_idle_timeout, default: 5, doc_type: Integer, docstring: '') @@ -30,10 +34,37 @@ class NetHttp < Plugin option(:ssl_ca_store, default: nil, doc_type: String, docstring: '') + option(:ssl_timeout, default: nil, doc_type: Float, docstring: '') do |cfg| + resolve_ssl_timeout(cfg) + end + option(:logger) # for backwards compat handler(Client::NetHttp::Handler, step: :send) + def self.resolve_http_open_timeout(cfg) + default_mode_value = + if cfg.respond_to?(:defaults_mode_config_resolver) + cfg.defaults_mode_config_resolver.resolve(:http_open_timeout) + end + default_mode_value || 15 + end + + def self.resolve_http_read_timeout(cfg) + default_mode_value = + if cfg.respond_to?(:defaults_mode_config_resolver) + cfg.defaults_mode_config_resolver.resolve(:http_read_timeout) + end + default_mode_value || 60 + end + + def self.resolve_ssl_timeout(cfg) + default_mode_value = + if cfg.respond_to?(:defaults_mode_config_resolver) + cfg.defaults_mode_config_resolver.resolve(:ssl_timeout) + end + default_mode_value || nil + end end end end diff --git a/gems/aws-sdk-core/spec/aws-defaults/defaults_mode_config_resolver_spec.rb b/gems/aws-sdk-core/spec/aws-defaults/defaults_mode_config_resolver_spec.rb new file mode 100644 index 00000000000..1150f2cc9a0 --- /dev/null +++ b/gems/aws-sdk-core/spec/aws-defaults/defaults_mode_config_resolver_spec.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +require_relative '../spec_helper' + +module Aws + describe DefaultsModeConfigResolver do + let(:test_case_defaults) do + dir = File.expand_path('../../fixtures/defaults', __FILE__) + Json.load_file("#{dir}/default-resolution.json") + end + + let(:defaults_mode) { 'standard' } + let(:client_region) { 'region-1' } + let(:cfg) do + double('config', + defaults_mode: defaults_mode, + region: client_region + ) + end + let(:subject) do + DefaultsModeConfigResolver.new(test_case_defaults, cfg) + end + + context 'defaults_mode is standard' do + let(:defaults_mode) { 'standard' } + + it 'resolves the values' do + expect(subject.resolve(:http_open_timeout)).to eq(2.0) + expect(subject.resolve(:ssl_timeout)).to eq(2.0) + expect(subject.resolve(:sts_regional_endpoints)).to eq('regional') + expect(subject.resolve(:s3_us_east_1_regional_endpoint)).to eq('regional') + expect(subject.resolve(:retry_mode)).to eq('standard') + end + end + + context 'defaults_mode is in-region' do + let(:defaults_mode) { 'in-region' } + + it 'resolves the values' do + expect(subject.resolve(:http_open_timeout)).to eq(1.0) + expect(subject.resolve(:ssl_timeout)).to eq(1.0) + expect(subject.resolve(:sts_regional_endpoints)).to eq('regional') + expect(subject.resolve(:s3_us_east_1_regional_endpoint)).to eq('regional') + expect(subject.resolve(:retry_mode)).to eq('standard') + end + end + + context 'defaults_mode is cross-region' do + let(:defaults_mode) { 'cross-region' } + + it 'resolves the values' do + expect(subject.resolve(:http_open_timeout)).to be_within(0.001).of(2.8) + expect(subject.resolve(:ssl_timeout)).to be_within(0.001).of(2.8) + expect(subject.resolve(:sts_regional_endpoints)).to eq('regional') + expect(subject.resolve(:s3_us_east_1_regional_endpoint)).to eq('regional') + expect(subject.resolve(:retry_mode)).to eq('standard') + end + end + + context 'defaults_mode is mobile' do + let(:defaults_mode) { 'mobile' } + + it 'resolves the values' do + expect(subject.resolve(:http_open_timeout)).to eq(10.0) + expect(subject.resolve(:ssl_timeout)).to eq(4.0) + expect(subject.resolve(:sts_regional_endpoints)).to eq('regional') + expect(subject.resolve(:s3_us_east_1_regional_endpoint)).to eq('regional') + expect(subject.resolve(:retry_mode)).to eq('adaptive') + end + end + + context 'defaults_mode is legacy' do + let(:defaults_mode) { 'legacy' } + + it 'returns nils' do + expect(subject.resolve(:http_open_timeout)).to be_nil + expect(subject.resolve(:ssl_timeout)).to be_nil + expect(subject.resolve(:sts_regional_endpoints)).to be_nil + expect(subject.resolve(:s3_us_east_1_regional_endpoint)).to be_nil + expect(subject.resolve(:retry_mode)).to be_nil + end + end + + context 'defaults_mode is auto' do + let(:execution_env) { 'AWS_Lambda_ruby' } + let(:defaults_mode) { 'auto' } + let(:client_region) { 'us-east-1' } + let(:imds_client) { double('imds_client') } + before do + # bust the cache + DefaultsModeConfigResolver.class_variable_set(:@@application_region, nil) + DefaultsModeConfigResolver.class_variable_set(:@@imds_client, imds_client) + end + + context 'mobile' do + it 'resolves mode to mobile' do + expect(subject).to receive(:env_mobile?).and_return(true) + expect(subject.send(:resolve_auto_mode)).to eq('mobile') + end + end + + context 'AWS_EXECUTION_ENV and AWS_REGION set to client region' do + before do + stub_const('ENV', { + 'AWS_EXECUTION_ENV' => execution_env, + 'AWS_REGION' => client_region + }) + end + + it 'resolves mode to in-region' do + expect(subject.send(:resolve_auto_mode)).to eq('in-region') + end + end + + + context 'AWS_EXECUTION_ENV and AWS_DEFAULT_REGION set to non-client region' do + before do + stub_const('ENV', { + 'AWS_EXECUTION_ENV' => execution_env, + 'AWS_DEFAULT_REGION' => 'us-west-2' + }) + end + + it 'resolves mode to cross-region' do + expect(subject.send(:resolve_auto_mode)).to eq('cross-region') + end + end + + context 'AWS_EXECUTION_ENV set and imds returns non-client region' do + before do + stub_const('ENV', { + 'AWS_EXECUTION_ENV' => execution_env, + }) + expect(imds_client) + .to receive(:get) + .with('/latest/meta-data/placement/region') + .and_return('us-west-2') + end + + it 'resolves mode to cross-region' do + expect(subject.send(:resolve_auto_mode)).to eq('cross-region') + end + end + + context 'AWS_REGION set and imds returns client region' do + before do + stub_const('ENV', { + 'AWS_REGION' => 'us-west-2', + }) + expect(imds_client) + .to receive(:get) + .with('/latest/meta-data/placement/region') + .and_return(client_region) + end + + it 'resolves mode to in-region' do + expect(subject.send(:resolve_auto_mode)).to eq('in-region') + end + end + + context 'AWS_EC2_METADATA_DISABLED is false and imds returns non-client region' do + before do + stub_const('ENV', { + 'AWS_EC2_METADATA_DISABLED' => 'false', + }) + expect(imds_client) + .to receive(:get) + .with('/latest/meta-data/placement/region') + .and_return('us-west-2') + end + + it 'resolves mode to in-region' do + expect(subject.send(:resolve_auto_mode)).to eq('cross-region') + end + end + + context 'AWS_EC2_METADATA_DISABLED is false and imds raises' do + before do + stub_const('ENV', { + 'AWS_EC2_METADATA_DISABLED' => 'false', + }) + expect(imds_client) + .to receive(:get) + .with('/latest/meta-data/placement/region') + .and_raise(StandardError.new) + end + + it 'resolves mode to standard' do + expect(subject.send(:resolve_auto_mode)).to eq('standard') + end + end + + # Note: Not currently in spec + context 'AWS_EC2_METADATA_DISABLED is true' do + before do + stub_const('ENV', { + 'AWS_EC2_METADATA_DISABLED' => 'true', + }) + end + + it 'does not call imds and resolves to standard' do + expect(imds_client).not_to receive(:get) + expect(subject.send(:resolve_auto_mode)).to eq('standard') + end + end + end + end +end \ No newline at end of file diff --git a/gems/aws-sdk-core/spec/aws/plugins/defaults_mode_spec.rb b/gems/aws-sdk-core/spec/aws/plugins/defaults_mode_spec.rb new file mode 100644 index 00000000000..009859bc1f1 --- /dev/null +++ b/gems/aws-sdk-core/spec/aws/plugins/defaults_mode_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require_relative '../../spec_helper' + +module Aws + module Plugins + describe DefaultsMode do + Client = ApiHelper.sample_service.const_get(:Client) + + let(:env) { {} } + + let(:client_class) { Client } + + let(:cfg) do + { + stub_responses: true + } + end + + before do + stub_const('ENV', env) + end + + describe 'defaults_mode option' do + it 'adds a :defaults_mode configuration option' do + expect(client_class.new(cfg.merge(defaults_mode: 'standard')).config.defaults_mode) + .to eq('standard') + end + + it 'defaults to legacy' do + expect(client_class.new(cfg).config.defaults_mode).to eq('legacy') + end + + it 'defaults to ENV["AWS_DEFAULTS_MODE"]' do + env['AWS_DEFAULTS_MODE'] = 'env-mode' + expect(client_class.new(cfg).config.defaults_mode).to eq('env-mode') + end + + it 'can be set directly, overriding the ENV["AWS_DEFAULTS_MODE"]' do + env['AWS_DEFAULTS_MODE'] = 'env-mode' + expect(client_class.new(cfg.merge(defaults_mode: 'client-mode')).config.defaults_mode) + .to eq('client-mode') + end + + it 'can be configured with shared config' do + allow_any_instance_of(Aws::SharedConfig) + .to receive(:defaults_mode).and_return('shared-mode') + expect(client_class.new(cfg).config.defaults_mode).to eq('shared-mode') + end + end + end + end +end diff --git a/gems/aws-sdk-core/spec/aws/plugins/retry_errors_spec.rb b/gems/aws-sdk-core/spec/aws/plugins/retry_errors_spec.rb index fdb25b67ff4..9b56972f352 100644 --- a/gems/aws-sdk-core/spec/aws/plugins/retry_errors_spec.rb +++ b/gems/aws-sdk-core/spec/aws/plugins/retry_errors_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative '../../spec_helper' +require_relative '../../retry_errors_helper' module Aws module Plugins @@ -20,6 +21,14 @@ module Plugins expect(client.config.retry_mode).to eq('adaptive') end + it 'can configure retry_mode using defaults mode' do + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve) + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve).with(:retry_mode).and_return('standard') + expect(client.config.retry_mode).to eq('standard') + end + it 'raises when retry_mode is not legacy, standard, or adaptive' do ENV['AWS_RETRY_MODE'] = 'peccy' expect { client }.to raise_error(ArgumentError) diff --git a/gems/aws-sdk-core/spec/aws/shared_config_spec.rb b/gems/aws-sdk-core/spec/aws/shared_config_spec.rb index 4fd6a897be6..b8139145bad 100644 --- a/gems/aws-sdk-core/spec/aws/shared_config_spec.rb +++ b/gems/aws-sdk-core/spec/aws/shared_config_spec.rb @@ -327,5 +327,16 @@ module Aws expect(config.ec2_metadata_service_endpoint_mode).to eq('IPv6') end end + + context 'defaults_mode' do + it 'can resolve defaults_mode from config file' do + config = SharedConfig.new( + config_path: mock_config_file, + config_enabled: true, + profile_name: 'defaults_mode_standard' + ) + expect(config.defaults_mode).to eq('standard') + end + end end end diff --git a/gems/aws-sdk-core/spec/aws/sts/sts_regional_endpoints_spec.rb b/gems/aws-sdk-core/spec/aws/sts/sts_regional_endpoints_spec.rb index 23b007d9467..e19a7e1dbd0 100644 --- a/gems/aws-sdk-core/spec/aws/sts/sts_regional_endpoints_spec.rb +++ b/gems/aws-sdk-core/spec/aws/sts/sts_regional_endpoints_spec.rb @@ -64,6 +64,20 @@ module STS 'https://sts.us-west-2.amazonaws.com') end + it 'can be set from defaults_mode' do + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve) + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve).with(:sts_regional_endpoints).and_return('legacy') + client = Client.new( + stub_responses: true, + region: 'us-west-2', + retry_mode: 'standard', + defaults_mode: 'standard' + ) + expect(client.config.sts_regional_endpoints).to eq('legacy') + end + it 'has no effect on fips endpoint' do allow(Aws::Plugins::RegionalEndpoint).to receive(:warn) diff --git a/gems/aws-sdk-core/spec/fixtures/credentials/mock_shared_config b/gems/aws-sdk-core/spec/fixtures/credentials/mock_shared_config index 68e4517d567..bdcc8530e37 100644 --- a/gems/aws-sdk-core/spec/fixtures/credentials/mock_shared_config +++ b/gems/aws-sdk-core/spec/fixtures/credentials/mock_shared_config @@ -242,3 +242,6 @@ role_arn = arn:aws:iam:123456789012:role/role_a [profile assume_role_chain_loop_b] source_profile = assume_role_chain_loop_a role_arn = arn:aws:iam:123456789012:role/role_b + +[profile defaults_mode_standard] +defaults_mode = standard diff --git a/gems/aws-sdk-core/spec/fixtures/defaults/default-resolution.json b/gems/aws-sdk-core/spec/fixtures/defaults/default-resolution.json new file mode 100644 index 00000000000..513133b1585 --- /dev/null +++ b/gems/aws-sdk-core/spec/fixtures/defaults/default-resolution.json @@ -0,0 +1,64 @@ +{ + "version": 1, + "base": { + "retryMode": "standard", + "stsRegionalEndpoints": "regional", + "s3UsEast1RegionalEndpoints": "regional", + "connectTimeoutInMillis": 1000, + "tlsNegotiationTimeoutInMillis": 1000 + }, + "modes": { + "standard": { + "connectTimeoutInMillis": { + "multiply":2 + }, + "tlsNegotiationTimeoutInMillis": { + "multiply":2 + } + }, + "in-region": { + "connectTimeoutInMillis": { + "multiply": 1 + }, + "tlsNegotiationTimeoutInMillis": { + "multiply": 1 + } + }, + "cross-region": { + "connectTimeoutInMillis": { + "multiply": 2.8 + }, + "tlsNegotiationTimeoutInMillis": { + "multiply": 2.8 + } + }, + "mobile": { + "connectTimeoutInMillis": { + "override": 10000 + }, + "tlsNegotiationTimeoutInMillis": { + "add": 3000 + }, + "retryMode": { + "override": "adaptive" + } + } + }, + "documentation": { + "modes": { + "standard": "PLACEHOLDER", + "in-region": "PLACEHOLDER", + "cross-region": "PLACEHOLDER", + "mobile": "PLACEHOLDER", + "auto": "PLACEHOLDER", + "legacy": "PLACEHOLDER" + }, + "configuration": { + "retryMode": "PLACEHOLDER", + "stsRegionalEndpoints": "PLACEHOLDER", + "s3UsEast1RegionalEndpoints": "PLACEHOLDER", + "connectTimeoutInMillis": "PLACEHOLDER", + "tlsNegotiationTimeoutInMillis": "PLACEHOLDER" + } + } +} diff --git a/gems/aws-sdk-core/spec/seahorse/client/plugins/net_http_spec.rb b/gems/aws-sdk-core/spec/seahorse/client/plugins/net_http_spec.rb index 10d27f497b2..9258789e7b0 100644 --- a/gems/aws-sdk-core/spec/seahorse/client/plugins/net_http_spec.rb +++ b/gems/aws-sdk-core/spec/seahorse/client/plugins/net_http_spec.rb @@ -11,6 +11,7 @@ module Plugins Configuration.new.tap do |config| config.add_option(:profile, nil) NetHttp.new.add_options(config) + Aws::Plugins::DefaultsMode.new.add_options(config) end.build! end @@ -19,6 +20,14 @@ module Plugins expect(config.http_proxy).to eq(nil) end + it 'uses defaults mode for http_open_timeout' do + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve) + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve).with(:http_open_timeout).and_return(10) + expect(config.http_open_timeout).to eq(10) + end + it 'adds a :http_open_timeout option with default' do expect(config.http_open_timeout).to eq(15) end @@ -27,6 +36,14 @@ module Plugins expect(config.http_read_timeout).to eq(60) end + it 'uses defaults mode for http_read_timeout' do + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve) + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve).with(:http_read_timeout).and_return(10) + expect(config.http_read_timeout).to eq(10) + end + it 'adds a :http_idle_timeout option with default' do expect(config.http_idle_timeout).to eq(5) end @@ -47,6 +64,18 @@ module Plugins expect(config.ssl_verify_peer).to eq(true) end + it 'adds a :ssl_timeout option with no default' do + expect(config.ssl_timeout).to eq(nil) + end + + it 'uses defaults mode for ssl_timeout' do + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve) + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve).with(:ssl_timeout).and_return(10) + expect(config.ssl_timeout).to eq(10) + end + context ':ssl_ca_bundle' do it 'adds a :ssl_ca_bundle option without default' do expect(config.ssl_ca_bundle).to be(nil) diff --git a/gems/aws-sdk-dynamodb/aws-sdk-dynamodb.gemspec b/gems/aws-sdk-dynamodb/aws-sdk-dynamodb.gemspec index 6d6f4bbd96e..0715902022c 100644 --- a/gems/aws-sdk-dynamodb/aws-sdk-dynamodb.gemspec +++ b/gems/aws-sdk-dynamodb/aws-sdk-dynamodb.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |spec| 'changelog_uri' => 'https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/aws-sdk-dynamodb/CHANGELOG.md' } - spec.add_dependency('aws-sdk-core', '~> 3', '>= 3.122.0') + spec.add_dependency('aws-sdk-core', '~> 3', '>= 3.125.0') spec.add_dependency('aws-sigv4', '~> 1.1') spec.required_ruby_version = '>= 2.3' diff --git a/gems/aws-sdk-dynamodb/lib/aws-sdk-dynamodb/client.rb b/gems/aws-sdk-dynamodb/lib/aws-sdk-dynamodb/client.rb index e80549a750b..e858496e6e2 100644 --- a/gems/aws-sdk-dynamodb/lib/aws-sdk-dynamodb/client.rb +++ b/gems/aws-sdk-dynamodb/lib/aws-sdk-dynamodb/client.rb @@ -27,6 +27,7 @@ require 'aws-sdk-core/plugins/client_metrics_send_plugin.rb' require 'aws-sdk-core/plugins/transfer_encoding.rb' require 'aws-sdk-core/plugins/http_checksum.rb' +require 'aws-sdk-core/plugins/defaults_mode.rb' require 'aws-sdk-core/plugins/signature_v4.rb' require 'aws-sdk-core/plugins/protocols/json_rpc.rb' require 'aws-sdk-dynamodb/plugins/extended_retries.rb' @@ -76,6 +77,7 @@ class Client < Seahorse::Client::Base add_plugin(Aws::Plugins::ClientMetricsSendPlugin) add_plugin(Aws::Plugins::TransferEncoding) add_plugin(Aws::Plugins::HttpChecksum) + add_plugin(Aws::Plugins::DefaultsMode) add_plugin(Aws::Plugins::SignatureV4) add_plugin(Aws::Plugins::Protocols::JsonRpc) add_plugin(Aws::DynamoDB::Plugins::ExtendedRetries) @@ -187,6 +189,10 @@ class Client < Seahorse::Client::Base # Used only in `standard` and adaptive retry modes. Specifies whether to apply # a clock skew correction and retry requests with skewed client clocks. # + # @option options [String] :defaults_mode ("legacy") + # See {Aws::DefaultsModeConfiguration} for a list of the + # accepted modes and the configuration defaults that are included. + # # @option options [Boolean] :disable_host_prefix_injection (false) # Set to true to disable SDK automatically adding host prefix # to default service endpoint when available. diff --git a/gems/aws-sdk-s3/aws-sdk-s3.gemspec b/gems/aws-sdk-s3/aws-sdk-s3.gemspec index ee5549db5f4..1ce55d5bad9 100644 --- a/gems/aws-sdk-s3/aws-sdk-s3.gemspec +++ b/gems/aws-sdk-s3/aws-sdk-s3.gemspec @@ -27,7 +27,7 @@ Gem::Specification.new do |spec| spec.add_dependency('aws-sdk-kms', '~> 1') spec.add_dependency('aws-sigv4', '~> 1.4') - spec.add_dependency('aws-sdk-core', '~> 3', '>= 3.122.0') + spec.add_dependency('aws-sdk-core', '~> 3', '>= 3.125.0') spec.required_ruby_version = '>= 2.3' end diff --git a/gems/aws-sdk-s3/lib/aws-sdk-s3/client.rb b/gems/aws-sdk-s3/lib/aws-sdk-s3/client.rb index f20ed595fa1..bee8d584e09 100644 --- a/gems/aws-sdk-s3/lib/aws-sdk-s3/client.rb +++ b/gems/aws-sdk-s3/lib/aws-sdk-s3/client.rb @@ -27,6 +27,7 @@ require 'aws-sdk-core/plugins/client_metrics_send_plugin.rb' require 'aws-sdk-core/plugins/transfer_encoding.rb' require 'aws-sdk-core/plugins/http_checksum.rb' +require 'aws-sdk-core/plugins/defaults_mode.rb' require 'aws-sdk-core/plugins/protocols/rest_xml.rb' require 'aws-sdk-s3/plugins/accelerate.rb' require 'aws-sdk-s3/plugins/arn.rb' @@ -91,6 +92,7 @@ class Client < Seahorse::Client::Base add_plugin(Aws::Plugins::ClientMetricsSendPlugin) add_plugin(Aws::Plugins::TransferEncoding) add_plugin(Aws::Plugins::HttpChecksum) + add_plugin(Aws::Plugins::DefaultsMode) add_plugin(Aws::Plugins::Protocols::RestXml) add_plugin(Aws::S3::Plugins::Accelerate) add_plugin(Aws::S3::Plugins::ARN) @@ -218,6 +220,11 @@ class Client < Seahorse::Client::Base # Used only in `standard` and adaptive retry modes. Specifies whether to apply # a clock skew correction and retry requests with skewed client clocks. # + # @option options [String] :defaults_mode ("legacy") + # TODO: See Aws::ConfigurationDefaults Documentation + # + # @option options [Aws::DefaultsModeConfigResolver] :defaults_mode_config_resolver + # # @option options [Boolean] :disable_host_prefix_injection (false) # Set to true to disable SDK automatically adding host prefix # to default service endpoint when available. diff --git a/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb b/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb index 752d00d4ae7..29edb4182c4 100644 --- a/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +++ b/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb @@ -48,8 +48,14 @@ def self.legacy_host(host) private def self.resolve_iad_regional_endpoint(cfg) + default_mode_value = + if cfg.respond_to?(:defaults_mode_config_resolver) + cfg.defaults_mode_config_resolver.resolve(:s3_us_east_1_regional_endpoint) + end + mode = ENV['AWS_S3_US_EAST_1_REGIONAL_ENDPOINT'] || Aws.shared_config.s3_us_east_1_regional_endpoint(profile: cfg.profile) || + default_mode_value || 'legacy' mode = mode.downcase unless %w(legacy regional).include?(mode) diff --git a/gems/aws-sdk-s3/spec/client/iad_regional_endpoint_spec.rb b/gems/aws-sdk-s3/spec/client/iad_regional_endpoint_spec.rb index cc7ac8170e4..e9113ee7594 100644 --- a/gems/aws-sdk-s3/spec/client/iad_regional_endpoint_spec.rb +++ b/gems/aws-sdk-s3/spec/client/iad_regional_endpoint_spec.rb @@ -63,6 +63,20 @@ module S3 'https://s3.us-east-1.amazonaws.com/') end + it 'can be set through defaults mode' do + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve) + allow_any_instance_of(Aws::DefaultsModeConfigResolver) + .to receive(:resolve).with(:s3_us_east_1_regional_endpoint).and_return('regional') + client = Client.new( + stub_responses: true, + region: 'us-west-2', + retry_mode: 'standard', + defaults_mode: 'standard' + ) + expect(client.config.s3_us_east_1_regional_endpoint).to eq('regional') + end + it 'is case insensitive' do ENV['AWS_S3_US_EAST_1_REGIONAL_ENDPOINT'] = 'LEGACY' client = Client.new( diff --git a/tasks/update-defaults-mode.rake b/tasks/update-defaults-mode.rake new file mode 100644 index 00000000000..6b8ec1aafb0 --- /dev/null +++ b/tasks/update-defaults-mode.rake @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +# updates the defaults mode configuration +task 'update-defaults-mode', [:defaults_file] do |t, args| + + defaults_file = args[:defaults_file] + if defaults_file.nil? || defaults_file.empty? + raise ArgumentError, 'Missing required defaults_file, please provide one as a rake argument.' + end + + puts "Loading defaults from: #{defaults_file}" + defaults = JSON.load(File.read(defaults_file)) + + default_mode_docs = ["
The following :default_mode
values are supported:
'#{mode}'
- #{docs}Based on the provided mode, the SDK will vend sensible default values tailored to the mode for the following settings:
" + default_mode_docs << ":#{ruby_name}
- #{docs}