diff --git a/aws/config.go b/aws/config.go index d3a18847b53..c526f493391 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,10 @@ type Config struct { AllowedAccountIds []string ForbiddenAccountIds []string - Endpoints map[string]string - Insecure bool + Endpoints map[string]string + IgnoreTagPrefixes []string + IgnoreTags []string + Insecure bool SkipCredsValidation bool SkipGetEC2Platforms bool @@ -239,6 +242,8 @@ type AWSClient struct { glueconn *glue.Glue guarddutyconn *guardduty.GuardDuty iamconn *iam.IAM + ignoreTagPrefixes keyvaluetags.KeyValueTags + ignoreTags keyvaluetags.KeyValueTags inspectorconn *inspector.Inspector iotconn *iot.IoT iotanalyticsconn *iotanalytics.IoTAnalytics @@ -432,6 +437,8 @@ 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"])})), + ignoreTagPrefixes: keyvaluetags.New(c.IgnoreTagPrefixes), + 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/internal/keyvaluetags/key_value_tags.go b/aws/internal/keyvaluetags/key_value_tags.go index 6ac2e380b3a..63e9ad5fc1d 100644 --- a/aws/internal/keyvaluetags/key_value_tags.go +++ b/aws/internal/keyvaluetags/key_value_tags.go @@ -57,6 +57,30 @@ func (tags KeyValueTags) IgnoreElasticbeanstalk() KeyValueTags { return result } +// IgnorePrefixes returns non-matching tag key prefixes. +func (tags KeyValueTags) IgnorePrefixes(ignoreTagPrefixes KeyValueTags) KeyValueTags { + result := make(KeyValueTags) + + for k, v := range tags { + var ignore bool + + for ignoreTagPrefix := range ignoreTagPrefixes { + if strings.HasPrefix(k, ignoreTagPrefix) { + ignore = true + break + } + } + + if ignore { + continue + } + + result[k] = v + } + + return result +} + // IgnoreRDS returns non-AWS and non-RDS tag keys. func (tags KeyValueTags) IgnoreRds() KeyValueTags { result := make(KeyValueTags) @@ -178,6 +202,14 @@ func New(i interface{}) KeyValueTags { kvtm[k] = &str } + return kvtm + case []string: + kvtm := make(KeyValueTags, len(value)) + + for _, v := range value { + kvtm[v] = nil + } + return kvtm case []interface{}: kvtm := make(KeyValueTags, len(value)) diff --git a/aws/internal/keyvaluetags/key_value_tags_test.go b/aws/internal/keyvaluetags/key_value_tags_test.go index 9a7f80365ad..cef344d61e7 100644 --- a/aws/internal/keyvaluetags/key_value_tags_test.go +++ b/aws/internal/keyvaluetags/key_value_tags_test.go @@ -118,6 +118,93 @@ func TestKeyValueTagsIgnoreElasticbeanstalk(t *testing.T) { } } +func TestKeyValueTagsIgnorePrefixes(t *testing.T) { + testCases := []struct { + name string + tags KeyValueTags + ignoreTagPrefixes KeyValueTags + want map[string]string + }{ + { + name: "empty", + tags: New(map[string]string{}), + ignoreTagPrefixes: New([]string{ + "key1", + "key2", + "key3", + }), + want: map[string]string{}, + }, + { + name: "all_exact", + tags: New(map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }), + ignoreTagPrefixes: New([]string{ + "key1", + "key2", + "key3", + }), + want: map[string]string{}, + }, + { + name: "all_prefix", + tags: New(map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }), + ignoreTagPrefixes: New([]string{ + "key", + }), + want: map[string]string{}, + }, + { + name: "mixed", + tags: New(map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }), + ignoreTagPrefixes: New([]string{ + "key1", + }), + want: map[string]string{ + "key2": "value2", + "key3": "value3", + }, + }, + { + name: "none", + tags: New(map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }), + ignoreTagPrefixes: New([]string{ + "key4", + "key5", + "key6", + }), + want: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + got := testCase.tags.IgnorePrefixes(testCase.ignoreTagPrefixes) + + testKeyValueTagsVerifyMap(t, got.Map(), testCase.want) + }) + } +} + func TestKeyValueTagsIgnoreRds(t *testing.T) { testCases := []struct { name string @@ -256,12 +343,45 @@ func TestKeyValueTagsKeys(t *testing.T) { want []string }{ { - name: "empty", + name: "empty_map_string_interface", + tags: New(map[string]interface{}{}), + want: []string{}, + }, + { + name: "empty_map_string_stringPointer", + tags: New(map[string]*string{}), + want: []string{}, + }, + { + name: "empty_map_string_string", tags: New(map[string]string{}), want: []string{}, }, { - name: "non_empty", + name: "empty_slice_interface", + tags: New(map[string]interface{}{}), + want: []string{}, + }, + { + name: "empty_slice_string", + tags: New(map[string]string{}), + want: []string{}, + }, + { + name: "non_empty_map_string_interface", + tags: New(map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }), + want: []string{ + "key1", + "key2", + "key3", + }, + }, + { + name: "non_empty_map_string_string", tags: New(map[string]string{ "key1": "value1", "key2": "value2", @@ -273,6 +393,45 @@ func TestKeyValueTagsKeys(t *testing.T) { "key3", }, }, + { + name: "non_empty_map_string_stringPointer", + tags: New(map[string]*string{ + "key1": testStringPtr("value1"), + "key2": testStringPtr("value2"), + "key3": testStringPtr("value3"), + }), + want: []string{ + "key1", + "key2", + "key3", + }, + }, + { + name: "non_empty_slice_interface", + tags: New([]interface{}{ + "key1", + "key2", + "key3", + }), + want: []string{ + "key1", + "key2", + "key3", + }, + }, + { + name: "non_empty_slice_string", + tags: New([]string{ + "key1", + "key2", + "key3", + }), + want: []string{ + "key1", + "key2", + "key3", + }, + }, } for _, testCase := range testCases { diff --git a/aws/provider.go b/aws/provider.go index 587e2013734..56dec7545b4 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -90,6 +90,22 @@ func Provider() terraform.ResourceProvider { "endpoints": endpointsSchema(), + "ignore_tag_prefixes": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Description: "Resource tag key prefixes to ignore across all resources.", + }, + + "ignore_tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Description: "Resource tag keys to ignore across all resources.", + }, + "insecure": { Type: schema.TypeBool, Optional: true, @@ -1093,6 +1109,18 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa } } + if v, ok := d.GetOk("ignore_tag_prefixes"); ok { + for _, ignoreTagPrefixRaw := range v.(*schema.Set).List() { + config.IgnoreTagPrefixes = append(config.IgnoreTagPrefixes, ignoreTagPrefixRaw.(string)) + } + } + + 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 c98da9c9d91..aca09ee98a0 100644 --- a/aws/provider_test.go +++ b/aws/provider_test.go @@ -287,6 +287,22 @@ provider "aws" { `, testAccGetAlternateRegion()) } +func testAccProviderConfigIgnoreTagPrefixes1(keyPrefix1 string) string { + return fmt.Sprintf(` +provider "aws" { + ignore_tag_prefixes = [%[1]q] +} +`, keyPrefix1) +} + +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 +492,114 @@ func TestAccAWSProvider_Endpoints_Deprecated(t *testing.T) { }) } +func TestAccAWSProvider_IgnoreTagPrefixes_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: testAccAWSProviderConfigIgnoreTagPrefixes0(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSProviderIgnoreTagPrefixes(&providers, []string{}), + ), + }, + }, + }) +} + +func TestAccAWSProvider_IgnoreTagPrefixes_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: testAccAWSProviderConfigIgnoreTagPrefixes1("test"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSProviderIgnoreTagPrefixes(&providers, []string{"test"}), + ), + }, + }, + }) +} + +func TestAccAWSProvider_IgnoreTagPrefixes_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: testAccAWSProviderConfigIgnoreTagPrefixes2("test1", "test2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSProviderIgnoreTagPrefixes(&providers, []string{"test1", "test2"}), + ), + }, + }, + }) +} + +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 +815,114 @@ func testAccCheckAWSProviderEndpointsDeprecated(providers *[]*schema.Provider) r } } +func testAccCheckAWSProviderIgnoreTagPrefixes(providers *[]*schema.Provider, expectedIgnoreTagPrefixes []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) + + actualIgnoreTagPrefixes := providerClient.ignoreTagPrefixes.Keys() + + if len(actualIgnoreTagPrefixes) != len(expectedIgnoreTagPrefixes) { + return fmt.Errorf("expected ignore_tag_prefixes (%d) length, got: %d", len(expectedIgnoreTagPrefixes), len(actualIgnoreTagPrefixes)) + } + + for _, expectedElement := range expectedIgnoreTagPrefixes { + var found bool + + for _, actualElement := range actualIgnoreTagPrefixes { + if actualElement == expectedElement { + found = true + break + } + } + + if !found { + return fmt.Errorf("expected ignore_tag_prefixes element, but was missing: %s", expectedElement) + } + } + + for _, actualElement := range actualIgnoreTagPrefixes { + var found bool + + for _, expectedElement := range expectedIgnoreTagPrefixes { + if actualElement == expectedElement { + found = true + break + } + } + + if !found { + return fmt.Errorf("unexpected ignore_tag_prefixes element: %s", actualElement) + } + } + } + + return nil + } +} + +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 +965,106 @@ data "aws_arn" "test" { `, endpoints) } +func testAccAWSProviderConfigIgnoreTagPrefixes0() 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 testAccAWSProviderConfigIgnoreTagPrefixes1(tagPrefix1 string) string { + return fmt.Sprintf(` +provider "aws" { + ignore_tag_prefixes = [%[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" +} +`, tagPrefix1) +} + +func testAccAWSProviderConfigIgnoreTagPrefixes2(tagPrefix1, tagPrefix2 string) string { + return fmt.Sprintf(` +provider "aws" { + ignore_tag_prefixes = [%[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" +} +`, tagPrefix1, tagPrefix2) +} + +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/aws/resource_aws_subnet.go b/aws/resource_aws_subnet.go index 56c8c402d1c..4615e89eee2 100644 --- a/aws/resource_aws_subnet.go +++ b/aws/resource_aws_subnet.go @@ -208,6 +208,8 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn + ignoreTags := meta.(*AWSClient).ignoreTags + ignoreTagPrefixes := meta.(*AWSClient).ignoreTagPrefixes resp, err := conn.DescribeSubnets(&ec2.DescribeSubnetsInput{ SubnetIds: []*string{aws.String(d.Id())}, @@ -248,7 +250,7 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { d.Set("arn", subnet.SubnetArn) - if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(subnet.Tags).IgnoreAws().Map()); err != nil { + if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(subnet.Tags).IgnoreAws().IgnorePrefixes(ignoreTagPrefixes).Ignore(ignoreTags).Map()); err != nil { return fmt.Errorf("error setting tags: %s", err) } diff --git a/aws/resource_aws_subnet_test.go b/aws/resource_aws_subnet_test.go index 76c3ad5de45..ec52050a7b8 100644 --- a/aws/resource_aws_subnet_test.go +++ b/aws/resource_aws_subnet_test.go @@ -11,7 +11,9 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) // add sweeper to delete known test subnets @@ -159,6 +161,36 @@ func TestAccAWSSubnet_basic(t *testing.T) { }) } +func TestAccAWSSubnet_ignoreTags(t *testing.T) { + var providers []*schema.Provider + var subnet ec2.Subnet + resourceName := "aws_subnet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckVpcDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSubnetConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckSubnetExists(resourceName, &subnet), + testAccCheckSubnetUpdateTags(&subnet, nil, map[string]string{"ignorekey1": "ignorevalue1"}), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccProviderConfigIgnoreTagPrefixes1("ignorekey") + testAccSubnetConfig, + PlanOnly: true, + }, + { + Config: testAccProviderConfigIgnoreTags1("ignorekey1") + testAccSubnetConfig, + PlanOnly: true, + }, + }, + }) +} + func TestAccAWSSubnet_ipv6(t *testing.T) { var before, after ec2.Subnet resourceName := "aws_subnet.test" @@ -361,6 +393,14 @@ func testAccCheckSubnetExists(n string, v *ec2.Subnet) resource.TestCheckFunc { } } +func testAccCheckSubnetUpdateTags(subnet *ec2.Subnet, oldTags, newTags map[string]string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + return keyvaluetags.Ec2UpdateTags(conn, aws.StringValue(subnet.SubnetId), oldTags, newTags) + } +} + const testAccSubnetConfig = ` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" diff --git a/aws/resource_aws_vpc.go b/aws/resource_aws_vpc.go index 42d5f85e959..771c0d2abdb 100644 --- a/aws/resource_aws_vpc.go +++ b/aws/resource_aws_vpc.go @@ -266,6 +266,8 @@ func resourceAwsVpcCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn + ignoreTags := meta.(*AWSClient).ignoreTags + ignoreTagPrefixes := meta.(*AWSClient).ignoreTagPrefixes // Refresh the VPC state vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Id())() @@ -294,7 +296,7 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { }.String() d.Set("arn", arn) - if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(vpc.Tags).IgnoreAws().Map()); err != nil { + if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(vpc.Tags).IgnoreAws().IgnorePrefixes(ignoreTagPrefixes).Ignore(ignoreTags).Map()); err != nil { return fmt.Errorf("error setting tags: %s", err) } diff --git a/aws/resource_aws_vpc_test.go b/aws/resource_aws_vpc_test.go index 6bcf727f7e2..1ced2627004 100644 --- a/aws/resource_aws_vpc_test.go +++ b/aws/resource_aws_vpc_test.go @@ -11,7 +11,9 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) // add sweeper to delete known test vpcs @@ -142,6 +144,36 @@ func TestAccAWSVpc_disappears(t *testing.T) { }) } +func TestAccAWSVpc_ignoreTags(t *testing.T) { + var providers []*schema.Provider + var vpc ec2.Vpc + resourceName := "aws_vpc.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + CheckDestroy: testAccCheckVpcDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcConfigTags, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcExists(resourceName, &vpc), + testAccCheckVpcUpdateTags(&vpc, nil, map[string]string{"ignorekey1": "ignorevalue1"}), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccProviderConfigIgnoreTagPrefixes1("ignorekey") + testAccVpcConfigTags, + PlanOnly: true, + }, + { + Config: testAccProviderConfigIgnoreTags1("ignorekey1") + testAccVpcConfigTags, + PlanOnly: true, + }, + }, + }) +} + func TestAccAWSVpc_AssignGeneratedIpv6CidrBlock(t *testing.T) { var vpc ec2.Vpc resourceName := "aws_vpc.test" @@ -332,6 +364,14 @@ func testAccCheckVpcDestroy(s *terraform.State) error { return nil } +func testAccCheckVpcUpdateTags(vpc *ec2.Vpc, oldTags, newTags map[string]string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + return keyvaluetags.Ec2UpdateTags(conn, aws.StringValue(vpc.VpcId), oldTags, newTags) + } +} + func testAccCheckVpcCidr(vpc *ec2.Vpc, expected string) resource.TestCheckFunc { return func(s *terraform.State) error { if aws.StringValue(vpc.CidrBlock) != expected { diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index e8c41f87241..0eeeafd1d86 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -201,6 +201,14 @@ 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_tag_prefixes` - (Optional) **NOTE: This functionality is in public preview and there are no compatibility promises with future versions of the Terraform AWS Provider until a general availability announcement.** List of resource tag key prefixes 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 any tag key matching the prefixes in any `tags` attributes and displaying any configuration difference for those tag values. If any resource configuration still has a tag matching one of the prefixes 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. This functionality is only supported in the following resources: + - `aws_subnet` + - `aws_vpc` + +* `ignore_tags` - (Optional) **NOTE: This functionality is in public preview and there are no compatibility promises with future versions of the Terraform AWS Provider until a general availability announcement.** 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. This functionality is only supported in the following resources: + - `aws_subnet` + - `aws_vpc` + * `insecure` - (Optional) Explicitly allow the provider to perform "insecure" SSL requests. If omitted, default value is `false`.