Skip to content

Commit

Permalink
Merge branch 'master' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
ybourgery committed Jan 13, 2025
2 parents ad4b799 + 9e81e8d commit 530ac31
Show file tree
Hide file tree
Showing 491 changed files with 18,221 additions and 16,838 deletions.
11 changes: 4 additions & 7 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ jobs:
os: [ubuntu-latest, macos-latest]
# When updating this, make sure to also update the
# latest_go_version variable in internal/testing/runchecks.sh.
# Restore previous version when go 1.23 is out; go 1.22
# purposefully broke some backwards compatibility that breaks
# the golden tests, so I've disabled them for now.
go-version: [1.22.x]
#include:
#- go-version: 1.21.x
#os: ubuntu-latest
go-version: [1.23.x]
include:
- go-version: 1.22.x
os: ubuntu-latest

runs-on: ${{ matrix.os }}
steps:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ The Go CDK provides generic APIs for:
* Unstructured binary (blob) storage
* Publish/Subscribe (pubsub)
* Variables that change at runtime (runtimevar)
* Connecting to MySQL and PostgreSQL databases (mysql, postgres)
* Connecting to MySQL (including MariaDB) and PostgreSQL databases (mysql, postgres)
* Server startup and diagnostics: request logging, tracing, and health
checking (server)

Expand Down
117 changes: 99 additions & 18 deletions aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ import (
"strconv"

awsv2 "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/ratelimit"
"github.com/aws/aws-sdk-go-v2/aws/retry"
"github.com/aws/aws-sdk-go-v2/config"
awsv2cfg "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/google/wire"
)
Expand Down Expand Up @@ -79,8 +83,10 @@ func (co ConfigOverrider) ClientConfig(serviceName string, cfgs ...*aws.Config)
// The following query options are supported:
// - region: The AWS region for requests; sets aws.Config.Region.
// - endpoint: The endpoint URL (hostname only or fully qualified URI); sets aws.Config.Endpoint.
// - disableSSL: A value of "true" disables SSL when sending requests; sets aws.Config.DisableSSL.
// - s3ForcePathStyle: A value of "true" forces the request to use path-style addressing; sets aws.Config.S3ForcePathStyle.
// - disable_ssl (or disableSSL): A value of "true" disables SSL when sending requests; sets aws.Config.DisableSSL.
// - s3_force_path_style (or s3ForcePathStyle): A value of "true" forces the request to use path-style addressing; sets aws.Config.S3ForcePathStyle.
// - dualstack: A value of "true" enables dual stack (IPv4 and IPv6) endpoints
// - fips: A value of "true" enables the use of FIPS endpoints
func ConfigFromURLParams(q url.Values) (*aws.Config, error) {
var cfg aws.Config
for param, values := range q {
Expand All @@ -90,18 +96,32 @@ func ConfigFromURLParams(q url.Values) (*aws.Config, error) {
cfg.Region = aws.String(value)
case "endpoint":
cfg.Endpoint = aws.String(value)
case "disableSSL":
case "disable_ssl", "disableSSL":
b, err := strconv.ParseBool(value)
if err != nil {
return nil, fmt.Errorf("invalid value for query parameter %q: %v", param, err)
}
cfg.DisableSSL = aws.Bool(b)
case "s3ForcePathStyle":
case "s3_force_path_style", "s3ForcePathStyle":
b, err := strconv.ParseBool(value)
if err != nil {
return nil, fmt.Errorf("invalid value for query parameter %q: %v", param, err)
}
cfg.S3ForcePathStyle = aws.Bool(b)
case "dualstack":
b, err := strconv.ParseBool(value)
if err != nil {
return nil, fmt.Errorf("invalid value for query parameter %q: %v", param, err)
}
cfg.UseDualStack = aws.Bool(b)
case "fips":
b, err := strconv.ParseBool(value)
if err != nil {
return nil, fmt.Errorf("invalid value for query parameter %q: %v", param, err)
}
if b {
cfg.UseFIPSEndpoint = endpoints.FIPSEndpointStateEnabled
}
case "awssdk":
// ignore, should be handled before this
default:
Expand Down Expand Up @@ -148,15 +168,14 @@ func NewSessionFromURLParams(q url.Values) (*session.Session, url.Values, error)
//
// "awssdk=v1" will force V1.
// "awssdk=v2" will force V2.
// No "awssdk" parameter (or any other value) will return the default, currently V1.
// Note that the default may change in the future.
// No "awssdk" parameter (or any other value) will return the default, currently V2.
func UseV2(q url.Values) bool {
if values, ok := q["awssdk"]; ok {
if values[0] == "v2" || values[0] == "V2" {
return true
if values[0] == "v1" || values[0] == "V1" {
return false
}
}
return false
return true
}

// NewDefaultV2Config returns a aws.Config for AWS SDK v2, using the default options.
Expand All @@ -176,32 +195,94 @@ func NewDefaultV2Config(ctx context.Context) (awsv2.Config, error) {
//
// The following query options are supported:
// - region: The AWS region for requests; sets WithRegion.
// - anonymous: A value of "true" forces use of anonymous credentials.
// - profile: The shared config profile to use; sets SharedConfigProfile.
// - endpoint: The AWS service endpoint to send HTTP request.
// - hostname_immutable: Make the hostname immutable, only works if endpoint is also set.
// - dualstack: A value of "true" enables dual stack (IPv4 and IPv6) endpoints.
// - fips: A value of "true" enables the use of FIPS endpoints.
// - rate_limiter_capacity: A integer value configures the capacity of a token bucket used
// in client-side rate limits. If no value is set, the client-side rate limiting is disabled.
// See https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/retries-timeouts/#client-side-rate-limiting.
func V2ConfigFromURLParams(ctx context.Context, q url.Values) (awsv2.Config, error) {
var endpoint string
var hostnameImmutable bool
var rateLimitCapacity int64
var opts []func(*awsv2cfg.LoadOptions) error
for param, values := range q {
value := values[0]
switch param {
case "hostname_immutable":
var err error
hostnameImmutable, err = strconv.ParseBool(value)
if err != nil {
return awsv2.Config{}, fmt.Errorf("invalid value for hostname_immutable: %w", err)
}
case "region":
opts = append(opts, awsv2cfg.WithRegion(value))
case "endpoint":
customResolver := awsv2.EndpointResolverWithOptionsFunc(
func(service, region string, options ...interface{}) (awsv2.Endpoint, error) {
return awsv2.Endpoint{
PartitionID: "aws",
URL: value,
SigningRegion: region,
}, nil
})
opts = append(opts, awsv2cfg.WithEndpointResolverWithOptions(customResolver))
endpoint = value
case "profile":
opts = append(opts, awsv2cfg.WithSharedConfigProfile(value))
case "dualstack":
dualStack, err := strconv.ParseBool(value)
if err != nil {
return awsv2.Config{}, fmt.Errorf("invalid value for dualstack: %w", err)
}
if dualStack {
opts = append(opts, awsv2cfg.WithUseDualStackEndpoint(awsv2.DualStackEndpointStateEnabled))
}
case "fips":
fips, err := strconv.ParseBool(value)
if err != nil {
return awsv2.Config{}, fmt.Errorf("invalid value for fips: %w", err)
}
if fips {
opts = append(opts, awsv2cfg.WithUseFIPSEndpoint(awsv2.FIPSEndpointStateEnabled))
}
case "rate_limiter_capacity":
var err error
rateLimitCapacity, err = strconv.ParseInt(value, 10, 32)
if err != nil {
return awsv2.Config{}, fmt.Errorf("invalid value for capacity: %w", err)
}
case "anonymous":
anon, err := strconv.ParseBool(value)
if err != nil {
return awsv2.Config{}, fmt.Errorf("invalid value for anonymous: %w", err)
}
if anon {
opts = append(opts, awsv2cfg.WithCredentialsProvider(awsv2.AnonymousCredentials{}))
}
case "awssdk":
// ignore, should be handled before this
default:
return awsv2.Config{}, fmt.Errorf("unknown query parameter %q", param)
}
}
if endpoint != "" {
customResolver := awsv2.EndpointResolverWithOptionsFunc(
func(service, region string, options ...any) (awsv2.Endpoint, error) {
return awsv2.Endpoint{
PartitionID: "aws",
URL: endpoint,
SigningRegion: region,
HostnameImmutable: hostnameImmutable,
}, nil
})
opts = append(opts, awsv2cfg.WithEndpointResolverWithOptions(customResolver))
}

var rateLimiter retry.RateLimiter
rateLimiter = ratelimit.None
if rateLimitCapacity > 0 {
rateLimiter = ratelimit.NewTokenRateLimit(uint(rateLimitCapacity))
}
opts = append(opts, config.WithRetryer(func() awsv2.Retryer {
return retry.NewStandard(func(so *retry.StandardOptions) {
so.RateLimiter = rateLimiter
})
}))

return awsv2cfg.LoadDefaultConfig(ctx, opts...)
}
69 changes: 65 additions & 4 deletions aws/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ package aws_test
import (
"context"
"net/url"
"reflect"
"testing"

awsv2 "github.com/aws/aws-sdk-go-v2/aws"
awsv2retry "github.com/aws/aws-sdk-go-v2/aws/retry"
"github.com/aws/aws-sdk-go/aws"
"github.com/google/go-cmp/cmp"
gcaws "gocloud.dev/aws"
Expand Down Expand Up @@ -51,6 +54,11 @@ func TestConfigFromURLParams(t *testing.T) {
query: url.Values{"endpoint": {"foo"}},
wantCfg: &aws.Config{Endpoint: aws.String("foo")},
},
{
name: "disable_ssl true",
query: url.Values{"disable_ssl": {"true"}},
wantCfg: &aws.Config{DisableSSL: aws.Bool(true)},
},
{
name: "DisableSSL true",
query: url.Values{"disableSSL": {"true"}},
Expand All @@ -66,6 +74,11 @@ func TestConfigFromURLParams(t *testing.T) {
query: url.Values{"disableSSL": {"invalid"}},
wantErr: true,
},
{
name: "s3_force_path_style true",
query: url.Values{"s3_force_path_style": {"true"}},
wantCfg: &aws.Config{S3ForcePathStyle: aws.Bool(true)},
},
{
name: "S3ForcePathStyle true",
query: url.Values{"s3ForcePathStyle": {"true"}},
Expand Down Expand Up @@ -108,14 +121,17 @@ func TestUseV2(t *testing.T) {
{
name: "No overrides",
query: url.Values{},
want: true,
},
{
name: "unused param",
query: url.Values{"foo": {"bar"}},
want: true,
},
{
name: "force v1",
query: url.Values{"awssdk": {"v1"}},
want: false,
},
{
name: "force v1 cap",
Expand Down Expand Up @@ -144,12 +160,16 @@ func TestUseV2(t *testing.T) {
}

func TestV2ConfigFromURLParams(t *testing.T) {
const service = "s3"
const region = "us-east-1"
const partitionID = "aws"
ctx := context.Background()
tests := []struct {
name string
query url.Values
wantRegion string
wantErr bool
name string
query url.Values
wantRegion string
wantErr bool
wantEndpoint *awsv2.Endpoint
}{
{
name: "No overrides",
Expand All @@ -165,6 +185,28 @@ func TestV2ConfigFromURLParams(t *testing.T) {
query: url.Values{"region": {"my_region"}},
wantRegion: "my_region",
},
{
name: "Endpoint and hostname immutable",
query: url.Values{"endpoint": {"foo"}, "hostname_immutable": {"true"}},
wantEndpoint: &awsv2.Endpoint{
PartitionID: partitionID,
SigningRegion: region,
URL: "foo",
HostnameImmutable: true,
},
},
{
name: "FIPS and dual stack",
query: url.Values{"fips": {"true"}, "dualstack": {"true"}},
},
{
name: "anonymous",
query: url.Values{"anonymous": {"true"}},
},
{
name: "Rate limit capacity",
query: url.Values{"rate_limiter_capacity": {"500"}},
},
// Can't test "profile", since AWS validates that the profile exists.
}

Expand All @@ -181,6 +223,25 @@ func TestV2ConfigFromURLParams(t *testing.T) {
if test.wantRegion != "" && got.Region != test.wantRegion {
t.Errorf("got region %q, want %q", got.Region, test.wantRegion)
}

if test.wantEndpoint != nil {
if got.EndpointResolverWithOptions == nil {
t.Fatalf("expected an EndpointResolverWithOptions, got nil")
}
gotE, err := got.EndpointResolverWithOptions.ResolveEndpoint(service, region)
if err != nil {
return
}
if !reflect.DeepEqual(gotE, *test.wantEndpoint) {
t.Errorf("got endpoint %+v, want %+v", gotE, *test.wantEndpoint)
}
}

// Unfortunately, we can't look at the options set for the rate limiter.
r, ok := got.Retryer().(*awsv2retry.Standard)
if !ok {
t.Errorf("expected a standard retryer, got %v, expected awsv2retry.Standard", r)
}
})
}
}
Loading

0 comments on commit 530ac31

Please sign in to comment.