Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix custom endpoint bug #21657

Merged
merged 7 commits into from
Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/21657.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
provider: Additional fixes to allow setting endpoints with non-standard, legacy keys.
```
118 changes: 118 additions & 0 deletions internal/acctest/acctest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,29 @@ func TestAccAcctestProvider_fipsEndpoint(t *testing.T) {
})
}

func TestAccAcctestProvider_unusualEndpoints(t *testing.T) {
var providers []*schema.Provider

unusual1 := []string{"es", "elasticsearch", "http://notarealendpoint"}
unusual2 := []string{"databasemigration", "dms", "http://alsonotarealendpoint"}
unusual3 := []string{"lexmodels", "lexmodelbuilding", "http://kingofspain"}

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { PreCheck(t) },
ErrorCheck: ErrorCheck(t),
ProviderFactories: FactoriesInternal(&providers),
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: testAccUnusualEndpointsConfig(unusual1, unusual2, unusual3),
Check: resource.ComposeTestCheckFunc(
testAccCheckUnusualEndpoints(&providers, unusual1, unusual2, unusual3),
),
},
},
})
}

func TestAccAcctestProvider_IgnoreTags_emptyBlock(t *testing.T) {
var providers []*schema.Provider

Expand Down Expand Up @@ -782,6 +805,81 @@ func testAccCheckEndpoints(providers *[]*schema.Provider) resource.TestCheckFunc
}
}

func testAccCheckUnusualEndpoints(providers *[]*schema.Provider, unusual1, unusual2, unusual3 []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if providers == nil {
return fmt.Errorf("no providers initialized")
}

// Match conns.AWSClient struct field names to endpoint configuration names
endpointFieldNameF := func(key string) func(string) bool {
return func(name string) bool {
serviceUpper := ""
var err error
if serviceUpper, err = conns.ServiceProviderNameUpper(key); err != nil {
return false
}

// exception to dropping "service" because Config collides with various other "Config"s
if name == "ConfigServiceConn" && fmt.Sprintf("%sConn", serviceUpper) == "ConfigConn" {
return true
}

return name == fmt.Sprintf("%sConn", serviceUpper)
}
}

for _, provo := range *providers {
if provo == nil || provo.Meta() == nil || provo.Meta().(*conns.AWSClient) == nil {
continue
}

providerClient := provo.Meta().(*conns.AWSClient)

providerClientField := reflect.Indirect(reflect.ValueOf(providerClient)).FieldByNameFunc(endpointFieldNameF(unusual1[1]))

if !providerClientField.IsValid() {
return fmt.Errorf("unable to match conns.AWSClient struct field name for endpoint name: %s", unusual1[1])
}

actualEndpoint := reflect.Indirect(reflect.Indirect(providerClientField).FieldByName("Config").FieldByName("Endpoint")).String()
expectedEndpoint := unusual1[2]

if actualEndpoint != expectedEndpoint {
return fmt.Errorf("expected endpoint (%s) value (%s), got: %s", unusual1[1], expectedEndpoint, actualEndpoint)
}

providerClientField = reflect.Indirect(reflect.ValueOf(providerClient)).FieldByNameFunc(endpointFieldNameF(unusual2[1]))

if !providerClientField.IsValid() {
return fmt.Errorf("unable to match conns.AWSClient struct field name for endpoint name: %s", unusual2[1])
}

actualEndpoint = reflect.Indirect(reflect.Indirect(providerClientField).FieldByName("Config").FieldByName("Endpoint")).String()
expectedEndpoint = unusual2[2]

if actualEndpoint != expectedEndpoint {
return fmt.Errorf("expected endpoint (%s) value (%s), got: %s", unusual2[1], expectedEndpoint, actualEndpoint)
}

providerClientField = reflect.Indirect(reflect.ValueOf(providerClient)).FieldByNameFunc(endpointFieldNameF(unusual3[1]))

if !providerClientField.IsValid() {
return fmt.Errorf("unable to match conns.AWSClient struct field name for endpoint name: %s", unusual3[1])
}

actualEndpoint = reflect.Indirect(reflect.Indirect(providerClientField).FieldByName("Config").FieldByName("Endpoint")).String()
expectedEndpoint = unusual3[2]

if actualEndpoint != expectedEndpoint {
return fmt.Errorf("expected endpoint (%s) value (%s), got: %s", unusual3[1], expectedEndpoint, actualEndpoint)
}
}

return nil
}
}

func testAccEndpointsConfig(endpoints string) string {
//lintignore:AT004
return ConfigCompose(
Expand Down Expand Up @@ -819,6 +917,26 @@ resource "aws_s3_bucket" "test" {
`, endpoint, rName))
}

func testAccUnusualEndpointsConfig(unusual1, unusual2, unusual3 []string) string {
//lintignore:AT004
return ConfigCompose(
testAccProviderConfigBase,
fmt.Sprintf(`
provider "aws" {
skip_credentials_validation = true
skip_get_ec2_platforms = true
skip_metadata_api_check = true
skip_requesting_account_id = true

endpoints {
%[1]s = %[2]q
%[3]s = %[4]q
%[5]s = %[6]q
}
}
`, unusual1[0], unusual1[2], unusual2[0], unusual2[2], unusual3[0], unusual3[2]))
}

func testAccIgnoreTagsKeys0Config() string {
//lintignore:AT004
return ConfigCompose(
Expand Down
17 changes: 11 additions & 6 deletions internal/conns/conns.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import (
"github.com/aws/aws-sdk-go/service/clouddirectory"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/aws/aws-sdk-go/service/cloudhsm"
"github.com/aws/aws-sdk-go/service/cloudhsmv2"
"github.com/aws/aws-sdk-go/service/cloudsearch"
"github.com/aws/aws-sdk-go/service/cloudsearchdomain"
Expand Down Expand Up @@ -321,7 +320,6 @@ const (
CloudDirectory = "clouddirectory"
CloudFormation = "cloudformation"
CloudFront = "cloudfront"
CloudHSM = "cloudhsm"
CloudHSMV2 = "cloudhsmv2"
CloudSearch = "cloudsearch"
CloudSearchDomain = "cloudsearchdomain"
Expand Down Expand Up @@ -609,7 +607,6 @@ func init() {
serviceData[CloudDirectory] = &ServiceDatum{AWSClientName: "CloudDirectory", AWSServiceName: clouddirectory.ServiceName, AWSEndpointsID: clouddirectory.EndpointsID, AWSServiceID: clouddirectory.ServiceID, ProviderNameUpper: "CloudDirectory", HCLKeys: []string{"clouddirectory"}}
serviceData[CloudFormation] = &ServiceDatum{AWSClientName: "CloudFormation", AWSServiceName: cloudformation.ServiceName, AWSEndpointsID: cloudformation.EndpointsID, AWSServiceID: cloudformation.ServiceID, ProviderNameUpper: "CloudFormation", HCLKeys: []string{"cloudformation"}}
serviceData[CloudFront] = &ServiceDatum{AWSClientName: "CloudFront", AWSServiceName: cloudfront.ServiceName, AWSEndpointsID: cloudfront.EndpointsID, AWSServiceID: cloudfront.ServiceID, ProviderNameUpper: "CloudFront", HCLKeys: []string{"cloudfront"}}
serviceData[CloudHSM] = &ServiceDatum{AWSClientName: "CloudHSM", AWSServiceName: cloudhsm.ServiceName, AWSEndpointsID: cloudhsm.EndpointsID, AWSServiceID: cloudhsm.ServiceID, ProviderNameUpper: "CloudHSM", HCLKeys: []string{"cloudhsmv1"}}
serviceData[CloudHSMV2] = &ServiceDatum{AWSClientName: "CloudHSMV2", AWSServiceName: cloudhsmv2.ServiceName, AWSEndpointsID: cloudhsmv2.EndpointsID, AWSServiceID: cloudhsmv2.ServiceID, ProviderNameUpper: "CloudHSMV2", HCLKeys: []string{"cloudhsm", "cloudhsmv2"}}
serviceData[CloudSearch] = &ServiceDatum{AWSClientName: "CloudSearch", AWSServiceName: cloudsearch.ServiceName, AWSEndpointsID: cloudsearch.EndpointsID, AWSServiceID: cloudsearch.ServiceID, ProviderNameUpper: "CloudSearch", HCLKeys: []string{"cloudsearch"}}
serviceData[CloudSearchDomain] = &ServiceDatum{AWSClientName: "CloudSearchDomain", AWSServiceName: cloudsearchdomain.ServiceName, AWSEndpointsID: cloudsearchdomain.EndpointsID, AWSServiceID: cloudsearchdomain.ServiceID, ProviderNameUpper: "CloudSearchDomain", HCLKeys: []string{"cloudsearchdomain"}}
Expand Down Expand Up @@ -919,7 +916,6 @@ type AWSClient struct {
CloudDirectoryConn *clouddirectory.CloudDirectory
CloudFormationConn *cloudformation.CloudFormation
CloudFrontConn *cloudfront.CloudFront
CloudHSMConn *cloudhsm.CloudHSM
CloudHSMV2Conn *cloudhsmv2.CloudHSMV2
CloudSearchConn *cloudsearch.CloudSearch
CloudSearchDomainConn *cloudsearchdomain.CloudSearchDomain
Expand Down Expand Up @@ -1273,7 +1269,6 @@ func (c *Config) Client() (interface{}, error) {
CloudDirectoryConn: clouddirectory.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudDirectory])})),
CloudFormationConn: cloudformation.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudFormation])})),
CloudFrontConn: cloudfront.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudFront])})),
CloudHSMConn: cloudhsm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudHSM])})),
CloudHSMV2Conn: cloudhsmv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudHSMV2])})),
CloudSearchConn: cloudsearch.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudSearch])})),
CloudSearchDomainConn: cloudsearchdomain.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudSearchDomain])})),
Expand Down Expand Up @@ -1883,7 +1878,7 @@ func ReverseDNS(hostname string) string {
// This is a global MutexKV for use within this plugin.
var GlobalMutexKV = NewMutexKV()

func ServiceKeyForHCLKey(s string) (string, error) {
func ServiceForHCLKey(s string) (string, error) {
for k, v := range serviceData {
for _, hclKey := range v.HCLKeys {
if s == hclKey {
Expand All @@ -1907,6 +1902,16 @@ func ServiceKeys() []string {
return keys
}

func HCLKeys() []string {
keys := make([]string, 0)

for _, v := range serviceData {
keys = append(keys, v.HCLKeys...)
}

return keys
}

func ServiceProviderNameUpper(key string) (string, error) {
if v, ok := serviceData[key]; ok {
return v.ProviderNameUpper, nil
Expand Down
16 changes: 13 additions & 3 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package provider

import (
"fmt"
"log"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -1828,8 +1829,17 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa

for _, endpointsSetI := range endpointsSet.List() {
endpoints := endpointsSetI.(map[string]interface{})
for _, serviceKey := range conns.ServiceKeys() {
config.Endpoints[serviceKey] = endpoints[serviceKey].(string)

for _, hclKey := range conns.HCLKeys() {
var serviceKey string
var err error
if serviceKey, err = conns.ServiceForHCLKey(hclKey); err != nil {
return nil, fmt.Errorf("failed to assign endpoint (%s): %w", hclKey, err)
}

if config.Endpoints[serviceKey] == "" && endpoints[hclKey].(string) != "" {
config.Endpoints[serviceKey] = endpoints[hclKey].(string)
}
}
}

Expand Down Expand Up @@ -1911,7 +1921,7 @@ func assumeRoleSchema() *schema.Schema {
func endpointsSchema() *schema.Schema {
endpointsAttributes := make(map[string]*schema.Schema)

for _, serviceKey := range conns.ServiceKeys() {
for _, serviceKey := range conns.HCLKeys() {
endpointsAttributes[serviceKey] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Expand Down
15 changes: 14 additions & 1 deletion website/docs/guides/custom-service-endpoints.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,20 @@ If multiple, different Terraform AWS Provider configurations are required, see t

## Available Endpoint Customizations

The Terraform AWS Provider allows the following endpoints to be customized. **Note:** The Provider allows some service endpoints to be customized before supporting those services.
The Terraform AWS Provider allows the following endpoints to be customized.

**Note:** The Provider allows some service endpoints to be customized despite not supporting those services.

**Note:** For backward compatibility, some endpoints can be assigned using multiple service "keys" (_e.g._, `dms`, `databasemigration`, or `databasemigrationservice`). If you use more than one equivalent service key in your configuration, the provider will use the _first_ endpoint value set. For example, in the configuration below we have set the DMS service endpoints using both `dms` and `databasemigration`. The provider will set the endpoint to whichever appears first. Subsequent values are ignored.

```terraform
provider "aws" {
endpoints {
dms = "http://this.value.will.be.used.com"
databasemigration = "http://this.value.will.be.ignored.com"
}
}
```

<!-- markdownlint-disable MD033 -->
<!--
Expand Down