1package config
  2
  3import (
  4	"context"
  5	"os"
  6
  7	"github.com/aws/aws-sdk-go-v2/aws"
  8)
  9
 10// defaultAWSConfigResolvers are a slice of functions that will resolve external
 11// configuration values into AWS configuration values.
 12//
 13// This will setup the AWS configuration's Region,
 14var defaultAWSConfigResolvers = []awsConfigResolver{
 15	// Resolves the default configuration the SDK's aws.Config will be
 16	// initialized with.
 17	resolveDefaultAWSConfig,
 18
 19	// Sets the logger to be used. Could be user provided logger, and client
 20	// logging mode.
 21	resolveLogger,
 22	resolveClientLogMode,
 23
 24	// Sets the HTTP client and configuration to use for making requests using
 25	// the HTTP transport.
 26	resolveHTTPClient,
 27	resolveCustomCABundle,
 28
 29	// Sets the endpoint resolving behavior the API Clients will use for making
 30	// requests to. Clients default to their own clients this allows overrides
 31	// to be specified. The resolveEndpointResolver option is deprecated, but
 32	// we still need to set it for backwards compatibility on config
 33	// construction.
 34	resolveEndpointResolver,
 35	resolveEndpointResolverWithOptions,
 36
 37	// Sets the retry behavior API clients will use within their retry attempt
 38	// middleware. Defaults to unset, allowing API clients to define their own
 39	// retry behavior.
 40	resolveRetryer,
 41
 42	// Sets the region the API Clients should use for making requests to.
 43	resolveRegion,
 44	resolveEC2IMDSRegion,
 45	resolveDefaultRegion,
 46
 47	// Sets the additional set of middleware stack mutators that will custom
 48	// API client request pipeline middleware.
 49	resolveAPIOptions,
 50
 51	// Resolves the DefaultsMode that should be used by SDK clients. If this
 52	// mode is set to DefaultsModeAuto.
 53	//
 54	// Comes after HTTPClient and CustomCABundle to ensure the HTTP client is
 55	// configured if provided before invoking IMDS if mode is auto. Comes
 56	// before resolving credentials so that those subsequent clients use the
 57	// configured auto mode.
 58	resolveDefaultsModeOptions,
 59
 60	// Sets the resolved credentials the API clients will use for
 61	// authentication. Provides the SDK's default credential chain.
 62	//
 63	// Should probably be the last step in the resolve chain to ensure that all
 64	// other configurations are resolved first in case downstream credentials
 65	// implementations depend on or can be configured with earlier resolved
 66	// configuration options.
 67	resolveCredentials,
 68
 69	// Sets the resolved bearer authentication token API clients will use for
 70	// httpBearerAuth authentication scheme.
 71	resolveBearerAuthToken,
 72
 73	// Sets the sdk app ID if present in env var or shared config profile
 74	resolveAppID,
 75
 76	resolveBaseEndpoint,
 77
 78	// Sets the DisableRequestCompression if present in env var or shared config profile
 79	resolveDisableRequestCompression,
 80
 81	// Sets the RequestMinCompressSizeBytes if present in env var or shared config profile
 82	resolveRequestMinCompressSizeBytes,
 83
 84	// Sets the AccountIDEndpointMode if present in env var or shared config profile
 85	resolveAccountIDEndpointMode,
 86}
 87
 88// A Config represents a generic configuration value or set of values. This type
 89// will be used by the AWSConfigResolvers to extract
 90//
 91// General the Config type will use type assertion against the Provider interfaces
 92// to extract specific data from the Config.
 93type Config interface{}
 94
 95// A loader is used to load external configuration data and returns it as
 96// a generic Config type.
 97//
 98// The loader should return an error if it fails to load the external configuration
 99// or the configuration data is malformed, or required components missing.
100type loader func(context.Context, configs) (Config, error)
101
102// An awsConfigResolver will extract configuration data from the configs slice
103// using the provider interfaces to extract specific functionality. The extracted
104// configuration values will be written to the AWS Config value.
105//
106// The resolver should return an error if it it fails to extract the data, the
107// data is malformed, or incomplete.
108type awsConfigResolver func(ctx context.Context, cfg *aws.Config, configs configs) error
109
110// configs is a slice of Config values. These values will be used by the
111// AWSConfigResolvers to extract external configuration values to populate the
112// AWS Config type.
113//
114// Use AppendFromLoaders to add additional external Config values that are
115// loaded from external sources.
116//
117// Use ResolveAWSConfig after external Config values have been added or loaded
118// to extract the loaded configuration values into the AWS Config.
119type configs []Config
120
121// AppendFromLoaders iterates over the slice of loaders passed in calling each
122// loader function in order. The external config value returned by the loader
123// will be added to the returned configs slice.
124//
125// If a loader returns an error this method will stop iterating and return
126// that error.
127func (cs configs) AppendFromLoaders(ctx context.Context, loaders []loader) (configs, error) {
128	for _, fn := range loaders {
129		cfg, err := fn(ctx, cs)
130		if err != nil {
131			return nil, err
132		}
133
134		cs = append(cs, cfg)
135	}
136
137	return cs, nil
138}
139
140// ResolveAWSConfig returns a AWS configuration populated with values by calling
141// the resolvers slice passed in. Each resolver is called in order. Any resolver
142// may overwrite the AWS Configuration value of a previous resolver.
143//
144// If an resolver returns an error this method will return that error, and stop
145// iterating over the resolvers.
146func (cs configs) ResolveAWSConfig(ctx context.Context, resolvers []awsConfigResolver) (aws.Config, error) {
147	var cfg aws.Config
148
149	for _, fn := range resolvers {
150		if err := fn(ctx, &cfg, cs); err != nil {
151			return aws.Config{}, err
152		}
153	}
154
155	return cfg, nil
156}
157
158// ResolveConfig calls the provide function passing slice of configuration sources.
159// This implements the aws.ConfigResolver interface.
160func (cs configs) ResolveConfig(f func(configs []interface{}) error) error {
161	var cfgs []interface{}
162	for i := range cs {
163		cfgs = append(cfgs, cs[i])
164	}
165	return f(cfgs)
166}
167
168// LoadDefaultConfig reads the SDK's default external configurations, and
169// populates an AWS Config with the values from the external configurations.
170//
171// An optional variadic set of additional Config values can be provided as input
172// that will be prepended to the configs slice. Use this to add custom configuration.
173// The custom configurations must satisfy the respective providers for their data
174// or the custom data will be ignored by the resolvers and config loaders.
175//
176//	cfg, err := config.LoadDefaultConfig( context.TODO(),
177//	   config.WithSharedConfigProfile("test-profile"),
178//	)
179//	if err != nil {
180//	   panic(fmt.Sprintf("failed loading config, %v", err))
181//	}
182//
183// The default configuration sources are:
184// * Environment Variables
185// * Shared Configuration and Shared Credentials files.
186func LoadDefaultConfig(ctx context.Context, optFns ...func(*LoadOptions) error) (cfg aws.Config, err error) {
187	var options LoadOptions
188	for _, optFn := range optFns {
189		if err := optFn(&options); err != nil {
190			return aws.Config{}, err
191		}
192	}
193
194	// assign Load Options to configs
195	var cfgCpy = configs{options}
196
197	cfgCpy, err = cfgCpy.AppendFromLoaders(ctx, resolveConfigLoaders(&options))
198	if err != nil {
199		return aws.Config{}, err
200	}
201
202	cfg, err = cfgCpy.ResolveAWSConfig(ctx, defaultAWSConfigResolvers)
203	if err != nil {
204		return aws.Config{}, err
205	}
206
207	return cfg, nil
208}
209
210func resolveConfigLoaders(options *LoadOptions) []loader {
211	loaders := make([]loader, 2)
212	loaders[0] = loadEnvConfig
213
214	// specification of a profile should cause a load failure if it doesn't exist
215	if os.Getenv(awsProfileEnvVar) != "" || options.SharedConfigProfile != "" {
216		loaders[1] = loadSharedConfig
217	} else {
218		loaders[1] = loadSharedConfigIgnoreNotExist
219	}
220
221	return loaders
222}