Skip to content

Conversation

aajtodd
Copy link
Contributor

@aajtodd aajtodd commented May 18, 2021

Issue #, if available:
closes smithy-kotlin#356

Description of changes:

  • Adds an environment based region detection provider that consults AWS_REGION and AWS_DEFAULT_REGION environment variables
  • Bootstraps a provider chain. Obviously not all of the providers mention exist yet. It's unclear to me whether the default chain should be expect/actual or not, how we want test it, etc. It likely depends on whether some providers don't make sense on all platforms (which is basically guaranteed with JVM system property and profile based providers). We could also just have internal expect/actual functions like platformProviderChain() -> List<AwsRegionProvider>. Rather than solve all this or implement them, I've left it for us to fill in as time allows since several of the providers are easy low hanging fruit.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@aajtodd aajtodd requested review from ianbotsf, kggilmer and rcoh May 18, 2021 15:39
Copy link
Contributor

@ianbotsf ianbotsf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I may have to rework my Environment Credentials Provider to take into account some best practices I'm seeing here.

Comment on lines 21 to 23
override fun toString(): String = buildString {
append("AwsRegionProviderChain")
providers.fold(this) { sb, provider ->
sb.append(" -> ")
sb.append(provider::class.simpleName)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Simplified:

override fun toString(): String =
    (listOf(this) + providers).map { it::class.simpleName }.joinToString(" -> ")

Comment on lines 29 to 42
override suspend fun getRegion(): String? {
// FIXME - 1.5 had firstNotNullOfOrNull()

for (provider in providers) {
try {
val region = provider.getRegion()
if (region != null) {
return region
}
} catch (ex: Exception) {
logger.debug { "unable to load region from $provider: ${ex.message}" }
}
}

return null
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is the right outcome from getting no provider to return null? Would throwing an exception be better? Is the answer different if the chain has no providers in it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good question. I considered throwing an exception but I'm not sure it's really an error condition but I can definitely make an argument for it to be that way.

I guess the answer comes down to "must a provider ALWAYS return a value"?

Which do you prefer and why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking more about how providers will be used and chained together, I prefer returning null. So never mind here.

Comment on lines +16 to +18
public open class AwsRegionProviderChain(
private vararg val providers: AwsRegionProvider
) : AwsRegionProvider {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is a chain with no providers "valid"?

val chain = AwsRegionProviderChain() // Is this valid?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no this is a good call out. I think we ought to require at least one.

Comment on lines 20 to 31
/**
* [AwsRegionProvider] that checks `AWS_REGION` and `AWS_DEFAULT` region environment variables
* @param environ the environment mapping to lookup keys in (defaults to the system environment)
*/
public class EnvironmentRegionProvider(
private val environ: Environment
) : AwsRegionProvider {
public constructor() : this(SystemEnvironment)

override suspend fun getRegion(): String? =
environ.get(AWS_ENVIRON_REGION) ?: environ.get(AWS_ENVIRON_DEFAULT_REGION)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Generally interfaces or types with single members are clearer as functional types. I might've written this as:

public class EnvironmentRegionProvider(
    private val getenv: (String) -> String,
) : AwsRegionProvider {
    public constructor() : this(Platform::getenv)

    override suspend fun getRegion(): String? =
        getenv(AWS_ENVIRON_REGION) ?: getenv(AWS_ENVIRON_DEFAULT_REGION)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface is defined as a functional interface.

fun interface Environment { ... }

Which means you should be able to do:

EnvironmentRegionProvider { key -> ... }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but you're right, and we can already do what you suggested I just missed it:

public constructor(): this(Platform::getenv)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants