From 989c73c7b596f3f2e12e97535edafa47aecaf07b Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 10 Jan 2024 14:41:40 -0800 Subject: [PATCH 1/7] Adds test for `accessanalyzer` (AWS SDK v2, without aliases, without custom envvars) --- .../service_endpoints_gen_test.go | 492 ++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 internal/service/accessanalyzer/service_endpoints_gen_test.go diff --git a/internal/service/accessanalyzer/service_endpoints_gen_test.go b/internal/service/accessanalyzer/service_endpoints_gen_test.go new file mode 100644 index 00000000000..10aa07dd0b7 --- /dev/null +++ b/internal/service/accessanalyzer/service_endpoints_gen_test.go @@ -0,0 +1,492 @@ +package accessanalyzer_test + +import ( + "context" + "errors" + "fmt" + "maps" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/accessanalyzer" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "accessanalyzer" + awsEnvVar = "AWS_ENDPOINT_URL_ACCESSANALYZER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "accessanalyzer" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := accessanalyzer.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), accessanalyzer.EndpointParameters{ + Region: aws.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.AccessAnalyzerClient(ctx) + + _, err := client.ListAnalyzers(ctx, &accessanalyzer.ListAnalyzersInput{}, + func(opts *accessanalyzer.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} From 7b2d04750b7294f85b5de35e615041d8130a594f Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 10 Jan 2024 15:40:31 -0800 Subject: [PATCH 2/7] Removes spurious generated file --- .../servicepackage/service_package_gen.go | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 internal/generate/servicepackage/service_package_gen.go diff --git a/internal/generate/servicepackage/service_package_gen.go b/internal/generate/servicepackage/service_package_gen.go deleted file mode 100644 index 463e7ea61df..00000000000 --- a/internal/generate/servicepackage/service_package_gen.go +++ /dev/null @@ -1,47 +0,0 @@ -// Code generated by internal/generate/servicepackages/main.go; DO NOT EDIT. - -package cognitoidentity - -import ( - "context" - - aws_sdkv1 "github.com/aws/aws-sdk-go/aws" - session_sdkv1 "github.com/aws/aws-sdk-go/aws/session" - cognitoidentity_sdkv1 "github.com/aws/aws-sdk-go/service/cognitoidentity" - "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/types" - "github.com/hashicorp/terraform-provider-aws/names" -) - -type servicePackage struct{} - -func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.ServicePackageFrameworkDataSource { - return []*types.ServicePackageFrameworkDataSource{} -} - -func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} -} - -func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { - return []*types.ServicePackageSDKDataSource{} -} - -func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { - return []*types.ServicePackageSDKResource{} -} - -func (p *servicePackage) ServicePackageName() string { - return names.CognitoIdentity -} - -// NewConn returns a new AWS SDK for Go v1 client for this service package's AWS API. -func (p *servicePackage) NewConn(ctx context.Context, config map[string]any) (*cognitoidentity_sdkv1.CognitoIdentity, error) { - sess := config["session"].(*session_sdkv1.Session) - - return cognitoidentity_sdkv1.New(sess.Copy(&aws_sdkv1.Config{Endpoint: aws_sdkv1.String(config["endpoint"].(string))})), nil -} - -func ServicePackage(ctx context.Context) conns.ServicePackage { - return &servicePackage{} -} From 2cbf2fbda1ec1911e53bd4ec1bd6dd4b68bb37fe Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 10 Jan 2024 15:41:41 -0800 Subject: [PATCH 3/7] Makes `ClientSDKV1` and `ClientSDKV2` return `bool` and adds `SDKVersion` --- internal/generate/awsclient/main.go | 11 ++--------- internal/generate/checknames/main.go | 8 ++++---- internal/generate/servicepackage/main.go | 11 ++--------- names/data/names_data.csv | 2 +- names/data/read.go | 23 +++++++++++++++++++---- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/internal/generate/awsclient/main.go b/internal/generate/awsclient/main.go index 135930e267e..1cc9454ed29 100644 --- a/internal/generate/awsclient/main.go +++ b/internal/generate/awsclient/main.go @@ -57,17 +57,10 @@ func main() { GoV2Package: l.GoV2Package(), } - if l.ClientSDKV1() != "" { - s.SDKVersion = "1" + s.SDKVersion = l.SDKVersion() + if l.ClientSDKV1() { s.GoV1ClientTypeName = l.GoV1ClientTypeName() } - if l.ClientSDKV2() != "" { - if l.ClientSDKV1() != "" { - s.SDKVersion = "1,2" - } else { - s.SDKVersion = "2" - } - } td.Services = append(td.Services, s) } diff --git a/internal/generate/checknames/main.go b/internal/generate/checknames/main.go index db0f599c9bb..30764e44046 100644 --- a/internal/generate/checknames/main.go +++ b/internal/generate/checknames/main.go @@ -21,7 +21,7 @@ import ( ) const ( - lineOffset = 1 // translate from 0-based to 1-based index + lineOffset = 2 // 1 for skipping header line + 1 to translate from 0-based to 1-based index ) // DocPrefix tests/column needs to be reworked for compatibility with tfproviderdocs @@ -98,15 +98,15 @@ func main() { } } - if l.ClientSDKV1() == "" && l.ClientSDKV2() == "" && !l.Exclude() { + if !l.ClientSDKV1() && !l.ClientSDKV2() && !l.Exclude() { log.Fatalf("in service data, line %d, for service %s, at least one of ClientSDKV1 or ClientSDKV2 must have a value if Exclude is blank", i+lineOffset, l.HumanFriendly()) } - if l.ClientSDKV1() != "" && (l.GoV1Package() == "" || l.GoV1ClientTypeName() == "") { + if l.ClientSDKV1() && (l.GoV1Package() == "" || l.GoV1ClientTypeName() == "") { log.Fatalf("in service data, line %d, for service %s, SDKVersion is set to 1 so neither GoV1Package nor GoV1ClientTypeName can be blank", i+lineOffset, l.HumanFriendly()) } - if l.ClientSDKV2() != "" && l.GoV2Package() == "" { + if l.ClientSDKV2() && l.GoV2Package() == "" { log.Fatalf("in service data, line %d, for service %s, SDKVersion is set to 2 so GoV2Package cannot be blank", i+lineOffset, l.HumanFriendly()) } diff --git a/internal/generate/servicepackage/main.go b/internal/generate/servicepackage/main.go index f47abf8761c..21863372052 100644 --- a/internal/generate/servicepackage/main.go +++ b/internal/generate/servicepackage/main.go @@ -76,17 +76,10 @@ func main() { SDKResources: v.sdkResources, } - if l.ClientSDKV1() != "" { - s.SDKVersion = "1" + s.SDKVersion = l.SDKVersion() + if l.ClientSDKV1() { s.GoV1ClientTypeName = l.GoV1ClientTypeName() } - if l.ClientSDKV2() != "" { - if l.ClientSDKV1() != "" { - s.SDKVersion = "1,2" - } else { - s.SDKVersion = "2" - } - } sort.SliceStable(s.FrameworkDataSources, func(i, j int) bool { return s.FrameworkDataSources[i].FactoryName < s.FrameworkDataSources[j].FactoryName diff --git a/names/data/names_data.csv b/names/data/names_data.csv index 7f8c69f4637..678f5c992a0 100644 --- a/names/data/names_data.csv +++ b/names/data/names_data.csv @@ -229,7 +229,7 @@ lookoutvision,lookoutvision,lookoutforvision,lookoutvision,,lookoutvision,,looko machinelearning,machinelearning,machinelearning,machinelearning,,machinelearning,,,MachineLearning,MachineLearning,,1,,,aws_machinelearning_,,machinelearning_,Machine Learning,Amazon,,x,,,,,Machine Learning,,, macie2,macie2,macie2,macie2,,macie2,,,Macie2,Macie2,,1,,,aws_macie2_,,macie2_,Macie,Amazon,,,,,,,Macie2,,, macie,macie,macie,macie,,macie,,,Macie,Macie,,1,,,aws_macie_,,macie_,Macie Classic,Amazon,,x,,,,,Macie,,, -m2,m2,m2,m2,,m2,,,M2,M2,,,2,,aws_m2_,,m2_,Mainframe Modernization,AWS,,,,,,,,,, +m2,m2,m2,m2,,m2,,,M2,M2,,,2,,aws_m2_,,m2_,Mainframe Modernization,AWS,,,,,,,m2,,, managedblockchain,managedblockchain,managedblockchain,managedblockchain,,managedblockchain,,,ManagedBlockchain,ManagedBlockchain,,1,,,aws_managedblockchain_,,managedblockchain_,Managed Blockchain,Amazon,,x,,,,,ManagedBlockchain,,, grafana,grafana,managedgrafana,grafana,,grafana,,managedgrafana;amg,Grafana,ManagedGrafana,,1,,,aws_grafana_,,grafana_,Managed Grafana,Amazon,,,,,,,grafana,,, kafka,kafka,kafka,kafka,,kafka,,msk,Kafka,Kafka,x,,2,aws_msk_,aws_kafka_,,msk_,Managed Streaming for Kafka,Amazon,,,,,,,Kafka,,, diff --git a/names/data/read.go b/names/data/read.go index 66452301c18..3e689216d5d 100644 --- a/names/data/read.go +++ b/names/data/read.go @@ -70,12 +70,27 @@ func (sr ServiceRecord) SkipClientGenerate() bool { return sr[colSkipClientGenerate] != "" } -func (sr ServiceRecord) ClientSDKV1() string { - return sr[colClientSDKV1] +func (sr ServiceRecord) ClientSDKV1() bool { + return sr[colClientSDKV1] != "" } -func (sr ServiceRecord) ClientSDKV2() string { - return sr[colClientSDKV2] +func (sr ServiceRecord) ClientSDKV2() bool { + return sr[colClientSDKV2] != "" +} + +// SDKVersion returns: +// * "1" if only SDK v1 is implemented +// * "2" if only SDK v2 is implemented +// * "1,2" if both are implemented +func (sr ServiceRecord) SDKVersion() string { + if sr.ClientSDKV1() && sr.ClientSDKV2() { + return "1,2" + } else if sr.ClientSDKV1() { + return "1" + } else if sr.ClientSDKV2() { + return "2" + } + return "" } func (sr ServiceRecord) ResourcePrefix() string { From 66fe2a35debc812e2cf9733b002424a09cae6945 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 12 Jan 2024 14:25:31 -0800 Subject: [PATCH 4/7] Generates endpoint configuration tests for services implemented with AWS SDK v2, without aliases, without custom envvars --- .../generate/serviceendpointtests/README.md | 3 + .../generate/serviceendpointtests/file.tmpl | 496 ++++++++++++++++++ .../generate/serviceendpointtests/generate.go | 7 + .../generate/serviceendpointtests/main.go | 99 ++++ .../service_endpoints_gen_test.go | 18 +- .../account/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/acm/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../appconfig/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../appfabric/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../appflow/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../apprunner/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../athena/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../bedrock/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../cleanrooms/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../comprehend/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/ec2/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/ecr/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/eks/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../elasticache/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/emr/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../finspace/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../firehose/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/fis/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../glacier/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../healthlake/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 496 ++++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../ivschat/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../kendra/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../keyspaces/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../kinesis/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../lambda/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../lightsail/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/m2/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../medialive/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/mq/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../pipes/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../polly/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../pricing/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../qbusiness/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../qldb/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/rds/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../rekognition/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../scheduler/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../securityhub/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../sesv2/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../signer/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/sns/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/sqs/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/ssm/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../ssmcontacts/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../ssmsap/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../ssoadmin/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service/swf/service_endpoints_gen_test.go | 496 ++++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../vpclattice/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../service_endpoints_gen_test.go | 494 +++++++++++++++++ .../workspaces/service_endpoints_gen_test.go | 494 +++++++++++++++++ .../xray/service_endpoints_gen_test.go | 494 +++++++++++++++++ names/data/names_data.csv | 176 +++---- 89 files changed, 41709 insertions(+), 96 deletions(-) create mode 100644 internal/generate/serviceendpointtests/README.md create mode 100644 internal/generate/serviceendpointtests/file.tmpl create mode 100644 internal/generate/serviceendpointtests/generate.go create mode 100644 internal/generate/serviceendpointtests/main.go create mode 100644 internal/service/account/service_endpoints_gen_test.go create mode 100644 internal/service/acm/service_endpoints_gen_test.go create mode 100644 internal/service/appconfig/service_endpoints_gen_test.go create mode 100644 internal/service/appfabric/service_endpoints_gen_test.go create mode 100644 internal/service/appflow/service_endpoints_gen_test.go create mode 100644 internal/service/apprunner/service_endpoints_gen_test.go create mode 100644 internal/service/athena/service_endpoints_gen_test.go create mode 100644 internal/service/auditmanager/service_endpoints_gen_test.go create mode 100644 internal/service/bedrock/service_endpoints_gen_test.go create mode 100644 internal/service/chimesdkmediapipelines/service_endpoints_gen_test.go create mode 100644 internal/service/chimesdkvoice/service_endpoints_gen_test.go create mode 100644 internal/service/cleanrooms/service_endpoints_gen_test.go create mode 100644 internal/service/codeguruprofiler/service_endpoints_gen_test.go create mode 100644 internal/service/codepipeline/service_endpoints_gen_test.go create mode 100644 internal/service/codestarconnections/service_endpoints_gen_test.go create mode 100644 internal/service/codestarnotifications/service_endpoints_gen_test.go create mode 100644 internal/service/comprehend/service_endpoints_gen_test.go create mode 100644 internal/service/computeoptimizer/service_endpoints_gen_test.go create mode 100644 internal/service/connectcases/service_endpoints_gen_test.go create mode 100644 internal/service/controltower/service_endpoints_gen_test.go create mode 100644 internal/service/customerprofiles/service_endpoints_gen_test.go create mode 100644 internal/service/docdbelastic/service_endpoints_gen_test.go create mode 100644 internal/service/ec2/service_endpoints_gen_test.go create mode 100644 internal/service/ecr/service_endpoints_gen_test.go create mode 100644 internal/service/eks/service_endpoints_gen_test.go create mode 100644 internal/service/elasticache/service_endpoints_gen_test.go create mode 100644 internal/service/emr/service_endpoints_gen_test.go create mode 100644 internal/service/emrserverless/service_endpoints_gen_test.go create mode 100644 internal/service/finspace/service_endpoints_gen_test.go create mode 100644 internal/service/firehose/service_endpoints_gen_test.go create mode 100644 internal/service/fis/service_endpoints_gen_test.go create mode 100644 internal/service/glacier/service_endpoints_gen_test.go create mode 100644 internal/service/groundstation/service_endpoints_gen_test.go create mode 100644 internal/service/healthlake/service_endpoints_gen_test.go create mode 100644 internal/service/identitystore/service_endpoints_gen_test.go create mode 100644 internal/service/internetmonitor/service_endpoints_gen_test.go create mode 100644 internal/service/ivschat/service_endpoints_gen_test.go create mode 100644 internal/service/kendra/service_endpoints_gen_test.go create mode 100644 internal/service/keyspaces/service_endpoints_gen_test.go create mode 100644 internal/service/kinesis/service_endpoints_gen_test.go create mode 100644 internal/service/lambda/service_endpoints_gen_test.go create mode 100644 internal/service/launchwizard/service_endpoints_gen_test.go create mode 100644 internal/service/lightsail/service_endpoints_gen_test.go create mode 100644 internal/service/lookoutmetrics/service_endpoints_gen_test.go create mode 100644 internal/service/m2/service_endpoints_gen_test.go create mode 100644 internal/service/mediaconnect/service_endpoints_gen_test.go create mode 100644 internal/service/medialive/service_endpoints_gen_test.go create mode 100644 internal/service/mediapackage/service_endpoints_gen_test.go create mode 100644 internal/service/mediapackagev2/service_endpoints_gen_test.go create mode 100644 internal/service/mq/service_endpoints_gen_test.go create mode 100644 internal/service/opensearchserverless/service_endpoints_gen_test.go create mode 100644 internal/service/pcaconnectorad/service_endpoints_gen_test.go create mode 100644 internal/service/pipes/service_endpoints_gen_test.go create mode 100644 internal/service/polly/service_endpoints_gen_test.go create mode 100644 internal/service/pricing/service_endpoints_gen_test.go create mode 100644 internal/service/qbusiness/service_endpoints_gen_test.go create mode 100644 internal/service/qldb/service_endpoints_gen_test.go create mode 100644 internal/service/rds/service_endpoints_gen_test.go create mode 100644 internal/service/rekognition/service_endpoints_gen_test.go create mode 100644 internal/service/resourceexplorer2/service_endpoints_gen_test.go create mode 100644 internal/service/resourcegroups/service_endpoints_gen_test.go create mode 100644 internal/service/rolesanywhere/service_endpoints_gen_test.go create mode 100644 internal/service/route53domains/service_endpoints_gen_test.go create mode 100644 internal/service/scheduler/service_endpoints_gen_test.go create mode 100644 internal/service/secretsmanager/service_endpoints_gen_test.go create mode 100644 internal/service/securityhub/service_endpoints_gen_test.go create mode 100644 internal/service/securitylake/service_endpoints_gen_test.go create mode 100644 internal/service/servicequotas/service_endpoints_gen_test.go create mode 100644 internal/service/sesv2/service_endpoints_gen_test.go create mode 100644 internal/service/signer/service_endpoints_gen_test.go create mode 100644 internal/service/sns/service_endpoints_gen_test.go create mode 100644 internal/service/sqs/service_endpoints_gen_test.go create mode 100644 internal/service/ssm/service_endpoints_gen_test.go create mode 100644 internal/service/ssmcontacts/service_endpoints_gen_test.go create mode 100644 internal/service/ssmincidents/service_endpoints_gen_test.go create mode 100644 internal/service/ssmsap/service_endpoints_gen_test.go create mode 100644 internal/service/ssoadmin/service_endpoints_gen_test.go create mode 100644 internal/service/swf/service_endpoints_gen_test.go create mode 100644 internal/service/verifiedpermissions/service_endpoints_gen_test.go create mode 100644 internal/service/vpclattice/service_endpoints_gen_test.go create mode 100644 internal/service/wellarchitected/service_endpoints_gen_test.go create mode 100644 internal/service/workspaces/service_endpoints_gen_test.go create mode 100644 internal/service/xray/service_endpoints_gen_test.go diff --git a/internal/generate/serviceendpointtests/README.md b/internal/generate/serviceendpointtests/README.md new file mode 100644 index 00000000000..45aecfbc859 --- /dev/null +++ b/internal/generate/serviceendpointtests/README.md @@ -0,0 +1,3 @@ +# serviceendpointtests + +The `serviceendpointtests` generator creates tests for endpoint configuration precedence. diff --git a/internal/generate/serviceendpointtests/file.tmpl b/internal/generate/serviceendpointtests/file.tmpl new file mode 100644 index 00000000000..f8cbb8399e7 --- /dev/null +++ b/internal/generate/serviceendpointtests/file.tmpl @@ -0,0 +1,496 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package {{ .PackageName }}_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + {{ .GoV2Package }}_sdkv2 "github.com/aws/aws-sdk-go-v2/service/{{ .GoV2Package }}" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "{{ .PackageName }}" + awsEnvVar = "{{ .AwsEnvVar }}" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "{{ .ConfigParameter }}" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "{{ .Region }}" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := {{ .GoV2Package }}_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), {{ .GoV2Package }}_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.{{ .ProviderNameUpper }}Client(ctx) + + _, err := client.{{ .APICall }}(ctx, &{{ .GoV2Package }}_sdkv2.{{ .APICall }}Input{ + {{ if ne .APICallParams "" }}{{ .APICallParams }},{{ end }} + }, + func(opts *{{ .GoV2Package }}_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/generate/serviceendpointtests/generate.go b/internal/generate/serviceendpointtests/generate.go new file mode 100644 index 00000000000..b6317211c4f --- /dev/null +++ b/internal/generate/serviceendpointtests/generate.go @@ -0,0 +1,7 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:generate go run main.go +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package serviceendpointtests diff --git a/internal/generate/serviceendpointtests/main.go b/internal/generate/serviceendpointtests/main.go new file mode 100644 index 00000000000..4c0f1db1579 --- /dev/null +++ b/internal/generate/serviceendpointtests/main.go @@ -0,0 +1,99 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build generate +// +build generate + +package main + +import ( + _ "embed" + "path/filepath" + + "github.com/hashicorp/terraform-provider-aws/internal/generate/common" + "github.com/hashicorp/terraform-provider-aws/names/data" +) + +const ( + relativePath = `../../service` + filename = `service_endpoints_gen_test.go` +) + +func main() { + g := common.NewGenerator() + + services, err := data.ReadAllServiceData() + + if err != nil { + g.Fatalf("error reading service data: %s", err) + } + + for _, l := range services { + packageName := l.ProviderPackage() + + switch packageName { + case "codecatalyst", // Bearer auth token needs special handling + "s3control", // Resolver modifies URL + "timestreamwrite": // Use endpoint discovery + continue + } + + if !l.ClientSDKV2() { + continue + } + + if len(l.Aliases()) > 0 { + continue + } + + if l.DeprecatedEnvVar() != "" || l.TfAwsEnvVar() != "" { + continue + } + + g.Infof("Generating internal/service/%s/%s", packageName, filename) + + td := TemplateData{ + PackageName: packageName, + GoV2Package: l.GoV2Package(), + ProviderNameUpper: l.ProviderNameUpper(), + Region: "us-west-2", + APICall: l.EndpointAPICall(), + APICallParams: l.EndpointAPIParams(), + AwsEnvVar: l.AwsServiceEnvVar(), + ConfigParameter: l.AwsConfigParameter(), + } + + switch packageName { + case "route53domains": + td.Region = "us-east-1" + } + + if td.APICall == "" { + td.APICall = "PLACEHOLDER" + } + + d := g.NewGoFileDestination(filepath.Join(relativePath, packageName, filename)) + + if err := d.WriteTemplate("serviceendpointtests", tmpl, td); err != nil { + g.Fatalf("error generating service endpoint tests: %s", err) + } + + if err := d.Write(); err != nil { + g.Fatalf("generating file (internal/service/%s/%s): %s", packageName, filename, err) + } + } +} + +type TemplateData struct { + PackageName string + GoV2Package string + ProviderNameUpper string + Region string + APICall string + APICallParams string + AwsEnvVar string + ConfigParameter string +} + +//go:embed file.tmpl +var tmpl string diff --git a/internal/service/accessanalyzer/service_endpoints_gen_test.go b/internal/service/accessanalyzer/service_endpoints_gen_test.go index 10aa07dd0b7..1b1f56c3d4f 100644 --- a/internal/service/accessanalyzer/service_endpoints_gen_test.go +++ b/internal/service/accessanalyzer/service_endpoints_gen_test.go @@ -1,18 +1,19 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + package accessanalyzer_test import ( "context" "errors" "fmt" - "maps" "os" "path/filepath" "reflect" "strings" "testing" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/accessanalyzer" + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + accessanalyzer_sdkv2 "github.com/aws/aws-sdk-go-v2/service/accessanalyzer" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/google/go-cmp/cmp" @@ -23,6 +24,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" ) type endpointTestCase struct { @@ -208,10 +210,10 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S } func defaultEndpoint(region string) string { - r := accessanalyzer.NewDefaultEndpointResolverV2() + r := accessanalyzer_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), accessanalyzer.EndpointParameters{ - Region: aws.String(region), + ep, err := r.ResolveEndpoint(context.TODO(), accessanalyzer_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), }) if err != nil { return err.Error() @@ -231,8 +233,8 @@ func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) strin client := meta.AccessAnalyzerClient(ctx) - _, err := client.ListAnalyzers(ctx, &accessanalyzer.ListAnalyzersInput{}, - func(opts *accessanalyzer.Options) { + _, err := client.ListAnalyzers(ctx, &accessanalyzer_sdkv2.ListAnalyzersInput{}, + func(opts *accessanalyzer_sdkv2.Options) { opts.APIOptions = append(opts.APIOptions, addRetrieveEndpointURLMiddleware(t, &endpoint), addCancelRequestMiddleware(), diff --git a/internal/service/account/service_endpoints_gen_test.go b/internal/service/account/service_endpoints_gen_test.go new file mode 100644 index 00000000000..85ddbb049e3 --- /dev/null +++ b/internal/service/account/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package account_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + account_sdkv2 "github.com/aws/aws-sdk-go-v2/service/account" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "account" + awsEnvVar = "AWS_ENDPOINT_URL_ACCOUNT" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "account" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := account_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), account_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.AccountClient(ctx) + + _, err := client.ListRegions(ctx, &account_sdkv2.ListRegionsInput{}, + func(opts *account_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/acm/service_endpoints_gen_test.go b/internal/service/acm/service_endpoints_gen_test.go new file mode 100644 index 00000000000..957c5a9852a --- /dev/null +++ b/internal/service/acm/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package acm_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + acm_sdkv2 "github.com/aws/aws-sdk-go-v2/service/acm" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "acm" + awsEnvVar = "AWS_ENDPOINT_URL_ACM" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "acm" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := acm_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), acm_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ACMClient(ctx) + + _, err := client.ListCertificates(ctx, &acm_sdkv2.ListCertificatesInput{}, + func(opts *acm_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/appconfig/service_endpoints_gen_test.go b/internal/service/appconfig/service_endpoints_gen_test.go new file mode 100644 index 00000000000..f0aa82b1875 --- /dev/null +++ b/internal/service/appconfig/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package appconfig_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + appconfig_sdkv2 "github.com/aws/aws-sdk-go-v2/service/appconfig" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "appconfig" + awsEnvVar = "AWS_ENDPOINT_URL_APPCONFIG" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "appconfig" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := appconfig_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), appconfig_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.AppConfigClient(ctx) + + _, err := client.ListApplications(ctx, &appconfig_sdkv2.ListApplicationsInput{}, + func(opts *appconfig_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/appfabric/service_endpoints_gen_test.go b/internal/service/appfabric/service_endpoints_gen_test.go new file mode 100644 index 00000000000..30fffe1c468 --- /dev/null +++ b/internal/service/appfabric/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package appfabric_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + appfabric_sdkv2 "github.com/aws/aws-sdk-go-v2/service/appfabric" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "appfabric" + awsEnvVar = "AWS_ENDPOINT_URL_APPFABRIC" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "appfabric" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := appfabric_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), appfabric_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.AppFabricClient(ctx) + + _, err := client.ListAppBundles(ctx, &appfabric_sdkv2.ListAppBundlesInput{}, + func(opts *appfabric_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/appflow/service_endpoints_gen_test.go b/internal/service/appflow/service_endpoints_gen_test.go new file mode 100644 index 00000000000..8be8966dabe --- /dev/null +++ b/internal/service/appflow/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package appflow_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + appflow_sdkv2 "github.com/aws/aws-sdk-go-v2/service/appflow" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "appflow" + awsEnvVar = "AWS_ENDPOINT_URL_APPFLOW" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "appflow" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := appflow_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), appflow_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.AppFlowClient(ctx) + + _, err := client.ListFlows(ctx, &appflow_sdkv2.ListFlowsInput{}, + func(opts *appflow_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/apprunner/service_endpoints_gen_test.go b/internal/service/apprunner/service_endpoints_gen_test.go new file mode 100644 index 00000000000..b84eb859099 --- /dev/null +++ b/internal/service/apprunner/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package apprunner_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + apprunner_sdkv2 "github.com/aws/aws-sdk-go-v2/service/apprunner" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "apprunner" + awsEnvVar = "AWS_ENDPOINT_URL_APPRUNNER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "apprunner" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := apprunner_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), apprunner_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.AppRunnerClient(ctx) + + _, err := client.ListConnections(ctx, &apprunner_sdkv2.ListConnectionsInput{}, + func(opts *apprunner_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/athena/service_endpoints_gen_test.go b/internal/service/athena/service_endpoints_gen_test.go new file mode 100644 index 00000000000..707deabdc9b --- /dev/null +++ b/internal/service/athena/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package athena_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + athena_sdkv2 "github.com/aws/aws-sdk-go-v2/service/athena" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "athena" + awsEnvVar = "AWS_ENDPOINT_URL_ATHENA" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "athena" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := athena_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), athena_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.AthenaClient(ctx) + + _, err := client.ListDataCatalogs(ctx, &athena_sdkv2.ListDataCatalogsInput{}, + func(opts *athena_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/auditmanager/service_endpoints_gen_test.go b/internal/service/auditmanager/service_endpoints_gen_test.go new file mode 100644 index 00000000000..dd9555dcabe --- /dev/null +++ b/internal/service/auditmanager/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package auditmanager_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + auditmanager_sdkv2 "github.com/aws/aws-sdk-go-v2/service/auditmanager" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "auditmanager" + awsEnvVar = "AWS_ENDPOINT_URL_AUDITMANAGER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "auditmanager" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := auditmanager_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), auditmanager_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.AuditManagerClient(ctx) + + _, err := client.GetAccountStatus(ctx, &auditmanager_sdkv2.GetAccountStatusInput{}, + func(opts *auditmanager_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/bedrock/service_endpoints_gen_test.go b/internal/service/bedrock/service_endpoints_gen_test.go new file mode 100644 index 00000000000..e4f4713b3bc --- /dev/null +++ b/internal/service/bedrock/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package bedrock_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + bedrock_sdkv2 "github.com/aws/aws-sdk-go-v2/service/bedrock" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "bedrock" + awsEnvVar = "AWS_ENDPOINT_URL_BEDROCK" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "bedrock" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := bedrock_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), bedrock_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.BedrockClient(ctx) + + _, err := client.ListFoundationModels(ctx, &bedrock_sdkv2.ListFoundationModelsInput{}, + func(opts *bedrock_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/chimesdkmediapipelines/service_endpoints_gen_test.go b/internal/service/chimesdkmediapipelines/service_endpoints_gen_test.go new file mode 100644 index 00000000000..d6366cff0a2 --- /dev/null +++ b/internal/service/chimesdkmediapipelines/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package chimesdkmediapipelines_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + chimesdkmediapipelines_sdkv2 "github.com/aws/aws-sdk-go-v2/service/chimesdkmediapipelines" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "chimesdkmediapipelines" + awsEnvVar = "AWS_ENDPOINT_URL_CHIME_SDK_MEDIA_PIPELINES" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "chime_sdk_media_pipelines" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := chimesdkmediapipelines_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), chimesdkmediapipelines_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ChimeSDKMediaPipelinesClient(ctx) + + _, err := client.ListMediaPipelines(ctx, &chimesdkmediapipelines_sdkv2.ListMediaPipelinesInput{}, + func(opts *chimesdkmediapipelines_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/chimesdkvoice/service_endpoints_gen_test.go b/internal/service/chimesdkvoice/service_endpoints_gen_test.go new file mode 100644 index 00000000000..cfd13fa05e3 --- /dev/null +++ b/internal/service/chimesdkvoice/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package chimesdkvoice_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + chimesdkvoice_sdkv2 "github.com/aws/aws-sdk-go-v2/service/chimesdkvoice" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "chimesdkvoice" + awsEnvVar = "AWS_ENDPOINT_URL_CHIME_SDK_VOICE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "chime_sdk_voice" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := chimesdkvoice_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), chimesdkvoice_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ChimeSDKVoiceClient(ctx) + + _, err := client.ListPhoneNumbers(ctx, &chimesdkvoice_sdkv2.ListPhoneNumbersInput{}, + func(opts *chimesdkvoice_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/cleanrooms/service_endpoints_gen_test.go b/internal/service/cleanrooms/service_endpoints_gen_test.go new file mode 100644 index 00000000000..01dc1b2b0c9 --- /dev/null +++ b/internal/service/cleanrooms/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package cleanrooms_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + cleanrooms_sdkv2 "github.com/aws/aws-sdk-go-v2/service/cleanrooms" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "cleanrooms" + awsEnvVar = "AWS_ENDPOINT_URL_CLEANROOMS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "cleanrooms" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := cleanrooms_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), cleanrooms_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.CleanRoomsClient(ctx) + + _, err := client.ListCollaborations(ctx, &cleanrooms_sdkv2.ListCollaborationsInput{}, + func(opts *cleanrooms_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/codeguruprofiler/service_endpoints_gen_test.go b/internal/service/codeguruprofiler/service_endpoints_gen_test.go new file mode 100644 index 00000000000..15a8680d124 --- /dev/null +++ b/internal/service/codeguruprofiler/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package codeguruprofiler_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + codeguruprofiler_sdkv2 "github.com/aws/aws-sdk-go-v2/service/codeguruprofiler" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "codeguruprofiler" + awsEnvVar = "AWS_ENDPOINT_URL_CODEGURUPROFILER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "codeguruprofiler" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := codeguruprofiler_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), codeguruprofiler_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.CodeGuruProfilerClient(ctx) + + _, err := client.ListProfilingGroups(ctx, &codeguruprofiler_sdkv2.ListProfilingGroupsInput{}, + func(opts *codeguruprofiler_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/codepipeline/service_endpoints_gen_test.go b/internal/service/codepipeline/service_endpoints_gen_test.go new file mode 100644 index 00000000000..ec645be414e --- /dev/null +++ b/internal/service/codepipeline/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package codepipeline_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + codepipeline_sdkv2 "github.com/aws/aws-sdk-go-v2/service/codepipeline" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "codepipeline" + awsEnvVar = "AWS_ENDPOINT_URL_CODEPIPELINE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "codepipeline" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := codepipeline_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), codepipeline_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.CodePipelineClient(ctx) + + _, err := client.ListPipelines(ctx, &codepipeline_sdkv2.ListPipelinesInput{}, + func(opts *codepipeline_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/codestarconnections/service_endpoints_gen_test.go b/internal/service/codestarconnections/service_endpoints_gen_test.go new file mode 100644 index 00000000000..7c241ef11c4 --- /dev/null +++ b/internal/service/codestarconnections/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package codestarconnections_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + codestarconnections_sdkv2 "github.com/aws/aws-sdk-go-v2/service/codestarconnections" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "codestarconnections" + awsEnvVar = "AWS_ENDPOINT_URL_CODESTAR_CONNECTIONS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "codestar_connections" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := codestarconnections_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), codestarconnections_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.CodeStarConnectionsClient(ctx) + + _, err := client.ListConnections(ctx, &codestarconnections_sdkv2.ListConnectionsInput{}, + func(opts *codestarconnections_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/codestarnotifications/service_endpoints_gen_test.go b/internal/service/codestarnotifications/service_endpoints_gen_test.go new file mode 100644 index 00000000000..e393acdaac5 --- /dev/null +++ b/internal/service/codestarnotifications/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package codestarnotifications_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + codestarnotifications_sdkv2 "github.com/aws/aws-sdk-go-v2/service/codestarnotifications" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "codestarnotifications" + awsEnvVar = "AWS_ENDPOINT_URL_CODESTAR_NOTIFICATIONS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "codestar_notifications" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := codestarnotifications_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), codestarnotifications_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.CodeStarNotificationsClient(ctx) + + _, err := client.ListTargets(ctx, &codestarnotifications_sdkv2.ListTargetsInput{}, + func(opts *codestarnotifications_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/comprehend/service_endpoints_gen_test.go b/internal/service/comprehend/service_endpoints_gen_test.go new file mode 100644 index 00000000000..687066cae7f --- /dev/null +++ b/internal/service/comprehend/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package comprehend_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + comprehend_sdkv2 "github.com/aws/aws-sdk-go-v2/service/comprehend" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "comprehend" + awsEnvVar = "AWS_ENDPOINT_URL_COMPREHEND" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "comprehend" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := comprehend_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), comprehend_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ComprehendClient(ctx) + + _, err := client.ListDocumentClassifiers(ctx, &comprehend_sdkv2.ListDocumentClassifiersInput{}, + func(opts *comprehend_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/computeoptimizer/service_endpoints_gen_test.go b/internal/service/computeoptimizer/service_endpoints_gen_test.go new file mode 100644 index 00000000000..33facc8914f --- /dev/null +++ b/internal/service/computeoptimizer/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package computeoptimizer_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + computeoptimizer_sdkv2 "github.com/aws/aws-sdk-go-v2/service/computeoptimizer" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "computeoptimizer" + awsEnvVar = "AWS_ENDPOINT_URL_COMPUTE_OPTIMIZER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "compute_optimizer" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := computeoptimizer_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), computeoptimizer_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ComputeOptimizerClient(ctx) + + _, err := client.GetEnrollmentStatus(ctx, &computeoptimizer_sdkv2.GetEnrollmentStatusInput{}, + func(opts *computeoptimizer_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/connectcases/service_endpoints_gen_test.go b/internal/service/connectcases/service_endpoints_gen_test.go new file mode 100644 index 00000000000..6768ab69b83 --- /dev/null +++ b/internal/service/connectcases/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package connectcases_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + connectcases_sdkv2 "github.com/aws/aws-sdk-go-v2/service/connectcases" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "connectcases" + awsEnvVar = "AWS_ENDPOINT_URL_CONNECTCASES" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "connectcases" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := connectcases_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), connectcases_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ConnectCasesClient(ctx) + + _, err := client.ListDomains(ctx, &connectcases_sdkv2.ListDomainsInput{}, + func(opts *connectcases_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/controltower/service_endpoints_gen_test.go b/internal/service/controltower/service_endpoints_gen_test.go new file mode 100644 index 00000000000..451a3639922 --- /dev/null +++ b/internal/service/controltower/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package controltower_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + controltower_sdkv2 "github.com/aws/aws-sdk-go-v2/service/controltower" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "controltower" + awsEnvVar = "AWS_ENDPOINT_URL_CONTROLTOWER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "controltower" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := controltower_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), controltower_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ControlTowerClient(ctx) + + _, err := client.ListLandingZones(ctx, &controltower_sdkv2.ListLandingZonesInput{}, + func(opts *controltower_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/customerprofiles/service_endpoints_gen_test.go b/internal/service/customerprofiles/service_endpoints_gen_test.go new file mode 100644 index 00000000000..e97bb9ea6cf --- /dev/null +++ b/internal/service/customerprofiles/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package customerprofiles_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + customerprofiles_sdkv2 "github.com/aws/aws-sdk-go-v2/service/customerprofiles" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "customerprofiles" + awsEnvVar = "AWS_ENDPOINT_URL_CUSTOMER_PROFILES" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "customer_profiles" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := customerprofiles_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), customerprofiles_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.CustomerProfilesClient(ctx) + + _, err := client.ListDomains(ctx, &customerprofiles_sdkv2.ListDomainsInput{}, + func(opts *customerprofiles_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/docdbelastic/service_endpoints_gen_test.go b/internal/service/docdbelastic/service_endpoints_gen_test.go new file mode 100644 index 00000000000..354074f77f2 --- /dev/null +++ b/internal/service/docdbelastic/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package docdbelastic_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + docdbelastic_sdkv2 "github.com/aws/aws-sdk-go-v2/service/docdbelastic" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "docdbelastic" + awsEnvVar = "AWS_ENDPOINT_URL_DOCDB_ELASTIC" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "docdb_elastic" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := docdbelastic_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), docdbelastic_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.DocDBElasticClient(ctx) + + _, err := client.ListClusters(ctx, &docdbelastic_sdkv2.ListClustersInput{}, + func(opts *docdbelastic_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/ec2/service_endpoints_gen_test.go b/internal/service/ec2/service_endpoints_gen_test.go new file mode 100644 index 00000000000..b5233d5a111 --- /dev/null +++ b/internal/service/ec2/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package ec2_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + ec2_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "ec2" + awsEnvVar = "AWS_ENDPOINT_URL_EC2" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "ec2" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := ec2_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), ec2_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.EC2Client(ctx) + + _, err := client.DescribeVpcs(ctx, &ec2_sdkv2.DescribeVpcsInput{}, + func(opts *ec2_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/ecr/service_endpoints_gen_test.go b/internal/service/ecr/service_endpoints_gen_test.go new file mode 100644 index 00000000000..b6ddaef6a5c --- /dev/null +++ b/internal/service/ecr/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package ecr_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + ecr_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ecr" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "ecr" + awsEnvVar = "AWS_ENDPOINT_URL_ECR" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "ecr" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := ecr_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), ecr_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ECRClient(ctx) + + _, err := client.DescribeRepositories(ctx, &ecr_sdkv2.DescribeRepositoriesInput{}, + func(opts *ecr_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/eks/service_endpoints_gen_test.go b/internal/service/eks/service_endpoints_gen_test.go new file mode 100644 index 00000000000..892c28ddb96 --- /dev/null +++ b/internal/service/eks/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package eks_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + eks_sdkv2 "github.com/aws/aws-sdk-go-v2/service/eks" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "eks" + awsEnvVar = "AWS_ENDPOINT_URL_EKS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "eks" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := eks_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), eks_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.EKSClient(ctx) + + _, err := client.ListClusters(ctx, &eks_sdkv2.ListClustersInput{}, + func(opts *eks_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/elasticache/service_endpoints_gen_test.go b/internal/service/elasticache/service_endpoints_gen_test.go new file mode 100644 index 00000000000..9e344ad7700 --- /dev/null +++ b/internal/service/elasticache/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package elasticache_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + elasticache_sdkv2 "github.com/aws/aws-sdk-go-v2/service/elasticache" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "elasticache" + awsEnvVar = "AWS_ENDPOINT_URL_ELASTICACHE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "elasticache" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := elasticache_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), elasticache_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ElastiCacheClient(ctx) + + _, err := client.DescribeCacheClusters(ctx, &elasticache_sdkv2.DescribeCacheClustersInput{}, + func(opts *elasticache_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/emr/service_endpoints_gen_test.go b/internal/service/emr/service_endpoints_gen_test.go new file mode 100644 index 00000000000..29e02e7c6db --- /dev/null +++ b/internal/service/emr/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package emr_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + emr_sdkv2 "github.com/aws/aws-sdk-go-v2/service/emr" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "emr" + awsEnvVar = "AWS_ENDPOINT_URL_EMR" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "emr" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := emr_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), emr_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.EMRClient(ctx) + + _, err := client.ListClusters(ctx, &emr_sdkv2.ListClustersInput{}, + func(opts *emr_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/emrserverless/service_endpoints_gen_test.go b/internal/service/emrserverless/service_endpoints_gen_test.go new file mode 100644 index 00000000000..c1bb13867ce --- /dev/null +++ b/internal/service/emrserverless/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package emrserverless_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + emrserverless_sdkv2 "github.com/aws/aws-sdk-go-v2/service/emrserverless" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "emrserverless" + awsEnvVar = "AWS_ENDPOINT_URL_EMR_SERVERLESS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "emr_serverless" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := emrserverless_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), emrserverless_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.EMRServerlessClient(ctx) + + _, err := client.ListApplications(ctx, &emrserverless_sdkv2.ListApplicationsInput{}, + func(opts *emrserverless_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/finspace/service_endpoints_gen_test.go b/internal/service/finspace/service_endpoints_gen_test.go new file mode 100644 index 00000000000..dff80f60b24 --- /dev/null +++ b/internal/service/finspace/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package finspace_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + finspace_sdkv2 "github.com/aws/aws-sdk-go-v2/service/finspace" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "finspace" + awsEnvVar = "AWS_ENDPOINT_URL_FINSPACE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "finspace" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := finspace_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), finspace_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.FinSpaceClient(ctx) + + _, err := client.ListEnvironments(ctx, &finspace_sdkv2.ListEnvironmentsInput{}, + func(opts *finspace_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/firehose/service_endpoints_gen_test.go b/internal/service/firehose/service_endpoints_gen_test.go new file mode 100644 index 00000000000..6130f81d3ce --- /dev/null +++ b/internal/service/firehose/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package firehose_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + firehose_sdkv2 "github.com/aws/aws-sdk-go-v2/service/firehose" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "firehose" + awsEnvVar = "AWS_ENDPOINT_URL_FIREHOSE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "firehose" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := firehose_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), firehose_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.FirehoseClient(ctx) + + _, err := client.ListDeliveryStreams(ctx, &firehose_sdkv2.ListDeliveryStreamsInput{}, + func(opts *firehose_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/fis/service_endpoints_gen_test.go b/internal/service/fis/service_endpoints_gen_test.go new file mode 100644 index 00000000000..53c66d1141e --- /dev/null +++ b/internal/service/fis/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package fis_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + fis_sdkv2 "github.com/aws/aws-sdk-go-v2/service/fis" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "fis" + awsEnvVar = "AWS_ENDPOINT_URL_FIS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "fis" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := fis_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), fis_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.FISClient(ctx) + + _, err := client.ListExperiments(ctx, &fis_sdkv2.ListExperimentsInput{}, + func(opts *fis_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/glacier/service_endpoints_gen_test.go b/internal/service/glacier/service_endpoints_gen_test.go new file mode 100644 index 00000000000..2ff375daa84 --- /dev/null +++ b/internal/service/glacier/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package glacier_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + glacier_sdkv2 "github.com/aws/aws-sdk-go-v2/service/glacier" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "glacier" + awsEnvVar = "AWS_ENDPOINT_URL_GLACIER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "glacier" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := glacier_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), glacier_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.GlacierClient(ctx) + + _, err := client.ListVaults(ctx, &glacier_sdkv2.ListVaultsInput{}, + func(opts *glacier_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/groundstation/service_endpoints_gen_test.go b/internal/service/groundstation/service_endpoints_gen_test.go new file mode 100644 index 00000000000..b6b72f627a2 --- /dev/null +++ b/internal/service/groundstation/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package groundstation_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + groundstation_sdkv2 "github.com/aws/aws-sdk-go-v2/service/groundstation" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "groundstation" + awsEnvVar = "AWS_ENDPOINT_URL_GROUNDSTATION" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "groundstation" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := groundstation_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), groundstation_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.GroundStationClient(ctx) + + _, err := client.ListConfigs(ctx, &groundstation_sdkv2.ListConfigsInput{}, + func(opts *groundstation_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/healthlake/service_endpoints_gen_test.go b/internal/service/healthlake/service_endpoints_gen_test.go new file mode 100644 index 00000000000..d87cfd21436 --- /dev/null +++ b/internal/service/healthlake/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package healthlake_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + healthlake_sdkv2 "github.com/aws/aws-sdk-go-v2/service/healthlake" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "healthlake" + awsEnvVar = "AWS_ENDPOINT_URL_HEALTHLAKE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "healthlake" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := healthlake_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), healthlake_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.HealthLakeClient(ctx) + + _, err := client.ListFHIRDatastores(ctx, &healthlake_sdkv2.ListFHIRDatastoresInput{}, + func(opts *healthlake_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/identitystore/service_endpoints_gen_test.go b/internal/service/identitystore/service_endpoints_gen_test.go new file mode 100644 index 00000000000..2f7324e3268 --- /dev/null +++ b/internal/service/identitystore/service_endpoints_gen_test.go @@ -0,0 +1,496 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package identitystore_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + identitystore_sdkv2 "github.com/aws/aws-sdk-go-v2/service/identitystore" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "identitystore" + awsEnvVar = "AWS_ENDPOINT_URL_IDENTITYSTORE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "identitystore" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := identitystore_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), identitystore_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.IdentityStoreClient(ctx) + + _, err := client.ListUsers(ctx, &identitystore_sdkv2.ListUsersInput{ + IdentityStoreId: aws_sdkv2.String("d-1234567890"), + }, + func(opts *identitystore_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/internetmonitor/service_endpoints_gen_test.go b/internal/service/internetmonitor/service_endpoints_gen_test.go new file mode 100644 index 00000000000..aa94e96c963 --- /dev/null +++ b/internal/service/internetmonitor/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package internetmonitor_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + internetmonitor_sdkv2 "github.com/aws/aws-sdk-go-v2/service/internetmonitor" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "internetmonitor" + awsEnvVar = "AWS_ENDPOINT_URL_INTERNETMONITOR" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "internetmonitor" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := internetmonitor_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), internetmonitor_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.InternetMonitorClient(ctx) + + _, err := client.ListMonitors(ctx, &internetmonitor_sdkv2.ListMonitorsInput{}, + func(opts *internetmonitor_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/ivschat/service_endpoints_gen_test.go b/internal/service/ivschat/service_endpoints_gen_test.go new file mode 100644 index 00000000000..33dfd797ccf --- /dev/null +++ b/internal/service/ivschat/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package ivschat_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + ivschat_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ivschat" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "ivschat" + awsEnvVar = "AWS_ENDPOINT_URL_IVSCHAT" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "ivschat" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := ivschat_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), ivschat_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.IVSChatClient(ctx) + + _, err := client.ListRooms(ctx, &ivschat_sdkv2.ListRoomsInput{}, + func(opts *ivschat_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/kendra/service_endpoints_gen_test.go b/internal/service/kendra/service_endpoints_gen_test.go new file mode 100644 index 00000000000..9f37483a94d --- /dev/null +++ b/internal/service/kendra/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package kendra_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + kendra_sdkv2 "github.com/aws/aws-sdk-go-v2/service/kendra" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "kendra" + awsEnvVar = "AWS_ENDPOINT_URL_KENDRA" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "kendra" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := kendra_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), kendra_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.KendraClient(ctx) + + _, err := client.ListIndices(ctx, &kendra_sdkv2.ListIndicesInput{}, + func(opts *kendra_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/keyspaces/service_endpoints_gen_test.go b/internal/service/keyspaces/service_endpoints_gen_test.go new file mode 100644 index 00000000000..85d88b819ef --- /dev/null +++ b/internal/service/keyspaces/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package keyspaces_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + keyspaces_sdkv2 "github.com/aws/aws-sdk-go-v2/service/keyspaces" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "keyspaces" + awsEnvVar = "AWS_ENDPOINT_URL_KEYSPACES" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "keyspaces" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := keyspaces_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), keyspaces_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.KeyspacesClient(ctx) + + _, err := client.ListKeyspaces(ctx, &keyspaces_sdkv2.ListKeyspacesInput{}, + func(opts *keyspaces_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/kinesis/service_endpoints_gen_test.go b/internal/service/kinesis/service_endpoints_gen_test.go new file mode 100644 index 00000000000..979e89b2216 --- /dev/null +++ b/internal/service/kinesis/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package kinesis_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + kinesis_sdkv2 "github.com/aws/aws-sdk-go-v2/service/kinesis" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "kinesis" + awsEnvVar = "AWS_ENDPOINT_URL_KINESIS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "kinesis" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := kinesis_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), kinesis_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.KinesisClient(ctx) + + _, err := client.ListStreams(ctx, &kinesis_sdkv2.ListStreamsInput{}, + func(opts *kinesis_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/lambda/service_endpoints_gen_test.go b/internal/service/lambda/service_endpoints_gen_test.go new file mode 100644 index 00000000000..c3ba796a384 --- /dev/null +++ b/internal/service/lambda/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package lambda_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + lambda_sdkv2 "github.com/aws/aws-sdk-go-v2/service/lambda" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "lambda" + awsEnvVar = "AWS_ENDPOINT_URL_LAMBDA" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "lambda" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := lambda_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), lambda_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.LambdaClient(ctx) + + _, err := client.ListFunctions(ctx, &lambda_sdkv2.ListFunctionsInput{}, + func(opts *lambda_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/launchwizard/service_endpoints_gen_test.go b/internal/service/launchwizard/service_endpoints_gen_test.go new file mode 100644 index 00000000000..69c207199ee --- /dev/null +++ b/internal/service/launchwizard/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package launchwizard_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + launchwizard_sdkv2 "github.com/aws/aws-sdk-go-v2/service/launchwizard" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "launchwizard" + awsEnvVar = "AWS_ENDPOINT_URL_LAUNCH_WIZARD" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "launch_wizard" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := launchwizard_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), launchwizard_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.LaunchWizardClient(ctx) + + _, err := client.ListWorkloads(ctx, &launchwizard_sdkv2.ListWorkloadsInput{}, + func(opts *launchwizard_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/lightsail/service_endpoints_gen_test.go b/internal/service/lightsail/service_endpoints_gen_test.go new file mode 100644 index 00000000000..20fe088126b --- /dev/null +++ b/internal/service/lightsail/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package lightsail_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + lightsail_sdkv2 "github.com/aws/aws-sdk-go-v2/service/lightsail" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "lightsail" + awsEnvVar = "AWS_ENDPOINT_URL_LIGHTSAIL" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "lightsail" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := lightsail_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), lightsail_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.LightsailClient(ctx) + + _, err := client.GetInstances(ctx, &lightsail_sdkv2.GetInstancesInput{}, + func(opts *lightsail_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/lookoutmetrics/service_endpoints_gen_test.go b/internal/service/lookoutmetrics/service_endpoints_gen_test.go new file mode 100644 index 00000000000..9d2332438c4 --- /dev/null +++ b/internal/service/lookoutmetrics/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package lookoutmetrics_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + lookoutmetrics_sdkv2 "github.com/aws/aws-sdk-go-v2/service/lookoutmetrics" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "lookoutmetrics" + awsEnvVar = "AWS_ENDPOINT_URL_LOOKOUTMETRICS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "lookoutmetrics" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := lookoutmetrics_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), lookoutmetrics_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.LookoutMetricsClient(ctx) + + _, err := client.ListMetricSets(ctx, &lookoutmetrics_sdkv2.ListMetricSetsInput{}, + func(opts *lookoutmetrics_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/m2/service_endpoints_gen_test.go b/internal/service/m2/service_endpoints_gen_test.go new file mode 100644 index 00000000000..81b2666499a --- /dev/null +++ b/internal/service/m2/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package m2_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + m2_sdkv2 "github.com/aws/aws-sdk-go-v2/service/m2" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "m2" + awsEnvVar = "AWS_ENDPOINT_URL_M2" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "m2" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := m2_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), m2_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.M2Client(ctx) + + _, err := client.ListApplications(ctx, &m2_sdkv2.ListApplicationsInput{}, + func(opts *m2_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/mediaconnect/service_endpoints_gen_test.go b/internal/service/mediaconnect/service_endpoints_gen_test.go new file mode 100644 index 00000000000..2ea83dc8cd4 --- /dev/null +++ b/internal/service/mediaconnect/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package mediaconnect_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + mediaconnect_sdkv2 "github.com/aws/aws-sdk-go-v2/service/mediaconnect" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "mediaconnect" + awsEnvVar = "AWS_ENDPOINT_URL_MEDIACONNECT" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "mediaconnect" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := mediaconnect_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), mediaconnect_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.MediaConnectClient(ctx) + + _, err := client.ListBridges(ctx, &mediaconnect_sdkv2.ListBridgesInput{}, + func(opts *mediaconnect_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/medialive/service_endpoints_gen_test.go b/internal/service/medialive/service_endpoints_gen_test.go new file mode 100644 index 00000000000..ab11eed7ca7 --- /dev/null +++ b/internal/service/medialive/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package medialive_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + medialive_sdkv2 "github.com/aws/aws-sdk-go-v2/service/medialive" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "medialive" + awsEnvVar = "AWS_ENDPOINT_URL_MEDIALIVE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "medialive" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := medialive_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), medialive_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.MediaLiveClient(ctx) + + _, err := client.ListOfferings(ctx, &medialive_sdkv2.ListOfferingsInput{}, + func(opts *medialive_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/mediapackage/service_endpoints_gen_test.go b/internal/service/mediapackage/service_endpoints_gen_test.go new file mode 100644 index 00000000000..35b1256f5db --- /dev/null +++ b/internal/service/mediapackage/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package mediapackage_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + mediapackage_sdkv2 "github.com/aws/aws-sdk-go-v2/service/mediapackage" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "mediapackage" + awsEnvVar = "AWS_ENDPOINT_URL_MEDIAPACKAGE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "mediapackage" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := mediapackage_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), mediapackage_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.MediaPackageClient(ctx) + + _, err := client.ListChannels(ctx, &mediapackage_sdkv2.ListChannelsInput{}, + func(opts *mediapackage_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/mediapackagev2/service_endpoints_gen_test.go b/internal/service/mediapackagev2/service_endpoints_gen_test.go new file mode 100644 index 00000000000..5111ec027cd --- /dev/null +++ b/internal/service/mediapackagev2/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package mediapackagev2_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + mediapackagev2_sdkv2 "github.com/aws/aws-sdk-go-v2/service/mediapackagev2" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "mediapackagev2" + awsEnvVar = "AWS_ENDPOINT_URL_MEDIAPACKAGEV2" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "mediapackagev2" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := mediapackagev2_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), mediapackagev2_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.MediaPackageV2Client(ctx) + + _, err := client.ListChannelGroups(ctx, &mediapackagev2_sdkv2.ListChannelGroupsInput{}, + func(opts *mediapackagev2_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/mq/service_endpoints_gen_test.go b/internal/service/mq/service_endpoints_gen_test.go new file mode 100644 index 00000000000..03ba82669b6 --- /dev/null +++ b/internal/service/mq/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package mq_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + mq_sdkv2 "github.com/aws/aws-sdk-go-v2/service/mq" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "mq" + awsEnvVar = "AWS_ENDPOINT_URL_MQ" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "mq" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := mq_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), mq_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.MQClient(ctx) + + _, err := client.ListBrokers(ctx, &mq_sdkv2.ListBrokersInput{}, + func(opts *mq_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/opensearchserverless/service_endpoints_gen_test.go b/internal/service/opensearchserverless/service_endpoints_gen_test.go new file mode 100644 index 00000000000..4a11ab8f015 --- /dev/null +++ b/internal/service/opensearchserverless/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package opensearchserverless_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + opensearchserverless_sdkv2 "github.com/aws/aws-sdk-go-v2/service/opensearchserverless" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "opensearchserverless" + awsEnvVar = "AWS_ENDPOINT_URL_OPENSEARCHSERVERLESS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "opensearchserverless" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := opensearchserverless_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), opensearchserverless_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.OpenSearchServerlessClient(ctx) + + _, err := client.ListCollections(ctx, &opensearchserverless_sdkv2.ListCollectionsInput{}, + func(opts *opensearchserverless_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/pcaconnectorad/service_endpoints_gen_test.go b/internal/service/pcaconnectorad/service_endpoints_gen_test.go new file mode 100644 index 00000000000..7b21ea2e12d --- /dev/null +++ b/internal/service/pcaconnectorad/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package pcaconnectorad_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + pcaconnectorad_sdkv2 "github.com/aws/aws-sdk-go-v2/service/pcaconnectorad" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "pcaconnectorad" + awsEnvVar = "AWS_ENDPOINT_URL_PCA_CONNECTOR_AD" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "pca_connector_ad" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := pcaconnectorad_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), pcaconnectorad_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.PCAConnectorADClient(ctx) + + _, err := client.ListConnectors(ctx, &pcaconnectorad_sdkv2.ListConnectorsInput{}, + func(opts *pcaconnectorad_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/pipes/service_endpoints_gen_test.go b/internal/service/pipes/service_endpoints_gen_test.go new file mode 100644 index 00000000000..8afa1ccf407 --- /dev/null +++ b/internal/service/pipes/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package pipes_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + pipes_sdkv2 "github.com/aws/aws-sdk-go-v2/service/pipes" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "pipes" + awsEnvVar = "AWS_ENDPOINT_URL_PIPES" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "pipes" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := pipes_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), pipes_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.PipesClient(ctx) + + _, err := client.ListPipes(ctx, &pipes_sdkv2.ListPipesInput{}, + func(opts *pipes_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/polly/service_endpoints_gen_test.go b/internal/service/polly/service_endpoints_gen_test.go new file mode 100644 index 00000000000..dcda88a13a9 --- /dev/null +++ b/internal/service/polly/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package polly_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + polly_sdkv2 "github.com/aws/aws-sdk-go-v2/service/polly" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "polly" + awsEnvVar = "AWS_ENDPOINT_URL_POLLY" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "polly" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := polly_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), polly_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.PollyClient(ctx) + + _, err := client.ListLexicons(ctx, &polly_sdkv2.ListLexiconsInput{}, + func(opts *polly_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/pricing/service_endpoints_gen_test.go b/internal/service/pricing/service_endpoints_gen_test.go new file mode 100644 index 00000000000..523275ecd49 --- /dev/null +++ b/internal/service/pricing/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package pricing_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + pricing_sdkv2 "github.com/aws/aws-sdk-go-v2/service/pricing" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "pricing" + awsEnvVar = "AWS_ENDPOINT_URL_PRICING" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "pricing" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := pricing_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), pricing_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.PricingClient(ctx) + + _, err := client.DescribeServices(ctx, &pricing_sdkv2.DescribeServicesInput{}, + func(opts *pricing_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/qbusiness/service_endpoints_gen_test.go b/internal/service/qbusiness/service_endpoints_gen_test.go new file mode 100644 index 00000000000..e1edfca80e1 --- /dev/null +++ b/internal/service/qbusiness/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package qbusiness_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + qbusiness_sdkv2 "github.com/aws/aws-sdk-go-v2/service/qbusiness" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "qbusiness" + awsEnvVar = "AWS_ENDPOINT_URL_QBUSINESS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "qbusiness" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := qbusiness_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), qbusiness_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.QBusinessClient(ctx) + + _, err := client.ListApplications(ctx, &qbusiness_sdkv2.ListApplicationsInput{}, + func(opts *qbusiness_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/qldb/service_endpoints_gen_test.go b/internal/service/qldb/service_endpoints_gen_test.go new file mode 100644 index 00000000000..e6b43eb582e --- /dev/null +++ b/internal/service/qldb/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package qldb_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + qldb_sdkv2 "github.com/aws/aws-sdk-go-v2/service/qldb" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "qldb" + awsEnvVar = "AWS_ENDPOINT_URL_QLDB" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "qldb" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := qldb_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), qldb_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.QLDBClient(ctx) + + _, err := client.ListLedgers(ctx, &qldb_sdkv2.ListLedgersInput{}, + func(opts *qldb_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/rds/service_endpoints_gen_test.go b/internal/service/rds/service_endpoints_gen_test.go new file mode 100644 index 00000000000..6b35fe4607f --- /dev/null +++ b/internal/service/rds/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package rds_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + rds_sdkv2 "github.com/aws/aws-sdk-go-v2/service/rds" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "rds" + awsEnvVar = "AWS_ENDPOINT_URL_RDS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "rds" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := rds_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), rds_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.RDSClient(ctx) + + _, err := client.DescribeDBInstances(ctx, &rds_sdkv2.DescribeDBInstancesInput{}, + func(opts *rds_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/rekognition/service_endpoints_gen_test.go b/internal/service/rekognition/service_endpoints_gen_test.go new file mode 100644 index 00000000000..b1d53225a58 --- /dev/null +++ b/internal/service/rekognition/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package rekognition_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + rekognition_sdkv2 "github.com/aws/aws-sdk-go-v2/service/rekognition" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "rekognition" + awsEnvVar = "AWS_ENDPOINT_URL_REKOGNITION" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "rekognition" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := rekognition_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), rekognition_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.RekognitionClient(ctx) + + _, err := client.ListCollections(ctx, &rekognition_sdkv2.ListCollectionsInput{}, + func(opts *rekognition_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/resourceexplorer2/service_endpoints_gen_test.go b/internal/service/resourceexplorer2/service_endpoints_gen_test.go new file mode 100644 index 00000000000..037147f7466 --- /dev/null +++ b/internal/service/resourceexplorer2/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package resourceexplorer2_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + resourceexplorer2_sdkv2 "github.com/aws/aws-sdk-go-v2/service/resourceexplorer2" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "resourceexplorer2" + awsEnvVar = "AWS_ENDPOINT_URL_RESOURCE_EXPLORER_2" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "resource_explorer_2" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := resourceexplorer2_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), resourceexplorer2_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ResourceExplorer2Client(ctx) + + _, err := client.ListIndexes(ctx, &resourceexplorer2_sdkv2.ListIndexesInput{}, + func(opts *resourceexplorer2_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/resourcegroups/service_endpoints_gen_test.go b/internal/service/resourcegroups/service_endpoints_gen_test.go new file mode 100644 index 00000000000..ae39db33546 --- /dev/null +++ b/internal/service/resourcegroups/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package resourcegroups_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + resourcegroups_sdkv2 "github.com/aws/aws-sdk-go-v2/service/resourcegroups" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "resourcegroups" + awsEnvVar = "AWS_ENDPOINT_URL_RESOURCE_GROUPS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "resource_groups" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := resourcegroups_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), resourcegroups_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ResourceGroupsClient(ctx) + + _, err := client.ListGroups(ctx, &resourcegroups_sdkv2.ListGroupsInput{}, + func(opts *resourcegroups_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/rolesanywhere/service_endpoints_gen_test.go b/internal/service/rolesanywhere/service_endpoints_gen_test.go new file mode 100644 index 00000000000..f002445161c --- /dev/null +++ b/internal/service/rolesanywhere/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package rolesanywhere_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + rolesanywhere_sdkv2 "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "rolesanywhere" + awsEnvVar = "AWS_ENDPOINT_URL_ROLESANYWHERE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "rolesanywhere" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := rolesanywhere_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), rolesanywhere_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.RolesAnywhereClient(ctx) + + _, err := client.ListProfiles(ctx, &rolesanywhere_sdkv2.ListProfilesInput{}, + func(opts *rolesanywhere_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/route53domains/service_endpoints_gen_test.go b/internal/service/route53domains/service_endpoints_gen_test.go new file mode 100644 index 00000000000..0ebda6cb386 --- /dev/null +++ b/internal/service/route53domains/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package route53domains_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + route53domains_sdkv2 "github.com/aws/aws-sdk-go-v2/service/route53domains" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "route53domains" + awsEnvVar = "AWS_ENDPOINT_URL_ROUTE_53_DOMAINS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "route_53_domains" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-east-1" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := route53domains_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), route53domains_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.Route53DomainsClient(ctx) + + _, err := client.ListDomains(ctx, &route53domains_sdkv2.ListDomainsInput{}, + func(opts *route53domains_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/scheduler/service_endpoints_gen_test.go b/internal/service/scheduler/service_endpoints_gen_test.go new file mode 100644 index 00000000000..84c921f5313 --- /dev/null +++ b/internal/service/scheduler/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package scheduler_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + scheduler_sdkv2 "github.com/aws/aws-sdk-go-v2/service/scheduler" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "scheduler" + awsEnvVar = "AWS_ENDPOINT_URL_SCHEDULER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "scheduler" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := scheduler_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), scheduler_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SchedulerClient(ctx) + + _, err := client.ListSchedules(ctx, &scheduler_sdkv2.ListSchedulesInput{}, + func(opts *scheduler_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/secretsmanager/service_endpoints_gen_test.go b/internal/service/secretsmanager/service_endpoints_gen_test.go new file mode 100644 index 00000000000..50fb78457b1 --- /dev/null +++ b/internal/service/secretsmanager/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package secretsmanager_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + secretsmanager_sdkv2 "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "secretsmanager" + awsEnvVar = "AWS_ENDPOINT_URL_SECRETS_MANAGER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "secrets_manager" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := secretsmanager_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), secretsmanager_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SecretsManagerClient(ctx) + + _, err := client.ListSecrets(ctx, &secretsmanager_sdkv2.ListSecretsInput{}, + func(opts *secretsmanager_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/securityhub/service_endpoints_gen_test.go b/internal/service/securityhub/service_endpoints_gen_test.go new file mode 100644 index 00000000000..d1fe5ab6f44 --- /dev/null +++ b/internal/service/securityhub/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package securityhub_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + securityhub_sdkv2 "github.com/aws/aws-sdk-go-v2/service/securityhub" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "securityhub" + awsEnvVar = "AWS_ENDPOINT_URL_SECURITYHUB" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "securityhub" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := securityhub_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), securityhub_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SecurityHubClient(ctx) + + _, err := client.ListAutomationRules(ctx, &securityhub_sdkv2.ListAutomationRulesInput{}, + func(opts *securityhub_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/securitylake/service_endpoints_gen_test.go b/internal/service/securitylake/service_endpoints_gen_test.go new file mode 100644 index 00000000000..9755421743c --- /dev/null +++ b/internal/service/securitylake/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package securitylake_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + securitylake_sdkv2 "github.com/aws/aws-sdk-go-v2/service/securitylake" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "securitylake" + awsEnvVar = "AWS_ENDPOINT_URL_SECURITYLAKE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "securitylake" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := securitylake_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), securitylake_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SecurityLakeClient(ctx) + + _, err := client.ListDataLakes(ctx, &securitylake_sdkv2.ListDataLakesInput{}, + func(opts *securitylake_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/servicequotas/service_endpoints_gen_test.go b/internal/service/servicequotas/service_endpoints_gen_test.go new file mode 100644 index 00000000000..caaaac89323 --- /dev/null +++ b/internal/service/servicequotas/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package servicequotas_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + servicequotas_sdkv2 "github.com/aws/aws-sdk-go-v2/service/servicequotas" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "servicequotas" + awsEnvVar = "AWS_ENDPOINT_URL_SERVICE_QUOTAS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "service_quotas" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := servicequotas_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), servicequotas_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.ServiceQuotasClient(ctx) + + _, err := client.ListServices(ctx, &servicequotas_sdkv2.ListServicesInput{}, + func(opts *servicequotas_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/sesv2/service_endpoints_gen_test.go b/internal/service/sesv2/service_endpoints_gen_test.go new file mode 100644 index 00000000000..56b57fc9d79 --- /dev/null +++ b/internal/service/sesv2/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package sesv2_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + sesv2_sdkv2 "github.com/aws/aws-sdk-go-v2/service/sesv2" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "sesv2" + awsEnvVar = "AWS_ENDPOINT_URL_SESV2" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "sesv2" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := sesv2_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), sesv2_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SESV2Client(ctx) + + _, err := client.ListContactLists(ctx, &sesv2_sdkv2.ListContactListsInput{}, + func(opts *sesv2_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/signer/service_endpoints_gen_test.go b/internal/service/signer/service_endpoints_gen_test.go new file mode 100644 index 00000000000..5f985197f40 --- /dev/null +++ b/internal/service/signer/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package signer_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + signer_sdkv2 "github.com/aws/aws-sdk-go-v2/service/signer" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "signer" + awsEnvVar = "AWS_ENDPOINT_URL_SIGNER" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "signer" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := signer_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), signer_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SignerClient(ctx) + + _, err := client.ListSigningJobs(ctx, &signer_sdkv2.ListSigningJobsInput{}, + func(opts *signer_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/sns/service_endpoints_gen_test.go b/internal/service/sns/service_endpoints_gen_test.go new file mode 100644 index 00000000000..343d0be1daf --- /dev/null +++ b/internal/service/sns/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package sns_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + sns_sdkv2 "github.com/aws/aws-sdk-go-v2/service/sns" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "sns" + awsEnvVar = "AWS_ENDPOINT_URL_SNS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "sns" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := sns_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), sns_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SNSClient(ctx) + + _, err := client.ListSubscriptions(ctx, &sns_sdkv2.ListSubscriptionsInput{}, + func(opts *sns_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/sqs/service_endpoints_gen_test.go b/internal/service/sqs/service_endpoints_gen_test.go new file mode 100644 index 00000000000..2bd91f19c32 --- /dev/null +++ b/internal/service/sqs/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package sqs_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + sqs_sdkv2 "github.com/aws/aws-sdk-go-v2/service/sqs" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "sqs" + awsEnvVar = "AWS_ENDPOINT_URL_SQS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "sqs" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := sqs_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), sqs_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SQSClient(ctx) + + _, err := client.ListQueues(ctx, &sqs_sdkv2.ListQueuesInput{}, + func(opts *sqs_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/ssm/service_endpoints_gen_test.go b/internal/service/ssm/service_endpoints_gen_test.go new file mode 100644 index 00000000000..25c214d2b9b --- /dev/null +++ b/internal/service/ssm/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package ssm_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + ssm_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ssm" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "ssm" + awsEnvVar = "AWS_ENDPOINT_URL_SSM" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "ssm" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := ssm_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), ssm_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SSMClient(ctx) + + _, err := client.ListDocuments(ctx, &ssm_sdkv2.ListDocumentsInput{}, + func(opts *ssm_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/ssmcontacts/service_endpoints_gen_test.go b/internal/service/ssmcontacts/service_endpoints_gen_test.go new file mode 100644 index 00000000000..74a293148c5 --- /dev/null +++ b/internal/service/ssmcontacts/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package ssmcontacts_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + ssmcontacts_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "ssmcontacts" + awsEnvVar = "AWS_ENDPOINT_URL_SSM_CONTACTS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "ssm_contacts" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := ssmcontacts_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), ssmcontacts_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SSMContactsClient(ctx) + + _, err := client.ListContacts(ctx, &ssmcontacts_sdkv2.ListContactsInput{}, + func(opts *ssmcontacts_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/ssmincidents/service_endpoints_gen_test.go b/internal/service/ssmincidents/service_endpoints_gen_test.go new file mode 100644 index 00000000000..1578dbf4f9c --- /dev/null +++ b/internal/service/ssmincidents/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package ssmincidents_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + ssmincidents_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ssmincidents" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "ssmincidents" + awsEnvVar = "AWS_ENDPOINT_URL_SSM_INCIDENTS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "ssm_incidents" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := ssmincidents_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), ssmincidents_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SSMIncidentsClient(ctx) + + _, err := client.ListResponsePlans(ctx, &ssmincidents_sdkv2.ListResponsePlansInput{}, + func(opts *ssmincidents_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/ssmsap/service_endpoints_gen_test.go b/internal/service/ssmsap/service_endpoints_gen_test.go new file mode 100644 index 00000000000..b5ce8ca0ae3 --- /dev/null +++ b/internal/service/ssmsap/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package ssmsap_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + ssmsap_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ssmsap" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "ssmsap" + awsEnvVar = "AWS_ENDPOINT_URL_SSM_SAP" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "ssm_sap" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := ssmsap_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), ssmsap_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SSMSAPClient(ctx) + + _, err := client.ListApplications(ctx, &ssmsap_sdkv2.ListApplicationsInput{}, + func(opts *ssmsap_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/ssoadmin/service_endpoints_gen_test.go b/internal/service/ssoadmin/service_endpoints_gen_test.go new file mode 100644 index 00000000000..d932d3c41a4 --- /dev/null +++ b/internal/service/ssoadmin/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package ssoadmin_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + ssoadmin_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ssoadmin" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "ssoadmin" + awsEnvVar = "AWS_ENDPOINT_URL_SSO_ADMIN" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "sso_admin" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := ssoadmin_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), ssoadmin_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SSOAdminClient(ctx) + + _, err := client.ListInstances(ctx, &ssoadmin_sdkv2.ListInstancesInput{}, + func(opts *ssoadmin_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/swf/service_endpoints_gen_test.go b/internal/service/swf/service_endpoints_gen_test.go new file mode 100644 index 00000000000..9bb5e4a4818 --- /dev/null +++ b/internal/service/swf/service_endpoints_gen_test.go @@ -0,0 +1,496 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package swf_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + swf_sdkv2 "github.com/aws/aws-sdk-go-v2/service/swf" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "swf" + awsEnvVar = "AWS_ENDPOINT_URL_SWF" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "swf" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := swf_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), swf_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.SWFClient(ctx) + + _, err := client.ListDomains(ctx, &swf_sdkv2.ListDomainsInput{ + RegistrationStatus: "REGISTERED", + }, + func(opts *swf_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/verifiedpermissions/service_endpoints_gen_test.go b/internal/service/verifiedpermissions/service_endpoints_gen_test.go new file mode 100644 index 00000000000..f03810d7c70 --- /dev/null +++ b/internal/service/verifiedpermissions/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package verifiedpermissions_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + verifiedpermissions_sdkv2 "github.com/aws/aws-sdk-go-v2/service/verifiedpermissions" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "verifiedpermissions" + awsEnvVar = "AWS_ENDPOINT_URL_VERIFIEDPERMISSIONS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "verifiedpermissions" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := verifiedpermissions_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), verifiedpermissions_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.VerifiedPermissionsClient(ctx) + + _, err := client.ListPolicyStores(ctx, &verifiedpermissions_sdkv2.ListPolicyStoresInput{}, + func(opts *verifiedpermissions_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/vpclattice/service_endpoints_gen_test.go b/internal/service/vpclattice/service_endpoints_gen_test.go new file mode 100644 index 00000000000..9b9428844a4 --- /dev/null +++ b/internal/service/vpclattice/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package vpclattice_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + vpclattice_sdkv2 "github.com/aws/aws-sdk-go-v2/service/vpclattice" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "vpclattice" + awsEnvVar = "AWS_ENDPOINT_URL_VPC_LATTICE" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "vpc_lattice" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := vpclattice_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), vpclattice_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.VPCLatticeClient(ctx) + + _, err := client.ListServices(ctx, &vpclattice_sdkv2.ListServicesInput{}, + func(opts *vpclattice_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/wellarchitected/service_endpoints_gen_test.go b/internal/service/wellarchitected/service_endpoints_gen_test.go new file mode 100644 index 00000000000..74ba9fffa1b --- /dev/null +++ b/internal/service/wellarchitected/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package wellarchitected_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + wellarchitected_sdkv2 "github.com/aws/aws-sdk-go-v2/service/wellarchitected" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "wellarchitected" + awsEnvVar = "AWS_ENDPOINT_URL_WELLARCHITECTED" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "wellarchitected" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := wellarchitected_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), wellarchitected_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.WellArchitectedClient(ctx) + + _, err := client.ListProfiles(ctx, &wellarchitected_sdkv2.ListProfilesInput{}, + func(opts *wellarchitected_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/workspaces/service_endpoints_gen_test.go b/internal/service/workspaces/service_endpoints_gen_test.go new file mode 100644 index 00000000000..9c20ba73a44 --- /dev/null +++ b/internal/service/workspaces/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package workspaces_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + workspaces_sdkv2 "github.com/aws/aws-sdk-go-v2/service/workspaces" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "workspaces" + awsEnvVar = "AWS_ENDPOINT_URL_WORKSPACES" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "workspaces" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := workspaces_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), workspaces_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.WorkSpacesClient(ctx) + + _, err := client.DescribeWorkspaces(ctx, &workspaces_sdkv2.DescribeWorkspacesInput{}, + func(opts *workspaces_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/xray/service_endpoints_gen_test.go b/internal/service/xray/service_endpoints_gen_test.go new file mode 100644 index 00000000000..9279c15c711 --- /dev/null +++ b/internal/service/xray/service_endpoints_gen_test.go @@ -0,0 +1,494 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package xray_test + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + xray_sdkv2 "github.com/aws/aws-sdk-go-v2/service/xray" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "golang.org/x/exp/maps" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string +} + +type setupFunc func(setup *caseSetup) + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "xray" + awsEnvVar = "AWS_ENDPOINT_URL_XRAY" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "xray" +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const region = "us-west-2" //lintignore:AWSAT003 + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(region), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase) + }) + } +} + +func defaultEndpoint(region string) string { + r := xray_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.TODO(), xray_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return err.Error() + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI.String() +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { + t.Helper() + + var endpoint string + + client := meta.XRayClient(ctx) + + _, err := client.ListResourcePolicies(ctx, &xray_sdkv2.ListResourcePoliciesInput{}, + func(opts *xray_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return endpoint +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config["endpoints"]; !ok { + setup.config["endpoints"] = []any{ + map[string]any{}, + } + } + endpoints := setup.config["endpoints"].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func expectDefaultEndpoint(region string) caseExpectations { + return caseExpectations{ + endpoint: defaultEndpoint(region), + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + "access_key": servicemocks.MockStaticAccessKey, + "secret_key": servicemocks.MockStaticSecretKey, + "region": region, + "skip_credentials_validation": true, + "skip_requesting_account_id": true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config["profile"] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + endpoint := callService(ctx, t, meta) + + if endpoint != testcase.expected.endpoint { + t.Errorf("expected endpoint %q, got %q", testcase.expected.endpoint, endpoint) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +var errCancelOperation = fmt.Errorf("Test: Cancelling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)["shared_config_files"]; !ok { + (*config)["shared_config_files"] = []any{file.Name()} + } else { + (*config)["shared_config_files"] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/names/data/names_data.csv b/names/data/names_data.csv index 678f5c992a0..78ee2fdf22b 100644 --- a/names/data/names_data.csv +++ b/names/data/names_data.csv @@ -1,8 +1,8 @@ -AWSCLIV2Command,AWSCLIV2CommandNoDashes,GoV1Package,GoV2Package,ProviderPackageActual,ProviderPackageCorrect,SplitPackageRealPackage,Aliases,ProviderNameUpper,GoV1ClientTypeName,SkipClientGenerate,ClientSDKV1,ClientSDKV2,ResourcePrefixActual,ResourcePrefixCorrect,FilePrefix,DocPrefix,HumanFriendly,Brand,Exclude,NotImplemented,EndpointOnly,AllowedSubcategory,DeprecatedEnvVar,TfAwsEnvVar,SdkId,EndpointApiCall,EndpointAPIParams,Note -accessanalyzer,accessanalyzer,accessanalyzer,accessanalyzer,,accessanalyzer,,,AccessAnalyzer,AccessAnalyzer,,,2,,aws_accessanalyzer_,,accessanalyzer_,IAM Access Analyzer,AWS,,,,,,,AccessAnalyzer,,, -account,account,account,account,,account,,,Account,Account,,,2,,aws_account_,,account_,Account Management,AWS,,,,,,,Account,,, -acm,acm,acm,acm,,acm,,,ACM,ACM,,,2,,aws_acm_,,acm_,ACM (Certificate Manager),AWS,,,,,,,ACM,,, -acm-pca,acmpca,acmpca,acmpca,,acmpca,,,ACMPCA,ACMPCA,,1,,,aws_acmpca_,,acmpca_,ACM PCA (Certificate Manager Private Certificate Authority),AWS,,,,,,,ACM PCA,,, +AWSCLIV2Command,AWSCLIV2CommandNoDashes,GoV1Package,GoV2Package,ProviderPackageActual,ProviderPackageCorrect,SplitPackageRealPackage,Aliases,ProviderNameUpper,GoV1ClientTypeName,SkipClientGenerate,ClientSDKV1,ClientSDKV2,ResourcePrefixActual,ResourcePrefixCorrect,FilePrefix,DocPrefix,HumanFriendly,Brand,Exclude,NotImplemented,EndpointOnly,AllowedSubcategory,DeprecatedEnvVar,TfAwsEnvVar,SdkId,EndpointAPICall,EndpointAPIParams,Note +accessanalyzer,accessanalyzer,accessanalyzer,accessanalyzer,,accessanalyzer,,,AccessAnalyzer,AccessAnalyzer,,,2,,aws_accessanalyzer_,,accessanalyzer_,IAM Access Analyzer,AWS,,,,,,,AccessAnalyzer,ListAnalyzers,, +account,account,account,account,,account,,,Account,Account,,,2,,aws_account_,,account_,Account Management,AWS,,,,,,,Account,ListRegions,, +acm,acm,acm,acm,,acm,,,ACM,ACM,,,2,,aws_acm_,,acm_,ACM (Certificate Manager),AWS,,,,,,,ACM,ListCertificates,, +acm-pca,acmpca,acmpca,acmpca,,acmpca,,,ACMPCA,ACMPCA,,1,,,aws_acmpca_,,acmpca_,ACM PCA (Certificate Manager Private Certificate Authority),AWS,,,,,,,ACM PCA,ListCertificateAuthorities,, alexaforbusiness,alexaforbusiness,alexaforbusiness,alexaforbusiness,,alexaforbusiness,,,AlexaForBusiness,AlexaForBusiness,,1,,,aws_alexaforbusiness_,,alexaforbusiness_,Alexa for Business,,,x,,,,,Alexa For Business,,, amp,amp,prometheusservice,amp,,amp,,prometheus;prometheusservice,AMP,PrometheusService,,,2,aws_prometheus_,aws_amp_,,prometheus_,AMP (Managed Prometheus),Amazon,,,,,,,amp,,, amplify,amplify,amplify,amplify,,amplify,,,Amplify,Amplify,,1,,,aws_amplify_,,amplify_,Amplify,AWS,,,,,,,Amplify,,, @@ -12,13 +12,13 @@ amplifyuibuilder,amplifyuibuilder,amplifyuibuilder,amplifyuibuilder,,amplifyuibu apigateway,apigateway,apigateway,apigateway,,apigateway,,,APIGateway,APIGateway,,1,,aws_api_gateway_,aws_apigateway_,,api_gateway_,API Gateway,Amazon,,,,,,,API Gateway,,, apigatewaymanagementapi,apigatewaymanagementapi,apigatewaymanagementapi,apigatewaymanagementapi,,apigatewaymanagementapi,,,APIGatewayManagementAPI,ApiGatewayManagementApi,,1,,,aws_apigatewaymanagementapi_,,apigatewaymanagementapi_,API Gateway Management API,Amazon,,x,,,,,ApiGatewayManagementApi,,, apigatewayv2,apigatewayv2,apigatewayv2,apigatewayv2,,apigatewayv2,,,APIGatewayV2,ApiGatewayV2,,1,,,aws_apigatewayv2_,,apigatewayv2_,API Gateway V2,Amazon,,,,,,,ApiGatewayV2,,, -appfabric,appfabric,appfabric,appfabric,,appfabric,,,AppFabric,AppFabric,,,2,,aws_appfabric_,,appfabric_,AppFabric,AWS,,,,,,,AppFabric,,, +appfabric,appfabric,appfabric,appfabric,,appfabric,,,AppFabric,AppFabric,,,2,,aws_appfabric_,,appfabric_,AppFabric,AWS,,,,,,,AppFabric,ListAppBundles,, appmesh,appmesh,appmesh,appmesh,,appmesh,,,AppMesh,AppMesh,,1,,,aws_appmesh_,,appmesh_,App Mesh,AWS,,,,,,,App Mesh,,, -apprunner,apprunner,apprunner,apprunner,,apprunner,,,AppRunner,AppRunner,,,2,,aws_apprunner_,,apprunner_,App Runner,AWS,,,,,,,AppRunner,,, +apprunner,apprunner,apprunner,apprunner,,apprunner,,,AppRunner,AppRunner,,,2,,aws_apprunner_,,apprunner_,App Runner,AWS,,,,,,,AppRunner,ListConnections,, ,,,,,,,,,,,,,,,,,App2Container,AWS,x,,,,,,,,,No SDK support -appconfig,appconfig,appconfig,appconfig,,appconfig,,,AppConfig,AppConfig,,1,2,,aws_appconfig_,,appconfig_,AppConfig,AWS,,,,,,,AppConfig,,, +appconfig,appconfig,appconfig,appconfig,,appconfig,,,AppConfig,AppConfig,,1,2,,aws_appconfig_,,appconfig_,AppConfig,AWS,,,,,,,AppConfig,ListApplications,, appconfigdata,appconfigdata,appconfigdata,appconfigdata,,appconfigdata,,,AppConfigData,AppConfigData,,1,,,aws_appconfigdata_,,appconfigdata_,AppConfig Data,AWS,,x,,,,,AppConfigData,,, -appflow,appflow,appflow,appflow,,appflow,,,AppFlow,Appflow,,,2,,aws_appflow_,,appflow_,AppFlow,Amazon,,,,,,,Appflow,,, +appflow,appflow,appflow,appflow,,appflow,,,AppFlow,Appflow,,,2,,aws_appflow_,,appflow_,AppFlow,Amazon,,,,,,,Appflow,ListFlows,, appintegrations,appintegrations,appintegrationsservice,appintegrations,,appintegrations,,appintegrationsservice,AppIntegrations,AppIntegrationsService,,1,,,aws_appintegrations_,,appintegrations_,AppIntegrations,Amazon,,,,,,,AppIntegrations,,, application-autoscaling,applicationautoscaling,applicationautoscaling,applicationautoscaling,appautoscaling,applicationautoscaling,,applicationautoscaling,AppAutoScaling,ApplicationAutoScaling,,1,,aws_appautoscaling_,aws_applicationautoscaling_,,appautoscaling_,Application Auto Scaling,,,,,,,,Application Auto Scaling,,, applicationcostprofiler,applicationcostprofiler,applicationcostprofiler,applicationcostprofiler,,applicationcostprofiler,,,ApplicationCostProfiler,ApplicationCostProfiler,,1,,,aws_applicationcostprofiler_,,applicationcostprofiler_,Application Cost Profiler,AWS,,x,,,,,ApplicationCostProfiler,,, @@ -27,26 +27,26 @@ mgn,mgn,mgn,mgn,,mgn,,,Mgn,Mgn,,1,,,aws_mgn_,,mgn_,Application Migration (Mgn),A appstream,appstream,appstream,appstream,,appstream,,,AppStream,AppStream,,1,,,aws_appstream_,,appstream_,AppStream 2.0,Amazon,,,,,,,AppStream,,, appsync,appsync,appsync,appsync,,appsync,,,AppSync,AppSync,,1,,,aws_appsync_,,appsync_,AppSync,AWS,,,,,,,AppSync,,, ,,,,,,,,,,,,,,,,,Artifact,AWS,x,,,,,,,,,No SDK support -athena,athena,athena,athena,,athena,,,Athena,Athena,,,2,,aws_athena_,,athena_,Athena,Amazon,,,,,,,Athena,,, -auditmanager,auditmanager,auditmanager,auditmanager,,auditmanager,,,AuditManager,AuditManager,,,2,,aws_auditmanager_,,auditmanager_,Audit Manager,AWS,,,,,,,AuditManager,,, +athena,athena,athena,athena,,athena,,,Athena,Athena,,,2,,aws_athena_,,athena_,Athena,Amazon,,,,,,,Athena,ListDataCatalogs,, +auditmanager,auditmanager,auditmanager,auditmanager,,auditmanager,,,AuditManager,AuditManager,,,2,,aws_auditmanager_,,auditmanager_,Audit Manager,AWS,,,,,,,AuditManager,GetAccountStatus,, autoscaling,autoscaling,autoscaling,autoscaling,,autoscaling,,,AutoScaling,AutoScaling,,1,,aws_(autoscaling_|launch_configuration),aws_autoscaling_,,autoscaling_;launch_configuration,Auto Scaling,,,,,,,,Auto Scaling,,, autoscaling-plans,autoscalingplans,autoscalingplans,autoscalingplans,,autoscalingplans,,,AutoScalingPlans,AutoScalingPlans,,1,,,aws_autoscalingplans_,,autoscalingplans_,Auto Scaling Plans,,,,,,,,Auto Scaling Plans,,, ,,,,,,,,,,,,,,,,,Backint Agent for SAP HANA,AWS,x,,,,,,,,,No SDK support backup,backup,backup,backup,,backup,,,Backup,Backup,,1,,,aws_backup_,,backup_,Backup,AWS,,,,,,,Backup,,, backup-gateway,backupgateway,backupgateway,backupgateway,,backupgateway,,,BackupGateway,BackupGateway,,1,,,aws_backupgateway_,,backupgateway_,Backup Gateway,AWS,,x,,,,,Backup Gateway,,, batch,batch,batch,batch,,batch,,,Batch,Batch,,1,,,aws_batch_,,batch_,Batch,AWS,,,,,,,Batch,,, -bedrock,bedrock,bedrock,bedrock,,bedrock,,,Bedrock,Bedrock,,,2,,aws_bedrock_,,bedrock_,Amazon Bedrock,Amazon,,,,,,,Bedrock,,, +bedrock,bedrock,bedrock,bedrock,,bedrock,,,Bedrock,Bedrock,,,2,,aws_bedrock_,,bedrock_,Amazon Bedrock,Amazon,,,,,,,Bedrock,ListFoundationModels,, billingconductor,billingconductor,billingconductor,,,billingconductor,,,BillingConductor,BillingConductor,,1,,,aws_billingconductor_,,billingconductor_,Billing Conductor,AWS,,x,,,,,billingconductor,,, braket,braket,braket,braket,,braket,,,Braket,Braket,,1,,,aws_braket_,,braket_,Braket,Amazon,,x,,,,,Braket,,, ce,ce,costexplorer,costexplorer,,ce,,costexplorer,CE,CostExplorer,,1,,,aws_ce_,,ce_,CE (Cost Explorer),AWS,,,,,,,Cost Explorer,,, ,,,,,,,,,,,,,,,,,Chatbot,AWS,x,,,,,,,,,No SDK support chime,chime,chime,chime,,chime,,,Chime,Chime,,1,,,aws_chime_,,chime_,Chime,Amazon,,,,,,,Chime,,, chime-sdk-identity,chimesdkidentity,chimesdkidentity,chimesdkidentity,,chimesdkidentity,,,ChimeSDKIdentity,ChimeSDKIdentity,,1,,,aws_chimesdkidentity_,,chimesdkidentity_,Chime SDK Identity,Amazon,,x,,,,,Chime SDK Identity,,, -chime-sdk-mediapipelines,chimesdkmediapipelines,chimesdkmediapipelines,chimesdkmediapipelines,,chimesdkmediapipelines,,,ChimeSDKMediaPipelines,ChimeSDKMediaPipelines,,,2,,aws_chimesdkmediapipelines_,,chimesdkmediapipelines_,Chime SDK Media Pipelines,Amazon,,,,,,,Chime SDK Media Pipelines,,, +chime-sdk-mediapipelines,chimesdkmediapipelines,chimesdkmediapipelines,chimesdkmediapipelines,,chimesdkmediapipelines,,,ChimeSDKMediaPipelines,ChimeSDKMediaPipelines,,,2,,aws_chimesdkmediapipelines_,,chimesdkmediapipelines_,Chime SDK Media Pipelines,Amazon,,,,,,,Chime SDK Media Pipelines,ListMediaPipelines,, chime-sdk-meetings,chimesdkmeetings,chimesdkmeetings,chimesdkmeetings,,chimesdkmeetings,,,ChimeSDKMeetings,ChimeSDKMeetings,,1,,,aws_chimesdkmeetings_,,chimesdkmeetings_,Chime SDK Meetings,Amazon,,x,,,,,Chime SDK Meetings,,, chime-sdk-messaging,chimesdkmessaging,chimesdkmessaging,chimesdkmessaging,,chimesdkmessaging,,,ChimeSDKMessaging,ChimeSDKMessaging,,1,,,aws_chimesdkmessaging_,,chimesdkmessaging_,Chime SDK Messaging,Amazon,,x,,,,,Chime SDK Messaging,,, -chime-sdk-voice,chimesdkvoice,chimesdkvoice,chimesdkvoice,,chimesdkvoice,,,ChimeSDKVoice,ChimeSDKVoice,,,2,,aws_chimesdkvoice_,,chimesdkvoice_,Chime SDK Voice,Amazon,,,,,,,Chime SDK Voice,,, -cleanrooms,cleanrooms,cleanrooms,cleanrooms,,cleanrooms,,,CleanRooms,CleanRooms,,,2,,aws_cleanrooms_,,cleanrooms_,Clean Rooms,AWS,,,,,,,CleanRooms,,, +chime-sdk-voice,chimesdkvoice,chimesdkvoice,chimesdkvoice,,chimesdkvoice,,,ChimeSDKVoice,ChimeSDKVoice,,,2,,aws_chimesdkvoice_,,chimesdkvoice_,Chime SDK Voice,Amazon,,,,,,,Chime SDK Voice,ListPhoneNumbers,, +cleanrooms,cleanrooms,cleanrooms,cleanrooms,,cleanrooms,,,CleanRooms,CleanRooms,,,2,,aws_cleanrooms_,,cleanrooms_,Clean Rooms,AWS,,,,,,,CleanRooms,ListCollaborations,, ,,,,,,,,,,,,,,,,,CLI (Command Line Interface),AWS,x,,,,,,,,,No SDK support configure,configure,,,,,,,,,,,,,,,,CLI Configure options,AWS,x,,,,,,,,,CLI only ddb,ddb,,,,,,,,,,,,,,,,CLI High-level DynamoDB commands,AWS,x,,,,,,,,,Part of DynamoDB @@ -70,7 +70,7 @@ cloudtrail,cloudtrail,cloudtrail,cloudtrail,,cloudtrail,,,CloudTrail,CloudTrail, cloudwatch,cloudwatch,cloudwatch,cloudwatch,,cloudwatch,,,CloudWatch,CloudWatch,,1,,aws_cloudwatch_(?!(event_|log_|query_)),aws_cloudwatch_,,cloudwatch_dashboard;cloudwatch_metric_;cloudwatch_composite_,CloudWatch,Amazon,,,,,,,CloudWatch,,, application-insights,applicationinsights,applicationinsights,applicationinsights,,applicationinsights,,,ApplicationInsights,ApplicationInsights,,1,,,aws_applicationinsights_,,applicationinsights_,CloudWatch Application Insights,Amazon,,,,,,,Application Insights,,, evidently,evidently,cloudwatchevidently,evidently,,evidently,,cloudwatchevidently,Evidently,CloudWatchEvidently,,,2,,aws_evidently_,,evidently_,CloudWatch Evidently,Amazon,,,,,,,Evidently,,, -internetmonitor,internetmonitor,internetmonitor,internetmonitor,,internetmonitor,,,InternetMonitor,InternetMonitor,,,2,,aws_internetmonitor_,,internetmonitor_,CloudWatch Internet Monitor,Amazon,,,,,,,InternetMonitor,,, +internetmonitor,internetmonitor,internetmonitor,internetmonitor,,internetmonitor,,,InternetMonitor,InternetMonitor,,,2,,aws_internetmonitor_,,internetmonitor_,CloudWatch Internet Monitor,Amazon,,,,,,,InternetMonitor,ListMonitors,, logs,logs,cloudwatchlogs,cloudwatchlogs,,logs,,cloudwatchlog;cloudwatchlogs,Logs,CloudWatchLogs,,,2,aws_cloudwatch_(log_|query_),aws_logs_,,cloudwatch_log_;cloudwatch_query_,CloudWatch Logs,Amazon,,,,,,,CloudWatch Logs,,, rum,rum,cloudwatchrum,rum,,rum,,cloudwatchrum,RUM,CloudWatchRUM,,1,,,aws_rum_,,rum_,CloudWatch RUM,Amazon,,,,,,,RUM,,, synthetics,synthetics,synthetics,synthetics,,synthetics,,,Synthetics,Synthetics,,1,,,aws_synthetics_,,synthetics_,CloudWatch Synthetics,Amazon,,,,,,,synthetics,,, @@ -78,28 +78,28 @@ codeartifact,codeartifact,codeartifact,codeartifact,,codeartifact,,,CodeArtifact codebuild,codebuild,codebuild,codebuild,,codebuild,,,CodeBuild,CodeBuild,,1,,,aws_codebuild_,,codebuild_,CodeBuild,AWS,,,,,,,CodeBuild,,, codecommit,codecommit,codecommit,codecommit,,codecommit,,,CodeCommit,CodeCommit,,1,,,aws_codecommit_,,codecommit_,CodeCommit,AWS,,,,,,,CodeCommit,,, deploy,deploy,codedeploy,codedeploy,,deploy,,codedeploy,Deploy,CodeDeploy,,,2,aws_codedeploy_,aws_deploy_,,codedeploy_,CodeDeploy,AWS,,,,,,,CodeDeploy,,, -codeguruprofiler,codeguruprofiler,codeguruprofiler,codeguruprofiler,,codeguruprofiler,,,CodeGuruProfiler,CodeGuruProfiler,,,2,,aws_codeguruprofiler_,,codeguruprofiler_,CodeGuru Profiler,Amazon,,,,,,,CodeGuruProfiler,,, +codeguruprofiler,codeguruprofiler,codeguruprofiler,codeguruprofiler,,codeguruprofiler,,,CodeGuruProfiler,CodeGuruProfiler,,,2,,aws_codeguruprofiler_,,codeguruprofiler_,CodeGuru Profiler,Amazon,,,,,,,CodeGuruProfiler,ListProfilingGroups,, codeguru-reviewer,codegurureviewer,codegurureviewer,codegurureviewer,,codegurureviewer,,,CodeGuruReviewer,CodeGuruReviewer,,1,,,aws_codegurureviewer_,,codegurureviewer_,CodeGuru Reviewer,Amazon,,,,,,,CodeGuru Reviewer,,, -codepipeline,codepipeline,codepipeline,codepipeline,,codepipeline,,,CodePipeline,CodePipeline,,,2,aws_codepipeline,aws_codepipeline_,,codepipeline,CodePipeline,AWS,,,,,,,CodePipeline,,, +codepipeline,codepipeline,codepipeline,codepipeline,,codepipeline,,,CodePipeline,CodePipeline,,,2,aws_codepipeline,aws_codepipeline_,,codepipeline,CodePipeline,AWS,,,,,,,CodePipeline,ListPipelines,, codestar,codestar,codestar,codestar,,codestar,,,CodeStar,CodeStar,,1,,,aws_codestar_,,codestar_,CodeStar,AWS,,x,,,,,CodeStar,,, -codestar-connections,codestarconnections,codestarconnections,codestarconnections,,codestarconnections,,,CodeStarConnections,CodeStarConnections,,,2,,aws_codestarconnections_,,codestarconnections_,CodeStar Connections,AWS,,,,,,,CodeStar connections,,, -codestar-notifications,codestarnotifications,codestarnotifications,codestarnotifications,,codestarnotifications,,,CodeStarNotifications,CodeStarNotifications,,,2,,aws_codestarnotifications_,,codestarnotifications_,CodeStar Notifications,AWS,,,,,,,codestar notifications,,, +codestar-connections,codestarconnections,codestarconnections,codestarconnections,,codestarconnections,,,CodeStarConnections,CodeStarConnections,,,2,,aws_codestarconnections_,,codestarconnections_,CodeStar Connections,AWS,,,,,,,CodeStar connections,ListConnections,, +codestar-notifications,codestarnotifications,codestarnotifications,codestarnotifications,,codestarnotifications,,,CodeStarNotifications,CodeStarNotifications,,,2,,aws_codestarnotifications_,,codestarnotifications_,CodeStar Notifications,AWS,,,,,,,codestar notifications,ListTargets,, cognito-identity,cognitoidentity,cognitoidentity,cognitoidentity,,cognitoidentity,,,CognitoIdentity,CognitoIdentity,,1,,aws_cognito_identity_(?!provider),aws_cognitoidentity_,,cognito_identity_pool,Cognito Identity,Amazon,,,,,,,Cognito Identity,,, cognito-idp,cognitoidp,cognitoidentityprovider,cognitoidentityprovider,,cognitoidp,,cognitoidentityprovider,CognitoIDP,CognitoIdentityProvider,,1,,aws_cognito_(identity_provider|resource|user|risk),aws_cognitoidp_,,cognito_identity_provider;cognito_managed_user;cognito_resource_;cognito_user;cognito_risk,Cognito IDP (Identity Provider),Amazon,,,,,,,Cognito Identity Provider,,, cognito-sync,cognitosync,cognitosync,cognitosync,,cognitosync,,,CognitoSync,CognitoSync,,1,,,aws_cognitosync_,,cognitosync_,Cognito Sync,Amazon,,x,,,,,Cognito Sync,,, -comprehend,comprehend,comprehend,comprehend,,comprehend,,,Comprehend,Comprehend,,,2,,aws_comprehend_,,comprehend_,Comprehend,Amazon,,,,,,,Comprehend,,, +comprehend,comprehend,comprehend,comprehend,,comprehend,,,Comprehend,Comprehend,,,2,,aws_comprehend_,,comprehend_,Comprehend,Amazon,,,,,,,Comprehend,ListDocumentClassifiers,, comprehendmedical,comprehendmedical,comprehendmedical,comprehendmedical,,comprehendmedical,,,ComprehendMedical,ComprehendMedical,,1,,,aws_comprehendmedical_,,comprehendmedical_,Comprehend Medical,Amazon,,x,,,,,ComprehendMedical,,, -compute-optimizer,computeoptimizer,computeoptimizer,computeoptimizer,,computeoptimizer,,,ComputeOptimizer,ComputeOptimizer,,,2,,aws_computeoptimizer_,,computeoptimizer_,Compute Optimizer,AWS,,,,,,,Compute Optimizer,,, +compute-optimizer,computeoptimizer,computeoptimizer,computeoptimizer,,computeoptimizer,,,ComputeOptimizer,ComputeOptimizer,,,2,,aws_computeoptimizer_,,computeoptimizer_,Compute Optimizer,AWS,,,,,,,Compute Optimizer,GetEnrollmentStatus,, configservice,configservice,configservice,configservice,,configservice,,config,ConfigService,ConfigService,,1,,aws_config_,aws_configservice_,,config_,Config,AWS,,,,,,,Config Service,,, connect,connect,connect,connect,,connect,,,Connect,Connect,,1,,,aws_connect_,,connect_,Connect,Amazon,,,,,,,Connect,,, -connectcases,connectcases,connectcases,connectcases,,connectcases,,,ConnectCases,ConnectCases,,,2,,aws_connectcases_,,connectcases_,Connect Cases,Amazon,,,,,,,ConnectCases,,, +connectcases,connectcases,connectcases,connectcases,,connectcases,,,ConnectCases,ConnectCases,,,2,,aws_connectcases_,,connectcases_,Connect Cases,Amazon,,,,,,,ConnectCases,ListDomains,, connect-contact-lens,connectcontactlens,connectcontactlens,connectcontactlens,,connectcontactlens,,,ConnectContactLens,ConnectContactLens,,1,,,aws_connectcontactlens_,,connectcontactlens_,Connect Contact Lens,Amazon,,x,,,,,Connect Contact Lens,,, -customer-profiles,customerprofiles,customerprofiles,customerprofiles,,customerprofiles,,,CustomerProfiles,CustomerProfiles,,,2,,aws_customerprofiles_,,customerprofiles_,Connect Customer Profiles,Amazon,,,,,,,Customer Profiles,,, +customer-profiles,customerprofiles,customerprofiles,customerprofiles,,customerprofiles,,,CustomerProfiles,CustomerProfiles,,,2,,aws_customerprofiles_,,customerprofiles_,Connect Customer Profiles,Amazon,,,,,,,Customer Profiles,ListDomains,, connectparticipant,connectparticipant,connectparticipant,connectparticipant,,connectparticipant,,,ConnectParticipant,ConnectParticipant,,1,,,aws_connectparticipant_,,connectparticipant_,Connect Participant,Amazon,,x,,,,,ConnectParticipant,,, voice-id,voiceid,voiceid,voiceid,,voiceid,,,VoiceID,VoiceID,,1,,,aws_voiceid_,,voiceid_,Connect Voice ID,Amazon,,x,,,,,Voice ID,,, wisdom,wisdom,connectwisdomservice,wisdom,,wisdom,,connectwisdomservice,Wisdom,ConnectWisdomService,,1,,,aws_wisdom_,,wisdom_,Connect Wisdom,Amazon,,x,,,,,Wisdom,,, ,,,,,,,,,,,,,,,,,Console Mobile Application,AWS,x,,,,,,,,,No SDK support -controltower,controltower,controltower,controltower,,controltower,,,ControlTower,ControlTower,,,2,,aws_controltower_,,controltower_,Control Tower,AWS,,,,,,,ControlTower,,, +controltower,controltower,controltower,controltower,,controltower,,,ControlTower,ControlTower,,,2,,aws_controltower_,,controltower_,Control Tower,AWS,,,,,,,ControlTower,ListLandingZones,, cur,cur,costandusagereportservice,costandusagereportservice,,cur,,costandusagereportservice,CUR,CostandUsageReportService,,1,,,aws_cur_,,cur_,Cost and Usage Report,AWS,,,,,,,Cost and Usage Report Service,,, ,,,,,,,,,,,,,,,,,Crypto Tools,AWS,x,,,,,,,,,No SDK support ,,,,,,,,,,,,,,,,,Cryptographic Services Overview,AWS,x,,,,,,,,,No SDK support @@ -118,7 +118,7 @@ directconnect,directconnect,directconnect,directconnect,,directconnect,,,DirectC dlm,dlm,dlm,dlm,,dlm,,,DLM,DLM,,1,,,aws_dlm_,,dlm_,DLM (Data Lifecycle Manager),Amazon,,,,,,,DLM,,, dms,dms,databasemigrationservice,databasemigrationservice,,dms,,databasemigration;databasemigrationservice,DMS,DatabaseMigrationService,,1,,,aws_dms_,,dms_,DMS (Database Migration),AWS,,,,,,,Database Migration Service,,, docdb,docdb,docdb,docdb,,docdb,,,DocDB,DocDB,,1,,,aws_docdb_,,docdb_,DocumentDB,Amazon,,,,,,,DocDB,,, -docdb-elastic,docdbelastic,docdbelastic,docdbelastic,,docdbelastic,,,DocDBElastic,DocDBElastic,,,2,,aws_docdbelastic_,,docdbelastic_,DocumentDB Elastic,Amazon,,,,,,,DocDB Elastic,,, +docdb-elastic,docdbelastic,docdbelastic,docdbelastic,,docdbelastic,,,DocDBElastic,DocDBElastic,,,2,,aws_docdbelastic_,,docdbelastic_,DocumentDB Elastic,Amazon,,,,,,,DocDB Elastic,ListClusters,, drs,drs,drs,drs,,drs,,,DRS,Drs,,1,,,aws_drs_,,drs_,DRS (Elastic Disaster Recovery),AWS,,x,,,,,drs,,, ds,ds,directoryservice,directoryservice,,ds,,directoryservice,DS,DirectoryService,,1,2,aws_directory_service_,aws_ds_,,directory_service_,Directory Service,AWS,,,,,,,Directory Service,,, dynamodb,dynamodb,dynamodb,dynamodb,,dynamodb,,,DynamoDB,DynamoDB,,1,,,aws_dynamodb_,,dynamodb_,DynamoDB,Amazon,,,,,AWS_DYNAMODB_ENDPOINT,TF_AWS_DYNAMODB_ENDPOINT,DynamoDB,,, @@ -126,38 +126,38 @@ dax,dax,dax,dax,,dax,,,DAX,DAX,,1,,,aws_dax_,,dax_,DynamoDB Accelerator (DAX),Am dynamodbstreams,dynamodbstreams,dynamodbstreams,dynamodbstreams,,dynamodbstreams,,,DynamoDBStreams,DynamoDBStreams,,1,,,aws_dynamodbstreams_,,dynamodbstreams_,DynamoDB Streams,Amazon,,x,,,,,DynamoDB Streams,,, ,,,,,ec2ebs,ec2,,EC2EBS,,,,,aws_(ebs_|volume_attach|snapshot_create),aws_ec2ebs_,ebs_,ebs_;volume_attachment;snapshot_,EBS (EC2),Amazon,x,,,x,,,,,,Part of EC2 ebs,ebs,ebs,ebs,,ebs,,,EBS,EBS,,1,,,aws_ebs_,,changewhenimplemented,EBS (Elastic Block Store),Amazon,,x,,,,,EBS,,, -ec2,ec2,ec2,ec2,,ec2,ec2,,EC2,EC2,,1,2,aws_(ami|availability_zone|ec2_(availability|capacity|fleet|host|instance|public_ipv4_pool|serial|spot|tag)|eip|instance|key_pair|launch_template|placement_group|spot),aws_ec2_,ec2_,ami;availability_zone;ec2_availability_;ec2_capacity_;ec2_fleet;ec2_host;ec2_image_;ec2_instance_;ec2_public_ipv4_pool;ec2_serial_;ec2_spot_;ec2_tag;eip;instance;key_pair;launch_template;placement_group;spot_,EC2 (Elastic Compute Cloud),Amazon,,,,,,,EC2,,, +ec2,ec2,ec2,ec2,,ec2,ec2,,EC2,EC2,,1,2,aws_(ami|availability_zone|ec2_(availability|capacity|fleet|host|instance|public_ipv4_pool|serial|spot|tag)|eip|instance|key_pair|launch_template|placement_group|spot),aws_ec2_,ec2_,ami;availability_zone;ec2_availability_;ec2_capacity_;ec2_fleet;ec2_host;ec2_image_;ec2_instance_;ec2_public_ipv4_pool;ec2_serial_;ec2_spot_;ec2_tag;eip;instance;key_pair;launch_template;placement_group;spot_,EC2 (Elastic Compute Cloud),Amazon,,,,,,,EC2,DescribeVpcs,, imagebuilder,imagebuilder,imagebuilder,imagebuilder,,imagebuilder,,,ImageBuilder,Imagebuilder,,1,,,aws_imagebuilder_,,imagebuilder_,EC2 Image Builder,Amazon,,,,,,,imagebuilder,,, ec2-instance-connect,ec2instanceconnect,ec2instanceconnect,ec2instanceconnect,,ec2instanceconnect,,,EC2InstanceConnect,EC2InstanceConnect,,1,,,aws_ec2instanceconnect_,,ec2instanceconnect_,EC2 Instance Connect,AWS,,x,,,,,EC2 Instance Connect,,, -ecr,ecr,ecr,ecr,,ecr,,,ECR,ECR,,1,2,,aws_ecr_,,ecr_,ECR (Elastic Container Registry),Amazon,,,,,,,ECR,,, +ecr,ecr,ecr,ecr,,ecr,,,ECR,ECR,,1,2,,aws_ecr_,,ecr_,ECR (Elastic Container Registry),Amazon,,,,,,,ECR,DescribeRepositories,, ecr-public,ecrpublic,ecrpublic,ecrpublic,,ecrpublic,,,ECRPublic,ECRPublic,,1,,,aws_ecrpublic_,,ecrpublic_,ECR Public,Amazon,,,,,,,ECR PUBLIC,,, ecs,ecs,ecs,ecs,,ecs,,,ECS,ECS,,1,,,aws_ecs_,,ecs_,ECS (Elastic Container),Amazon,,,,,,,ECS,,, efs,efs,efs,efs,,efs,,,EFS,EFS,,1,,,aws_efs_,,efs_,EFS (Elastic File System),Amazon,,,,,,,EFS,,, -eks,eks,eks,eks,,eks,,,EKS,EKS,,,2,,aws_eks_,,eks_,EKS (Elastic Kubernetes),Amazon,,,,,,,EKS,,, +eks,eks,eks,eks,,eks,,,EKS,EKS,,,2,,aws_eks_,,eks_,EKS (Elastic Kubernetes),Amazon,,,,,,,EKS,ListClusters,, elasticbeanstalk,elasticbeanstalk,elasticbeanstalk,elasticbeanstalk,,elasticbeanstalk,,beanstalk,ElasticBeanstalk,ElasticBeanstalk,,1,,aws_elastic_beanstalk_,aws_elasticbeanstalk_,,elastic_beanstalk_,Elastic Beanstalk,AWS,,,,,,,Elastic Beanstalk,,, elastic-inference,elasticinference,elasticinference,elasticinference,,elasticinference,,,ElasticInference,ElasticInference,,1,,,aws_elasticinference_,,elasticinference_,Elastic Inference,Amazon,,x,,,,,Elastic Inference,,, elastictranscoder,elastictranscoder,elastictranscoder,elastictranscoder,,elastictranscoder,,,ElasticTranscoder,ElasticTranscoder,,1,,,aws_elastictranscoder_,,elastictranscoder_,Elastic Transcoder,Amazon,,,,,,,Elastic Transcoder,,, -elasticache,elasticache,elasticache,elasticache,,elasticache,,,ElastiCache,ElastiCache,,1,2,,aws_elasticache_,,elasticache_,ElastiCache,Amazon,,,,,,,ElastiCache,,, +elasticache,elasticache,elasticache,elasticache,,elasticache,,,ElastiCache,ElastiCache,,1,2,,aws_elasticache_,,elasticache_,ElastiCache,Amazon,,,,,,,ElastiCache,DescribeCacheClusters,, es,es,elasticsearchservice,elasticsearchservice,elasticsearch,es,,es;elasticsearchservice,Elasticsearch,ElasticsearchService,,1,,aws_elasticsearch_,aws_es_,,elasticsearch_,Elasticsearch,Amazon,,,,,,,Elasticsearch Service,,, elbv2,elbv2,elbv2,elasticloadbalancingv2,,elbv2,,elasticloadbalancingv2,ELBV2,ELBV2,,1,,aws_a?lb(\b|_listener|_target_group|s|_trust_store),aws_elbv2_,,lbs?\.;lb_listener;lb_target_group;lb_hosted;lb_trust_store,ELB (Elastic Load Balancing),,,,,,,,Elastic Load Balancing v2,,, elb,elb,elb,elasticloadbalancing,,elb,,elasticloadbalancing,ELB,ELB,,1,,aws_(app_cookie_stickiness_policy|elb|lb_cookie_stickiness_policy|lb_ssl_negotiation_policy|load_balancer_|proxy_protocol_policy),aws_elb_,,app_cookie_stickiness_policy;elb;lb_cookie_stickiness_policy;lb_ssl_negotiation_policy;load_balancer;proxy_protocol_policy,ELB Classic,,,,,,,,Elastic Load Balancing,,, -mediaconnect,mediaconnect,mediaconnect,mediaconnect,,mediaconnect,,,MediaConnect,MediaConnect,,,2,,aws_mediaconnect_,,mediaconnect_,Elemental MediaConnect,AWS,,,,,,,MediaConnect,,, +mediaconnect,mediaconnect,mediaconnect,mediaconnect,,mediaconnect,,,MediaConnect,MediaConnect,,,2,,aws_mediaconnect_,,mediaconnect_,Elemental MediaConnect,AWS,,,,,,,MediaConnect,ListBridges,, mediaconvert,mediaconvert,mediaconvert,mediaconvert,,mediaconvert,,,MediaConvert,MediaConvert,,1,,aws_media_convert_,aws_mediaconvert_,,media_convert_,Elemental MediaConvert,AWS,,,,,,,MediaConvert,,, -medialive,medialive,medialive,medialive,,medialive,,,MediaLive,MediaLive,,,2,,aws_medialive_,,medialive_,Elemental MediaLive,AWS,,,,,,,MediaLive,,, -mediapackage,mediapackage,mediapackage,mediapackage,,mediapackage,,,MediaPackage,MediaPackage,,,2,aws_media_package_,aws_mediapackage_,,media_package_,Elemental MediaPackage,AWS,,,,,,,MediaPackage,,, +medialive,medialive,medialive,medialive,,medialive,,,MediaLive,MediaLive,,,2,,aws_medialive_,,medialive_,Elemental MediaLive,AWS,,,,,,,MediaLive,ListOfferings,, +mediapackage,mediapackage,mediapackage,mediapackage,,mediapackage,,,MediaPackage,MediaPackage,,,2,aws_media_package_,aws_mediapackage_,,media_package_,Elemental MediaPackage,AWS,,,,,,,MediaPackage,ListChannels,, mediapackage-vod,mediapackagevod,mediapackagevod,mediapackagevod,,mediapackagevod,,,MediaPackageVOD,MediaPackageVod,,1,,,aws_mediapackagevod_,,mediapackagevod_,Elemental MediaPackage VOD,AWS,,x,,,,,MediaPackage Vod,,, mediastore,mediastore,mediastore,mediastore,,mediastore,,,MediaStore,MediaStore,,1,,aws_media_store_,aws_mediastore_,,media_store_,Elemental MediaStore,AWS,,,,,,,MediaStore,,, mediastore-data,mediastoredata,mediastoredata,mediastoredata,,mediastoredata,,,MediaStoreData,MediaStoreData,,1,,,aws_mediastoredata_,,mediastoredata_,Elemental MediaStore Data,AWS,,x,,,,,MediaStore Data,,, mediatailor,mediatailor,mediatailor,mediatailor,,mediatailor,,,MediaTailor,MediaTailor,,1,,,aws_mediatailor_,,media_tailor_,Elemental MediaTailor,AWS,,x,,,,,MediaTailor,,, ,,,,,,,,,,,,,,,,,Elemental On-Premises,AWS,x,,,,,,,,,No SDK support -emr,emr,emr,emr,,emr,,,EMR,EMR,,1,2,,aws_emr_,,emr_,EMR,Amazon,,,,,,,EMR,,, +emr,emr,emr,emr,,emr,,,EMR,EMR,,1,2,,aws_emr_,,emr_,EMR,Amazon,,,,,,,EMR,ListClusters,, emr-containers,emrcontainers,emrcontainers,emrcontainers,,emrcontainers,,,EMRContainers,EMRContainers,,1,,,aws_emrcontainers_,,emrcontainers_,EMR Containers,Amazon,,,,,,,EMR containers,,, -emr-serverless,emrserverless,emrserverless,emrserverless,,emrserverless,,,EMRServerless,EMRServerless,,,2,,aws_emrserverless_,,emrserverless_,EMR Serverless,Amazon,,,,,,,EMR Serverless,,, +emr-serverless,emrserverless,emrserverless,emrserverless,,emrserverless,,,EMRServerless,EMRServerless,,,2,,aws_emrserverless_,,emrserverless_,EMR Serverless,Amazon,,,,,,,EMR Serverless,ListApplications,, ,,,,,,,,,,,,,,,,,End-of-Support Migration Program (EMP) for Windows Server,AWS,x,,,,,,,,,No SDK support events,events,eventbridge,eventbridge,,events,,eventbridge;cloudwatchevents,Events,EventBridge,,1,,aws_cloudwatch_event_,aws_events_,,cloudwatch_event_,EventBridge,Amazon,,,,,,,EventBridge,,, schemas,schemas,schemas,schemas,,schemas,,,Schemas,Schemas,,1,,,aws_schemas_,,schemas_,EventBridge Schemas,Amazon,,,,,,,schemas,,, -fis,fis,fis,fis,,fis,,,FIS,FIS,,,2,,aws_fis_,,fis_,FIS (Fault Injection Simulator),AWS,,,,,,,fis,,, -finspace,finspace,finspace,finspace,,finspace,,,FinSpace,Finspace,,,2,,aws_finspace_,,finspace_,FinSpace,Amazon,,,,,,,finspace,,, +fis,fis,fis,fis,,fis,,,FIS,FIS,,,2,,aws_fis_,,fis_,FIS (Fault Injection Simulator),AWS,,,,,,,fis,ListExperiments,, +finspace,finspace,finspace,finspace,,finspace,,,FinSpace,Finspace,,,2,,aws_finspace_,,finspace_,FinSpace,Amazon,,,,,,,finspace,ListEnvironments,, finspace-data,finspacedata,finspacedata,finspacedata,,finspacedata,,,FinSpaceData,FinSpaceData,,1,,,aws_finspacedata_,,finspacedata_,FinSpace Data,Amazon,,x,,,,,finspace data,,, fms,fms,fms,fms,,fms,,,FMS,FMS,,1,,,aws_fms_,,fms_,FMS (Firewall Manager),AWS,,,,,,,FMS,,, forecast,forecast,forecastservice,forecast,,forecast,,forecastservice,Forecast,ForecastService,,1,,,aws_forecast_,,forecast_,Forecast,Amazon,,x,,,,,forecast,,, @@ -169,10 +169,10 @@ gamelift,gamelift,gamelift,gamelift,,gamelift,,,GameLift,GameLift,,1,,,aws_gamel globalaccelerator,globalaccelerator,globalaccelerator,globalaccelerator,,globalaccelerator,,,GlobalAccelerator,GlobalAccelerator,x,1,,,aws_globalaccelerator_,,globalaccelerator_,Global Accelerator,AWS,,,,,,,Global Accelerator,,, glue,glue,glue,glue,,glue,,,Glue,Glue,,1,,,aws_glue_,,glue_,Glue,AWS,,,,,,,Glue,,, databrew,databrew,gluedatabrew,databrew,,databrew,,gluedatabrew,DataBrew,GlueDataBrew,,1,,,aws_databrew_,,databrew_,Glue DataBrew,AWS,,x,,,,,DataBrew,,, -groundstation,groundstation,groundstation,groundstation,,groundstation,,,GroundStation,GroundStation,,,2,,aws_groundstation_,,groundstation_,Ground Station,AWS,,,,,,,GroundStation,,, +groundstation,groundstation,groundstation,groundstation,,groundstation,,,GroundStation,GroundStation,,,2,,aws_groundstation_,,groundstation_,Ground Station,AWS,,,,,,,GroundStation,ListConfigs,, guardduty,guardduty,guardduty,guardduty,,guardduty,,,GuardDuty,GuardDuty,,1,,,aws_guardduty_,,guardduty_,GuardDuty,Amazon,,,,,,,GuardDuty,,, health,health,health,health,,health,,,Health,Health,,1,,,aws_health_,,health_,Health,AWS,,x,,,,,Health,,, -healthlake,healthlake,healthlake,healthlake,,healthlake,,,HealthLake,HealthLake,,,2,,aws_healthlake_,,healthlake_,HealthLake,Amazon,,,,,,,HealthLake,,, +healthlake,healthlake,healthlake,healthlake,,healthlake,,,HealthLake,HealthLake,,,2,,aws_healthlake_,,healthlake_,HealthLake,Amazon,,,,,,,HealthLake,ListFHIRDatastores,, honeycode,honeycode,honeycode,honeycode,,honeycode,,,Honeycode,Honeycode,,1,,,aws_honeycode_,,honeycode_,Honeycode,Amazon,,x,,,,,Honeycode,,, iam,iam,iam,iam,,iam,,,IAM,IAM,,1,,,aws_iam_,,iam_,IAM (Identity & Access Management),AWS,,,,,AWS_IAM_ENDPOINT,TF_AWS_IAM_ENDPOINT,IAM,,, inspector,inspector,inspector,inspector,,inspector,,,Inspector,Inspector,,1,,,aws_inspector_,,inspector_,Inspector Classic,Amazon,,,,,,,Inspector,,, @@ -200,36 +200,36 @@ iottwinmaker,iottwinmaker,iottwinmaker,iottwinmaker,,iottwinmaker,,,IoTTwinMaker iotwireless,iotwireless,iotwireless,iotwireless,,iotwireless,,,IoTWireless,IoTWireless,,1,,,aws_iotwireless_,,iotwireless_,IoT Wireless,AWS,,x,,,,,IoT Wireless,,, ,,,,,,,,,,,,,,,,,IQ,AWS,x,,,,,,,,,No SDK support ivs,ivs,ivs,ivs,,ivs,,,IVS,IVS,,1,,,aws_ivs_,,ivs_,IVS (Interactive Video),Amazon,,,,,,,ivs,,, -ivschat,ivschat,ivschat,ivschat,,ivschat,,,IVSChat,Ivschat,,,2,,aws_ivschat_,,ivschat_,IVS (Interactive Video) Chat,Amazon,,,,,,,ivschat,,, -kendra,kendra,kendra,kendra,,kendra,,,Kendra,Kendra,,,2,,aws_kendra_,,kendra_,Kendra,Amazon,,,,,,,kendra,,, -keyspaces,keyspaces,keyspaces,keyspaces,,keyspaces,,,Keyspaces,Keyspaces,,,2,,aws_keyspaces_,,keyspaces_,Keyspaces (for Apache Cassandra),Amazon,,,,,,,Keyspaces,,, -kinesis,kinesis,kinesis,kinesis,,kinesis,,,Kinesis,Kinesis,x,,2,aws_kinesis_stream,aws_kinesis_,,kinesis_stream;kinesis_resource_policy,Kinesis,Amazon,,,,,,,Kinesis,,, +ivschat,ivschat,ivschat,ivschat,,ivschat,,,IVSChat,Ivschat,,,2,,aws_ivschat_,,ivschat_,IVS (Interactive Video) Chat,Amazon,,,,,,,ivschat,ListRooms,, +kendra,kendra,kendra,kendra,,kendra,,,Kendra,Kendra,,,2,,aws_kendra_,,kendra_,Kendra,Amazon,,,,,,,kendra,ListIndices,, +keyspaces,keyspaces,keyspaces,keyspaces,,keyspaces,,,Keyspaces,Keyspaces,,,2,,aws_keyspaces_,,keyspaces_,Keyspaces (for Apache Cassandra),Amazon,,,,,,,Keyspaces,ListKeyspaces,, +kinesis,kinesis,kinesis,kinesis,,kinesis,,,Kinesis,Kinesis,x,,2,aws_kinesis_stream,aws_kinesis_,,kinesis_stream;kinesis_resource_policy,Kinesis,Amazon,,,,,,,Kinesis,ListStreams,, kinesisanalytics,kinesisanalytics,kinesisanalytics,kinesisanalytics,,kinesisanalytics,,,KinesisAnalytics,KinesisAnalytics,,1,,aws_kinesis_analytics_,aws_kinesisanalytics_,,kinesis_analytics_,Kinesis Analytics,Amazon,,,,,,,Kinesis Analytics,,, kinesisanalyticsv2,kinesisanalyticsv2,kinesisanalyticsv2,kinesisanalyticsv2,,kinesisanalyticsv2,,,KinesisAnalyticsV2,KinesisAnalyticsV2,,1,,,aws_kinesisanalyticsv2_,,kinesisanalyticsv2_,Kinesis Analytics V2,Amazon,,,,,,,Kinesis Analytics V2,,, -firehose,firehose,firehose,firehose,,firehose,,,Firehose,Firehose,,,2,aws_kinesis_firehose_,aws_firehose_,,kinesis_firehose_,Kinesis Firehose,Amazon,,,,,,,Firehose,,, +firehose,firehose,firehose,firehose,,firehose,,,Firehose,Firehose,,,2,aws_kinesis_firehose_,aws_firehose_,,kinesis_firehose_,Kinesis Firehose,Amazon,,,,,,,Firehose,ListDeliveryStreams,, kinesisvideo,kinesisvideo,kinesisvideo,kinesisvideo,,kinesisvideo,,,KinesisVideo,KinesisVideo,,1,,,aws_kinesisvideo_,,kinesis_video_,Kinesis Video,Amazon,,,,,,,Kinesis Video,,, kinesis-video-archived-media,kinesisvideoarchivedmedia,kinesisvideoarchivedmedia,kinesisvideoarchivedmedia,,kinesisvideoarchivedmedia,,,KinesisVideoArchivedMedia,KinesisVideoArchivedMedia,,1,,,aws_kinesisvideoarchivedmedia_,,kinesisvideoarchivedmedia_,Kinesis Video Archived Media,Amazon,,x,,,,,Kinesis Video Archived Media,,, kinesis-video-media,kinesisvideomedia,kinesisvideomedia,kinesisvideomedia,,kinesisvideomedia,,,KinesisVideoMedia,KinesisVideoMedia,,1,,,aws_kinesisvideomedia_,,kinesisvideomedia_,Kinesis Video Media,Amazon,,x,,,,,Kinesis Video Media,,, kinesis-video-signaling,kinesisvideosignaling,kinesisvideosignalingchannels,kinesisvideosignaling,,kinesisvideosignaling,,kinesisvideosignalingchannels,KinesisVideoSignaling,KinesisVideoSignalingChannels,,1,,,aws_kinesisvideosignaling_,,kinesisvideosignaling_,Kinesis Video Signaling,Amazon,,x,,,,,Kinesis Video Signaling,,, kms,kms,kms,kms,,kms,,,KMS,KMS,,1,,,aws_kms_,,kms_,KMS (Key Management),AWS,,,,,,,KMS,,, lakeformation,lakeformation,lakeformation,lakeformation,,lakeformation,,,LakeFormation,LakeFormation,,1,,,aws_lakeformation_,,lakeformation_,Lake Formation,AWS,,,,,,,LakeFormation,,, -lambda,lambda,lambda,lambda,,lambda,,,Lambda,Lambda,,1,2,,aws_lambda_,,lambda_,Lambda,AWS,,,,,,,Lambda,,, -launch-wizard,launchwizard,launchwizard,launchwizard,,launchwizard,,,LaunchWizard,LaunchWizard,,,2,,aws_launchwizard_,,launchwizard_,Launch Wizard,AWS,,,,,,,Launch Wizard,,, +lambda,lambda,lambda,lambda,,lambda,,,Lambda,Lambda,,1,2,,aws_lambda_,,lambda_,Lambda,AWS,,,,,,,Lambda,ListFunctions,, +launch-wizard,launchwizard,launchwizard,launchwizard,,launchwizard,,,LaunchWizard,LaunchWizard,,,2,,aws_launchwizard_,,launchwizard_,Launch Wizard,AWS,,,,,,,Launch Wizard,ListWorkloads,, lex-models,lexmodels,lexmodelbuildingservice,lexmodelbuildingservice,,lexmodels,,lexmodelbuilding;lexmodelbuildingservice;lex,LexModels,LexModelBuildingService,,1,,aws_lex_,aws_lexmodels_,,lex_,Lex Model Building,Amazon,,,,,,,Lex Model Building Service,,, lexv2-models,lexv2models,lexmodelsv2,lexmodelsv2,,lexv2models,,lexmodelsv2,LexV2Models,LexModelsV2,,,2,,aws_lexv2models_,,lexv2models_,Lex V2 Models,Amazon,,,,,,,Lex Models V2,,, lex-runtime,lexruntime,lexruntimeservice,lexruntimeservice,,lexruntime,,lexruntimeservice,LexRuntime,LexRuntimeService,,1,,,aws_lexruntime_,,lexruntime_,Lex Runtime,Amazon,,x,,,,,Lex Runtime Service,,, lexv2-runtime,lexv2runtime,lexruntimev2,lexruntimev2,,lexruntimev2,,lexv2runtime,LexRuntimeV2,LexRuntimeV2,,1,,,aws_lexruntimev2_,,lexruntimev2_,Lex Runtime V2,Amazon,,x,,,,,Lex Runtime V2,,, license-manager,licensemanager,licensemanager,licensemanager,,licensemanager,,,LicenseManager,LicenseManager,,1,,,aws_licensemanager_,,licensemanager_,License Manager,AWS,,,,,,,License Manager,,, -lightsail,lightsail,lightsail,lightsail,,lightsail,,,Lightsail,Lightsail,x,,2,,aws_lightsail_,,lightsail_,Lightsail,Amazon,,,,,,,Lightsail,,, +lightsail,lightsail,lightsail,lightsail,,lightsail,,,Lightsail,Lightsail,x,,2,,aws_lightsail_,,lightsail_,Lightsail,Amazon,,,,,,,Lightsail,GetInstances,, location,location,locationservice,location,,location,,locationservice,Location,LocationService,,1,,,aws_location_,,location_,Location,Amazon,,,,,,,Location,,, lookoutequipment,lookoutequipment,lookoutequipment,lookoutequipment,,lookoutequipment,,,LookoutEquipment,LookoutEquipment,,1,,,aws_lookoutequipment_,,lookoutequipment_,Lookout for Equipment,Amazon,,x,,,,,LookoutEquipment,,, -lookoutmetrics,lookoutmetrics,lookoutmetrics,lookoutmetrics,,lookoutmetrics,,,LookoutMetrics,LookoutMetrics,,,2,,aws_lookoutmetrics_,,lookoutmetrics_,Lookout for Metrics,Amazon,,,,,,,LookoutMetrics,,, +lookoutmetrics,lookoutmetrics,lookoutmetrics,lookoutmetrics,,lookoutmetrics,,,LookoutMetrics,LookoutMetrics,,,2,,aws_lookoutmetrics_,,lookoutmetrics_,Lookout for Metrics,Amazon,,,,,,,LookoutMetrics,ListMetricSets,, lookoutvision,lookoutvision,lookoutforvision,lookoutvision,,lookoutvision,,lookoutforvision,LookoutVision,LookoutForVision,,1,,,aws_lookoutvision_,,lookoutvision_,Lookout for Vision,Amazon,,x,,,,,LookoutVision,,, ,,,,,,,,,,,,,,,,,Lumberyard,Amazon,x,,,,,,,,,No SDK support machinelearning,machinelearning,machinelearning,machinelearning,,machinelearning,,,MachineLearning,MachineLearning,,1,,,aws_machinelearning_,,machinelearning_,Machine Learning,Amazon,,x,,,,,Machine Learning,,, macie2,macie2,macie2,macie2,,macie2,,,Macie2,Macie2,,1,,,aws_macie2_,,macie2_,Macie,Amazon,,,,,,,Macie2,,, macie,macie,macie,macie,,macie,,,Macie,Macie,,1,,,aws_macie_,,macie_,Macie Classic,Amazon,,x,,,,,Macie,,, -m2,m2,m2,m2,,m2,,,M2,M2,,,2,,aws_m2_,,m2_,Mainframe Modernization,AWS,,,,,,,m2,,, +m2,m2,m2,m2,,m2,,,M2,M2,,,2,,aws_m2_,,m2_,Mainframe Modernization,AWS,,,,,,,m2,ListApplications,, managedblockchain,managedblockchain,managedblockchain,managedblockchain,,managedblockchain,,,ManagedBlockchain,ManagedBlockchain,,1,,,aws_managedblockchain_,,managedblockchain_,Managed Blockchain,Amazon,,x,,,,,ManagedBlockchain,,, grafana,grafana,managedgrafana,grafana,,grafana,,managedgrafana;amg,Grafana,ManagedGrafana,,1,,,aws_grafana_,,grafana_,Managed Grafana,Amazon,,,,,,,grafana,,, kafka,kafka,kafka,kafka,,kafka,,msk,Kafka,Kafka,x,,2,aws_msk_,aws_kafka_,,msk_,Managed Streaming for Kafka,Amazon,,,,,,,Kafka,,, @@ -251,7 +251,7 @@ mobile,mobile,mobile,mobile,,mobile,,,Mobile,Mobile,,1,,,aws_mobile_,,mobile_,Mo ,,,,,,,,,,,,,,,,,Mobile SDK for Unity,AWS,x,,,,,,,,,No SDK support ,,,,,,,,,,,,,,,,,Mobile SDK for Xamarin,AWS,x,,,,,,,,,No SDK support ,,,,,,,,,,,,,,,,,Monitron,Amazon,x,,,,,,,,,No SDK support -mq,mq,mq,mq,,mq,,,MQ,MQ,,,2,,aws_mq_,,mq_,MQ,Amazon,,,,,,,mq,,, +mq,mq,mq,mq,,mq,,,MQ,MQ,,,2,,aws_mq_,,mq_,MQ,Amazon,,,,,,,mq,ListBrokers,, mturk,mturk,mturk,mturk,,mturk,,,MTurk,MTurk,,1,,,aws_mturk_,,mturk_,MTurk (Mechanical Turk),Amazon,,x,,,,,MTurk,,, mwaa,mwaa,mwaa,mwaa,,mwaa,,,MWAA,MWAA,,1,,,aws_mwaa_,,mwaa_,MWAA (Managed Workflows for Apache Airflow),Amazon,,,,,,,MWAA,,, neptune,neptune,neptune,neptune,,neptune,,,Neptune,Neptune,,1,,,aws_neptune_,,neptune_,Neptune,Amazon,,,,,,,Neptune,,, @@ -261,7 +261,7 @@ networkmanager,networkmanager,networkmanager,networkmanager,,networkmanager,,,Ne nimble,nimble,nimblestudio,nimble,,nimble,,nimblestudio,Nimble,NimbleStudio,,1,,,aws_nimble_,,nimble_,Nimble Studio,Amazon,,x,,,,,nimble,,, oam,oam,oam,oam,,oam,,cloudwatchobservabilityaccessmanager,ObservabilityAccessManager,OAM,,,2,,aws_oam_,,oam_,CloudWatch Observability Access Manager,Amazon,,,,,,,OAM,,, opensearch,opensearch,opensearchservice,opensearch,,opensearch,,opensearchservice,OpenSearch,OpenSearchService,,1,,,aws_opensearch_,,opensearch_,OpenSearch,Amazon,,,,,,,OpenSearch,,, -opensearchserverless,opensearchserverless,opensearchserverless,opensearchserverless,,opensearchserverless,,,OpenSearchServerless,OpenSearchServerless,,,2,,aws_opensearchserverless_,,opensearchserverless_,OpenSearch Serverless,Amazon,,,,,,,OpenSearchServerless,,, +opensearchserverless,opensearchserverless,opensearchserverless,opensearchserverless,,opensearchserverless,,,OpenSearchServerless,OpenSearchServerless,,,2,,aws_opensearchserverless_,,opensearchserverless_,OpenSearch Serverless,Amazon,,,,,,,OpenSearchServerless,ListCollections,, osis,osis,osis,osis,,osis,,opensearchingestion,OpenSearchIngestion,OSIS,,,2,,aws_osis_,,osis_,OpenSearch Ingestion,Amazon,,,,,,,OSIS,,, opsworks,opsworks,opsworks,opsworks,,opsworks,,,OpsWorks,OpsWorks,,1,,,aws_opsworks_,,opsworks_,OpsWorks,AWS,,,,,,,OpsWorks,,, opsworks-cm,opsworkscm,opsworkscm,opsworkscm,,opsworkscm,,,OpsWorksCM,OpsWorksCM,,1,,,aws_opsworkscm_,,opsworkscm_,OpsWorks CM,AWS,,x,,,,,OpsWorksCM,,, @@ -270,24 +270,24 @@ outposts,outposts,outposts,outposts,,outposts,,,Outposts,Outposts,,1,,,aws_outpo ,,,,,ec2outposts,ec2,,EC2Outposts,,,,,aws_ec2_(coip_pool|local_gateway),aws_ec2outposts_,outposts_,ec2_coip_pool;ec2_local_gateway,Outposts (EC2),AWS,x,,,x,,,,,,Part of EC2 panorama,panorama,panorama,panorama,,panorama,,,Panorama,Panorama,,1,,,aws_panorama_,,panorama_,Panorama,AWS,,x,,,,,Panorama,,, ,,,,,,,,,,,,,,,,,ParallelCluster,AWS,x,,,,,,,,,No SDK support -pca-connector-ad,pcaconnectorad,pcaconnectorad,pcaconnectorad,,pcaconnectorad,,,PCAConnectorAD,PcaConnectorAd,,,2,,aws_pcaconnectorad_,,pcaconnectorad_,Private CA Connector for Active Directory,AWS,,,,,,,Pca Connector Ad,,, +pca-connector-ad,pcaconnectorad,pcaconnectorad,pcaconnectorad,,pcaconnectorad,,,PCAConnectorAD,PcaConnectorAd,,,2,,aws_pcaconnectorad_,,pcaconnectorad_,Private CA Connector for Active Directory,AWS,,,,,,,Pca Connector Ad,ListConnectors,, personalize,personalize,personalize,personalize,,personalize,,,Personalize,Personalize,,1,,,aws_personalize_,,personalize_,Personalize,Amazon,,x,,,,,Personalize,,, personalize-events,personalizeevents,personalizeevents,personalizeevents,,personalizeevents,,,PersonalizeEvents,PersonalizeEvents,,1,,,aws_personalizeevents_,,personalizeevents_,Personalize Events,Amazon,,x,,,,,Personalize Events,,, personalize-runtime,personalizeruntime,personalizeruntime,personalizeruntime,,personalizeruntime,,,PersonalizeRuntime,PersonalizeRuntime,,1,,,aws_personalizeruntime_,,personalizeruntime_,Personalize Runtime,Amazon,,x,,,,,Personalize Runtime,,, pinpoint,pinpoint,pinpoint,pinpoint,,pinpoint,,,Pinpoint,Pinpoint,,1,,,aws_pinpoint_,,pinpoint_,Pinpoint,Amazon,,,,,,,Pinpoint,,, pinpoint-email,pinpointemail,pinpointemail,pinpointemail,,pinpointemail,,,PinpointEmail,PinpointEmail,,1,,,aws_pinpointemail_,,pinpointemail_,Pinpoint Email,Amazon,,x,,,,,Pinpoint Email,,, pinpoint-sms-voice,pinpointsmsvoice,pinpointsmsvoice,pinpointsmsvoice,,pinpointsmsvoice,,,PinpointSMSVoice,PinpointSMSVoice,,1,,,aws_pinpointsmsvoice_,,pinpointsmsvoice_,Pinpoint SMS and Voice,Amazon,,x,,,,,Pinpoint SMS Voice,,, -pipes,pipes,pipes,pipes,,pipes,,,Pipes,Pipes,,,2,,aws_pipes_,,pipes_,EventBridge Pipes,Amazon,,,,,,,Pipes,,, -polly,polly,polly,polly,,polly,,,Polly,Polly,,,2,,aws_polly_,,polly_,Polly,Amazon,,,,,,,Polly,,, +pipes,pipes,pipes,pipes,,pipes,,,Pipes,Pipes,,,2,,aws_pipes_,,pipes_,EventBridge Pipes,Amazon,,,,,,,Pipes,ListPipes,, +polly,polly,polly,polly,,polly,,,Polly,Polly,,,2,,aws_polly_,,polly_,Polly,Amazon,,,,,,,Polly,ListLexicons,, ,,,,,,,,,,,,,,,,,Porting Assistant for .NET,,x,,,,,,,,,No SDK support -pricing,pricing,pricing,pricing,,pricing,,,Pricing,Pricing,,,2,,aws_pricing_,,pricing_,Pricing Calculator,AWS,,,,,,,Pricing,,, +pricing,pricing,pricing,pricing,,pricing,,,Pricing,Pricing,,,2,,aws_pricing_,,pricing_,Pricing Calculator,AWS,,,,,,,Pricing,DescribeServices,, proton,proton,proton,proton,,proton,,,Proton,Proton,,1,,,aws_proton_,,proton_,Proton,AWS,,x,,,,,Proton,,, -qbusiness,qbusiness,qbusiness,qbusiness,,qbusiness,,,QBusiness,QBusiness,,,2,,aws_qbusiness_,,qbusiness_,Amazon Q Business,Amazon,,,,,,,QBusiness,,, -qldb,qldb,qldb,qldb,,qldb,,,QLDB,QLDB,,,2,,aws_qldb_,,qldb_,QLDB (Quantum Ledger Database),Amazon,,,,,,,QLDB,,, +qbusiness,qbusiness,qbusiness,qbusiness,,qbusiness,,,QBusiness,QBusiness,,,2,,aws_qbusiness_,,qbusiness_,Amazon Q Business,Amazon,,,,,,,QBusiness,ListApplications,, +qldb,qldb,qldb,qldb,,qldb,,,QLDB,QLDB,,,2,,aws_qldb_,,qldb_,QLDB (Quantum Ledger Database),Amazon,,,,,,,QLDB,ListLedgers,, qldb-session,qldbsession,qldbsession,qldbsession,,qldbsession,,,QLDBSession,QLDBSession,,1,,,aws_qldbsession_,,qldbsession_,QLDB Session,Amazon,,x,,,,,QLDB Session,,, quicksight,quicksight,quicksight,quicksight,,quicksight,,,QuickSight,QuickSight,,1,,,aws_quicksight_,,quicksight_,QuickSight,Amazon,,,,,,,QuickSight,,, ram,ram,ram,ram,,ram,,,RAM,RAM,,1,,,aws_ram_,,ram_,RAM (Resource Access Manager),AWS,,,,,,,RAM,,, -rds,rds,rds,rds,,rds,,,RDS,RDS,,1,2,aws_(db_|rds_),aws_rds_,,rds_;db_,RDS (Relational Database),Amazon,,,,,,,RDS,,, +rds,rds,rds,rds,,rds,,,RDS,RDS,,1,2,aws_(db_|rds_),aws_rds_,,rds_;db_,RDS (Relational Database),Amazon,,,,,,,RDS,DescribeDBInstances,, rds-data,rdsdata,rdsdataservice,rdsdata,,rdsdata,,rdsdataservice,RDSData,RDSDataService,,1,,,aws_rdsdata_,,rdsdata_,RDS Data,Amazon,,x,,,,,RDS Data,,, pi,pi,pi,pi,,pi,,,PI,PI,,1,,,aws_pi_,,pi_,RDS Performance Insights (PI),Amazon,,x,,,,,PI,,, rbin,rbin,recyclebin,rbin,,rbin,,recyclebin,RBin,RecycleBin,,,2,,aws_rbin_,,rbin_,Recycle Bin (RBin),Amazon,,,,,,,rbin,,, @@ -295,22 +295,22 @@ rbin,rbin,recyclebin,rbin,,rbin,,recyclebin,RBin,RecycleBin,,,2,,aws_rbin_,,rbin redshift,redshift,redshift,redshift,,redshift,,,Redshift,Redshift,,1,,,aws_redshift_,,redshift_,Redshift,Amazon,,,,,,,Redshift,,, redshift-data,redshiftdata,redshiftdataapiservice,redshiftdata,,redshiftdata,,redshiftdataapiservice,RedshiftData,RedshiftDataAPIService,,,2,,aws_redshiftdata_,,redshiftdata_,Redshift Data,Amazon,,,,,,,Redshift Data,,, redshift-serverless,redshiftserverless,redshiftserverless,redshiftserverless,,redshiftserverless,,,RedshiftServerless,RedshiftServerless,,1,,,aws_redshiftserverless_,,redshiftserverless_,Redshift Serverless,Amazon,,,,,,,Redshift Serverless,,, -rekognition,rekognition,rekognition,rekognition,,rekognition,,,Rekognition,Rekognition,,,2,,aws_rekognition_,,rekognition_,Rekognition,Amazon,,,,,,,Rekognition,,, +rekognition,rekognition,rekognition,rekognition,,rekognition,,,Rekognition,Rekognition,,,2,,aws_rekognition_,,rekognition_,Rekognition,Amazon,,,,,,,Rekognition,ListCollections,, resiliencehub,resiliencehub,resiliencehub,resiliencehub,,resiliencehub,,,ResilienceHub,ResilienceHub,,1,,,aws_resiliencehub_,,resiliencehub_,Resilience Hub,AWS,,x,,,,,resiliencehub,,, -resource-explorer-2,resourceexplorer2,resourceexplorer2,resourceexplorer2,,resourceexplorer2,,,ResourceExplorer2,ResourceExplorer2,,,2,,aws_resourceexplorer2_,,resourceexplorer2_,Resource Explorer,AWS,,,,,,,Resource Explorer 2,,, -resource-groups,resourcegroups,resourcegroups,resourcegroups,,resourcegroups,,,ResourceGroups,ResourceGroups,,,2,,aws_resourcegroups_,,resourcegroups_,Resource Groups,AWS,,,,,,,Resource Groups,,, +resource-explorer-2,resourceexplorer2,resourceexplorer2,resourceexplorer2,,resourceexplorer2,,,ResourceExplorer2,ResourceExplorer2,,,2,,aws_resourceexplorer2_,,resourceexplorer2_,Resource Explorer,AWS,,,,,,,Resource Explorer 2,ListIndexes,, +resource-groups,resourcegroups,resourcegroups,resourcegroups,,resourcegroups,,,ResourceGroups,ResourceGroups,,,2,,aws_resourcegroups_,,resourcegroups_,Resource Groups,AWS,,,,,,,Resource Groups,ListGroups,, resourcegroupstaggingapi,resourcegroupstaggingapi,resourcegroupstaggingapi,resourcegroupstaggingapi,,resourcegroupstaggingapi,,resourcegroupstagging,ResourceGroupsTaggingAPI,ResourceGroupsTaggingAPI,,,2,,aws_resourcegroupstaggingapi_,,resourcegroupstaggingapi_,Resource Groups Tagging,AWS,,,,,,,Resource Groups Tagging API,,, robomaker,robomaker,robomaker,robomaker,,robomaker,,,RoboMaker,RoboMaker,,1,,,aws_robomaker_,,robomaker_,RoboMaker,AWS,,x,,,,,RoboMaker,,, -rolesanywhere,rolesanywhere,rolesanywhere,rolesanywhere,,rolesanywhere,,,RolesAnywhere,RolesAnywhere,,,2,,aws_rolesanywhere_,,rolesanywhere_,Roles Anywhere,AWS,,,,,,,RolesAnywhere,,, +rolesanywhere,rolesanywhere,rolesanywhere,rolesanywhere,,rolesanywhere,,,RolesAnywhere,RolesAnywhere,,,2,,aws_rolesanywhere_,,rolesanywhere_,Roles Anywhere,AWS,,,,,,,RolesAnywhere,ListProfiles,, route53,route53,route53,route53,,route53,,,Route53,Route53,x,1,,aws_route53_(?!resolver_),aws_route53_,,route53_cidr_;route53_delegation_;route53_health_;route53_hosted_;route53_key_;route53_query_;route53_record;route53_traffic_;route53_vpc_;route53_zone,Route 53,Amazon,,,,,,,Route 53,,, -route53domains,route53domains,route53domains,route53domains,,route53domains,,,Route53Domains,Route53Domains,x,,2,,aws_route53domains_,,route53domains_,Route 53 Domains,Amazon,,,,,,,Route 53 Domains,,, +route53domains,route53domains,route53domains,route53domains,,route53domains,,,Route53Domains,Route53Domains,x,,2,,aws_route53domains_,,route53domains_,Route 53 Domains,Amazon,,,,,,,Route 53 Domains,ListDomains,, route53-recovery-cluster,route53recoverycluster,route53recoverycluster,route53recoverycluster,,route53recoverycluster,,,Route53RecoveryCluster,Route53RecoveryCluster,,1,,,aws_route53recoverycluster_,,route53recoverycluster_,Route 53 Recovery Cluster,Amazon,,x,,,,,Route53 Recovery Cluster,,, route53-recovery-control-config,route53recoverycontrolconfig,route53recoverycontrolconfig,route53recoverycontrolconfig,,route53recoverycontrolconfig,,,Route53RecoveryControlConfig,Route53RecoveryControlConfig,x,1,,,aws_route53recoverycontrolconfig_,,route53recoverycontrolconfig_,Route 53 Recovery Control Config,Amazon,,,,,,,Route53 Recovery Control Config,,, route53-recovery-readiness,route53recoveryreadiness,route53recoveryreadiness,route53recoveryreadiness,,route53recoveryreadiness,,,Route53RecoveryReadiness,Route53RecoveryReadiness,x,1,,,aws_route53recoveryreadiness_,,route53recoveryreadiness_,Route 53 Recovery Readiness,Amazon,,,,,,,Route53 Recovery Readiness,,, route53resolver,route53resolver,route53resolver,route53resolver,,route53resolver,,,Route53Resolver,Route53Resolver,,1,,aws_route53_resolver_,aws_route53resolver_,,route53_resolver_,Route 53 Resolver,Amazon,,,,,,,Route53Resolver,,, s3api,s3api,s3,s3,,s3,,s3api,S3,S3,x,,2,aws_(canonical_user_id|s3_bucket|s3_object|s3_directory_bucket),aws_s3_,,s3_bucket;s3_directory_bucket;s3_object;canonical_user_id,S3 (Simple Storage),Amazon,,,,,AWS_S3_ENDPOINT,TF_AWS_S3_ENDPOINT,S3,,, -s3control,s3control,s3control,s3control,,s3control,,,S3Control,S3Control,,,2,aws_(s3_account_|s3control_|s3_access_),aws_s3control_,,s3control;s3_account_;s3_access_,S3 Control,Amazon,,,,,,,S3 Control,,, -glacier,glacier,glacier,glacier,,glacier,,,Glacier,Glacier,,,2,,aws_glacier_,,glacier_,S3 Glacier,Amazon,,,,,,,Glacier,,, +s3control,s3control,s3control,s3control,,s3control,,,S3Control,S3Control,,,2,aws_(s3_account_|s3control_|s3_access_),aws_s3control_,,s3control;s3_account_;s3_access_,S3 Control,Amazon,,,,,,,S3 Control,ListJobs,, +glacier,glacier,glacier,glacier,,glacier,,,Glacier,Glacier,,,2,,aws_glacier_,,glacier_,S3 Glacier,Amazon,,,,,,,Glacier,ListVaults,, s3outposts,s3outposts,s3outposts,s3outposts,,s3outposts,,,S3Outposts,S3Outposts,,1,,,aws_s3outposts_,,s3outposts_,S3 on Outposts,Amazon,,,,,,,S3Outposts,,, sagemaker,sagemaker,sagemaker,sagemaker,,sagemaker,,,SageMaker,SageMaker,,1,,,aws_sagemaker_,,sagemaker_,SageMaker,Amazon,,,,,,,SageMaker,,, sagemaker-a2i-runtime,sagemakera2iruntime,augmentedairuntime,sagemakera2iruntime,,sagemakera2iruntime,,augmentedairuntime,SageMakerA2IRuntime,AugmentedAIRuntime,,1,,,aws_sagemakera2iruntime_,,sagemakera2iruntime_,SageMaker A2I (Augmented AI),Amazon,,x,,,,,SageMaker A2I Runtime,,, @@ -321,41 +321,41 @@ sagemaker-runtime,sagemakerruntime,sagemakerruntime,sagemakerruntime,,sagemakerr savingsplans,savingsplans,savingsplans,savingsplans,,savingsplans,,,SavingsPlans,SavingsPlans,,1,,,aws_savingsplans_,,savingsplans_,Savings Plans,AWS,,x,,,,,savingsplans,,, ,,,,,,,,,,,,,,,,,Schema Conversion Tool,AWS,x,,,,,,,,,No SDK support sdb,sdb,simpledb,,simpledb,sdb,,sdb,SimpleDB,SimpleDB,,1,,aws_simpledb_,aws_sdb_,,simpledb_,SDB (SimpleDB),Amazon,,,,,,,SimpleDB,,, -scheduler,scheduler,scheduler,scheduler,,scheduler,,,Scheduler,Scheduler,,,2,,aws_scheduler_,,scheduler_,EventBridge Scheduler,Amazon,,,,,,,Scheduler,,, -secretsmanager,secretsmanager,secretsmanager,secretsmanager,,secretsmanager,,,SecretsManager,SecretsManager,,,2,,aws_secretsmanager_,,secretsmanager_,Secrets Manager,AWS,,,,,,,Secrets Manager,,, -securityhub,securityhub,securityhub,securityhub,,securityhub,,,SecurityHub,SecurityHub,,,2,,aws_securityhub_,,securityhub_,Security Hub,AWS,,,,,,,SecurityHub,,, -securitylake,securitylake,securitylake,securitylake,,securitylake,,,SecurityLake,SecurityLake,,,2,,aws_securitylake_,,securitylake_,Security Lake,Amazon,,,,,,,SecurityLake,,, +scheduler,scheduler,scheduler,scheduler,,scheduler,,,Scheduler,Scheduler,,,2,,aws_scheduler_,,scheduler_,EventBridge Scheduler,Amazon,,,,,,,Scheduler,ListSchedules,, +secretsmanager,secretsmanager,secretsmanager,secretsmanager,,secretsmanager,,,SecretsManager,SecretsManager,,,2,,aws_secretsmanager_,,secretsmanager_,Secrets Manager,AWS,,,,,,,Secrets Manager,ListSecrets,, +securityhub,securityhub,securityhub,securityhub,,securityhub,,,SecurityHub,SecurityHub,,,2,,aws_securityhub_,,securityhub_,Security Hub,AWS,,,,,,,SecurityHub,ListAutomationRules,, +securitylake,securitylake,securitylake,securitylake,,securitylake,,,SecurityLake,SecurityLake,,,2,,aws_securitylake_,,securitylake_,Security Lake,Amazon,,,,,,,SecurityLake,ListDataLakes,, serverlessrepo,serverlessrepo,serverlessapplicationrepository,serverlessapplicationrepository,,serverlessrepo,,serverlessapprepo;serverlessapplicationrepository,ServerlessRepo,ServerlessApplicationRepository,,1,,aws_serverlessapplicationrepository_,aws_serverlessrepo_,,serverlessapplicationrepository_,Serverless Application Repository,AWS,,,,,,,ServerlessApplicationRepository,,, servicecatalog,servicecatalog,servicecatalog,servicecatalog,,servicecatalog,,,ServiceCatalog,ServiceCatalog,,1,,,aws_servicecatalog_,,servicecatalog_,Service Catalog,AWS,,,,,,,Service Catalog,,, servicecatalog-appregistry,servicecatalogappregistry,appregistry,servicecatalogappregistry,,servicecatalogappregistry,,appregistry,ServiceCatalogAppRegistry,AppRegistry,,,2,,aws_servicecatalogappregistry_,,servicecatalogappregistry_,Service Catalog AppRegistry,AWS,,,,,,,Service Catalog AppRegistry,,, -service-quotas,servicequotas,servicequotas,servicequotas,,servicequotas,,,ServiceQuotas,ServiceQuotas,,,2,,aws_servicequotas_,,servicequotas_,Service Quotas,,,,,,,,Service Quotas,,, +service-quotas,servicequotas,servicequotas,servicequotas,,servicequotas,,,ServiceQuotas,ServiceQuotas,,,2,,aws_servicequotas_,,servicequotas_,Service Quotas,,,,,,,,Service Quotas,ListServices,, ses,ses,ses,ses,,ses,,,SES,SES,,1,,,aws_ses_,,ses_,SES (Simple Email),Amazon,,,,,,,SES,,, -sesv2,sesv2,sesv2,sesv2,,sesv2,,,SESV2,SESV2,,,2,,aws_sesv2_,,sesv2_,SESv2 (Simple Email V2),Amazon,,,,,,,SESv2,,, +sesv2,sesv2,sesv2,sesv2,,sesv2,,,SESV2,SESV2,,,2,,aws_sesv2_,,sesv2_,SESv2 (Simple Email V2),Amazon,,,,,,,SESv2,ListContactLists,, stepfunctions,stepfunctions,sfn,sfn,,sfn,,stepfunctions,SFN,SFN,,1,,,aws_sfn_,,sfn_,SFN (Step Functions),AWS,,,,,,,SFN,,, shield,shield,shield,shield,,shield,,,Shield,Shield,x,1,,,aws_shield_,,shield_,Shield,AWS,,,,,,,Shield,,, -signer,signer,signer,signer,,signer,,,Signer,Signer,,,2,,aws_signer_,,signer_,Signer,AWS,,,,,,,signer,,, +signer,signer,signer,signer,,signer,,,Signer,Signer,,,2,,aws_signer_,,signer_,Signer,AWS,,,,,,,signer,ListSigningJobs,, sms,sms,sms,sms,,sms,,,SMS,SMS,,1,,,aws_sms_,,sms_,SMS (Server Migration),AWS,,x,,,,,SMS,,, snow-device-management,snowdevicemanagement,snowdevicemanagement,snowdevicemanagement,,snowdevicemanagement,,,SnowDeviceManagement,SnowDeviceManagement,,1,,,aws_snowdevicemanagement_,,snowdevicemanagement_,Snow Device Management,AWS,,x,,,,,Snow Device Management,,, snowball,snowball,snowball,snowball,,snowball,,,Snowball,Snowball,,1,,,aws_snowball_,,snowball_,Snow Family,AWS,,x,,,,,Snowball,,, -sns,sns,sns,sns,,sns,,,SNS,SNS,,,2,,aws_sns_,,sns_,SNS (Simple Notification),Amazon,,,,,,,SNS,,, -sqs,sqs,sqs,sqs,,sqs,,,SQS,SQS,,,2,,aws_sqs_,,sqs_,SQS (Simple Queue),Amazon,,,,,,,SQS,,, -ssm,ssm,ssm,ssm,,ssm,,,SSM,SSM,,1,2,,aws_ssm_,,ssm_,SSM (Systems Manager),AWS,,,,,,,SSM,,, -ssm-contacts,ssmcontacts,ssmcontacts,ssmcontacts,,ssmcontacts,,,SSMContacts,SSMContacts,,,2,,aws_ssmcontacts_,,ssmcontacts_,SSM Contacts,AWS,,,,,,,SSM Contacts,,, -ssm-incidents,ssmincidents,ssmincidents,ssmincidents,,ssmincidents,,,SSMIncidents,SSMIncidents,,,2,,aws_ssmincidents_,,ssmincidents_,SSM Incident Manager Incidents,AWS,,,,,,,SSM Incidents,,, -ssm-sap,ssmsap,ssmsap,ssmsap,,ssmsap,,,SSMSAP,SsmSap,,,2,,aws_ssmsap_,,ssmsap_,Systems Manager for SAP,AWS,,,,,,,Ssm Sap,,, +sns,sns,sns,sns,,sns,,,SNS,SNS,,,2,,aws_sns_,,sns_,SNS (Simple Notification),Amazon,,,,,,,SNS,ListSubscriptions,, +sqs,sqs,sqs,sqs,,sqs,,,SQS,SQS,,,2,,aws_sqs_,,sqs_,SQS (Simple Queue),Amazon,,,,,,,SQS,ListQueues,, +ssm,ssm,ssm,ssm,,ssm,,,SSM,SSM,,1,2,,aws_ssm_,,ssm_,SSM (Systems Manager),AWS,,,,,,,SSM,ListDocuments,, +ssm-contacts,ssmcontacts,ssmcontacts,ssmcontacts,,ssmcontacts,,,SSMContacts,SSMContacts,,,2,,aws_ssmcontacts_,,ssmcontacts_,SSM Contacts,AWS,,,,,,,SSM Contacts,ListContacts,, +ssm-incidents,ssmincidents,ssmincidents,ssmincidents,,ssmincidents,,,SSMIncidents,SSMIncidents,,,2,,aws_ssmincidents_,,ssmincidents_,SSM Incident Manager Incidents,AWS,,,,,,,SSM Incidents,ListResponsePlans,, +ssm-sap,ssmsap,ssmsap,ssmsap,,ssmsap,,,SSMSAP,SsmSap,,,2,,aws_ssmsap_,,ssmsap_,Systems Manager for SAP,AWS,,,,,,,Ssm Sap,ListApplications,, sso,sso,sso,sso,,sso,,,SSO,SSO,,1,,,aws_sso_,,sso_,SSO (Single Sign-On),AWS,,x,x,,,,SSO,,, -sso-admin,ssoadmin,ssoadmin,ssoadmin,,ssoadmin,,,SSOAdmin,SSOAdmin,x,,2,,aws_ssoadmin_,,ssoadmin_,SSO Admin,AWS,,,,,,,SSO Admin,,, -identitystore,identitystore,identitystore,identitystore,,identitystore,,,IdentityStore,IdentityStore,,,2,,aws_identitystore_,,identitystore_,SSO Identity Store,AWS,,,,,,,identitystore,,, +sso-admin,ssoadmin,ssoadmin,ssoadmin,,ssoadmin,,,SSOAdmin,SSOAdmin,x,,2,,aws_ssoadmin_,,ssoadmin_,SSO Admin,AWS,,,,,,,SSO Admin,ListInstances,, +identitystore,identitystore,identitystore,identitystore,,identitystore,,,IdentityStore,IdentityStore,,,2,,aws_identitystore_,,identitystore_,SSO Identity Store,AWS,,,,,,,identitystore,ListUsers,"IdentityStoreId: aws_sdkv2.String(""d-1234567890"")", sso-oidc,ssooidc,ssooidc,ssooidc,,ssooidc,,,SSOOIDC,SSOOIDC,,1,,,aws_ssooidc_,,ssooidc_,SSO OIDC,AWS,,x,,,,,SSO OIDC,,, storagegateway,storagegateway,storagegateway,storagegateway,,storagegateway,,,StorageGateway,StorageGateway,,1,,,aws_storagegateway_,,storagegateway_,Storage Gateway,AWS,,,,,,,Storage Gateway,,, sts,sts,sts,sts,,sts,,,STS,STS,x,1,2,aws_caller_identity,aws_sts_,,caller_identity,STS (Security Token),AWS,,,,,AWS_STS_ENDPOINT,TF_AWS_STS_ENDPOINT,STS,,, ,,,,,,,,,,,,,,,,,Sumerian,Amazon,x,,,,,,,,,No SDK support support,support,support,support,,support,,,Support,Support,,1,,,aws_support_,,support_,Support,AWS,,x,,,,,Support,,, -swf,swf,swf,swf,,swf,,,SWF,SWF,,,2,,aws_swf_,,swf_,SWF (Simple Workflow),Amazon,,,,,,,SWF,,, +swf,swf,swf,swf,,swf,,,SWF,SWF,,,2,,aws_swf_,,swf_,SWF (Simple Workflow),Amazon,,,,,,,SWF,ListDomains,"RegistrationStatus: ""REGISTERED""", ,,,,,,,,,,,,,,,,,Tag Editor,AWS,x,,,,,,,,,Part of Resource Groups Tagging textract,textract,textract,textract,,textract,,,Textract,Textract,,1,,,aws_textract_,,textract_,Textract,Amazon,,x,,,,,Textract,,, timestream-query,timestreamquery,timestreamquery,timestreamquery,,timestreamquery,,,TimestreamQuery,TimestreamQuery,,1,,,aws_timestreamquery_,,timestreamquery_,Timestream Query,Amazon,,x,,,,,Timestream Query,,, -timestream-write,timestreamwrite,timestreamwrite,timestreamwrite,,timestreamwrite,,,TimestreamWrite,TimestreamWrite,,,2,,aws_timestreamwrite_,,timestreamwrite_,Timestream Write,Amazon,,,,,,,Timestream Write,,, +timestream-write,timestreamwrite,timestreamwrite,timestreamwrite,,timestreamwrite,,,TimestreamWrite,TimestreamWrite,,,2,,aws_timestreamwrite_,,timestreamwrite_,Timestream Write,Amazon,,,,,,,Timestream Write,ListDatabases,, ,,,,,,,,,,,,,,,,,Tools for PowerShell,AWS,x,,,,,,,,,No SDK support ,,,,,,,,,,,,,,,,,Training and Certification,AWS,x,,,,,,,,,No SDK support transcribe,transcribe,transcribeservice,transcribe,,transcribe,,transcribeservice,Transcribe,TranscribeService,,,2,,aws_transcribe_,,transcribe_,Transcribe,Amazon,,,,,,,Transcribe,,, @@ -366,7 +366,7 @@ translate,translate,translate,translate,,translate,,,Translate,Translate,,1,,,aw ,,,,,,,,,,,,,,,,,Trusted Advisor,AWS,x,,,,,,,,,Part of Support ,,,,,verifiedaccess,ec2,,VerifiedAccess,,,,,aws_verifiedaccess,aws_verifiedaccess_,verifiedaccess_,verifiedaccess_,Verified Access,AWS,x,,,x,,,,,,Part of EC2 ,,,,,vpc,ec2,,VPC,,,,,aws_((default_)?(network_acl|route_table|security_group|subnet|vpc(?!_ipam))|ec2_(managed|network|subnet|traffic)|egress_only_internet|flow_log|internet_gateway|main_route_table_association|nat_gateway|network_interface|prefix_list|route\b),aws_vpc_,vpc_,default_network_;default_route_;default_security_;default_subnet;default_vpc;ec2_managed_;ec2_network_;ec2_subnet_;ec2_traffic_;egress_only_;flow_log;internet_gateway;main_route_;nat_;network_;prefix_list;route_;route\.;security_group;subnet;vpc_dhcp_;vpc_endpoint;vpc_ipv;vpc_network_performance;vpc_peering_;vpc_security_group_;vpc\.;vpcs\.,VPC (Virtual Private Cloud),Amazon,x,,,x,,,,,,Part of EC2 -vpc-lattice,vpclattice,vpclattice,vpclattice,,vpclattice,,,VPCLattice,VPCLattice,,,2,,aws_vpclattice_,,vpclattice_,VPC Lattice,Amazon,,,,,,,VPC Lattice,,, +vpc-lattice,vpclattice,vpclattice,vpclattice,,vpclattice,,,VPCLattice,VPCLattice,,,2,,aws_vpclattice_,,vpclattice_,VPC Lattice,Amazon,,,,,,,VPC Lattice,ListServices,, ,,,,,ipam,ec2,,IPAM,,,,,aws_vpc_ipam,aws_ipam_,ipam_,vpc_ipam,VPC IPAM (IP Address Manager),Amazon,x,,,x,,,,,,Part of EC2 ,,,,,vpnclient,ec2,,ClientVPN,,,,,aws_ec2_client_vpn,aws_vpnclient_,vpnclient_,ec2_client_vpn_,VPN (Client),AWS,x,,,x,,,,,,Part of EC2 ,,,,,vpnsite,ec2,,SiteVPN,,,,,aws_(customer_gateway|vpn_),aws_vpnsite_,vpnsite_,customer_gateway;vpn_,VPN (Site-to-Site),AWS,x,,,x,,,,,,Part of EC2 @@ -376,14 +376,14 @@ waf-regional,wafregional,wafregional,wafregional,,wafregional,,,WAFRegional,WAFR ,,,,,,,,,,,,,,,,,WAM (WorkSpaces Application Manager),Amazon,x,,,,,,,,,No SDK support ,,,,,wavelength,ec2,,Wavelength,,,,,aws_ec2_carrier_gateway,aws_wavelength_,wavelength_,ec2_carrier_,Wavelength,AWS,x,,,x,,,,,,Part of EC2 budgets,budgets,budgets,budgets,,budgets,,,Budgets,Budgets,,1,,,aws_budgets_,,budgets_,Web Services Budgets,Amazon,,,,,,,Budgets,,, -wellarchitected,wellarchitected,wellarchitected,wellarchitected,,wellarchitected,,,WellArchitected,WellArchitected,,,2,,aws_wellarchitected_,,wellarchitected_,Well-Architected Tool,AWS,,,,,,,WellArchitected,,, +wellarchitected,wellarchitected,wellarchitected,wellarchitected,,wellarchitected,,,WellArchitected,WellArchitected,,,2,,aws_wellarchitected_,,wellarchitected_,Well-Architected Tool,AWS,,,,,,,WellArchitected,ListProfiles,, workdocs,workdocs,workdocs,workdocs,,workdocs,,,WorkDocs,WorkDocs,,1,,,aws_workdocs_,,workdocs_,WorkDocs,Amazon,,x,,,,,WorkDocs,,, worklink,worklink,worklink,worklink,,worklink,,,WorkLink,WorkLink,,1,,,aws_worklink_,,worklink_,WorkLink,Amazon,,,,,,,WorkLink,,, workmail,workmail,workmail,workmail,,workmail,,,WorkMail,WorkMail,,1,,,aws_workmail_,,workmail_,WorkMail,Amazon,,x,,,,,WorkMail,,, workmailmessageflow,workmailmessageflow,workmailmessageflow,workmailmessageflow,,workmailmessageflow,,,WorkMailMessageFlow,WorkMailMessageFlow,,1,,,aws_workmailmessageflow_,,workmailmessageflow_,WorkMail Message Flow,Amazon,,x,,,,,WorkMailMessageFlow,,, -workspaces,workspaces,workspaces,workspaces,,workspaces,,,WorkSpaces,WorkSpaces,,,2,,aws_workspaces_,,workspaces_,WorkSpaces,Amazon,,,,,,,WorkSpaces,,, +workspaces,workspaces,workspaces,workspaces,,workspaces,,,WorkSpaces,WorkSpaces,,,2,,aws_workspaces_,,workspaces_,WorkSpaces,Amazon,,,,,,,WorkSpaces,DescribeWorkspaces,, workspaces-web,workspacesweb,workspacesweb,workspacesweb,,workspacesweb,,,WorkSpacesWeb,WorkSpacesWeb,,1,,,aws_workspacesweb_,,workspacesweb_,WorkSpaces Web,Amazon,,x,,,,,WorkSpaces Web,,, -xray,xray,xray,xray,,xray,,,XRay,XRay,,,2,,aws_xray_,,xray_,X-Ray,AWS,,,,,,,XRay,,, -verifiedpermissions,verifiedpermissions,verifiedpermissions,verifiedpermissions,,verifiedpermissions,,,VerifiedPermissions,VerifiedPermissions,,,2,,aws_verifiedpermissions_,,verifiedpermissions_,Verified Permissions,Amazon,,,,,,,VerifiedPermissions,,, +xray,xray,xray,xray,,xray,,,XRay,XRay,,,2,,aws_xray_,,xray_,X-Ray,AWS,,,,,,,XRay,ListResourcePolicies,, +verifiedpermissions,verifiedpermissions,verifiedpermissions,verifiedpermissions,,verifiedpermissions,,,VerifiedPermissions,VerifiedPermissions,,,2,,aws_verifiedpermissions_,,verifiedpermissions_,Verified Permissions,Amazon,,,,,,,VerifiedPermissions,ListPolicyStores,, codecatalyst,codecatalyst,codecatalyst,codecatalyst,,codecatalyst,,,CodeCatalyst,CodeCatalyst,,,2,,aws_codecatalyst_,,codecatalyst_,CodeCatalyst,Amazon,,,,,,,CodeCatalyst,,, -mediapackagev2,mediapackagev2,mediapackagev2,mediapackagev2,,mediapackagev2,,,MediaPackageV2,MediaPackageV2,,,2,aws_media_packagev2_,aws_mediapackagev2_,,media_packagev2_,Elemental MediaPackage Version 2,AWS,,,,,,,MediaPackageV2,,, \ No newline at end of file +mediapackagev2,mediapackagev2,mediapackagev2,mediapackagev2,,mediapackagev2,,,MediaPackageV2,MediaPackageV2,,,2,aws_media_packagev2_,aws_mediapackagev2_,,media_packagev2_,Elemental MediaPackage Version 2,AWS,,,,,,,MediaPackageV2,ListChannelGroups,, From ef726029d71ce46ee6c0b13f787f45f547947480 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Tue, 16 Jan 2024 16:32:05 -0800 Subject: [PATCH 5/7] Adds `checknames` check --- internal/generate/checknames/main.go | 8 +++++++- names/data/names_data.csv | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/generate/checknames/main.go b/internal/generate/checknames/main.go index 30764e44046..fdf75072dba 100644 --- a/internal/generate/checknames/main.go +++ b/internal/generate/checknames/main.go @@ -177,6 +177,12 @@ func main() { log.Printf("in service data, line %d, for service %s, SdkId is required unless Exclude is set", i+lineOffset, l.HumanFriendly()) } + if l.EndpointAPICall() == "" { + if l.ClientSDKV2() && len(l.Aliases()) == 0 && l.DeprecatedEnvVar() == "" && l.TfAwsEnvVar() == "" { + log.Printf("in service data, line %d, for service %s, EndpointAPICall is required for SDKv2 services without aliases or custom envvars", i+lineOffset, l.HumanFriendly()) + } + } + rre := l.ResourcePrefixActual() if rre == "" { @@ -191,7 +197,7 @@ func main() { allChecks++ } - fmt.Printf(" Performed %d checks on service data, 0 errors.\n", (allChecks * 39)) + fmt.Printf(" Performed %d checks on service data, 0 errors.\n", (allChecks * 40)) var fileErrs bool diff --git a/names/data/names_data.csv b/names/data/names_data.csv index 78ee2fdf22b..d505f18e6fd 100644 --- a/names/data/names_data.csv +++ b/names/data/names_data.csv @@ -385,5 +385,5 @@ workspaces,workspaces,workspaces,workspaces,,workspaces,,,WorkSpaces,WorkSpaces, workspaces-web,workspacesweb,workspacesweb,workspacesweb,,workspacesweb,,,WorkSpacesWeb,WorkSpacesWeb,,1,,,aws_workspacesweb_,,workspacesweb_,WorkSpaces Web,Amazon,,x,,,,,WorkSpaces Web,,, xray,xray,xray,xray,,xray,,,XRay,XRay,,,2,,aws_xray_,,xray_,X-Ray,AWS,,,,,,,XRay,ListResourcePolicies,, verifiedpermissions,verifiedpermissions,verifiedpermissions,verifiedpermissions,,verifiedpermissions,,,VerifiedPermissions,VerifiedPermissions,,,2,,aws_verifiedpermissions_,,verifiedpermissions_,Verified Permissions,Amazon,,,,,,,VerifiedPermissions,ListPolicyStores,, -codecatalyst,codecatalyst,codecatalyst,codecatalyst,,codecatalyst,,,CodeCatalyst,CodeCatalyst,,,2,,aws_codecatalyst_,,codecatalyst_,CodeCatalyst,Amazon,,,,,,,CodeCatalyst,,, +codecatalyst,codecatalyst,codecatalyst,codecatalyst,,codecatalyst,,,CodeCatalyst,CodeCatalyst,,,2,,aws_codecatalyst_,,codecatalyst_,CodeCatalyst,Amazon,,,,,,,CodeCatalyst,ListAccessTokens,, mediapackagev2,mediapackagev2,mediapackagev2,mediapackagev2,,mediapackagev2,,,MediaPackageV2,MediaPackageV2,,,2,aws_media_packagev2_,aws_mediapackagev2_,,media_packagev2_,Elemental MediaPackage Version 2,AWS,,,,,,,MediaPackageV2,ListChannelGroups,, From 51da04e6cd49c8e90233dc91e8f2ce4af4c00fc9 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Wed, 17 Jan 2024 18:03:01 -0800 Subject: [PATCH 6/7] Semgrep fixes --- .ci/.semgrep-caps-aws-ec2.yml | 3 +++ .ci/semgrep/migrate/context.yml | 4 ++++ internal/generate/serviceendpointtests/file.tmpl | 2 +- internal/service/accessanalyzer/service_endpoints_gen_test.go | 2 +- internal/service/account/service_endpoints_gen_test.go | 2 +- internal/service/acm/service_endpoints_gen_test.go | 2 +- internal/service/appconfig/service_endpoints_gen_test.go | 2 +- internal/service/appfabric/service_endpoints_gen_test.go | 2 +- internal/service/appflow/service_endpoints_gen_test.go | 2 +- internal/service/apprunner/service_endpoints_gen_test.go | 2 +- internal/service/athena/service_endpoints_gen_test.go | 2 +- internal/service/auditmanager/service_endpoints_gen_test.go | 2 +- internal/service/bedrock/service_endpoints_gen_test.go | 2 +- .../chimesdkmediapipelines/service_endpoints_gen_test.go | 2 +- internal/service/chimesdkvoice/service_endpoints_gen_test.go | 2 +- internal/service/cleanrooms/service_endpoints_gen_test.go | 2 +- .../service/codeguruprofiler/service_endpoints_gen_test.go | 2 +- internal/service/codepipeline/service_endpoints_gen_test.go | 2 +- .../service/codestarconnections/service_endpoints_gen_test.go | 2 +- .../codestarnotifications/service_endpoints_gen_test.go | 2 +- internal/service/comprehend/service_endpoints_gen_test.go | 2 +- .../service/computeoptimizer/service_endpoints_gen_test.go | 2 +- internal/service/connectcases/service_endpoints_gen_test.go | 2 +- internal/service/controltower/service_endpoints_gen_test.go | 2 +- .../service/customerprofiles/service_endpoints_gen_test.go | 2 +- internal/service/docdbelastic/service_endpoints_gen_test.go | 2 +- internal/service/ec2/service_endpoints_gen_test.go | 2 +- internal/service/ecr/service_endpoints_gen_test.go | 2 +- internal/service/eks/service_endpoints_gen_test.go | 2 +- internal/service/elasticache/service_endpoints_gen_test.go | 2 +- internal/service/emr/service_endpoints_gen_test.go | 2 +- internal/service/emrserverless/service_endpoints_gen_test.go | 2 +- internal/service/finspace/service_endpoints_gen_test.go | 2 +- internal/service/firehose/service_endpoints_gen_test.go | 2 +- internal/service/fis/service_endpoints_gen_test.go | 2 +- internal/service/glacier/service_endpoints_gen_test.go | 2 +- internal/service/groundstation/service_endpoints_gen_test.go | 2 +- internal/service/healthlake/service_endpoints_gen_test.go | 2 +- internal/service/identitystore/service_endpoints_gen_test.go | 2 +- .../service/internetmonitor/service_endpoints_gen_test.go | 2 +- internal/service/ivschat/service_endpoints_gen_test.go | 2 +- internal/service/kendra/service_endpoints_gen_test.go | 2 +- internal/service/keyspaces/service_endpoints_gen_test.go | 2 +- internal/service/kinesis/service_endpoints_gen_test.go | 2 +- internal/service/lambda/service_endpoints_gen_test.go | 2 +- internal/service/launchwizard/service_endpoints_gen_test.go | 2 +- internal/service/lightsail/service_endpoints_gen_test.go | 2 +- internal/service/lookoutmetrics/service_endpoints_gen_test.go | 2 +- internal/service/m2/service_endpoints_gen_test.go | 2 +- internal/service/mediaconnect/service_endpoints_gen_test.go | 2 +- internal/service/medialive/service_endpoints_gen_test.go | 2 +- internal/service/mediapackage/service_endpoints_gen_test.go | 2 +- internal/service/mediapackagev2/service_endpoints_gen_test.go | 2 +- internal/service/mq/service_endpoints_gen_test.go | 2 +- .../opensearchserverless/service_endpoints_gen_test.go | 2 +- internal/service/pcaconnectorad/service_endpoints_gen_test.go | 2 +- internal/service/pipes/service_endpoints_gen_test.go | 2 +- internal/service/polly/service_endpoints_gen_test.go | 2 +- internal/service/pricing/service_endpoints_gen_test.go | 2 +- internal/service/qbusiness/service_endpoints_gen_test.go | 2 +- internal/service/qldb/service_endpoints_gen_test.go | 2 +- internal/service/rds/service_endpoints_gen_test.go | 2 +- internal/service/rekognition/service_endpoints_gen_test.go | 2 +- .../service/resourceexplorer2/service_endpoints_gen_test.go | 2 +- internal/service/resourcegroups/service_endpoints_gen_test.go | 2 +- internal/service/rolesanywhere/service_endpoints_gen_test.go | 2 +- internal/service/route53domains/service_endpoints_gen_test.go | 2 +- internal/service/scheduler/service_endpoints_gen_test.go | 2 +- internal/service/secretsmanager/service_endpoints_gen_test.go | 2 +- internal/service/securityhub/service_endpoints_gen_test.go | 2 +- internal/service/securitylake/service_endpoints_gen_test.go | 2 +- internal/service/servicequotas/service_endpoints_gen_test.go | 2 +- internal/service/sesv2/service_endpoints_gen_test.go | 2 +- internal/service/signer/service_endpoints_gen_test.go | 2 +- internal/service/sns/service_endpoints_gen_test.go | 2 +- internal/service/sqs/service_endpoints_gen_test.go | 2 +- internal/service/ssm/service_endpoints_gen_test.go | 2 +- internal/service/ssmcontacts/service_endpoints_gen_test.go | 2 +- internal/service/ssmincidents/service_endpoints_gen_test.go | 2 +- internal/service/ssmsap/service_endpoints_gen_test.go | 2 +- internal/service/ssoadmin/service_endpoints_gen_test.go | 2 +- internal/service/swf/service_endpoints_gen_test.go | 2 +- .../service/verifiedpermissions/service_endpoints_gen_test.go | 2 +- internal/service/vpclattice/service_endpoints_gen_test.go | 2 +- .../service/wellarchitected/service_endpoints_gen_test.go | 2 +- internal/service/workspaces/service_endpoints_gen_test.go | 2 +- internal/service/xray/service_endpoints_gen_test.go | 2 +- 87 files changed, 92 insertions(+), 85 deletions(-) diff --git a/.ci/.semgrep-caps-aws-ec2.yml b/.ci/.semgrep-caps-aws-ec2.yml index e8b15edf0b7..f0ebe7797e4 100644 --- a/.ci/.semgrep-caps-aws-ec2.yml +++ b/.ci/.semgrep-caps-aws-ec2.yml @@ -10,6 +10,7 @@ rules: exclude: - internal/service/securitylake/aws_log_source.go - internal/service/securitylake/aws_log_source_test.go + - internal/service/**/service_endpoints_gen_test.go patterns: - pattern: func $NAME( ... ) { ... } - metavariable-pattern: @@ -27,6 +28,7 @@ rules: - internal exclude: - internal/service/securitylake/aws_log_source.go + - internal/service/**/service_endpoints_gen_test.go patterns: - pattern: const $NAME = ... - metavariable-pattern: @@ -44,6 +46,7 @@ rules: exclude: - internal/service/securitylake/aws_log_source.go - internal/service/securitylake/exports_test.go + - internal/service/**/service_endpoints_gen_test.go patterns: - pattern: var $NAME = ... - metavariable-pattern: diff --git a/.ci/semgrep/migrate/context.yml b/.ci/semgrep/migrate/context.yml index bb28f7e8213..571b98a0343 100644 --- a/.ci/semgrep/migrate/context.yml +++ b/.ci/semgrep/migrate/context.yml @@ -28,6 +28,10 @@ rules: - pattern-not: conn.Handlers.$X(...) - pattern-not: conn.Handlers.$X.$Y(...) - pattern-not: conn.Options() + - pattern-not: codestarconnections_sdkv2.$API() + - pattern-not: connectcases_sdkv2.$API() + - pattern-not: mediaconnect_sdkv2.$API() + - pattern-not: pcaconnectorad_sdkv2.$API() severity: ERROR - id: context-todo languages: [go] diff --git a/internal/generate/serviceendpointtests/file.tmpl b/internal/generate/serviceendpointtests/file.tmpl index f8cbb8399e7..a38f6721414 100644 --- a/internal/generate/serviceendpointtests/file.tmpl +++ b/internal/generate/serviceendpointtests/file.tmpl @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := {{ .GoV2Package }}_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), {{ .GoV2Package }}_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), {{ .GoV2Package }}_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/accessanalyzer/service_endpoints_gen_test.go b/internal/service/accessanalyzer/service_endpoints_gen_test.go index 1b1f56c3d4f..fdd9d5194c2 100644 --- a/internal/service/accessanalyzer/service_endpoints_gen_test.go +++ b/internal/service/accessanalyzer/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := accessanalyzer_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), accessanalyzer_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), accessanalyzer_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/account/service_endpoints_gen_test.go b/internal/service/account/service_endpoints_gen_test.go index 85ddbb049e3..5515f4b0618 100644 --- a/internal/service/account/service_endpoints_gen_test.go +++ b/internal/service/account/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := account_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), account_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), account_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/acm/service_endpoints_gen_test.go b/internal/service/acm/service_endpoints_gen_test.go index 957c5a9852a..8cd3facd4b4 100644 --- a/internal/service/acm/service_endpoints_gen_test.go +++ b/internal/service/acm/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := acm_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), acm_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), acm_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/appconfig/service_endpoints_gen_test.go b/internal/service/appconfig/service_endpoints_gen_test.go index f0aa82b1875..292e4363ccb 100644 --- a/internal/service/appconfig/service_endpoints_gen_test.go +++ b/internal/service/appconfig/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := appconfig_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), appconfig_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), appconfig_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/appfabric/service_endpoints_gen_test.go b/internal/service/appfabric/service_endpoints_gen_test.go index 30fffe1c468..30e5961e532 100644 --- a/internal/service/appfabric/service_endpoints_gen_test.go +++ b/internal/service/appfabric/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := appfabric_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), appfabric_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), appfabric_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/appflow/service_endpoints_gen_test.go b/internal/service/appflow/service_endpoints_gen_test.go index 8be8966dabe..9c4811a31df 100644 --- a/internal/service/appflow/service_endpoints_gen_test.go +++ b/internal/service/appflow/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := appflow_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), appflow_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), appflow_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/apprunner/service_endpoints_gen_test.go b/internal/service/apprunner/service_endpoints_gen_test.go index b84eb859099..eed742593bb 100644 --- a/internal/service/apprunner/service_endpoints_gen_test.go +++ b/internal/service/apprunner/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := apprunner_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), apprunner_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), apprunner_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/athena/service_endpoints_gen_test.go b/internal/service/athena/service_endpoints_gen_test.go index 707deabdc9b..cc5903d73a4 100644 --- a/internal/service/athena/service_endpoints_gen_test.go +++ b/internal/service/athena/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := athena_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), athena_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), athena_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/auditmanager/service_endpoints_gen_test.go b/internal/service/auditmanager/service_endpoints_gen_test.go index dd9555dcabe..060459b4f8f 100644 --- a/internal/service/auditmanager/service_endpoints_gen_test.go +++ b/internal/service/auditmanager/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := auditmanager_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), auditmanager_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), auditmanager_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/bedrock/service_endpoints_gen_test.go b/internal/service/bedrock/service_endpoints_gen_test.go index e4f4713b3bc..7443bd8c7c4 100644 --- a/internal/service/bedrock/service_endpoints_gen_test.go +++ b/internal/service/bedrock/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := bedrock_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), bedrock_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), bedrock_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/chimesdkmediapipelines/service_endpoints_gen_test.go b/internal/service/chimesdkmediapipelines/service_endpoints_gen_test.go index d6366cff0a2..8896267928f 100644 --- a/internal/service/chimesdkmediapipelines/service_endpoints_gen_test.go +++ b/internal/service/chimesdkmediapipelines/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := chimesdkmediapipelines_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), chimesdkmediapipelines_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), chimesdkmediapipelines_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/chimesdkvoice/service_endpoints_gen_test.go b/internal/service/chimesdkvoice/service_endpoints_gen_test.go index cfd13fa05e3..26a4aa6770c 100644 --- a/internal/service/chimesdkvoice/service_endpoints_gen_test.go +++ b/internal/service/chimesdkvoice/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := chimesdkvoice_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), chimesdkvoice_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), chimesdkvoice_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/cleanrooms/service_endpoints_gen_test.go b/internal/service/cleanrooms/service_endpoints_gen_test.go index 01dc1b2b0c9..b47275665a4 100644 --- a/internal/service/cleanrooms/service_endpoints_gen_test.go +++ b/internal/service/cleanrooms/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := cleanrooms_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), cleanrooms_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), cleanrooms_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/codeguruprofiler/service_endpoints_gen_test.go b/internal/service/codeguruprofiler/service_endpoints_gen_test.go index 15a8680d124..eab26f6890a 100644 --- a/internal/service/codeguruprofiler/service_endpoints_gen_test.go +++ b/internal/service/codeguruprofiler/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := codeguruprofiler_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), codeguruprofiler_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), codeguruprofiler_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/codepipeline/service_endpoints_gen_test.go b/internal/service/codepipeline/service_endpoints_gen_test.go index ec645be414e..006882fd0bd 100644 --- a/internal/service/codepipeline/service_endpoints_gen_test.go +++ b/internal/service/codepipeline/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := codepipeline_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), codepipeline_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), codepipeline_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/codestarconnections/service_endpoints_gen_test.go b/internal/service/codestarconnections/service_endpoints_gen_test.go index 7c241ef11c4..0658dee62ad 100644 --- a/internal/service/codestarconnections/service_endpoints_gen_test.go +++ b/internal/service/codestarconnections/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := codestarconnections_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), codestarconnections_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), codestarconnections_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/codestarnotifications/service_endpoints_gen_test.go b/internal/service/codestarnotifications/service_endpoints_gen_test.go index e393acdaac5..ed6f890d722 100644 --- a/internal/service/codestarnotifications/service_endpoints_gen_test.go +++ b/internal/service/codestarnotifications/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := codestarnotifications_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), codestarnotifications_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), codestarnotifications_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/comprehend/service_endpoints_gen_test.go b/internal/service/comprehend/service_endpoints_gen_test.go index 687066cae7f..6eb32cda920 100644 --- a/internal/service/comprehend/service_endpoints_gen_test.go +++ b/internal/service/comprehend/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := comprehend_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), comprehend_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), comprehend_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/computeoptimizer/service_endpoints_gen_test.go b/internal/service/computeoptimizer/service_endpoints_gen_test.go index 33facc8914f..a3a64d3c47c 100644 --- a/internal/service/computeoptimizer/service_endpoints_gen_test.go +++ b/internal/service/computeoptimizer/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := computeoptimizer_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), computeoptimizer_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), computeoptimizer_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/connectcases/service_endpoints_gen_test.go b/internal/service/connectcases/service_endpoints_gen_test.go index 6768ab69b83..4beae160d8e 100644 --- a/internal/service/connectcases/service_endpoints_gen_test.go +++ b/internal/service/connectcases/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := connectcases_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), connectcases_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), connectcases_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/controltower/service_endpoints_gen_test.go b/internal/service/controltower/service_endpoints_gen_test.go index 451a3639922..8867050d2bf 100644 --- a/internal/service/controltower/service_endpoints_gen_test.go +++ b/internal/service/controltower/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := controltower_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), controltower_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), controltower_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/customerprofiles/service_endpoints_gen_test.go b/internal/service/customerprofiles/service_endpoints_gen_test.go index e97bb9ea6cf..07eddfb3a6b 100644 --- a/internal/service/customerprofiles/service_endpoints_gen_test.go +++ b/internal/service/customerprofiles/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := customerprofiles_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), customerprofiles_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), customerprofiles_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/docdbelastic/service_endpoints_gen_test.go b/internal/service/docdbelastic/service_endpoints_gen_test.go index 354074f77f2..07ec7993c44 100644 --- a/internal/service/docdbelastic/service_endpoints_gen_test.go +++ b/internal/service/docdbelastic/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := docdbelastic_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), docdbelastic_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), docdbelastic_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/ec2/service_endpoints_gen_test.go b/internal/service/ec2/service_endpoints_gen_test.go index b5233d5a111..08c6a026653 100644 --- a/internal/service/ec2/service_endpoints_gen_test.go +++ b/internal/service/ec2/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := ec2_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), ec2_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), ec2_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/ecr/service_endpoints_gen_test.go b/internal/service/ecr/service_endpoints_gen_test.go index b6ddaef6a5c..8e047d43f9b 100644 --- a/internal/service/ecr/service_endpoints_gen_test.go +++ b/internal/service/ecr/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := ecr_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), ecr_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), ecr_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/eks/service_endpoints_gen_test.go b/internal/service/eks/service_endpoints_gen_test.go index 892c28ddb96..7a90de17c97 100644 --- a/internal/service/eks/service_endpoints_gen_test.go +++ b/internal/service/eks/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := eks_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), eks_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), eks_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/elasticache/service_endpoints_gen_test.go b/internal/service/elasticache/service_endpoints_gen_test.go index 9e344ad7700..16114e5f475 100644 --- a/internal/service/elasticache/service_endpoints_gen_test.go +++ b/internal/service/elasticache/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := elasticache_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), elasticache_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), elasticache_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/emr/service_endpoints_gen_test.go b/internal/service/emr/service_endpoints_gen_test.go index 29e02e7c6db..3dc5c402fe0 100644 --- a/internal/service/emr/service_endpoints_gen_test.go +++ b/internal/service/emr/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := emr_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), emr_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), emr_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/emrserverless/service_endpoints_gen_test.go b/internal/service/emrserverless/service_endpoints_gen_test.go index c1bb13867ce..09a14357b99 100644 --- a/internal/service/emrserverless/service_endpoints_gen_test.go +++ b/internal/service/emrserverless/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := emrserverless_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), emrserverless_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), emrserverless_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/finspace/service_endpoints_gen_test.go b/internal/service/finspace/service_endpoints_gen_test.go index dff80f60b24..45b4b58ffae 100644 --- a/internal/service/finspace/service_endpoints_gen_test.go +++ b/internal/service/finspace/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := finspace_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), finspace_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), finspace_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/firehose/service_endpoints_gen_test.go b/internal/service/firehose/service_endpoints_gen_test.go index 6130f81d3ce..d3ff782c859 100644 --- a/internal/service/firehose/service_endpoints_gen_test.go +++ b/internal/service/firehose/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := firehose_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), firehose_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), firehose_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/fis/service_endpoints_gen_test.go b/internal/service/fis/service_endpoints_gen_test.go index 53c66d1141e..af38d51eb09 100644 --- a/internal/service/fis/service_endpoints_gen_test.go +++ b/internal/service/fis/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := fis_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), fis_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), fis_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/glacier/service_endpoints_gen_test.go b/internal/service/glacier/service_endpoints_gen_test.go index 2ff375daa84..6080745ea78 100644 --- a/internal/service/glacier/service_endpoints_gen_test.go +++ b/internal/service/glacier/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := glacier_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), glacier_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), glacier_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/groundstation/service_endpoints_gen_test.go b/internal/service/groundstation/service_endpoints_gen_test.go index b6b72f627a2..33c6bf486a4 100644 --- a/internal/service/groundstation/service_endpoints_gen_test.go +++ b/internal/service/groundstation/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := groundstation_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), groundstation_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), groundstation_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/healthlake/service_endpoints_gen_test.go b/internal/service/healthlake/service_endpoints_gen_test.go index d87cfd21436..283a9856033 100644 --- a/internal/service/healthlake/service_endpoints_gen_test.go +++ b/internal/service/healthlake/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := healthlake_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), healthlake_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), healthlake_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/identitystore/service_endpoints_gen_test.go b/internal/service/identitystore/service_endpoints_gen_test.go index 2f7324e3268..d49f2c8b440 100644 --- a/internal/service/identitystore/service_endpoints_gen_test.go +++ b/internal/service/identitystore/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := identitystore_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), identitystore_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), identitystore_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/internetmonitor/service_endpoints_gen_test.go b/internal/service/internetmonitor/service_endpoints_gen_test.go index aa94e96c963..75eb715d488 100644 --- a/internal/service/internetmonitor/service_endpoints_gen_test.go +++ b/internal/service/internetmonitor/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := internetmonitor_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), internetmonitor_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), internetmonitor_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/ivschat/service_endpoints_gen_test.go b/internal/service/ivschat/service_endpoints_gen_test.go index 33dfd797ccf..dfb3fa1426d 100644 --- a/internal/service/ivschat/service_endpoints_gen_test.go +++ b/internal/service/ivschat/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := ivschat_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), ivschat_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), ivschat_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/kendra/service_endpoints_gen_test.go b/internal/service/kendra/service_endpoints_gen_test.go index 9f37483a94d..102bbec303a 100644 --- a/internal/service/kendra/service_endpoints_gen_test.go +++ b/internal/service/kendra/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := kendra_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), kendra_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), kendra_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/keyspaces/service_endpoints_gen_test.go b/internal/service/keyspaces/service_endpoints_gen_test.go index 85d88b819ef..b51d6a86f7e 100644 --- a/internal/service/keyspaces/service_endpoints_gen_test.go +++ b/internal/service/keyspaces/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := keyspaces_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), keyspaces_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), keyspaces_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/kinesis/service_endpoints_gen_test.go b/internal/service/kinesis/service_endpoints_gen_test.go index 979e89b2216..48512b352b4 100644 --- a/internal/service/kinesis/service_endpoints_gen_test.go +++ b/internal/service/kinesis/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := kinesis_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), kinesis_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), kinesis_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/lambda/service_endpoints_gen_test.go b/internal/service/lambda/service_endpoints_gen_test.go index c3ba796a384..39bdf2b4cfc 100644 --- a/internal/service/lambda/service_endpoints_gen_test.go +++ b/internal/service/lambda/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := lambda_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), lambda_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), lambda_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/launchwizard/service_endpoints_gen_test.go b/internal/service/launchwizard/service_endpoints_gen_test.go index 69c207199ee..4f51c03fa1d 100644 --- a/internal/service/launchwizard/service_endpoints_gen_test.go +++ b/internal/service/launchwizard/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := launchwizard_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), launchwizard_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), launchwizard_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/lightsail/service_endpoints_gen_test.go b/internal/service/lightsail/service_endpoints_gen_test.go index 20fe088126b..fc0e555f098 100644 --- a/internal/service/lightsail/service_endpoints_gen_test.go +++ b/internal/service/lightsail/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := lightsail_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), lightsail_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), lightsail_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/lookoutmetrics/service_endpoints_gen_test.go b/internal/service/lookoutmetrics/service_endpoints_gen_test.go index 9d2332438c4..02ceb1ae16e 100644 --- a/internal/service/lookoutmetrics/service_endpoints_gen_test.go +++ b/internal/service/lookoutmetrics/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := lookoutmetrics_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), lookoutmetrics_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), lookoutmetrics_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/m2/service_endpoints_gen_test.go b/internal/service/m2/service_endpoints_gen_test.go index 81b2666499a..ec13a085389 100644 --- a/internal/service/m2/service_endpoints_gen_test.go +++ b/internal/service/m2/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := m2_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), m2_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), m2_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/mediaconnect/service_endpoints_gen_test.go b/internal/service/mediaconnect/service_endpoints_gen_test.go index 2ea83dc8cd4..fe8b7547073 100644 --- a/internal/service/mediaconnect/service_endpoints_gen_test.go +++ b/internal/service/mediaconnect/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := mediaconnect_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), mediaconnect_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), mediaconnect_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/medialive/service_endpoints_gen_test.go b/internal/service/medialive/service_endpoints_gen_test.go index ab11eed7ca7..26ed0fb1dc7 100644 --- a/internal/service/medialive/service_endpoints_gen_test.go +++ b/internal/service/medialive/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := medialive_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), medialive_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), medialive_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/mediapackage/service_endpoints_gen_test.go b/internal/service/mediapackage/service_endpoints_gen_test.go index 35b1256f5db..71a83592a12 100644 --- a/internal/service/mediapackage/service_endpoints_gen_test.go +++ b/internal/service/mediapackage/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := mediapackage_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), mediapackage_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), mediapackage_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/mediapackagev2/service_endpoints_gen_test.go b/internal/service/mediapackagev2/service_endpoints_gen_test.go index 5111ec027cd..6d5605111eb 100644 --- a/internal/service/mediapackagev2/service_endpoints_gen_test.go +++ b/internal/service/mediapackagev2/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := mediapackagev2_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), mediapackagev2_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), mediapackagev2_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/mq/service_endpoints_gen_test.go b/internal/service/mq/service_endpoints_gen_test.go index 03ba82669b6..f39a8631256 100644 --- a/internal/service/mq/service_endpoints_gen_test.go +++ b/internal/service/mq/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := mq_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), mq_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), mq_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/opensearchserverless/service_endpoints_gen_test.go b/internal/service/opensearchserverless/service_endpoints_gen_test.go index 4a11ab8f015..db4a2a51965 100644 --- a/internal/service/opensearchserverless/service_endpoints_gen_test.go +++ b/internal/service/opensearchserverless/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := opensearchserverless_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), opensearchserverless_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), opensearchserverless_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/pcaconnectorad/service_endpoints_gen_test.go b/internal/service/pcaconnectorad/service_endpoints_gen_test.go index 7b21ea2e12d..981ad762fdb 100644 --- a/internal/service/pcaconnectorad/service_endpoints_gen_test.go +++ b/internal/service/pcaconnectorad/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := pcaconnectorad_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), pcaconnectorad_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), pcaconnectorad_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/pipes/service_endpoints_gen_test.go b/internal/service/pipes/service_endpoints_gen_test.go index 8afa1ccf407..919045052ef 100644 --- a/internal/service/pipes/service_endpoints_gen_test.go +++ b/internal/service/pipes/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := pipes_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), pipes_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), pipes_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/polly/service_endpoints_gen_test.go b/internal/service/polly/service_endpoints_gen_test.go index dcda88a13a9..cddd83350dd 100644 --- a/internal/service/polly/service_endpoints_gen_test.go +++ b/internal/service/polly/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := polly_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), polly_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), polly_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/pricing/service_endpoints_gen_test.go b/internal/service/pricing/service_endpoints_gen_test.go index 523275ecd49..1d4ec668b65 100644 --- a/internal/service/pricing/service_endpoints_gen_test.go +++ b/internal/service/pricing/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := pricing_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), pricing_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), pricing_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/qbusiness/service_endpoints_gen_test.go b/internal/service/qbusiness/service_endpoints_gen_test.go index e1edfca80e1..6b0c1340fca 100644 --- a/internal/service/qbusiness/service_endpoints_gen_test.go +++ b/internal/service/qbusiness/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := qbusiness_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), qbusiness_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), qbusiness_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/qldb/service_endpoints_gen_test.go b/internal/service/qldb/service_endpoints_gen_test.go index e6b43eb582e..1944e6a30ea 100644 --- a/internal/service/qldb/service_endpoints_gen_test.go +++ b/internal/service/qldb/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := qldb_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), qldb_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), qldb_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/rds/service_endpoints_gen_test.go b/internal/service/rds/service_endpoints_gen_test.go index 6b35fe4607f..aafd51b78cd 100644 --- a/internal/service/rds/service_endpoints_gen_test.go +++ b/internal/service/rds/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := rds_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), rds_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), rds_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/rekognition/service_endpoints_gen_test.go b/internal/service/rekognition/service_endpoints_gen_test.go index b1d53225a58..22ecc871af6 100644 --- a/internal/service/rekognition/service_endpoints_gen_test.go +++ b/internal/service/rekognition/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := rekognition_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), rekognition_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), rekognition_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/resourceexplorer2/service_endpoints_gen_test.go b/internal/service/resourceexplorer2/service_endpoints_gen_test.go index 037147f7466..39e15b78bcd 100644 --- a/internal/service/resourceexplorer2/service_endpoints_gen_test.go +++ b/internal/service/resourceexplorer2/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := resourceexplorer2_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), resourceexplorer2_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), resourceexplorer2_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/resourcegroups/service_endpoints_gen_test.go b/internal/service/resourcegroups/service_endpoints_gen_test.go index ae39db33546..969c4fda111 100644 --- a/internal/service/resourcegroups/service_endpoints_gen_test.go +++ b/internal/service/resourcegroups/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := resourcegroups_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), resourcegroups_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), resourcegroups_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/rolesanywhere/service_endpoints_gen_test.go b/internal/service/rolesanywhere/service_endpoints_gen_test.go index f002445161c..07d0fa9dea6 100644 --- a/internal/service/rolesanywhere/service_endpoints_gen_test.go +++ b/internal/service/rolesanywhere/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := rolesanywhere_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), rolesanywhere_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), rolesanywhere_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/route53domains/service_endpoints_gen_test.go b/internal/service/route53domains/service_endpoints_gen_test.go index 0ebda6cb386..806bcb340e9 100644 --- a/internal/service/route53domains/service_endpoints_gen_test.go +++ b/internal/service/route53domains/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := route53domains_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), route53domains_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), route53domains_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/scheduler/service_endpoints_gen_test.go b/internal/service/scheduler/service_endpoints_gen_test.go index 84c921f5313..f05ae1b8286 100644 --- a/internal/service/scheduler/service_endpoints_gen_test.go +++ b/internal/service/scheduler/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := scheduler_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), scheduler_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), scheduler_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/secretsmanager/service_endpoints_gen_test.go b/internal/service/secretsmanager/service_endpoints_gen_test.go index 50fb78457b1..5bac554241e 100644 --- a/internal/service/secretsmanager/service_endpoints_gen_test.go +++ b/internal/service/secretsmanager/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := secretsmanager_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), secretsmanager_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), secretsmanager_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/securityhub/service_endpoints_gen_test.go b/internal/service/securityhub/service_endpoints_gen_test.go index d1fe5ab6f44..6db29270368 100644 --- a/internal/service/securityhub/service_endpoints_gen_test.go +++ b/internal/service/securityhub/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := securityhub_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), securityhub_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), securityhub_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/securitylake/service_endpoints_gen_test.go b/internal/service/securitylake/service_endpoints_gen_test.go index 9755421743c..8d0b784041b 100644 --- a/internal/service/securitylake/service_endpoints_gen_test.go +++ b/internal/service/securitylake/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := securitylake_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), securitylake_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), securitylake_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/servicequotas/service_endpoints_gen_test.go b/internal/service/servicequotas/service_endpoints_gen_test.go index caaaac89323..4abc0fa6a1a 100644 --- a/internal/service/servicequotas/service_endpoints_gen_test.go +++ b/internal/service/servicequotas/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := servicequotas_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), servicequotas_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), servicequotas_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/sesv2/service_endpoints_gen_test.go b/internal/service/sesv2/service_endpoints_gen_test.go index 56b57fc9d79..feac9a92f4a 100644 --- a/internal/service/sesv2/service_endpoints_gen_test.go +++ b/internal/service/sesv2/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := sesv2_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), sesv2_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), sesv2_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/signer/service_endpoints_gen_test.go b/internal/service/signer/service_endpoints_gen_test.go index 5f985197f40..5195130d823 100644 --- a/internal/service/signer/service_endpoints_gen_test.go +++ b/internal/service/signer/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := signer_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), signer_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), signer_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/sns/service_endpoints_gen_test.go b/internal/service/sns/service_endpoints_gen_test.go index 343d0be1daf..39d8ac18345 100644 --- a/internal/service/sns/service_endpoints_gen_test.go +++ b/internal/service/sns/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := sns_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), sns_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), sns_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/sqs/service_endpoints_gen_test.go b/internal/service/sqs/service_endpoints_gen_test.go index 2bd91f19c32..3f599fe4e8d 100644 --- a/internal/service/sqs/service_endpoints_gen_test.go +++ b/internal/service/sqs/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := sqs_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), sqs_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), sqs_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/ssm/service_endpoints_gen_test.go b/internal/service/ssm/service_endpoints_gen_test.go index 25c214d2b9b..2100ded37bd 100644 --- a/internal/service/ssm/service_endpoints_gen_test.go +++ b/internal/service/ssm/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := ssm_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), ssm_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), ssm_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/ssmcontacts/service_endpoints_gen_test.go b/internal/service/ssmcontacts/service_endpoints_gen_test.go index 74a293148c5..f85eb266ac6 100644 --- a/internal/service/ssmcontacts/service_endpoints_gen_test.go +++ b/internal/service/ssmcontacts/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := ssmcontacts_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), ssmcontacts_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), ssmcontacts_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/ssmincidents/service_endpoints_gen_test.go b/internal/service/ssmincidents/service_endpoints_gen_test.go index 1578dbf4f9c..5ea192b10da 100644 --- a/internal/service/ssmincidents/service_endpoints_gen_test.go +++ b/internal/service/ssmincidents/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := ssmincidents_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), ssmincidents_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), ssmincidents_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/ssmsap/service_endpoints_gen_test.go b/internal/service/ssmsap/service_endpoints_gen_test.go index b5ce8ca0ae3..e493f7ee6ed 100644 --- a/internal/service/ssmsap/service_endpoints_gen_test.go +++ b/internal/service/ssmsap/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := ssmsap_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), ssmsap_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), ssmsap_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/ssoadmin/service_endpoints_gen_test.go b/internal/service/ssoadmin/service_endpoints_gen_test.go index d932d3c41a4..b84a481511c 100644 --- a/internal/service/ssoadmin/service_endpoints_gen_test.go +++ b/internal/service/ssoadmin/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := ssoadmin_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), ssoadmin_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), ssoadmin_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/swf/service_endpoints_gen_test.go b/internal/service/swf/service_endpoints_gen_test.go index 9bb5e4a4818..de102bdf831 100644 --- a/internal/service/swf/service_endpoints_gen_test.go +++ b/internal/service/swf/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := swf_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), swf_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), swf_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/verifiedpermissions/service_endpoints_gen_test.go b/internal/service/verifiedpermissions/service_endpoints_gen_test.go index f03810d7c70..9b0c3a68b27 100644 --- a/internal/service/verifiedpermissions/service_endpoints_gen_test.go +++ b/internal/service/verifiedpermissions/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := verifiedpermissions_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), verifiedpermissions_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), verifiedpermissions_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/vpclattice/service_endpoints_gen_test.go b/internal/service/vpclattice/service_endpoints_gen_test.go index 9b9428844a4..337b8f8b130 100644 --- a/internal/service/vpclattice/service_endpoints_gen_test.go +++ b/internal/service/vpclattice/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := vpclattice_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), vpclattice_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), vpclattice_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/wellarchitected/service_endpoints_gen_test.go b/internal/service/wellarchitected/service_endpoints_gen_test.go index 74ba9fffa1b..3beffe69154 100644 --- a/internal/service/wellarchitected/service_endpoints_gen_test.go +++ b/internal/service/wellarchitected/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := wellarchitected_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), wellarchitected_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), wellarchitected_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/workspaces/service_endpoints_gen_test.go b/internal/service/workspaces/service_endpoints_gen_test.go index 9c20ba73a44..4daed0d770c 100644 --- a/internal/service/workspaces/service_endpoints_gen_test.go +++ b/internal/service/workspaces/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := workspaces_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), workspaces_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), workspaces_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { diff --git a/internal/service/xray/service_endpoints_gen_test.go b/internal/service/xray/service_endpoints_gen_test.go index 9279c15c711..761175dd34c 100644 --- a/internal/service/xray/service_endpoints_gen_test.go +++ b/internal/service/xray/service_endpoints_gen_test.go @@ -212,7 +212,7 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S func defaultEndpoint(region string) string { r := xray_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.ResolveEndpoint(context.TODO(), xray_sdkv2.EndpointParameters{ + ep, err := r.ResolveEndpoint(context.Background(), xray_sdkv2.EndpointParameters{ Region: aws_sdkv2.String(region), }) if err != nil { From c249cccba10f287c53ea5d1777263fad81b0021c Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Thu, 18 Jan 2024 10:14:16 -0800 Subject: [PATCH 7/7] Fixes generation --- .ci/.semgrep-caps-aws-ec2.yml | 6 +++--- internal/generate/servicesemgrep/cae.tmpl | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.ci/.semgrep-caps-aws-ec2.yml b/.ci/.semgrep-caps-aws-ec2.yml index f0ebe7797e4..d56bee5fc4f 100644 --- a/.ci/.semgrep-caps-aws-ec2.yml +++ b/.ci/.semgrep-caps-aws-ec2.yml @@ -10,7 +10,7 @@ rules: exclude: - internal/service/securitylake/aws_log_source.go - internal/service/securitylake/aws_log_source_test.go - - internal/service/**/service_endpoints_gen_test.go + - internal/service/*/service_endpoints_gen_test.go patterns: - pattern: func $NAME( ... ) { ... } - metavariable-pattern: @@ -28,7 +28,7 @@ rules: - internal exclude: - internal/service/securitylake/aws_log_source.go - - internal/service/**/service_endpoints_gen_test.go + - internal/service/*/service_endpoints_gen_test.go patterns: - pattern: const $NAME = ... - metavariable-pattern: @@ -46,7 +46,7 @@ rules: exclude: - internal/service/securitylake/aws_log_source.go - internal/service/securitylake/exports_test.go - - internal/service/**/service_endpoints_gen_test.go + - internal/service/*/service_endpoints_gen_test.go patterns: - pattern: var $NAME = ... - metavariable-pattern: diff --git a/internal/generate/servicesemgrep/cae.tmpl b/internal/generate/servicesemgrep/cae.tmpl index 4463deea03c..a526d280d02 100644 --- a/internal/generate/servicesemgrep/cae.tmpl +++ b/internal/generate/servicesemgrep/cae.tmpl @@ -10,6 +10,7 @@ rules: exclude: - internal/service/securitylake/aws_log_source.go - internal/service/securitylake/aws_log_source_test.go + - internal/service/*/service_endpoints_gen_test.go patterns: - pattern: func $NAME( ... ) { ... } - metavariable-pattern: @@ -27,6 +28,7 @@ rules: - internal exclude: - internal/service/securitylake/aws_log_source.go + - internal/service/*/service_endpoints_gen_test.go patterns: - pattern: const $NAME = ... - metavariable-pattern: @@ -44,6 +46,7 @@ rules: exclude: - internal/service/securitylake/aws_log_source.go - internal/service/securitylake/exports_test.go + - internal/service/*/service_endpoints_gen_test.go patterns: - pattern: var $NAME = ... - metavariable-pattern: