Share one credential across components¶
You are using more than one AWS-backed component — configuration from Parameter Store, files from S3, signing through KMS — and you want them to resolve the AWS credential chain once between them.
Build one source, inject it everywhere¶
import (
"gitlab.com/phpboyscout/go/awsclient"
"gitlab.com/phpboyscout/go/config"
s3ambient "gitlab.com/phpboyscout/go/config-aws-s3/ambient"
ssmambient "gitlab.com/phpboyscout/go/config-aws-ssm/ambient"
)
src := awsclient.Ambient(awsclient.WithRegion("eu-west-2"))
paramStore, err := ssmambient.FromSource(ctx, src, "/app")
if err != nil {
return err
}
bucket, err := s3ambient.FSFromSource(ctx, src, "my-bucket")
if err != nil {
return err
}
store, err := config.NewStore(ctx,
config.WithBackend(paramStore),
config.WithFiles(bucket, "config.yaml"),
)
Note the import paths: the FromSource rung lives in each adapter's ambient
subpackage, not its root. That is deliberate — the root package must not carry
the AWS credential-resolution graph, and
keeping that boundary is why
this module exists as a separate import at all.
One chain resolution, two adapters. The same source can feed go/signing and
go/encryption — it is an estate module, not a config one.
What you are trading away¶
Without this, each adapter resolves its own chain. That is the default, and it is deliberate: components stay isolated, and one component's transient credential failure is not everybody's.
Sharing gives you one resolution and one failure domain. Both are the point — but they arrive together, so take the source-sharing route because you want the shared failure domain too, not merely to save a resolution.
Overriding what the chain resolves¶
WithLoadOptions passes the SDK's own options straight through, so anything
config.LoadDefaultConfig accepts is available:
src := awsclient.Ambient(
awsclient.WithRegion("eu-west-2"),
awsclient.WithLoadOptions(
awscfg.WithSharedConfigProfile("build"),
),
)
When you already have an aws.Config¶
If your process resolves AWS configuration for its own reasons, inject it rather than resolving a second time:
That rung validates and returns any error immediately, because there is nothing to defer — the configuration already exists.
Bounding the resolution¶
src := awsclient.Ambient(
awsclient.WithRegion("eu-west-2"),
awsclient.WithBuildTimeout(5*time.Second),
)
Per attempt, not a total budget. The bound is cooperative: it cancels the context the SDK is given, and a loader that ignores it runs to completion regardless.
Shutting down¶
WithLifetimeContext ties resolution attempts to the life of whatever owns the
source, so shutting that down abandons an attempt in flight:
src := awsclient.Ambient(
awsclient.WithRegion("eu-west-2"),
awsclient.WithLifetimeContext(appCtx),
)
It is deliberately not a call's context — a caller's cancellation must release that caller alone.