diff --git a/aws/config.go b/aws/config.go index d3a18847b53..517c239d2c3 100644 --- a/aws/config.go +++ b/aws/config.go @@ -139,6 +139,7 @@ import ( "github.com/aws/aws-sdk-go/service/xray" awsbase "github.com/hashicorp/aws-sdk-go-base" "github.com/hashicorp/terraform-plugin-sdk/helper/logging" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) type Config struct { @@ -158,8 +159,9 @@ type Config struct { AllowedAccountIds []string ForbiddenAccountIds []string - Endpoints map[string]string - Insecure bool + Endpoints map[string]string + IgnoreTags []string + Insecure bool SkipCredsValidation bool SkipGetEC2Platforms bool @@ -239,6 +241,7 @@ type AWSClient struct { glueconn *glue.Glue guarddutyconn *guardduty.GuardDuty iamconn *iam.IAM + ignoreTags keyvaluetags.KeyValueTags inspectorconn *inspector.Inspector iotconn *iot.IoT iotanalyticsconn *iotanalytics.IoTAnalytics @@ -432,6 +435,7 @@ func (c *Config) Client() (interface{}, error) { glueconn: glue.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["glue"])})), guarddutyconn: guardduty.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["guardduty"])})), iamconn: iam.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["iam"])})), + ignoreTags: keyvaluetags.New(c.IgnoreTags), inspectorconn: inspector.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["inspector"])})), iotconn: iot.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["iot"])})), iotanalyticsconn: iotanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["iotanalytics"])})), diff --git a/aws/provider.go b/aws/provider.go index 0655be2d4c6..d9efb8b9e24 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -90,6 +90,14 @@ func Provider() terraform.ResourceProvider { "endpoints": endpointsSchema(), + "ignore_tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Description: "Resource tag keys to ignore configuring across all resources.", + }, + "insecure": { Type: schema.TypeBool, Optional: true, @@ -1090,6 +1098,12 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa } } + if v, ok := d.GetOk("ignore_tags"); ok { + for _, ignoreTagRaw := range v.(*schema.Set).List() { + config.IgnoreTags = append(config.IgnoreTags, ignoreTagRaw.(string)) + } + } + if v, ok := d.GetOk("allowed_account_ids"); ok { for _, accountIDRaw := range v.(*schema.Set).List() { config.AllowedAccountIds = append(config.AllowedAccountIds, accountIDRaw.(string)) diff --git a/aws/provider_test.go b/aws/provider_test.go index 2f9aec9a198..abd0203223f 100644 --- a/aws/provider_test.go +++ b/aws/provider_test.go @@ -287,6 +287,14 @@ provider "aws" { `, testAccGetAlternateRegion()) } +func testAccProviderConfigIgnoreTags1(key1 string) string { + return fmt.Sprintf(` +provider "aws" { + ignore_tags = [%[1]q] +} +`, key1) +} + // Provider configuration hardcoded for us-east-1. // This should only be necessary for testing ACM Certificates with CloudFront // related infrastucture such as API Gateway Domain Names for EDGE endpoints, @@ -476,6 +484,60 @@ func TestAccAWSProvider_Endpoints_Deprecated(t *testing.T) { }) } +func TestAccAWSProvider_IgnoreTags_None(t *testing.T) { + var providers []*schema.Provider + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAWSProviderConfigIgnoreTags0(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSProviderIgnoreTags(&providers, []string{}), + ), + }, + }, + }) +} + +func TestAccAWSProvider_IgnoreTags_One(t *testing.T) { + var providers []*schema.Provider + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAWSProviderConfigIgnoreTags1("test"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSProviderIgnoreTags(&providers, []string{"test"}), + ), + }, + }, + }) +} + +func TestAccAWSProvider_IgnoreTags_Multiple(t *testing.T) { + var providers []*schema.Provider + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAWSProviderConfigIgnoreTags2("test1", "test2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSProviderIgnoreTags(&providers, []string{"test1", "test2"}), + ), + }, + }, + }) +} + func TestAccAWSProvider_Region_AwsChina(t *testing.T) { var providers []*schema.Provider @@ -691,6 +753,60 @@ func testAccCheckAWSProviderEndpointsDeprecated(providers *[]*schema.Provider) r } } +func testAccCheckAWSProviderIgnoreTags(providers *[]*schema.Provider, expectedIgnoreTags []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if providers == nil { + return fmt.Errorf("no providers initialized") + } + + for _, provider := range *providers { + if provider == nil || provider.Meta() == nil || provider.Meta().(*AWSClient) == nil { + continue + } + + providerClient := provider.Meta().(*AWSClient) + + actualIgnoreTags := providerClient.ignoreTags.Keys() + + if len(actualIgnoreTags) != len(expectedIgnoreTags) { + return fmt.Errorf("expected ignore_tags (%d) length, got: %d", len(expectedIgnoreTags), len(actualIgnoreTags)) + } + + for _, expectedElement := range expectedIgnoreTags { + var found bool + + for _, actualElement := range actualIgnoreTags { + if actualElement == expectedElement { + found = true + break + } + } + + if !found { + return fmt.Errorf("expected ignore_tags element, but was missing: %s", expectedElement) + } + } + + for _, actualElement := range actualIgnoreTags { + var found bool + + for _, expectedElement := range expectedIgnoreTags { + if actualElement == expectedElement { + found = true + break + } + } + + if !found { + return fmt.Errorf("unexpected ignore_tags element: %s", actualElement) + } + } + } + + return nil + } +} + func testAccCheckAWSProviderPartition(providers *[]*schema.Provider, expectedPartition string) resource.TestCheckFunc { return func(s *terraform.State) error { if providers == nil { @@ -733,6 +849,56 @@ data "aws_arn" "test" { `, endpoints) } +func testAccAWSProviderConfigIgnoreTags0() string { + return fmt.Sprintf(` +provider "aws" { + skip_credentials_validation = true + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_requesting_account_id = true +} + +# Required to initialize the provider +data "aws_arn" "test" { + arn = "arn:aws:s3:::test" +} +`) +} + +func testAccAWSProviderConfigIgnoreTags1(tag1 string) string { + return fmt.Sprintf(` +provider "aws" { + ignore_tags = [%[1]q] + skip_credentials_validation = true + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_requesting_account_id = true +} + +# Required to initialize the provider +data "aws_arn" "test" { + arn = "arn:aws:s3:::test" +} +`, tag1) +} + +func testAccAWSProviderConfigIgnoreTags2(tag1, tag2 string) string { + return fmt.Sprintf(` +provider "aws" { + ignore_tags = [%[1]q, %[2]q] + skip_credentials_validation = true + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_requesting_account_id = true +} + +# Required to initialize the provider +data "aws_arn" "test" { + arn = "arn:aws:s3:::test" +} +`, tag1, tag2) +} + func testAccAWSProviderConfigRegion(region string) string { return fmt.Sprintf(` provider "aws" { diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index e8c41f87241..333d69e7c6e 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -201,6 +201,8 @@ for more information about connecting to alternate AWS endpoints or AWS compatib potentially end up destroying a live environment). Conflicts with `allowed_account_ids`. +* `ignore_tags` - (Optional) List of exact resource tag keys to ignore across all resources handled by this provider (see the [Terraform multiple provider instances documentation](/docs/configuration/providers.html#alias-multiple-provider-instances) for more information about additional provider configurations). This is designed for situations where external systems are managing certain resource tags. It prevents Terraform from returning the tag in any `tags` attributes and displaying any configuration difference for the tag value. If any resource configuration still has this tag key configured in the `tags` argument, it will display a perpetual difference until the tag is removed from the argument or [`ignore_changes`](/docs/configuration/resources.html#ignore_changes) is also used. + * `insecure` - (Optional) Explicitly allow the provider to perform "insecure" SSL requests. If omitted, default value is `false`.