Skip to content

Commit 3cc4661

Browse files
authored
add loadoptions to configure BaseEndpoint (#2837)
1 parent 8ed647a commit 3cc4661

File tree

3 files changed

+231
-0
lines changed

3 files changed

+231
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "024e7efa-fa27-4001-b867-7eb90bd32260",
3+
"type": "feature",
4+
"description": "Adds the LoadOptions hook `WithBaseEndpoint` for setting global endpoint override in-code.",
5+
"modules": [
6+
"config"
7+
]
8+
}

config/load_options.go

+33
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ type LoadOptions struct {
217217
S3DisableExpressAuth *bool
218218

219219
AccountIDEndpointMode aws.AccountIDEndpointMode
220+
221+
// Service endpoint override. This value is not necessarily final and is
222+
// passed to the service's EndpointResolverV2 for further delegation.
223+
BaseEndpoint string
220224
}
221225

222226
func (o LoadOptions) getDefaultsMode(ctx context.Context) (aws.DefaultsMode, bool, error) {
@@ -284,6 +288,19 @@ func (o LoadOptions) getAccountIDEndpointMode(ctx context.Context) (aws.AccountI
284288
return o.AccountIDEndpointMode, len(o.AccountIDEndpointMode) > 0, nil
285289
}
286290

291+
func (o LoadOptions) getBaseEndpoint(context.Context) (string, bool, error) {
292+
return o.BaseEndpoint, o.BaseEndpoint != "", nil
293+
}
294+
295+
// GetServiceBaseEndpoint satisfies (internal/configsources).ServiceBaseEndpointProvider.
296+
//
297+
// The sdkID value is unused because LoadOptions only supports setting a GLOBAL
298+
// endpoint override. In-code, per-service endpoint overrides are performed via
299+
// functional options in service client space.
300+
func (o LoadOptions) GetServiceBaseEndpoint(context.Context, string) (string, bool, error) {
301+
return o.BaseEndpoint, o.BaseEndpoint != "", nil
302+
}
303+
287304
// WithRegion is a helper function to construct functional options
288305
// that sets Region on config's LoadOptions. Setting the region to
289306
// an empty string, will result in the region value being ignored.
@@ -1139,3 +1156,19 @@ func WithS3DisableExpressAuth(v bool) LoadOptionsFunc {
11391156
return nil
11401157
}
11411158
}
1159+
1160+
// WithBaseEndpoint is a helper function to construct functional options that
1161+
// sets BaseEndpoint on config's LoadOptions. Empty values have no effect, and
1162+
// subsequent calls to this API override previous ones.
1163+
//
1164+
// This is an in-code setting, therefore, any value set using this hook takes
1165+
// precedence over and will override ALL environment and shared config
1166+
// directives that set endpoint URLs. Functional options on service clients
1167+
// have higher specificity, and functional options that modify the value of
1168+
// BaseEndpoint on a client will take precedence over this setting.
1169+
func WithBaseEndpoint(v string) LoadOptionsFunc {
1170+
return func(o *LoadOptions) error {
1171+
o.BaseEndpoint = v
1172+
return nil
1173+
}
1174+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
//go:build integration
2+
// +build integration
3+
4+
package s3
5+
6+
import (
7+
"context"
8+
"os"
9+
"testing"
10+
11+
"github.com/aws/aws-sdk-go-v2/aws"
12+
"github.com/aws/aws-sdk-go-v2/config"
13+
"github.com/aws/aws-sdk-go-v2/service/s3"
14+
)
15+
16+
// From the SEP:
17+
// Service-specific endpoint configuration MUST be resolved with an endpoint URL provider chain with the following precedence:
18+
// - The value provided through code to an AWS SDK or tool via a command line
19+
// parameter or a client or configuration constructor; for example the
20+
// --endpoint-url command line parameter or the endpoint_url parameter
21+
// provided to the Python SDK client.
22+
// - The value provided by a service-specific environment variable.
23+
// - The value provided by the global endpoint environment variable
24+
// (AWS_ENDPOINT_URL).
25+
// - The value provided by a service-specific parameter from a services
26+
// definition section referenced in a profile in the shared configuration
27+
// file.
28+
// - The value provided by the global parameter from a profile in the shared
29+
// configuration file.
30+
// - The value resolved through the methods provided by the SDK or tool when
31+
// no explicit endpoint URL is provided.
32+
33+
func TestInteg_EndpointURL(t *testing.T) {
34+
for name, tt := range map[string]struct {
35+
Env map[string]string
36+
SharedConfig string
37+
LoadOpts []func(*config.LoadOptions) error
38+
ClientOpts []func(*s3.Options)
39+
Expect string
40+
}{
41+
"no values": {
42+
SharedConfig: `
43+
[default]
44+
`,
45+
Expect: "",
46+
},
47+
48+
"precedence 0: in-code, set via s3.Options": {
49+
Env: map[string]string{
50+
"AWS_ENDPOINT_URL": "https://global-env.com",
51+
"AWS_ENDPOINT_URL_S3": "https://service-env.com",
52+
},
53+
SharedConfig: `
54+
[default]
55+
endpoint_url = https://global-cfg.com
56+
services = service_cfg
57+
58+
[services service_cfg]
59+
s3 =
60+
endpoint_url = https://service-cfg.com
61+
`,
62+
LoadOpts: []func(*config.LoadOptions) error{
63+
config.WithBaseEndpoint("https://loadopts.com"),
64+
},
65+
ClientOpts: []func(*s3.Options){
66+
func(o *s3.Options) {
67+
o.BaseEndpoint = aws.String("https://clientopts.com")
68+
},
69+
},
70+
Expect: "https://clientopts.com",
71+
},
72+
73+
"precedence 0: in-code, set via config.LoadOptions": {
74+
Env: map[string]string{
75+
"AWS_ENDPOINT_URL": "https://global-env.com",
76+
"AWS_ENDPOINT_URL_S3": "https://service-env.com",
77+
},
78+
SharedConfig: `
79+
[default]
80+
endpoint_url = https://global-cfg.com
81+
services = service_cfg
82+
83+
[services service_cfg]
84+
s3 =
85+
endpoint_url = https://service-cfg.com
86+
`,
87+
LoadOpts: []func(*config.LoadOptions) error{
88+
config.WithBaseEndpoint("https://loadopts.com"),
89+
},
90+
Expect: "https://loadopts.com",
91+
},
92+
93+
"precedence 1: service env": {
94+
Env: map[string]string{
95+
"AWS_ENDPOINT_URL": "https://global-env.com",
96+
"AWS_ENDPOINT_URL_S3": "https://service-env.com",
97+
},
98+
SharedConfig: `
99+
[default]
100+
endpoint_url = https://global-cfg.com
101+
services = service_cfg
102+
103+
[services service_cfg]
104+
s3 =
105+
endpoint_url = https://service-cfg.com
106+
`,
107+
Expect: "https://service-env.com",
108+
},
109+
110+
"precedence 2: global env": {
111+
Env: map[string]string{
112+
"AWS_ENDPOINT_URL": "https://global-env.com",
113+
},
114+
SharedConfig: `
115+
[default]
116+
endpoint_url = https://global-cfg.com
117+
services = service_cfg
118+
119+
[services service_cfg]
120+
s3 =
121+
endpoint_url = https://service-cfg.com
122+
`,
123+
Expect: "https://global-env.com",
124+
},
125+
126+
"precedence 3: service cfg": {
127+
SharedConfig: `
128+
[default]
129+
endpoint_url = https://global-cfg.com
130+
services = service_cfg
131+
132+
[services service_cfg]
133+
s3 =
134+
endpoint_url = https://service-cfg.com
135+
`,
136+
Expect: "https://service-cfg.com",
137+
},
138+
139+
"precedence 4: global cfg": {
140+
SharedConfig: `
141+
[default]
142+
endpoint_url = https://global-cfg.com
143+
`,
144+
Expect: "https://global-cfg.com",
145+
},
146+
} {
147+
t.Run(name, func(t *testing.T) {
148+
reset, err := mockEnvironment(tt.Env, tt.SharedConfig)
149+
if err != nil {
150+
t.Fatalf("mock environment: %v", err)
151+
}
152+
defer reset()
153+
154+
loadopts := append(tt.LoadOpts,
155+
config.WithSharedConfigFiles([]string{"test_shared_config"}))
156+
cfg, err := config.LoadDefaultConfig(context.Background(), loadopts...)
157+
if err != nil {
158+
t.Fatalf("load config: %v", err)
159+
}
160+
161+
svc := s3.NewFromConfig(cfg, tt.ClientOpts...)
162+
actual := aws.ToString(svc.Options().BaseEndpoint)
163+
if tt.Expect != actual {
164+
t.Errorf("expect endpoint: %q != %q", tt.Expect, actual)
165+
}
166+
})
167+
}
168+
}
169+
170+
func mockEnvironment(env map[string]string, sharedCfg string) (func(), error) {
171+
for k, v := range env {
172+
os.Setenv(k, v)
173+
}
174+
f, err := os.Create("test_shared_config")
175+
if err != nil {
176+
return nil, err
177+
}
178+
if _, err := f.Write([]byte(sharedCfg)); err != nil {
179+
return nil, err
180+
}
181+
182+
return func() {
183+
for k := range env {
184+
os.Unsetenv(k)
185+
}
186+
if err := os.Remove("test_shared_config"); err != nil {
187+
panic(err)
188+
}
189+
}, nil
190+
}

0 commit comments

Comments
 (0)