From 7083b0d0fa9793e4c6e2fd72c8c8e8612d5e2bd0 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Thu, 7 Nov 2024 14:10:41 -0500 Subject: [PATCH] feat: new service `taxsettings` Adds the scaffolding and service package declarations to support the addition of new `taxsettings` resources and data sources. --- .ci/.semgrep-service-name0.yml | 19 + .ci/.semgrep-service-name1.yml | 48 +- .ci/.semgrep-service-name2.yml | 72 ++- .ci/.semgrep-service-name3.yml | 104 +-- .github/labeler-issue-triage.yml | 2 + .github/labeler-pr-triage.yml | 6 + .../components/generated/services_all.kt | 1 + go.mod | 1 + go.sum | 2 + infrastructure/repository/labels-service.tf | 1 + internal/conns/awsclient_gen.go | 5 + internal/provider/fwprovider/provider_gen.go | 7 + internal/provider/provider_gen.go | 8 + internal/provider/service_packages_gen.go | 2 + internal/service/taxsettings/generate.go | 7 + .../service_endpoint_resolver_gen.go | 82 +++ .../taxsettings/service_endpoints_gen_test.go | 608 ++++++++++++++++++ .../taxsettings/service_package_gen.go | 49 ++ internal/sweep/service_packages_gen_test.go | 2 + names/consts_gen.go | 2 + names/data/names_data.hcl | 23 + website/allowed-subcategories.txt | 1 + .../custom-service-endpoints.html.markdown | 1 + 23 files changed, 962 insertions(+), 91 deletions(-) create mode 100644 internal/service/taxsettings/generate.go create mode 100644 internal/service/taxsettings/service_endpoint_resolver_gen.go create mode 100644 internal/service/taxsettings/service_endpoints_gen_test.go create mode 100644 internal/service/taxsettings/service_package_gen.go diff --git a/.ci/.semgrep-service-name0.yml b/.ci/.semgrep-service-name0.yml index ffd8153d603..754ea6c3eb9 100644 --- a/.ci/.semgrep-service-name0.yml +++ b/.ci/.semgrep-service-name0.yml @@ -4296,3 +4296,22 @@ rules: patterns: - pattern-regex: "(?i)ConfigService" severity: WARNING + - id: connect-in-func-name + languages: + - go + message: Do not use "Connect" in func name inside connect package + paths: + include: + - internal/service/connect + exclude: + - internal/service/connect/list_pages_gen.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Connect" + - pattern-not-regex: .*uickConnect.* + - focus-metavariable: $NAME + - pattern-not: func $NAME($T *testing.T) + severity: WARNING diff --git a/.ci/.semgrep-service-name1.yml b/.ci/.semgrep-service-name1.yml index 598d355ab26..bd6eba723ac 100644 --- a/.ci/.semgrep-service-name1.yml +++ b/.ci/.semgrep-service-name1.yml @@ -1,24 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - id: connect-in-func-name - languages: - - go - message: Do not use "Connect" in func name inside connect package - paths: - include: - - internal/service/connect - exclude: - - internal/service/connect/list_pages_gen.go - patterns: - - pattern: func $NAME( ... ) - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Connect" - - pattern-not-regex: .*uickConnect.* - - focus-metavariable: $NAME - - pattern-not: func $NAME($T *testing.T) - severity: WARNING - id: connect-in-test-name languages: - go @@ -4295,3 +4276,32 @@ rules: - focus-metavariable: $NAME - pattern-not: func $NAME($T *testing.T) severity: WARNING + - id: iotanalytics-in-test-name + languages: + - go + message: Include "IoTAnalytics" in test name + paths: + include: + - internal/service/iotanalytics/*_test.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccIoTAnalytics" + - pattern-regex: ^TestAcc.* + severity: WARNING + - id: iotanalytics-in-const-name + languages: + - go + message: Do not use "IoTAnalytics" in const name inside iotanalytics package + paths: + include: + - internal/service/iotanalytics + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)IoTAnalytics" + severity: WARNING diff --git a/.ci/.semgrep-service-name2.yml b/.ci/.semgrep-service-name2.yml index 8c94e2def70..566413e357f 100644 --- a/.ci/.semgrep-service-name2.yml +++ b/.ci/.semgrep-service-name2.yml @@ -1,34 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - id: iotanalytics-in-test-name - languages: - - go - message: Include "IoTAnalytics" in test name - paths: - include: - - internal/service/iotanalytics/*_test.go - patterns: - - pattern: func $NAME( ... ) - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-not-regex: "^TestAccIoTAnalytics" - - pattern-regex: ^TestAcc.* - severity: WARNING - - id: iotanalytics-in-const-name - languages: - - go - message: Do not use "IoTAnalytics" in const name inside iotanalytics package - paths: - include: - - internal/service/iotanalytics - patterns: - - pattern: const $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)IoTAnalytics" - severity: WARNING - id: iotanalytics-in-var-name languages: - go @@ -4294,3 +4265,46 @@ rules: - focus-metavariable: $NAME - pattern-not: func $NAME($T *testing.T) severity: WARNING + - id: redshift-in-test-name + languages: + - go + message: Include "Redshift" in test name + paths: + include: + - internal/service/redshift/*_test.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccRedshift" + - pattern-regex: ^TestAcc.* + severity: WARNING + - id: redshift-in-const-name + languages: + - go + message: Do not use "Redshift" in const name inside redshift package + paths: + include: + - internal/service/redshift + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Redshift" + severity: WARNING + - id: redshift-in-var-name + languages: + - go + message: Do not use "Redshift" in var name inside redshift package + paths: + include: + - internal/service/redshift + patterns: + - pattern: var $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Redshift" + severity: WARNING diff --git a/.ci/.semgrep-service-name3.yml b/.ci/.semgrep-service-name3.yml index 68b960d652a..a8b2be25f36 100644 --- a/.ci/.semgrep-service-name3.yml +++ b/.ci/.semgrep-service-name3.yml @@ -1,48 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - id: redshift-in-test-name - languages: - - go - message: Include "Redshift" in test name - paths: - include: - - internal/service/redshift/*_test.go - patterns: - - pattern: func $NAME( ... ) - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-not-regex: "^TestAccRedshift" - - pattern-regex: ^TestAcc.* - severity: WARNING - - id: redshift-in-const-name - languages: - - go - message: Do not use "Redshift" in const name inside redshift package - paths: - include: - - internal/service/redshift - patterns: - - pattern: const $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Redshift" - severity: WARNING - - id: redshift-in-var-name - languages: - - go - message: Do not use "Redshift" in var name inside redshift package - paths: - include: - - internal/service/redshift - patterns: - - pattern: var $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Redshift" - severity: WARNING - id: redshiftdata-in-func-name languages: - go @@ -3293,6 +3250,67 @@ rules: patterns: - pattern-regex: "(?i)Synthetics" severity: WARNING + - id: taxsettings-in-func-name + languages: + - go + message: Do not use "TaxSettings" in func name inside taxsettings package + paths: + include: + - internal/service/taxsettings + exclude: + - internal/service/taxsettings/list_pages_gen.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)TaxSettings" + - focus-metavariable: $NAME + - pattern-not: func $NAME($T *testing.T) + severity: WARNING + - id: taxsettings-in-test-name + languages: + - go + message: Include "TaxSettings" in test name + paths: + include: + - internal/service/taxsettings/*_test.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccTaxSettings" + - pattern-regex: ^TestAcc.* + severity: WARNING + - id: taxsettings-in-const-name + languages: + - go + message: Do not use "TaxSettings" in const name inside taxsettings package + paths: + include: + - internal/service/taxsettings + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)TaxSettings" + severity: WARNING + - id: taxsettings-in-var-name + languages: + - go + message: Do not use "TaxSettings" in var name inside taxsettings package + paths: + include: + - internal/service/taxsettings + patterns: + - pattern: var $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)TaxSettings" + severity: WARNING - id: timestreaminfluxdb-in-func-name languages: - go diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index 0be6ffe0972..fac4523bf0b 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -679,6 +679,8 @@ service/swf: - '((\*|-)\s*`?|(data|resource)\s+"?)aws_swf_' service/synthetics: - '((\*|-)\s*`?|(data|resource)\s+"?)aws_synthetics_' +service/taxsettings: + - '((\*|-)\s*`?|(data|resource)\s+"?)aws_taxsettings_' service/textract: - '((\*|-)\s*`?|(data|resource)\s+"?)aws_textract_' service/timestreaminfluxdb: diff --git a/.github/labeler-pr-triage.yml b/.github/labeler-pr-triage.yml index 0f6e0b350f2..006d30413bf 100644 --- a/.github/labeler-pr-triage.yml +++ b/.github/labeler-pr-triage.yml @@ -2130,6 +2130,12 @@ service/synthetics: - any-glob-to-any-file: - 'internal/service/synthetics/**/*' - 'website/**/synthetics_*' +service/taxsettings: + - any: + - changed-files: + - any-glob-to-any-file: + - 'internal/service/taxsettings/**/*' + - 'website/**/taxsettings_*' service/textract: - any: - changed-files: diff --git a/.teamcity/components/generated/services_all.kt b/.teamcity/components/generated/services_all.kt index 90ac0708429..dcc12ef9129 100644 --- a/.teamcity/components/generated/services_all.kt +++ b/.teamcity/components/generated/services_all.kt @@ -231,6 +231,7 @@ val services = mapOf( "sts" to ServiceSpec("STS (Security Token)"), "swf" to ServiceSpec("SWF (Simple Workflow)"), "synthetics" to ServiceSpec("CloudWatch Synthetics", parallelismOverride = 10), + "taxsettings" to ServiceSpec("Tax Settings"), "timestreaminfluxdb" to ServiceSpec("Timestream for InfluxDB", vpcLock = true, parallelismOverride = 3), "timestreamwrite" to ServiceSpec("Timestream Write"), "transcribe" to ServiceSpec("Transcribe"), diff --git a/go.mod b/go.mod index 6482d08bead..4d0b2bcfd04 100644 --- a/go.mod +++ b/go.mod @@ -245,6 +245,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.32.4 github.com/aws/aws-sdk-go-v2/service/swf v1.27.4 github.com/aws/aws-sdk-go-v2/service/synthetics v1.29.4 + github.com/aws/aws-sdk-go-v2/service/taxsettings v1.6.1 github.com/aws/aws-sdk-go-v2/service/timestreaminfluxdb v1.6.4 github.com/aws/aws-sdk-go-v2/service/timestreamwrite v1.29.4 github.com/aws/aws-sdk-go-v2/service/transcribe v1.41.4 diff --git a/go.sum b/go.sum index c4232bce369..fc1821bee89 100644 --- a/go.sum +++ b/go.sum @@ -514,6 +514,8 @@ github.com/aws/aws-sdk-go-v2/service/swf v1.27.4 h1:lmYFiiZ+VCGgqt2d1xHSfp0SN7kD github.com/aws/aws-sdk-go-v2/service/swf v1.27.4/go.mod h1:7Is8HtyC+vQskJFxErP7YoF/Zu6/FeQ7w8DHbyqx0Hk= github.com/aws/aws-sdk-go-v2/service/synthetics v1.29.4 h1:LbOEGGiQv21rDS5uG+6accj80J0Ge7f6vKNvXaPyhOc= github.com/aws/aws-sdk-go-v2/service/synthetics v1.29.4/go.mod h1:h/0XMdglPF+MHuq0zLeN9SCedbW0CJ4/5T/ggtDs+s0= +github.com/aws/aws-sdk-go-v2/service/taxsettings v1.6.1 h1:kper8FCLg9zRFT3/ez1/XwDKdeEfo7nebvBmZOZn37U= +github.com/aws/aws-sdk-go-v2/service/taxsettings v1.6.1/go.mod h1:tmVIxtzE4zvoK4LaWFOqeAUl1oZmR3SiT42gfmoveWY= github.com/aws/aws-sdk-go-v2/service/timestreaminfluxdb v1.6.4 h1:p23Zsb1kHKA6lZsVTXkUC67v0MyMtEwnFsFBA2/M3TE= github.com/aws/aws-sdk-go-v2/service/timestreaminfluxdb v1.6.4/go.mod h1:tFPhAm5lWLaxKyZ8kC91+t1DtNf2wfxK8mxAkc0psHU= github.com/aws/aws-sdk-go-v2/service/timestreamwrite v1.29.4 h1:0+lRed/3INQCDdfhtw3kBv1P2ZNNxokoXSRALp/Vhhg= diff --git a/infrastructure/repository/labels-service.tf b/infrastructure/repository/labels-service.tf index 116b106f7f2..9473e9a0c6d 100644 --- a/infrastructure/repository/labels-service.tf +++ b/infrastructure/repository/labels-service.tf @@ -326,6 +326,7 @@ variable "service_labels" { "support", "swf", "synthetics", + "taxsettings", "textract", "timestreaminfluxdb", "timestreamquery", diff --git a/internal/conns/awsclient_gen.go b/internal/conns/awsclient_gen.go index 58cc7034cbb..146a8982839 100644 --- a/internal/conns/awsclient_gen.go +++ b/internal/conns/awsclient_gen.go @@ -233,6 +233,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/aws-sdk-go-v2/service/swf" "github.com/aws/aws-sdk-go-v2/service/synthetics" + "github.com/aws/aws-sdk-go-v2/service/taxsettings" "github.com/aws/aws-sdk-go-v2/service/timestreaminfluxdb" "github.com/aws/aws-sdk-go-v2/service/timestreamwrite" "github.com/aws/aws-sdk-go-v2/service/transcribe" @@ -1167,6 +1168,10 @@ func (c *AWSClient) SyntheticsClient(ctx context.Context) *synthetics.Client { return errs.Must(client[*synthetics.Client](ctx, c, names.Synthetics, make(map[string]any))) } +func (c *AWSClient) TaxSettingsClient(ctx context.Context) *taxsettings.Client { + return errs.Must(client[*taxsettings.Client](ctx, c, names.TaxSettings, make(map[string]any))) +} + func (c *AWSClient) TimestreamInfluxDBClient(ctx context.Context) *timestreaminfluxdb.Client { return errs.Must(client[*timestreaminfluxdb.Client](ctx, c, names.TimestreamInfluxDB, make(map[string]any))) } diff --git a/internal/provider/fwprovider/provider_gen.go b/internal/provider/fwprovider/provider_gen.go index c315465d946..ddbd248dd9c 100644 --- a/internal/provider/fwprovider/provider_gen.go +++ b/internal/provider/fwprovider/provider_gen.go @@ -1855,6 +1855,13 @@ func endpointsBlock() schema.SetNestedBlock { Description: "Use this to override the default service endpoint URL", }, + // taxsettings + + "taxsettings": schema.StringAttribute{ + Optional: true, + Description: "Use this to override the default service endpoint URL", + }, + // timestreaminfluxdb "timestreaminfluxdb": schema.StringAttribute{ diff --git a/internal/provider/provider_gen.go b/internal/provider/provider_gen.go index ae3c56b286d..aaf869c168f 100644 --- a/internal/provider/provider_gen.go +++ b/internal/provider/provider_gen.go @@ -2141,6 +2141,14 @@ func endpointsSchema() *schema.Schema { Description: "Use this to override the default service endpoint URL", }, + // taxsettings + + "taxsettings": { + Type: schema.TypeString, + Optional: true, + Description: "Use this to override the default service endpoint URL", + }, + // timestreaminfluxdb "timestreaminfluxdb": { diff --git a/internal/provider/service_packages_gen.go b/internal/provider/service_packages_gen.go index 112fabf4a8e..78084c4e207 100644 --- a/internal/provider/service_packages_gen.go +++ b/internal/provider/service_packages_gen.go @@ -238,6 +238,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/sts" "github.com/hashicorp/terraform-provider-aws/internal/service/swf" "github.com/hashicorp/terraform-provider-aws/internal/service/synthetics" + "github.com/hashicorp/terraform-provider-aws/internal/service/taxsettings" "github.com/hashicorp/terraform-provider-aws/internal/service/timestreaminfluxdb" "github.com/hashicorp/terraform-provider-aws/internal/service/timestreamwrite" "github.com/hashicorp/terraform-provider-aws/internal/service/transcribe" @@ -487,6 +488,7 @@ func servicePackages(ctx context.Context) []conns.ServicePackage { sts.ServicePackage(ctx), swf.ServicePackage(ctx), synthetics.ServicePackage(ctx), + taxsettings.ServicePackage(ctx), timestreaminfluxdb.ServicePackage(ctx), timestreamwrite.ServicePackage(ctx), transcribe.ServicePackage(ctx), diff --git a/internal/service/taxsettings/generate.go b/internal/service/taxsettings/generate.go new file mode 100644 index 00000000000..04d57db031c --- /dev/null +++ b/internal/service/taxsettings/generate.go @@ -0,0 +1,7 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:generate go run ../../generate/servicepackage/main.go +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package taxsettings diff --git a/internal/service/taxsettings/service_endpoint_resolver_gen.go b/internal/service/taxsettings/service_endpoint_resolver_gen.go new file mode 100644 index 00000000000..ec3e69fc539 --- /dev/null +++ b/internal/service/taxsettings/service_endpoint_resolver_gen.go @@ -0,0 +1,82 @@ +// Code generated by internal/generate/servicepackage/main.go; DO NOT EDIT. + +package taxsettings + +import ( + "context" + "fmt" + "net" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/taxsettings" + smithyendpoints "github.com/aws/smithy-go/endpoints" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-aws/internal/errs" +) + +var _ taxsettings.EndpointResolverV2 = resolverV2{} + +type resolverV2 struct { + defaultResolver taxsettings.EndpointResolverV2 +} + +func newEndpointResolverV2() resolverV2 { + return resolverV2{ + defaultResolver: taxsettings.NewDefaultEndpointResolverV2(), + } +} + +func (r resolverV2) ResolveEndpoint(ctx context.Context, params taxsettings.EndpointParameters) (endpoint smithyendpoints.Endpoint, err error) { + params = params.WithDefaults() + useFIPS := aws.ToBool(params.UseFIPS) + + if eps := params.Endpoint; aws.ToString(eps) != "" { + tflog.Debug(ctx, "setting endpoint", map[string]any{ + "tf_aws.endpoint": endpoint, + }) + + if useFIPS { + tflog.Debug(ctx, "endpoint set, ignoring UseFIPSEndpoint setting") + params.UseFIPS = aws.Bool(false) + } + + return r.defaultResolver.ResolveEndpoint(ctx, params) + } else if useFIPS { + ctx = tflog.SetField(ctx, "tf_aws.use_fips", useFIPS) + + endpoint, err = r.defaultResolver.ResolveEndpoint(ctx, params) + if err != nil { + return endpoint, err + } + + tflog.Debug(ctx, "endpoint resolved", map[string]any{ + "tf_aws.endpoint": endpoint.URI.String(), + }) + + hostname := endpoint.URI.Hostname() + _, err = net.LookupHost(hostname) + if err != nil { + if dnsErr, ok := errs.As[*net.DNSError](err); ok && dnsErr.IsNotFound { + tflog.Debug(ctx, "default endpoint host not found, disabling FIPS", map[string]any{ + "tf_aws.hostname": hostname, + }) + params.UseFIPS = aws.Bool(false) + } else { + err = fmt.Errorf("looking up taxsettings endpoint %q: %s", hostname, err) + return + } + } else { + return endpoint, err + } + } + + return r.defaultResolver.ResolveEndpoint(ctx, params) +} + +func withBaseEndpoint(endpoint string) func(*taxsettings.Options) { + return func(o *taxsettings.Options) { + if endpoint != "" { + o.BaseEndpoint = aws.String(endpoint) + } + } +} diff --git a/internal/service/taxsettings/service_endpoints_gen_test.go b/internal/service/taxsettings/service_endpoints_gen_test.go new file mode 100644 index 00000000000..4c737598da7 --- /dev/null +++ b/internal/service/taxsettings/service_endpoints_gen_test.go @@ -0,0 +1,608 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package taxsettings_test + +import ( + "context" + "errors" + "fmt" + "maps" + "net" + "net/url" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" + "github.com/aws/aws-sdk-go-v2/service/taxsettings" + "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" + "github.com/hashicorp/terraform-provider-aws/names" +) + +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 + region string +} + +type apiCallParams struct { + endpoint string + region string +} + +type setupFunc func(setup *caseSetup) + +type callFunc func(ctx context.Context, t *testing.T, meta *conns.AWSClient) apiCallParams + +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 = "taxsettings" + awsEnvVar = "AWS_ENDPOINT_URL_TAXSETTINGS" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "taxsettings" +) + +const ( + expectedCallRegion = "us-west-2" //lintignore:AWSAT003 +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const providerRegion = "us-west-2" //lintignore:AWSAT003 + const expectedEndpointRegion = providerRegion + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(t, expectedEndpointRegion), + }, + + // 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(), + }, + + // Use FIPS endpoint on Config + + "use fips config": { + with: []setupFunc{ + withUseFIPSInConfig, + }, + expected: expectDefaultFIPSEndpoint(t, expectedEndpointRegion), + }, + + "use fips config with package name endpoint config": { + with: []setupFunc{ + withUseFIPSInConfig, + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + t.Run(name, func(t *testing.T) { + testEndpointCase(t, providerRegion, testcase, callService) + }) + } +} + +func defaultEndpoint(region string) (url.URL, error) { + r := taxsettings.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.Background(), taxsettings.EndpointParameters{ + Region: aws.String(region), + }) + if err != nil { + return url.URL{}, err + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI, nil +} + +func defaultFIPSEndpoint(region string) (url.URL, error) { + r := taxsettings.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.Background(), taxsettings.EndpointParameters{ + Region: aws.String(region), + UseFIPS: aws.Bool(true), + }) + if err != nil { + return url.URL{}, err + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI, nil +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) apiCallParams { + t.Helper() + + client := meta.TaxSettingsClient(ctx) + + var result apiCallParams + + _, err := client.ListTaxRegistrations(ctx, &taxsettings.ListTaxRegistrationsInput{}, + func(opts *taxsettings.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &result.endpoint), + addRetrieveRegionMiddleware(&result.region), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return result +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config[names.AttrEndpoints]; !ok { + setup.config[names.AttrEndpoints] = []any{ + map[string]any{}, + } + } + endpoints := setup.config[names.AttrEndpoints].([]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 withUseFIPSInConfig(setup *caseSetup) { + setup.config["use_fips_endpoint"] = true +} + +func expectDefaultEndpoint(t *testing.T, region string) caseExpectations { + t.Helper() + + endpoint, err := defaultEndpoint(region) + if err != nil { + t.Fatalf("resolving accessanalyzer default endpoint: %s", err) + } + + return caseExpectations{ + endpoint: endpoint.String(), + region: expectedCallRegion, + } +} + +func expectDefaultFIPSEndpoint(t *testing.T, region string) caseExpectations { + t.Helper() + + endpoint, err := defaultFIPSEndpoint(region) + if err != nil { + t.Fatalf("resolving accessanalyzer FIPS endpoint: %s", err) + } + + hostname := endpoint.Hostname() + _, err = net.LookupHost(hostname) + if dnsErr, ok := errs.As[*net.DNSError](err); ok && dnsErr.IsNotFound { + return expectDefaultEndpoint(t, region) + } else if err != nil { + t.Fatalf("looking up accessanalyzer endpoint %q: %s", hostname, err) + } + + return caseExpectations{ + endpoint: endpoint.String(), + region: expectedCallRegion, + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + region: expectedCallRegion, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + region: expectedCallRegion, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + region: expectedCallRegion, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + region: expectedCallRegion, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + region: expectedCallRegion, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase, callF callFunc) { + 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{ + names.AttrAccessKey: servicemocks.MockStaticAccessKey, + names.AttrSecretKey: servicemocks.MockStaticSecretKey, + names.AttrRegion: region, + names.AttrSkipCredentialsValidation: true, + names.AttrSkipRequestingAccountID: true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config[names.AttrProfile] = "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) + + callParams := callF(ctx, t, meta) + + if e, a := testcase.expected.endpoint, callParams.endpoint; e != a { + t.Errorf("expected endpoint %q, got %q", e, a) + } + + if e, a := testcase.expected.region, callParams.region; e != a { + t.Errorf("expected region %q, got %q", e, a) + } +} + +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) + }) +} + +func addRetrieveRegionMiddleware(region *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Serialize.Add( + retrieveRegionMiddleware(region), + middleware.After, + ) + } +} + +func retrieveRegionMiddleware(region *string) middleware.SerializeMiddleware { + return middleware.SerializeMiddlewareFunc( + "Test: Retrieve Region", + func(ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler) (middleware.SerializeOutput, middleware.Metadata, error) { + *region = awsmiddleware.GetRegion(ctx) + + return next.HandleSerialize(ctx, in) + }, + ) +} + +var errCancelOperation = fmt.Errorf("Test: Canceling 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)[names.AttrSharedConfigFiles]; !ok { + (*config)[names.AttrSharedConfigFiles] = []any{file.Name()} + } else { + (*config)[names.AttrSharedConfigFiles] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/taxsettings/service_package_gen.go b/internal/service/taxsettings/service_package_gen.go new file mode 100644 index 00000000000..88cbc147dde --- /dev/null +++ b/internal/service/taxsettings/service_package_gen.go @@ -0,0 +1,49 @@ +// Code generated by internal/generate/servicepackage/main.go; DO NOT EDIT. + +package taxsettings + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/taxsettings" + "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.TaxSettings +} + +// NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. +func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*taxsettings.Client, error) { + cfg := *(config["aws_sdkv2_config"].(*aws.Config)) + + return taxsettings.NewFromConfig(cfg, + taxsettings.WithEndpointResolverV2(newEndpointResolverV2()), + withBaseEndpoint(config[names.AttrEndpoint].(string)), + ), nil +} + +func ServicePackage(ctx context.Context) conns.ServicePackage { + return &servicePackage{} +} diff --git a/internal/sweep/service_packages_gen_test.go b/internal/sweep/service_packages_gen_test.go index 14a85d9de94..4e11fb03bff 100644 --- a/internal/sweep/service_packages_gen_test.go +++ b/internal/sweep/service_packages_gen_test.go @@ -238,6 +238,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/sts" "github.com/hashicorp/terraform-provider-aws/internal/service/swf" "github.com/hashicorp/terraform-provider-aws/internal/service/synthetics" + "github.com/hashicorp/terraform-provider-aws/internal/service/taxsettings" "github.com/hashicorp/terraform-provider-aws/internal/service/timestreaminfluxdb" "github.com/hashicorp/terraform-provider-aws/internal/service/timestreamwrite" "github.com/hashicorp/terraform-provider-aws/internal/service/transcribe" @@ -487,6 +488,7 @@ func servicePackages(ctx context.Context) []conns.ServicePackage { sts.ServicePackage(ctx), swf.ServicePackage(ctx), synthetics.ServicePackage(ctx), + taxsettings.ServicePackage(ctx), timestreaminfluxdb.ServicePackage(ctx), timestreamwrite.ServicePackage(ctx), transcribe.ServicePackage(ctx), diff --git a/names/consts_gen.go b/names/consts_gen.go index 27b58f302c3..d6bb0032a28 100644 --- a/names/consts_gen.go +++ b/names/consts_gen.go @@ -232,6 +232,7 @@ const ( SimpleDB = "simpledb" StorageGateway = "storagegateway" Synthetics = "synthetics" + TaxSettings = "taxsettings" TimestreamInfluxDB = "timestreaminfluxdb" TimestreamWrite = "timestreamwrite" Transcribe = "transcribe" @@ -481,6 +482,7 @@ const ( SimpleDBServiceID = "SimpleDB" StorageGatewayServiceID = "Storage Gateway" SyntheticsServiceID = "synthetics" + TaxSettingsServiceID = "TaxSettings" TimestreamInfluxDBServiceID = "Timestream InfluxDB" TimestreamWriteServiceID = "Timestream Write" TranscribeServiceID = "Transcribe" diff --git a/names/data/names_data.hcl b/names/data/names_data.hcl index df6c37ac618..b46f324edbd 100644 --- a/names/data/names_data.hcl +++ b/names/data/names_data.hcl @@ -8577,6 +8577,29 @@ service "swf" { brand = "AWS" } +service "taxsettings" { + sdk { + id = "TaxSettings" + } + + names { + provider_name_upper = "TaxSettings" + human_friendly = "Tax Settings" + } + + endpoint_info { + endpoint_api_call = "ListTaxRegistrations" + } + + resource_prefix { + correct = "aws_taxsettings_" + } + + provider_package_correct = "taxsettings" + doc_prefix = ["taxsettings_"] + brand = "Amazon" +} + service "textract" { sdk { id = "Textract" diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt index 6fcd9b7738c..d4fff79113c 100644 --- a/website/allowed-subcategories.txt +++ b/website/allowed-subcategories.txt @@ -230,6 +230,7 @@ Shield Signer Storage Gateway Systems Manager for SAP +Tax Settings Timestream Write Timestream for InfluxDB Transcribe diff --git a/website/docs/guides/custom-service-endpoints.html.markdown b/website/docs/guides/custom-service-endpoints.html.markdown index 126ad3363a5..6ddd1209d10 100644 --- a/website/docs/guides/custom-service-endpoints.html.markdown +++ b/website/docs/guides/custom-service-endpoints.html.markdown @@ -313,6 +313,7 @@ provider "aws" { |STS (Security Token)|`sts`|`AWS_ENDPOINT_URL_STS`|`sts`| |SWF (Simple Workflow)|`swf`|`AWS_ENDPOINT_URL_SWF`|`swf`| |CloudWatch Synthetics|`synthetics`|`AWS_ENDPOINT_URL_SYNTHETICS`|`synthetics`| +|Tax Settings|`taxsettings`|`AWS_ENDPOINT_URL_TAXSETTINGS`|`taxsettings`| |Timestream for InfluxDB|`timestreaminfluxdb`|`AWS_ENDPOINT_URL_TIMESTREAM_INFLUXDB`|`timestream_influxdb`| |Timestream Write|`timestreamwrite`|`AWS_ENDPOINT_URL_TIMESTREAM_WRITE`|`timestream_write`| |Transcribe|`transcribe`(or `transcribeservice`)|`AWS_ENDPOINT_URL_TRANSCRIBE`|`transcribe`|