-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Configured endpoint resolution (aka service-specific endpoint c…
…onfiguration) (#1861) * Add the ignoreConfiguredEndpointURLs config field in runtime side. * Add ignoreConfiguredEndpointURLs config field to codegen side. * Change default value for endpoint in AWS SDK codegen to use AWSClientConfigDefaultsProvider.configuredEndpoint that resolves configured endpoint if needed. * Add configuredEndpoint static func to AWSClientConfigDefaultsProvider; it uses AWSEndpointConfig to resolve and return configured endpoint if needed & possible. * Create AWSEndpointConfig that resolves configured endpoint if needed & possible. Add unit tests for it too. * Add shared config's services section parsing logic to wrapper. Add a new test case for multiple services in services section case. Confirmed tests pass by depending on WIP branch of aws-crt-swift. * Bump CRT version to 0.43.0 * Update codegen test * Fix typos * Typos * Add trace-level logging for configured endpoint resolution. * Address swiftlint warning * Relocate configured endpoint resolution location from client config initialization to operation call stack. * Update codegen test * Add trace logging for configured endpoint, add error throw for when services section referenced by resolved profile doesn't exist, and add check for empty values for reoslved endpoint (empty values = proceed to next possible source). Implementation is 100% matching to SEP now. * Update docc comment on client config for disable flag. * Update runtime tests --------- Co-authored-by: Sichan Yoo <chanyoo@amazon.com>
- Loading branch information
Showing
13 changed files
with
321 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Endpoints/AWSEndpointConfig.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import class Foundation.ProcessInfo | ||
import enum Smithy.ClientError | ||
import struct Foundation.Locale | ||
import struct Smithy.SwiftLogger | ||
@_spi(FileBasedConfig) import AWSSDKCommon | ||
|
||
public enum AWSEndpointConfig { | ||
static func configuredEndpoint( | ||
sdkID: String, | ||
ignoreConfiguredEndpointURLs: Bool?, | ||
fileBasedConfig: FileBasedConfiguration | ||
) throws -> String? { | ||
// First, resolve disable flag | ||
let disableFlag = FieldResolver( | ||
configValue: ignoreConfiguredEndpointURLs, | ||
envVarName: "AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", | ||
configFieldName: "ignore_configured_endpoint_urls", | ||
fileBasedConfig: fileBasedConfig, | ||
profileName: nil, | ||
converter: { Bool($0) } | ||
).value ?? false | ||
|
||
// Resolve configured endpoint only if disableFlag is false | ||
if disableFlag { | ||
return nil | ||
} else { | ||
let logger = SwiftLogger(label: "ConfiguredEndpointResolver") | ||
let removedSpaceID = sdkID.replacingOccurrences(of: " ", with: "_") | ||
// 1. Environment variable | ||
let env = ProcessInfo.processInfo.environment | ||
// a. Service-specific configured endpoint from `AWS_ENDPOINT_URL_<SERVICE>` | ||
let uppercasedID = removedSpaceID.uppercased(with: Locale(identifier: "en_US")) | ||
if let val = env["AWS_ENDPOINT_URL_\(uppercasedID)"], val != "" { | ||
logger.trace("Resolved configured endpoint from AWS_ENDPOINT_URL_\(uppercasedID): \(val)") | ||
return val | ||
} | ||
// b. Global configured endpoint from `AWS_ENDPOINT_URL` | ||
if let val = env["AWS_ENDPOINT_URL"], val != "" { | ||
logger.trace("Resolved configured endpoint from AWS_ENDPOINT_URL: \(val)") | ||
return val | ||
} | ||
// 2. Shared config property | ||
let configuredEndpointKey = FileBasedConfigurationKey(rawValue: "endpoint_url") | ||
let profileName = env["AWS_PROFILE"] ?? "default" | ||
// a. For service-specific configured endpoint. | ||
// Profile section => referenced services section => nested subsection with service name | ||
/* E.g., | ||
[profile dev] | ||
services = devServices | ||
|
||
[services devServices] | ||
s3 = | ||
endpoint_url = https://abcde:9000 | ||
*/ | ||
if let servicesSectionName = fileBasedConfig.section(for: profileName)?.string(for: "services") { | ||
guard let servicesSection = fileBasedConfig.section(for: servicesSectionName, type: .services) else { | ||
throw ClientError.dataNotFound( | ||
"The [services \(servicesSectionName)] section doesn't exist!" | ||
) | ||
} | ||
let serviceSubsection = servicesSection.subproperties( | ||
for: FileBasedConfigurationKey( | ||
rawValue: removedSpaceID.lowercased(with: Locale(identifier: "en_US")) | ||
) | ||
) | ||
if let val = serviceSubsection?.value(for: configuredEndpointKey), val != "" { | ||
logger.trace( | ||
"Resolved configured endpoint from service-specific field in shared config file: \(val)" | ||
) | ||
return val | ||
} | ||
} | ||
// b. For global configured endpoint. Profile section => property | ||
/* E.g., | ||
[profile dev] | ||
services = devServices | ||
endpoint_url = http://localhost:5567 | ||
*/ | ||
if let val = fileBasedConfig.section(for: profileName)?.string(for: configuredEndpointKey), val != "" { | ||
logger.trace("Resolved configured endpoint from global field in shared config file: \(val)") | ||
return val | ||
} | ||
// 3. Configured endpoint not found anywhere; return nil. | ||
logger.trace("No configured endpoint found. Defaulting to SDK logic for resolving endpoint...") | ||
return nil | ||
} | ||
} | ||
} |
141 changes: 141 additions & 0 deletions
141
...Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Endpoints/ConfiguredEndpointTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import XCTest | ||
@testable @_spi(FileBasedConfig) import AWSClientRuntime | ||
@_spi(FileBasedConfig) @testable import AWSSDKCommon | ||
|
||
class ConfiguredEndpointTests: XCTestCase { | ||
// Shared values | ||
let fileBasedConfig = try! CRTFileBasedConfiguration( | ||
configFilePath: Bundle.module.path(forResource: "configured_endpoint_tests", ofType: nil)! | ||
) | ||
let sdkID = "Test Service" | ||
|
||
// After each test, unset all environment variables that could've been set | ||
override func tearDown() { | ||
unsetenv("AWS_PROFILE") | ||
unsetenv("AWS_ENDPOINT_URL") | ||
unsetenv("AWS_ENDPOINT_URL_TEST_SERVICE") | ||
unsetenv("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS") | ||
} | ||
|
||
/* | ||
DISABLE FLAG TESTS | ||
|
||
Needs to resolve furthest left option for flag: | ||
client-config | env-var | shared-config-file | ||
*/ | ||
|
||
func testClientConfigDisableFlag() throws { | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: sdkID, | ||
ignoreConfiguredEndpointURLs: true, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertNil(resolvedEndpoint) | ||
} | ||
|
||
func testEnvVarDisableFlag() throws { | ||
setenv("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true", 1) | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: sdkID, | ||
ignoreConfiguredEndpointURLs: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertNil(resolvedEndpoint) | ||
} | ||
|
||
func testSharedConfigFileDisableFlag() throws { | ||
setenv("AWS_PROFILE", "ignoreConfiguredEndpointProfile", 1) | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: sdkID, | ||
ignoreConfiguredEndpointURLs: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertNil(resolvedEndpoint) | ||
} | ||
|
||
/* | ||
CONFIGURED ENDPOINT RESOLUTION TESTS | ||
|
||
Needs to resolve furthest left option for configured endpoint: | ||
service-specific-env-var | global-env-var | service-specific-config-file | global-config-file | ||
*client-config is handled implicitly by executing configured endpoint resolution only if user didn't provide it | ||
*/ | ||
|
||
func testResolveServiceSpecificEnvVar() throws { | ||
setenv("AWS_ENDPOINT_URL", "https://env-global.com:9000", 1) | ||
setenv("AWS_ENDPOINT_URL_TEST_SERVICE", "https://env-specific.com:9000", 1) | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: sdkID, | ||
ignoreConfiguredEndpointURLs: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertEqual(resolvedEndpoint, "https://env-specific.com:9000") | ||
} | ||
|
||
func testResolveGlobalEnvVar() throws { | ||
setenv("AWS_ENDPOINT_URL", "https://env-global.com:9000", 1) | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: sdkID, | ||
ignoreConfiguredEndpointURLs: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertEqual(resolvedEndpoint, "https://env-global.com:9000") | ||
} | ||
|
||
func testResolveServiceSpecificConfig() throws { | ||
setenv("AWS_PROFILE", "serviceSpecificConfiguredEndpointProfile", 1) | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: sdkID, | ||
ignoreConfiguredEndpointURLs: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertEqual(resolvedEndpoint, "https://config-specific.com:1000") | ||
} | ||
|
||
func testResolveServiceSpecificConfig2() throws { | ||
setenv("AWS_PROFILE", "serviceSpecificConfiguredEndpointProfile", 1) | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: "Test Service 2", | ||
ignoreConfiguredEndpointURLs: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertEqual(resolvedEndpoint, "https://config-specific.com:2000") | ||
} | ||
|
||
func testResolveGlobalConfigWhenMissingServicesSection() throws { | ||
setenv("AWS_PROFILE", "serviceSpecificConfiguredEndpointProfile", 1) | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: "absent-service-name", | ||
ignoreConfiguredEndpointURLs: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertEqual(resolvedEndpoint, "https://config-global.com:2000") | ||
} | ||
|
||
func testResolveGlobalConfigwhenMissingMatchingServiceSubsection() throws { | ||
setenv("AWS_PROFILE", "globalConfiguredEndpointProfile", 1) | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: sdkID, | ||
ignoreConfiguredEndpointURLs: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertEqual(resolvedEndpoint, "https://config-global.com:1000") | ||
} | ||
|
||
func testResolveNilWhenNothingIsConfigured() throws { | ||
setenv("AWS_PROFILE", "noConfiguredEndpointProfile", 1) | ||
let resolvedEndpoint = try AWSEndpointConfig.configuredEndpoint( | ||
sdkID: sdkID, | ||
ignoreConfiguredEndpointURLs: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
XCTAssertNil(resolvedEndpoint) | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...ces/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Resources/configured_endpoint_tests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[profile noConfiguredEndpointProfile] | ||
region = dummyValue | ||
|
||
[profile globalConfiguredEndpointProfile] | ||
endpoint_url = https://config-global.com:1000 | ||
|
||
[profile serviceSpecificConfiguredEndpointProfile] | ||
endpoint_url = https://config-global.com:2000 | ||
services = serviceSpecificEndpoints | ||
|
||
[services serviceSpecificEndpoints] | ||
test_service = | ||
endpoint_url = https://config-specific.com:1000 | ||
test_service_2 = | ||
endpoint_url = https://config-specific.com:2000 | ||
|
||
[profile ignoreConfiguredEndpointProfile] | ||
ignore_configured_endpoint_urls = true | ||
endpoint_url = http://this-should-be-ignored |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.