diff --git a/.changelog/21295.txt b/.changelog/21295.txt new file mode 100644 index 00000000000..c4713231e23 --- /dev/null +++ b/.changelog/21295.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_fms_policy: Add `delete_unused_fm_managed_resources` argument +``` \ No newline at end of file diff --git a/.changelog/21299.txt b/.changelog/21299.txt new file mode 100644 index 00000000000..ee69eeb1941 --- /dev/null +++ b/.changelog/21299.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_fms_policy: Add `tags` argument and `tags_all` attribute to support resource tagging +``` diff --git a/internal/service/fms/fms_test.go b/internal/service/fms/fms_test.go index b6645d1a7f8..3b65a40a01e 100644 --- a/internal/service/fms/fms_test.go +++ b/internal/service/fms/fms_test.go @@ -14,6 +14,7 @@ func TestAccFMS_serial(t *testing.T) { "cloudfrontDistribution": testAccPolicy_cloudFrontDistribution, "includeMap": testAccPolicy_includeMap, "update": testAccPolicy_update, + "resourceTags": testAccPolicy_resourceTags, "tags": testAccPolicy_tags, }, } diff --git a/internal/service/fms/generate.go b/internal/service/fms/generate.go index b86606f1edc..ba9c2876cf8 100644 --- a/internal/service/fms/generate.go +++ b/internal/service/fms/generate.go @@ -1,4 +1,5 @@ -//go:generate go run ../../generate/tags/main.go -ServiceTagsSlice -TagType=ResourceTag +//go:generate go run -tags generate ../../generate/tags/main.go -ListTags -ListTagsOp=ListTagsForResource -ListTagsInIDElem=ResourceArn -ListTagsOutTagsElem=TagList -ServiceTagsSlice -TagOp=TagResource -TagInTagsElem=TagList -TagInIDElem=ResourceArn -UpdateTags -TagType=Tag + // ONLY generate directives and package declaration! Do not add anything else to this file. package fms diff --git a/internal/service/fms/policy.go b/internal/service/fms/policy.go index 207e1032043..9ad80086847 100644 --- a/internal/service/fms/policy.go +++ b/internal/service/fms/policy.go @@ -8,11 +8,13 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/fms" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) @@ -22,27 +24,32 @@ func ResourcePolicy() *schema.Resource { Read: resourcePolicyRead, Update: resourcePolicyUpdate, Delete: resourcePolicyDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + CustomizeDiff: verify.SetTagsDiff, + Schema: map[string]*schema.Schema{ - "name": { + "arn": { Type: schema.TypeString, - Required: true, + Computed: true, }, - "delete_all_policy_resources": { Type: schema.TypeBool, Optional: true, Default: true, }, - + "delete_unused_fm_managed_resources": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, "exclude_resource_tags": { Type: schema.TypeBool, Required: true, }, - "exclude_map": { Type: schema.TypeList, MaxItems: 1, @@ -67,7 +74,6 @@ func ResourcePolicy() *schema.Resource { }, }, }, - "include_map": { Type: schema.TypeList, MaxItems: 1, @@ -92,108 +98,190 @@ func ResourcePolicy() *schema.Resource { }, }, }, - + "name": { + Type: schema.TypeString, + Required: true, + }, + "policy_update_token": { + Type: schema.TypeString, + Computed: true, + }, "remediation_enabled": { Type: schema.TypeBool, Optional: true, }, - - "resource_type_list": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Set: schema.HashString, - ConflictsWith: []string{"resource_type"}, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringMatch(regexp.MustCompile(`^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$`), "must match a supported resource type, such as AWS::EC2::VPC, see also: https://docs.aws.amazon.com/fms/2018-01-01/APIReference/API_Policy.html"), - }, - }, - + "resource_tags": tftags.TagsSchema(), "resource_type": { Type: schema.TypeString, Optional: true, Computed: true, - ConflictsWith: []string{"resource_type_list"}, ValidateFunc: validation.StringMatch(regexp.MustCompile(`^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$`), "must match a supported resource type, such as AWS::EC2::VPC, see also: https://docs.aws.amazon.com/fms/2018-01-01/APIReference/API_Policy.html"), + ConflictsWith: []string{"resource_type_list"}, }, - - "policy_update_token": { - Type: schema.TypeString, + "resource_type_list": { + Type: schema.TypeSet, + Optional: true, Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$`), "must match a supported resource type, such as AWS::EC2::VPC, see also: https://docs.aws.amazon.com/fms/2018-01-01/APIReference/API_Policy.html"), + }, + ConflictsWith: []string{"resource_type"}, }, - - "resource_tags": tftags.TagsSchema(), - "security_service_policy_data": { Type: schema.TypeList, Required: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - }, "managed_service_data": { Type: schema.TypeString, Optional: true, DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, }, + "type": { + Type: schema.TypeString, + Required: true, + }, }, }, }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), }, } } func resourcePolicyCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).FMSConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - fmsPolicy := resourcePolicyExpandPolicy(d) - - params := &fms.PutPolicyInput{ - Policy: fmsPolicy, + input := &fms.PutPolicyInput{ + Policy: resourcePolicyExpandPolicy(d), + TagList: Tags(tags.IgnoreAWS()), } - var resp *fms.PutPolicyOutput - var err error - - resp, err = conn.PutPolicy(params) + output, err := conn.PutPolicy(input) if err != nil { - return fmt.Errorf("Creating Policy Failed: %s", err.Error()) + return fmt.Errorf("error creating FMS Policy: %w", err) } - d.SetId(aws.StringValue(resp.Policy.PolicyId)) + d.SetId(aws.StringValue(output.Policy.PolicyId)) return resourcePolicyRead(d, meta) } func resourcePolicyRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).FMSConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + output, err := FindPolicyByID(conn, d.Id()) - var resp *fms.GetPolicyOutput - var req = &fms.GetPolicyInput{ - PolicyId: aws.String(d.Id()), + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] FMS Policy %s not found, removing from state", d.Id()) + d.SetId("") + return nil } - resp, err := conn.GetPolicy(req) + if err != nil { + return fmt.Errorf("error reading FMS Policy (%s): %w", d.Id(), err) + } + + if err := resourcePolicyFlattenPolicy(d, output); err != nil { + return err + } + + tags, err := ListTags(conn, d.Get("arn").(string)) if err != nil { - if tfawserr.ErrMessageContains(err, fms.ErrCodeResourceNotFoundException, "") { - log.Printf("[WARN] FMS Policy (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil + return fmt.Errorf("error listing tags for FMS Policy (%s): %w", d.Id(), err) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + + return nil +} + +func resourcePolicyUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).FMSConn + + if d.HasChangesExcept("tags", "tags_all") { + input := &fms.PutPolicyInput{ + Policy: resourcePolicyExpandPolicy(d), } - return err + + _, err := conn.PutPolicy(input) + + if err != nil { + return fmt.Errorf("error updating FMS Policy (%s): %w", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating FMS Policy (%s) tags: %w", d.Id(), err) + } + } + + return resourcePolicyRead(d, meta) +} + +func resourcePolicyDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).FMSConn + + log.Printf("[DEBUG] Deleting FMS Policy: %s", d.Id()) + _, err := conn.DeletePolicy(&fms.DeletePolicyInput{ + PolicyId: aws.String(d.Id()), + DeleteAllPolicyResources: aws.Bool(d.Get("delete_all_policy_resources").(bool)), + }) + + if tfawserr.ErrCodeEquals(err, fms.ErrCodeResourceNotFoundException) { + return nil } - return resourcePolicyFlattenPolicy(d, resp) + if err != nil { + return fmt.Errorf("error deleting FMS Policy (%s): %w", d.Id(), err) + } + + return nil +} + +func FindPolicyByID(conn *fms.FMS, id string) (*fms.GetPolicyOutput, error) { + input := &fms.GetPolicyInput{ + PolicyId: aws.String(id), + } + + output, err := conn.GetPolicy(input) + + if tfawserr.ErrCodeEquals(err, fms.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil } func resourcePolicyFlattenPolicy(d *schema.ResourceData, resp *fms.GetPolicyOutput) error { @@ -211,6 +299,7 @@ func resourcePolicyFlattenPolicy(d *schema.ResourceData, resp *fms.GetPolicyOutp if err := d.Set("resource_type_list", resp.Policy.ResourceTypeList); err != nil { return err } + d.Set("delete_unused_fm_managed_resources", resp.Policy.DeleteUnusedFMManagedResources) d.Set("resource_type", resp.Policy.ResourceType) d.Set("policy_update_token", resp.Policy.PolicyUpdateToken) if err := d.Set("resource_tags", flattenFMSResourceTags(resp.Policy.ResourceTags)); err != nil { @@ -236,11 +325,12 @@ func resourcePolicyExpandPolicy(d *schema.ResourceData) *fms.Policy { } fmsPolicy := &fms.Policy{ - PolicyName: aws.String(d.Get("name").(string)), - RemediationEnabled: aws.Bool(d.Get("remediation_enabled").(bool)), - ResourceType: resourceType, - ResourceTypeList: resourceTypeList, - ExcludeResourceTags: aws.Bool(d.Get("exclude_resource_tags").(bool)), + PolicyName: aws.String(d.Get("name").(string)), + RemediationEnabled: aws.Bool(d.Get("remediation_enabled").(bool)), + ResourceType: resourceType, + ResourceTypeList: resourceTypeList, + ExcludeResourceTags: aws.Bool(d.Get("exclude_resource_tags").(bool)), + DeleteUnusedFMManagedResources: aws.Bool(d.Get("delete_unused_fm_managed_resources").(bool)), } if d.Id() != "" { @@ -263,41 +353,6 @@ func resourcePolicyExpandPolicy(d *schema.ResourceData) *fms.Policy { return fmsPolicy } -func resourcePolicyUpdate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*conns.AWSClient).FMSConn - - fmsPolicy := resourcePolicyExpandPolicy(d) - - params := &fms.PutPolicyInput{Policy: fmsPolicy} - _, err := conn.PutPolicy(params) - - if err != nil { - return fmt.Errorf("Error modifying FMS Policy Rule: %s", err) - } - - return resourcePolicyRead(d, meta) -} - -func resourcePolicyDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*conns.AWSClient).FMSConn - log.Printf("[DEBUG] Delete FMS Policy: %s", d.Id()) - - _, err := conn.DeletePolicy(&fms.DeletePolicyInput{ - PolicyId: aws.String(d.Id()), - DeleteAllPolicyResources: aws.Bool(d.Get("delete_all_policy_resources").(bool)), - }) - - if tfawserr.ErrMessageContains(err, fms.ErrCodeResourceNotFoundException, "") { - return nil - } - - if err != nil { - return fmt.Errorf("error deleting FMS Policy (%s): %s", d.Id(), err) - } - - return nil -} - func expandFMSPolicyMap(set []interface{}) map[string][]*string { fmsPolicyMap := map[string][]*string{} if len(set) > 0 { diff --git a/internal/service/fms/policy_test.go b/internal/service/fms/policy_test.go index ae98248a66d..9eb1feda6ab 100644 --- a/internal/service/fms/policy_test.go +++ b/internal/service/fms/policy_test.go @@ -4,41 +4,44 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/fms" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tffms "github.com/hashicorp/terraform-provider-aws/internal/service/fms" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func testAccPolicy_basic(t *testing.T) { - fmsPolicyName := fmt.Sprintf("tf-fms-%s", sdkacctest.RandString(5)) - wafRuleGroupName := fmt.Sprintf("tf-waf-rg-%s", sdkacctest.RandString(5)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_fms_policy.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) testAccPreCheckFmsAdmin(t) - acctest.PreCheckOrganizationsAccount(t) + acctest.PreCheckOrganizationsEnabled(t) + acctest.PreCheckOrganizationManagementAccount(t) }, ErrorCheck: acctest.ErrorCheck(t, fms.EndpointsID), Providers: acctest.Providers, CheckDestroy: testAccCheckPolicyDestroy, Steps: []resource.TestStep{ { - Config: testAccFmsPolicyConfig(fmsPolicyName, wafRuleGroupName), + Config: testAccFmsPolicyConfig(rName, rName), Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyExists("aws_fms_policy.test"), - acctest.CheckResourceAttrRegionalARNIgnoreRegionAndAccount("aws_fms_policy.test", "arn", "fms", "policy/.+"), - resource.TestCheckResourceAttr("aws_fms_policy.test", "name", fmsPolicyName), - resource.TestCheckResourceAttr("aws_fms_policy.test", "security_service_policy_data.#", "1"), + testAccCheckPolicyExists(resourceName), + acctest.CheckResourceAttrRegionalARNIgnoreRegionAndAccount(resourceName, "arn", "fms", "policy/.+"), + resource.TestCheckResourceAttr(resourceName, "delete_unused_fm_managed_resources", "false"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "security_service_policy_data.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { - ResourceName: "aws_fms_policy.test", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"policy_update_token", "delete_all_policy_resources"}, @@ -48,29 +51,30 @@ func testAccPolicy_basic(t *testing.T) { } func testAccPolicy_cloudFrontDistribution(t *testing.T) { - fmsPolicyName := fmt.Sprintf("tf-fms-%s", sdkacctest.RandString(5)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_fms_policy.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) testAccPreCheckFmsAdmin(t) - acctest.PreCheckOrganizationsAccount(t) + acctest.PreCheckOrganizationsEnabled(t) + acctest.PreCheckOrganizationManagementAccount(t) }, ErrorCheck: acctest.ErrorCheck(t, fms.EndpointsID), Providers: acctest.Providers, CheckDestroy: testAccCheckPolicyDestroy, Steps: []resource.TestStep{ { - Config: testAccFmsPolicyConfig_cloudfrontDistribution(fmsPolicyName), + Config: testAccFmsPolicyConfig_cloudfrontDistribution(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyExists("aws_fms_policy.test"), - acctest.CheckResourceAttrRegionalARNIgnoreRegionAndAccount("aws_fms_policy.test", "arn", "fms", "policy/.+"), - resource.TestCheckResourceAttr("aws_fms_policy.test", "name", fmsPolicyName), - resource.TestCheckResourceAttr("aws_fms_policy.test", "security_service_policy_data.#", "1"), + testAccCheckPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "security_service_policy_data.#", "1"), ), }, { - ResourceName: "aws_fms_policy.test", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"policy_update_token", "delete_all_policy_resources"}, @@ -80,30 +84,30 @@ func testAccPolicy_cloudFrontDistribution(t *testing.T) { } func testAccPolicy_includeMap(t *testing.T) { - fmsPolicyName := fmt.Sprintf("tf-fms-%s", sdkacctest.RandString(5)) - wafRuleGroupName := fmt.Sprintf("tf-waf-rg-%s", sdkacctest.RandString(5)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_fms_policy.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) testAccPreCheckFmsAdmin(t) - acctest.PreCheckOrganizationsAccount(t) + acctest.PreCheckOrganizationsEnabled(t) + acctest.PreCheckOrganizationManagementAccount(t) }, ErrorCheck: acctest.ErrorCheck(t, fms.EndpointsID), Providers: acctest.Providers, CheckDestroy: testAccCheckPolicyDestroy, Steps: []resource.TestStep{ { - Config: testAccFmsPolicyConfig_include(fmsPolicyName, wafRuleGroupName), + Config: testAccFmsPolicyConfig_include(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyExists("aws_fms_policy.test"), - acctest.CheckResourceAttrRegionalARNIgnoreRegionAndAccount("aws_fms_policy.test", "arn", "fms", "policy/.+"), - resource.TestCheckResourceAttr("aws_fms_policy.test", "name", fmsPolicyName), - resource.TestCheckResourceAttr("aws_fms_policy.test", "security_service_policy_data.#", "1"), + testAccCheckPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "security_service_policy_data.#", "1"), ), }, { - ResourceName: "aws_fms_policy.test", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"policy_update_token", "delete_all_policy_resources"}, @@ -113,66 +117,100 @@ func testAccPolicy_includeMap(t *testing.T) { } func testAccPolicy_update(t *testing.T) { - fmsPolicyName := fmt.Sprintf("tf-fms-%s", sdkacctest.RandString(5)) - fmsPolicyName2 := fmt.Sprintf("tf-fms-%s2", sdkacctest.RandString(5)) - wafRuleGroupName := fmt.Sprintf("tf-waf-rg-%s", sdkacctest.RandString(5)) + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_fms_policy.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) testAccPreCheckFmsAdmin(t) - acctest.PreCheckOrganizationsAccount(t) + acctest.PreCheckOrganizationsEnabled(t) + acctest.PreCheckOrganizationManagementAccount(t) }, ErrorCheck: acctest.ErrorCheck(t, fms.EndpointsID), Providers: acctest.Providers, CheckDestroy: testAccCheckPolicyDestroy, Steps: []resource.TestStep{ { - Config: testAccFmsPolicyConfig(fmsPolicyName, wafRuleGroupName), + Config: testAccFmsPolicyConfig(rName1, rName1), Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyExists("aws_fms_policy.test"), - acctest.CheckResourceAttrRegionalARNIgnoreRegionAndAccount("aws_fms_policy.test", "arn", "fms", "policy/.+"), - resource.TestCheckResourceAttr("aws_fms_policy.test", "name", fmsPolicyName), - resource.TestCheckResourceAttr("aws_fms_policy.test", "security_service_policy_data.#", "1"), + testAccCheckPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName1), + resource.TestCheckResourceAttr(resourceName, "security_service_policy_data.#", "1"), ), }, { - Config: testAccFmsPolicyConfig_updated(fmsPolicyName2, wafRuleGroupName), + Config: testAccFmsPolicyConfig_updated(rName2, rName1), + }, + }, + }) +} + +func testAccPolicy_resourceTags(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_fms_policy.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + testAccPreCheckFmsAdmin(t) + acctest.PreCheckOrganizationsEnabled(t) + acctest.PreCheckOrganizationManagementAccount(t) + }, + ErrorCheck: acctest.ErrorCheck(t, fms.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccFmsPolicyConfigResourceTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "resource_tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "resource_tags.key1", "value1"), + ), + }, + { + Config: testAccFmsPolicyConfigResourceTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "resource_tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "resource_tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "resource_tags.key2", "value2"), + ), }, }, }) } func testAccPolicy_tags(t *testing.T) { - fmsPolicyName := fmt.Sprintf("tf-fms-%s", sdkacctest.RandString(5)) - wafRuleGroupName := fmt.Sprintf("tf-waf-rg-%s", sdkacctest.RandString(5)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_fms_policy.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) testAccPreCheckFmsAdmin(t) - acctest.PreCheckOrganizationsAccount(t) + acctest.PreCheckOrganizationsEnabled(t) + acctest.PreCheckOrganizationManagementAccount(t) }, ErrorCheck: acctest.ErrorCheck(t, fms.EndpointsID), Providers: acctest.Providers, CheckDestroy: testAccCheckPolicyDestroy, Steps: []resource.TestStep{ { - Config: testAccFmsPolicyConfig_tags(fmsPolicyName, wafRuleGroupName), + Config: testAccFmsPolicyConfigTags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyExists("aws_fms_policy.test"), - resource.TestCheckResourceAttr("aws_fms_policy.test", "name", fmsPolicyName), - resource.TestCheckResourceAttr("aws_fms_policy.test", "resource_tags.%", "2"), - resource.TestCheckResourceAttr("aws_fms_policy.test", "resource_tags.Usage", "original"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { - Config: testAccFmsPolicyConfig_tagsChanged(fmsPolicyName, wafRuleGroupName), + Config: testAccFmsPolicyConfigTags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyExists("aws_fms_policy.test"), - resource.TestCheckResourceAttr("aws_fms_policy.test", "name", fmsPolicyName), - resource.TestCheckResourceAttr("aws_fms_policy.test", "resource_tags.%", "1"), - resource.TestCheckResourceAttr("aws_fms_policy.test", "resource_tags.Usage", "changed"), + testAccCheckPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), }, }, @@ -187,15 +225,9 @@ func testAccCheckPolicyDestroy(s *terraform.State) error { continue } - policyID := rs.Primary.Attributes["id"] - - input := &fms.GetPolicyInput{ - PolicyId: aws.String(policyID), - } - - resp, err := conn.GetPolicy(input) + _, err := tffms.FindPolicyByID(conn, rs.Primary.ID) - if tfawserr.ErrMessageContains(err, fms.ErrCodeResourceNotFoundException, "") { + if tfresource.NotFound(err) { continue } @@ -203,50 +235,50 @@ func testAccCheckPolicyDestroy(s *terraform.State) error { return err } - if resp.Policy.PolicyId != nil { - return fmt.Errorf("[DESTROY Error] Fms Policy (%s) not deleted", rs.Primary.ID) - } + return fmt.Errorf("FMS Policy %s still exists", rs.Primary.ID) } + return nil } -func testAccCheckPolicyExists(name string) resource.TestCheckFunc { +func testAccCheckPolicyExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { - _, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", name) + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No FMS Policy ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).FMSConn + + _, err := tffms.FindPolicyByID(conn, rs.Primary.ID) + + if err != nil { + return err } return nil } } -func testAccFmsPolicyConfigBase() string { - return acctest.ConfigCompose( - testAccFmsAdminRegionProviderConfig(), - ` +func testAccFmsPolicyConfigOrgMgmtAccountBase() string { + return acctest.ConfigCompose(testAccFmsAdminRegionProviderConfig(), ` data "aws_caller_identity" "current" {} -data "aws_partition" "current" {} - -resource "aws_organizations_organization" "test" { - aws_service_access_principals = ["fms.${data.aws_partition.current.dns_suffix}"] - feature_set = "ALL" -} - resource "aws_fms_admin_account" "test" { - account_id = aws_organizations_organization.test.master_account_id + account_id = data.aws_caller_identity.current.account_id } `) } -func testAccFmsPolicyConfig(name string, group string) string { - return acctest.ConfigCompose( - testAccFmsPolicyConfigBase(), - fmt.Sprintf(` +func testAccFmsPolicyConfig(policyName, ruleGroupName string) string { + return acctest.ConfigCompose(testAccFmsPolicyConfigOrgMgmtAccountBase(), fmt.Sprintf(` resource "aws_fms_policy" "test" { exclude_resource_tags = false - name = "%[1]s" + name = %[1]q remediation_enabled = false resource_type_list = ["AWS::ElasticLoadBalancingV2::LoadBalancer"] @@ -264,18 +296,16 @@ resource "aws_fms_policy" "test" { resource "aws_wafregional_rule_group" "test" { metric_name = "MyTest" - name = "%[2]s" + name = %[2]q } -`, name, group)) +`, policyName, ruleGroupName)) } -func testAccFmsPolicyConfig_cloudfrontDistribution(name string) string { - return acctest.ConfigCompose( - testAccFmsPolicyConfigBase(), - fmt.Sprintf(` +func testAccFmsPolicyConfig_cloudfrontDistribution(rName string) string { + return acctest.ConfigCompose(testAccFmsPolicyConfigOrgMgmtAccountBase(), fmt.Sprintf(` resource "aws_fms_policy" "test" { exclude_resource_tags = false - name = "%[1]s" + name = %[1]q remediation_enabled = false resource_type = "AWS::CloudFront::Distribution" @@ -336,7 +366,7 @@ resource "aws_s3_bucket" "test" { } resource "aws_kinesis_firehose_delivery_stream" "test" { - name = "aws-waf-logs-%[1]s" + name = %[1]q destination = "s3" s3_configuration { @@ -344,16 +374,14 @@ resource "aws_kinesis_firehose_delivery_stream" "test" { bucket_arn = aws_s3_bucket.test.arn } } -`, name)) +`, rName)) } -func testAccFmsPolicyConfig_updated(name string, group string) string { - return acctest.ConfigCompose( - testAccFmsPolicyConfigBase(), - fmt.Sprintf(` +func testAccFmsPolicyConfig_updated(policyName, ruleGroupName string) string { + return acctest.ConfigCompose(testAccFmsPolicyConfigOrgMgmtAccountBase(), fmt.Sprintf(` resource "aws_fms_policy" "test" { exclude_resource_tags = false - name = "%[1]s" + name = %[1]q remediation_enabled = true resource_type_list = ["AWS::ElasticLoadBalancingV2::LoadBalancer"] @@ -375,18 +403,16 @@ resource "aws_fms_policy" "test" { resource "aws_wafregional_rule_group" "test" { metric_name = "MyTest" - name = "%[2]s" + name = %[2]q } -`, name, group)) +`, policyName, ruleGroupName)) } -func testAccFmsPolicyConfig_include(name string, group string) string { - return acctest.ConfigCompose( - testAccFmsPolicyConfigBase(), - fmt.Sprintf(` +func testAccFmsPolicyConfig_include(rName string) string { + return acctest.ConfigCompose(testAccFmsPolicyConfigOrgMgmtAccountBase(), fmt.Sprintf(` resource "aws_fms_policy" "test" { exclude_resource_tags = false - name = "%[1]s" + name = %[1]q remediation_enabled = false resource_type_list = ["AWS::ElasticLoadBalancingV2::LoadBalancer"] @@ -404,18 +430,16 @@ resource "aws_fms_policy" "test" { resource "aws_wafregional_rule_group" "test" { metric_name = "MyTest" - name = "%[2]s" + name = %[1]q } -`, name, group)) +`, rName)) } -func testAccFmsPolicyConfig_tags(name string, group string) string { - return acctest.ConfigCompose( - testAccFmsPolicyConfigBase(), - fmt.Sprintf(` +func testAccFmsPolicyConfigResourceTags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccFmsPolicyConfigOrgMgmtAccountBase(), fmt.Sprintf(` resource "aws_fms_policy" "test" { exclude_resource_tags = false - name = "%[1]s" + name = %[1]q remediation_enabled = false resource_type_list = ["AWS::ElasticLoadBalancingV2::LoadBalancer"] @@ -425,8 +449,7 @@ resource "aws_fms_policy" "test" { } resource_tags = { - Environment = "Testing" - Usage = "original" + %[2]q = %[3]q } depends_on = [aws_fms_admin_account.test] @@ -434,18 +457,16 @@ resource "aws_fms_policy" "test" { resource "aws_wafregional_rule_group" "test" { metric_name = "MyTest" - name = "%[2]s" + name = %[1]q } -`, name, group)) +`, rName, tagKey1, tagValue1)) } -func testAccFmsPolicyConfig_tagsChanged(name string, group string) string { - return acctest.ConfigCompose( - testAccFmsPolicyConfigBase(), - fmt.Sprintf(` +func testAccFmsPolicyConfigResourceTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccFmsPolicyConfigOrgMgmtAccountBase(), fmt.Sprintf(` resource "aws_fms_policy" "test" { exclude_resource_tags = false - name = "%[1]s" + name = %[1]q remediation_enabled = false resource_type_list = ["AWS::ElasticLoadBalancingV2::LoadBalancer"] @@ -455,7 +476,63 @@ resource "aws_fms_policy" "test" { } resource_tags = { - Usage = "changed" + %[2]q = %[3]q + %[4]q = %[5]q + } + + depends_on = [aws_fms_admin_account.test] +} + +resource "aws_wafregional_rule_group" "test" { + metric_name = "MyTest" + name = %[1]q +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} + +func testAccFmsPolicyConfigTags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccFmsPolicyConfigOrgMgmtAccountBase(), fmt.Sprintf(` +resource "aws_fms_policy" "test" { + exclude_resource_tags = false + name = %[1]q + remediation_enabled = false + resource_type_list = ["AWS::ElasticLoadBalancingV2::LoadBalancer"] + + security_service_policy_data { + type = "WAF" + managed_service_data = "{\"type\": \"WAF\", \"ruleGroups\": [{\"id\":\"${aws_wafregional_rule_group.test.id}\", \"overrideAction\" : {\"type\": \"COUNT\"}}],\"defaultAction\": {\"type\": \"BLOCK\"}, \"overrideCustomerWebACLAssociation\": false}" + } + + tags = { + %[2]q = %[3]q + } + + depends_on = [aws_fms_admin_account.test] +} + +resource "aws_wafregional_rule_group" "test" { + metric_name = "MyTest" + name = %[1]q +} +`, rName, tagKey1, tagValue1)) +} + +func testAccFmsPolicyConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccFmsPolicyConfigOrgMgmtAccountBase(), fmt.Sprintf(` +resource "aws_fms_policy" "test" { + exclude_resource_tags = false + name = %[1]q + remediation_enabled = false + resource_type_list = ["AWS::ElasticLoadBalancingV2::LoadBalancer"] + + security_service_policy_data { + type = "WAF" + managed_service_data = "{\"type\": \"WAF\", \"ruleGroups\": [{\"id\":\"${aws_wafregional_rule_group.test.id}\", \"overrideAction\" : {\"type\": \"COUNT\"}}],\"defaultAction\": {\"type\": \"BLOCK\"}, \"overrideCustomerWebACLAssociation\": false}" + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q } depends_on = [aws_fms_admin_account.test] @@ -463,7 +540,7 @@ resource "aws_fms_policy" "test" { resource "aws_wafregional_rule_group" "test" { metric_name = "MyTest" - name = "%[2]s" + name = %[1]q } -`, name, group)) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) } diff --git a/internal/service/fms/tags_gen.go b/internal/service/fms/tags_gen.go index 94404c4544b..995bc36bfd1 100644 --- a/internal/service/fms/tags_gen.go +++ b/internal/service/fms/tags_gen.go @@ -2,19 +2,38 @@ package fms import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/fms" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" ) +// ListTags lists fms service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func ListTags(conn *fms.FMS, identifier string) (tftags.KeyValueTags, error) { + input := &fms.ListTagsForResourceInput{ + ResourceArn: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(input) + + if err != nil { + return tftags.New(nil), err + } + + return KeyValueTags(output.TagList), nil +} + // []*SERVICE.Tag handling // Tags returns fms service tags. -func Tags(tags tftags.KeyValueTags) []*fms.ResourceTag { - result := make([]*fms.ResourceTag, 0, len(tags)) +func Tags(tags tftags.KeyValueTags) []*fms.Tag { + result := make([]*fms.Tag, 0, len(tags)) for k, v := range tags.Map() { - tag := &fms.ResourceTag{ + tag := &fms.Tag{ Key: aws.String(k), Value: aws.String(v), } @@ -26,7 +45,7 @@ func Tags(tags tftags.KeyValueTags) []*fms.ResourceTag { } // KeyValueTags creates tftags.KeyValueTags from fms service tags. -func KeyValueTags(tags []*fms.ResourceTag) tftags.KeyValueTags { +func KeyValueTags(tags []*fms.Tag) tftags.KeyValueTags { m := make(map[string]*string, len(tags)) for _, tag := range tags { @@ -35,3 +54,39 @@ func KeyValueTags(tags []*fms.ResourceTag) tftags.KeyValueTags { return tftags.New(m) } + +// UpdateTags updates fms service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func UpdateTags(conn *fms.FMS, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error { + oldTags := tftags.New(oldTagsMap) + newTags := tftags.New(newTagsMap) + + if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 { + input := &fms.UntagResourceInput{ + ResourceArn: aws.String(identifier), + TagKeys: aws.StringSlice(removedTags.IgnoreAWS().Keys()), + } + + _, err := conn.UntagResource(input) + + if err != nil { + return fmt.Errorf("error untagging resource (%s): %w", identifier, err) + } + } + + if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 { + input := &fms.TagResourceInput{ + ResourceArn: aws.String(identifier), + TagList: Tags(updatedTags.IgnoreAWS()), + } + + _, err := conn.TagResource(input) + + if err != nil { + return fmt.Errorf("error tagging resource (%s): %w", identifier, err) + } + } + + return nil +} diff --git a/website/docs/r/fms_policy.html.markdown b/website/docs/r/fms_policy.html.markdown index f111f343725..1f0a8773149 100644 --- a/website/docs/r/fms_policy.html.markdown +++ b/website/docs/r/fms_policy.html.markdown @@ -36,6 +36,10 @@ resource "aws_fms_policy" "example" { overrideCustomerWebACLAssociation = false }) } + + tags = { + Name = "example-fms-policy" + } } resource "aws_wafregional_rule_group" "example" { @@ -50,6 +54,7 @@ The following arguments are supported: * `name` - (Required, Forces new resource) The friendly name of the AWS Firewall Manager Policy. * `delete_all_policy_resources` - (Optional) If true, the request will also perform a clean-up process. Defaults to `true`. More information can be found here [AWS Firewall Manager delete policy](https://docs.aws.amazon.com/fms/2018-01-01/APIReference/API_DeletePolicy.html) +* `delete_unused_fm_managed_resources` - (Optional) If true, Firewall Manager will automatically remove protections from resources that leave the policy scope. Defaults to `false`. More information can be found here [AWS Firewall Manager policy contents](https://docs.aws.amazon.com/fms/2018-01-01/APIReference/API_Policy.html) * `exclude_map` - (Optional) A map of lists of accounts and OU's to exclude from the policy. * `exclude_resource_tags` - (Required, Forces new resource) A boolean value, if true the tags that are specified in the `resource_tags` are not protected by this policy. If set to false and resource_tags are populated, resources that contain tags will be protected by this policy. * `include_map` - (Optional) A map of lists of accounts and OU's to include in the policy. @@ -58,6 +63,7 @@ The following arguments are supported: * `resource_type` - (Optional) A resource type to protect. Conflicts with `resource_type_list`. See the [FMS API Reference](https://docs.aws.amazon.com/fms/2018-01-01/APIReference/API_Policy.html#fms-Type-Policy-ResourceType) for more information about supported values. * `resource_type_list` - (Optional) A list of resource types to protect. Conflicts with `resource_type`. See the [FMS API Reference](https://docs.aws.amazon.com/fms/2018-01-01/APIReference/API_Policy.html#fms-Type-Policy-ResourceType) for more information about supported values. * `security_service_policy_data` - (Required) The objects to include in Security Service Policy Data. Documented below. +* `tags` - (Optional) Key-value mapping of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. ## `exclude_map` Configuration Block @@ -84,6 +90,7 @@ In addition to all arguments above, the following attributes are exported: * `id` - The AWS account ID of the AWS Firewall Manager administrator account. * `policy_update_token` - A unique identifier for each update to the policy. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). ## Import