Documentation
¶
Overview ¶
Package awsclient resolves the AWS configuration a service client is built from — once and shared, or afresh per operation, as the caller chooses.
It exists so that resolving the AWS credential chain is written once for the estate rather than in every adapter that needs a client. An adapter takes an aws.Config and calls its own service's NewFromConfig, which does no I/O; the expensive, failure-prone part is getting that config, and that is all this module does.
Choosing a rung ¶
awsclient.FromConfig(cfg) // you resolved it; used as-is, nothing is loaded awsclient.Ambient() // the ambient chain, resolved once and shared awsclient.PerCall() // the ambient chain, resolved afresh every time
Ambient is the usual choice for anything long-lived: it resolves lazily on first use, at most once concurrently, and — unlike sync.OnceValues — never caches a failure, so a flap at startup does not wedge the process.
PerCall is for the caller that must not hold credentials between operations. That is a posture, not a slower Ambient: a signing backend that resolves inside each mint does so deliberately, to avoid holding credentials longer than the operation requires, and adopting a memoising source would quietly change its security properties.
All three return the same Source, so switching is a one-word change.
This module never guesses a region ¶
There is no default region here, and there will not be one. A config that names no region names a key in an account nobody chose, so an empty region is ErrNoRegion from the accessor rather than a confusing failure several calls later. A caller that has a region passes it with WithRegion; a caller whose region is a user-facing default — a CLI flag, say — keeps that default in its own layer, where the choice has a reason.
Sharing is explicit ¶
Two adapters that each call Ambient get two independent sources and resolve the chain twice. That is correct: they may deliberately want different profiles, and a hidden process-wide cache would make the first one's transient failure everybody's. A caller who wants one chain builds one Source and hands it to each adapter.
Specified by org 0003 (P-2, P-3, P-12, P-13, P-14), which applies org 0002's connection lifecycle across the estate.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoRegion = errors.NewSentinel("awsclient.no_region",
"no AWS region configured; set one on the config, pass WithRegion, or set AWS_REGION")
ErrNoRegion reports a configuration that names no AWS region.
It is returned by FromConfig at construction and by an ambient source's accessor, because a region is not optional: without one the SDK has no endpoint to call and the failure surfaces far from its cause.
Functions ¶
This section is empty.
Types ¶
type Option ¶
type Option func(*options)
Option configures a source.
func WithBuildTimeout ¶
WithBuildTimeout bounds a single resolution attempt. It is per attempt, not a total budget: a failure caches nothing, so a later call gets a fresh allowance.
func WithLifetimeContext ¶
WithLifetimeContext scopes an Ambient source's resolution attempts to the life of whatever owns it, so shutting that down abandons an attempt in flight. PerCall ignores it, being already scoped to its caller.
func WithLoadOptions ¶
func WithLoadOptions(opts ...func(*awscfg.LoadOptions) error) Option
WithLoadOptions passes the SDK's own load options through to awscfg.LoadDefaultConfig — a shared-config profile, a custom endpoint resolver, an assumed role.
It is the escape hatch for everything WithRegion does not cover. Options accumulate across calls and are applied after the region, so an explicit awscfg.WithRegion here wins.
func WithLogger ¶
WithLogger enables DEBUG diagnostics. nil disables them, which is the default: a library that logs without being asked writes to somebody else's stderr.
Records carry only non-secret identifiers — the region, the credential provider's type, how long resolution took. Credentials are never logged, at any level, and nothing is logged above DEBUG.
func WithRegion ¶
WithRegion sets the region the ambient chain resolves for.
An empty string is ignored, so a caller threading through an unset flag does not accidentally clear a region set by another option. This module has no default of its own — see the package documentation.
type Source ¶
type Source interface {
// AWSConfig returns the configuration, resolving it if the source's strategy
// says to. It is safe for concurrent use.
AWSConfig(ctx context.Context) (aws.Config, error)
}
Source yields the AWS configuration a service client is built from.
The method is named for what it returns rather than a generic Get, so a call site reads as what it is and two providers' sources are not accidentally interchangeable.
func Ambient ¶
Ambient returns a source that resolves the ambient AWS configuration chain — profile, SSO session, instance role — the first time it is asked, and shares that result.
It returns immediately and performs no I/O; resolution happens off the construction path, at most once concurrently, and a failure is retried rather than cached.
Example ¶
ExampleAmbient shows the usual wiring. It has no Output because resolving the ambient chain needs real credentials; it is here to be read and to be kept compiling.
The source is built at startup and resolves nothing until something asks. The first AWSConfig call runs one bounded attempt, every later call reuses it, and a failure is retried rather than cached. Handing one source to several adapters is what collapses their credential-chain resolutions into one.
package main
import (
"context"
"gitlab.com/phpboyscout/go/awsclient"
)
func main() {
src := awsclient.Ambient(awsclient.WithRegion("eu-west-2"))
cfg, err := src.AWSConfig(context.Background())
if err != nil {
return
}
// Building the service clients from here is pure — no I/O, no error:
//
// ssmClient := ssm.NewFromConfig(cfg)
// s3Client := s3.NewFromConfig(cfg)
_ = cfg
}
Output:
func FromConfig ¶
FromConfig returns a source over a configuration the caller already resolved — a specific profile, an assumed role, a client pointed at LocalStack.
The config is used as-is and nothing is ever loaded, so this rung has no build policy to apply. The region is checked now rather than left to fail per call.
aws.Config is a struct, so there is no nil or typed-nil case to guard here as there is for the providers whose injected value is an interface.
Example ¶
ExampleFromConfig shows the injecting rung: the caller resolved the config, so nothing is loaded and the region is validated immediately.
package main
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"gitlab.com/phpboyscout/go/awsclient"
)
func main() {
src, err := awsclient.FromConfig(aws.Config{Region: "eu-west-2"})
if err != nil {
fmt.Println("construct:", err)
return
}
cfg, err := src.AWSConfig(context.Background())
if err != nil {
fmt.Println("resolve:", err)
return
}
fmt.Println(cfg.Region)
}
Output: eu-west-2
Example (NoRegion) ¶
ExampleFromConfig_noRegion shows the one thing this module refuses to guess. A config naming no region names a key in an account nobody chose, so it fails at construction rather than several calls later.
package main
import (
"errors"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"gitlab.com/phpboyscout/go/awsclient"
)
func main() {
_, err := awsclient.FromConfig(aws.Config{})
fmt.Println(errors.Is(err, awsclient.ErrNoRegion))
}
Output: true
func PerCall ¶
PerCall returns a source that resolves the ambient chain afresh on every call and retains nothing between them.
Use it where holding credentials between operations would be wrong. Concurrent calls are deliberately not collapsed: sharing one resolution between callers who asked not to share one is the thing this rung exists to avoid.
Example ¶
ExamplePerCall shows the posture for a caller that must not hold credentials between operations — a signing backend that resolves inside each mint. No Output, for the same reason as [ExampleAmbient].
package main
import (
"context"
"gitlab.com/phpboyscout/go/awsclient"
)
func main() {
src := awsclient.PerCall(awsclient.WithRegion("eu-west-2"))
// Each call resolves afresh and retains nothing, so credentials do not
// outlive the operation that needed them.
if _, err := src.AWSConfig(context.Background()); err != nil {
return
}
}
Output: