|
| 1 | +package aws.sdk.kotlin.runtime.config |
| 2 | + |
| 3 | +import aws.sdk.kotlin.runtime.AwsSdkSetting |
| 4 | +import aws.sdk.kotlin.runtime.InternalSdkApi |
| 5 | +import aws.sdk.kotlin.runtime.resolve |
| 6 | +import aws.smithy.kotlin.runtime.util.OsFamily |
| 7 | +import aws.smithy.kotlin.runtime.util.Platform |
| 8 | + |
| 9 | +/** |
| 10 | + * Load the properties of the specified or default AWS configuration profile. This |
| 11 | + * function will return the properties of the profile specified by the local environment |
| 12 | + * or the default profile if none is defined. |
| 13 | + * |
| 14 | + * This function performs no caching. File I/O will be performed with each call. |
| 15 | + * |
| 16 | + * @param platform used for unit testing |
| 17 | + * |
| 18 | + * @return an [AwsProfile] regardless if local configuration files are available |
| 19 | + */ |
| 20 | +@InternalSdkApi |
| 21 | +public suspend fun loadActiveAwsProfile(platform: Platform): AwsProfile { |
| 22 | + // Determine active profile and location of configuration files |
| 23 | + val source = resolveConfigSource(platform) |
| 24 | + |
| 25 | + // Read all profiles from local system |
| 26 | + val allProfiles = loadAwsProfiles(platform, source) |
| 27 | + |
| 28 | + // Return the active profile |
| 29 | + return AwsProfile(source.profile, allProfiles[source.profile] ?: emptyMap()) |
| 30 | +} |
| 31 | + |
| 32 | +/** |
| 33 | + * Load all profiles specified in local configuration files. |
| 34 | + * |
| 35 | + * @param platform Platform from which to resolve configuration data |
| 36 | + * @param source Specifies the location of the configuration files |
| 37 | + * |
| 38 | + * @return A map of all profiles, which each are a map of key/value pairs. |
| 39 | + */ |
| 40 | +private suspend fun loadAwsProfiles(platform: Platform, source: AwsConfigurationSource): Map<String, Map<String, String>> { |
| 41 | + |
| 42 | + // merged AWS configuration based on optional configuration and credential file contents |
| 43 | + return mergeProfiles( |
| 44 | + parse(FileType.CONFIGURATION, platform.readFileOrNull(source.configPath)?.decodeToString()), |
| 45 | + parse(FileType.CREDENTIAL, platform.readFileOrNull(source.credentialsPath)?.decodeToString()), |
| 46 | + ) |
| 47 | +} |
| 48 | + |
| 49 | +// Merge contents of profile maps |
| 50 | +internal fun mergeProfiles(vararg maps: ProfileMap) = buildMap<String, Map<String, String>> { |
| 51 | + maps.forEach { map -> |
| 52 | + map.entries.forEach { entry -> |
| 53 | + put(entry.key, (get(entry.key) ?: emptyMap()) + entry.value) |
| 54 | + } |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +// Specifies the active profile and configured (may not actually exist) locations of configuration files. |
| 59 | +internal data class AwsConfigurationSource(val profile: String, val configPath: String, val credentialsPath: String) |
| 60 | + |
| 61 | +/** |
| 62 | + * Determine the source of AWS configuration |
| 63 | + */ |
| 64 | +internal fun resolveConfigSource(platform: Platform) = |
| 65 | + AwsConfigurationSource( |
| 66 | + // If the user does not specify the profile to be used, the default profile must be used by the SDK. |
| 67 | + // The default profile must be overridable using the AWS_PROFILE environment variable. |
| 68 | + AwsSdkSetting.AwsProfile.resolve(platform) ?: Literals.DEFAULT_PROFILE, |
| 69 | + normalizePath(FileType.CONFIGURATION.path(platform), platform), |
| 70 | + normalizePath(FileType.CREDENTIAL.path(platform), platform) |
| 71 | + ) |
| 72 | + |
| 73 | +/** |
| 74 | + * Expands paths prefixed with '~' to the home directory under which the SDK is running. |
| 75 | + * |
| 76 | + * User Home Resolution: The user's home directory must be resolved when the file location starts with ~/ or ~ |
| 77 | + * followed by the operating system's default path separator by checking the following variables, in order: |
| 78 | + * |
| 79 | + * 1. (All Platforms) The HOME environment variable. |
| 80 | + * 2. (Windows Platforms) The USERPROFILE environment variable. |
| 81 | + * 3. (Windows Platforms) The HOMEDRIVE environment variable prepended to the HOMEPATH environment variable (ie. $HOMEDRIVE$HOMEPATH). |
| 82 | + * 4. (Optional) A language-specific home path resolution function or variable. |
| 83 | + */ |
| 84 | +internal fun normalizePath(path: String, platform: Platform): String { |
| 85 | + if (!path.trim().startsWith('~')) return path |
| 86 | + |
| 87 | + val home = resolveHomeDir(platform) ?: error("Unable to determine user home directory") |
| 88 | + |
| 89 | + return home + path.substring(1) |
| 90 | +} |
| 91 | + |
| 92 | +/** |
| 93 | + * Load the user's home directory based on the priorities: |
| 94 | + * |
| 95 | + * If the implementation cannot determine the customer's platform, the USERPROFILE and HOMEDRIVE + HOMEPATH environment |
| 96 | + * variables must be checked for all platforms. If the implementation can determine the customer's platform, the |
| 97 | + * USERPROFILE and HOMEDRIVE + HOMEPATH environment variables must not be checked on non-windows platforms. |
| 98 | + * |
| 99 | + * @param |
| 100 | + * @return the absolute path of the home directory from which the SDK is running, or null if unspecified by environment. |
| 101 | + */ |
| 102 | +private fun resolveHomeDir(platform: Platform): String? = |
| 103 | + with(platform) { |
| 104 | + when (osInfo().family) { |
| 105 | + OsFamily.Unknown, |
| 106 | + OsFamily.Windows -> |
| 107 | + getenv("HOME") |
| 108 | + ?: getenv("USERPROFILE") |
| 109 | + ?: (getenv("HOMEDRIVE") to getenv("HOMEPATH")).concatOrNull() |
| 110 | + ?: getProperty("user.home") |
| 111 | + else -> |
| 112 | + getenv("HOME") |
| 113 | + ?: getProperty("user.home") |
| 114 | + } |
| 115 | + } |
0 commit comments