From 72bb7543f225140f9940e84cbef6fa7a65b1cd24 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Thu, 25 Jan 2024 19:46:03 +0100 Subject: [PATCH 1/4] Create new aws_sesv2_email_identity_policy resource --- .../service/sesv2/email_identity_policy.go | 234 ++++++++++++++++++ internal/service/sesv2/service_package_gen.go | 5 + 2 files changed, 239 insertions(+) create mode 100644 internal/service/sesv2/email_identity_policy.go diff --git a/internal/service/sesv2/email_identity_policy.go b/internal/service/sesv2/email_identity_policy.go new file mode 100644 index 000000000000..3373a0415b7d --- /dev/null +++ b/internal/service/sesv2/email_identity_policy.go @@ -0,0 +1,234 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sesv2 + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sesv2" + "github.com/aws/aws-sdk-go-v2/service/sesv2/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_sesv2_email_identity_policy", name="Email Identity Policy") +func ResourceEmailIdentityPolicy() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceEmailIdentityPolicyCreate, + ReadWithoutTimeout: resourceEmailIdentityPolicyRead, + UpdateWithoutTimeout: resourceEmailIdentityPolicyUpdate, + DeleteWithoutTimeout: resourceEmailIdentityPolicyDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "email_identity": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "policy": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, + DiffSuppressOnRefresh: true, + StateFunc: func(v interface{}) string { + json, _ := structure.NormalizeJsonString(v) + return json + }, + }, + "policy_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 64), + }, + }, + } +} + +const ( + ResNameEmailIdentityPolicy = "Email Identity Policy" +) + +func resourceEmailIdentityPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SESV2Client(ctx) + + email_identity := d.Get("email_identity").(string) + policy_name := d.Get("policy_name").(string) + emailIdentityPolicyID := FormatEmailIdentityPolicyID(email_identity, policy_name) + + policy, err := structure.NormalizeJsonString(d.Get("policy").(string)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "policy (%s) is invalid JSON: %s", d.Get("policy").(string), err) + } + + in := &sesv2.CreateEmailIdentityPolicyInput{ + EmailIdentity: aws.String(email_identity), + Policy: aws.String(policy), + PolicyName: aws.String(policy_name), + } + + out, err := conn.CreateEmailIdentityPolicy(ctx, in) + if err != nil { + return create.AppendDiagError(diags, names.SESV2, create.ErrActionCreating, ResNameEmailIdentityPolicy, emailIdentityPolicyID, err) + } + + if out == nil { + return create.AppendDiagError(diags, names.SESV2, create.ErrActionCreating, ResNameEmailIdentityPolicy, emailIdentityPolicyID, errors.New("empty output")) + } + + d.SetId(emailIdentityPolicyID) + + return append(diags, resourceEmailIdentityPolicyRead(ctx, d, meta)...) +} + +func resourceEmailIdentityPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SESV2Client(ctx) + + policy, err := FindEmailIdentityPolicyByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SESV2 EmailIdentityPolicy (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return create.AppendDiagError(diags, names.SESV2, create.ErrActionReading, ResNameEmailIdentityPolicy, d.Id(), err) + } + + policy, err = verify.SecondJSONUnlessEquivalent(d.Get("policy").(string), policy) + if err != nil { + return create.AppendDiagError(diags, names.SESV2, create.ErrActionSetting, ResNameEmailIdentityPolicy, d.Id(), err) + } + + policy, err = structure.NormalizeJsonString(policy) + if err != nil { + return create.AppendDiagError(diags, names.SESV2, create.ErrActionSetting, ResNameEmailIdentityPolicy, d.Id(), err) + } + + d.Set("policy", policy) + + return diags +} + +func resourceEmailIdentityPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SESV2Client(ctx) + + policy, err := structure.NormalizeJsonString(d.Get("policy").(string)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "policy (%s) is invalid JSON: %s", d.Get("policy").(string), err) + } + + in := &sesv2.UpdateEmailIdentityPolicyInput{ + EmailIdentity: aws.String(d.Get("email_identity").(string)), + Policy: aws.String(policy), + PolicyName: aws.String(d.Get("policy_name").(string)), + } + + log.Printf("[DEBUG] Updating SESV2 EmailIdentityPolicy (%s): %#v", d.Id(), in) + _, err = conn.UpdateEmailIdentityPolicy(ctx, in) + if err != nil { + return create.AppendDiagError(diags, names.SESV2, create.ErrActionUpdating, ResNameEmailIdentityPolicy, d.Id(), err) + } + + return append(diags, resourceEmailIdentityPolicyRead(ctx, d, meta)...) +} + +func resourceEmailIdentityPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Client(ctx) + + log.Printf("[INFO] Deleting SESV2 EmailIdentityPolicy %s", d.Id()) + + emailIdentity, policyName, err := ParseEmailIdentityPolicyID(d.Id()) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionReading, ResNameEmailIdentityPolicy, d.Id(), err) + } + + _, err = conn.DeleteEmailIdentityPolicy(ctx, &sesv2.DeleteEmailIdentityPolicyInput{ + EmailIdentity: aws.String(emailIdentity), + PolicyName: aws.String(policyName), + }) + + if err != nil { + var nfe *types.NotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.SESV2, create.ErrActionDeleting, ResNameEmailIdentityPolicy, d.Id(), err) + } + + return nil +} + +func FindEmailIdentityPolicyByID(ctx context.Context, conn *sesv2.Client, id string) (string, error) { + emailIdentity, policyName, err := ParseEmailIdentityPolicyID(id) + if err != nil { + return "", err + } + + in := &sesv2.GetEmailIdentityPoliciesInput{ + EmailIdentity: aws.String(emailIdentity), + } + + out, err := conn.GetEmailIdentityPolicies(ctx, in) + if err != nil { + var nfe *types.NotFoundException + if errors.As(err, &nfe) { + return "", &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return "", err + } + + if out == nil { + return "", tfresource.NewEmptyResultError(in) + } + + for name, policy := range out.Policies { + if policyName == name { + return policy, nil + } + } + + return "", &retry.NotFoundError{} +} + +func FormatEmailIdentityPolicyID(emailIdentity, policyName string) string { + return fmt.Sprintf("%s|%s", emailIdentity, policyName) +} + +func ParseEmailIdentityPolicyID(id string) (string, string, error) { + idParts := strings.Split(id, "|") + if len(idParts) != 2 { + return "", "", errors.New("please make sure the ID is in the form EMAIL_IDENTITY|POLICY_NAME") + } + + return idParts[0], idParts[1], nil +} diff --git a/internal/service/sesv2/service_package_gen.go b/internal/service/sesv2/service_package_gen.go index 8bd83b3cbf8b..c033ef17194a 100644 --- a/internal/service/sesv2/service_package_gen.go +++ b/internal/service/sesv2/service_package_gen.go @@ -101,6 +101,11 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceEmailIdentityMailFromAttributes, TypeName: "aws_sesv2_email_identity_mail_from_attributes", }, + { + Factory: ResourceEmailIdentityPolicy, + TypeName: "aws_sesv2_email_identity_policy", + Name: "Email Identity Policy", + }, } } From 76e8ae1080b15d8dfffcd445d01712aa2109a2a5 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sat, 27 Jan 2024 15:53:34 +0100 Subject: [PATCH 2/4] Create acceptance tests --- .../service/sesv2/email_identity_policy.go | 7 + .../sesv2/email_identity_policy_test.go | 157 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 internal/service/sesv2/email_identity_policy_test.go diff --git a/internal/service/sesv2/email_identity_policy.go b/internal/service/sesv2/email_identity_policy.go index 3373a0415b7d..f62cecc0b44a 100644 --- a/internal/service/sesv2/email_identity_policy.go +++ b/internal/service/sesv2/email_identity_policy.go @@ -106,6 +106,11 @@ func resourceEmailIdentityPolicyRead(ctx context.Context, d *schema.ResourceData var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SESV2Client(ctx) + emailIdentity, policyName, err := ParseEmailIdentityPolicyID(d.Id()) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionReading, ResNameEmailIdentityPolicy, d.Id(), err) + } + policy, err := FindEmailIdentityPolicyByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { @@ -128,7 +133,9 @@ func resourceEmailIdentityPolicyRead(ctx context.Context, d *schema.ResourceData return create.AppendDiagError(diags, names.SESV2, create.ErrActionSetting, ResNameEmailIdentityPolicy, d.Id(), err) } + d.Set("email_identity", emailIdentity) d.Set("policy", policy) + d.Set("policy_name", policyName) return diags } diff --git a/internal/service/sesv2/email_identity_policy_test.go b/internal/service/sesv2/email_identity_policy_test.go new file mode 100644 index 000000000000..13402040edee --- /dev/null +++ b/internal/service/sesv2/email_identity_policy_test.go @@ -0,0 +1,157 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sesv2_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/sesv2/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tfsesv2 "github.com/hashicorp/terraform-provider-aws/internal/service/sesv2" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccSESV2EmailIdentityPolicy_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_email_identity_policy.test" + emailIdentity := acctest.RandomEmailAddress(acctest.RandomDomainName()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEmailIdentityPolicyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccEmailIdentityPolicyConfig_basic(emailIdentity, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckEmailIdentityPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "email_identity", emailIdentity), + resource.TestCheckResourceAttr(resourceName, "policy_name", rName), + resource.TestCheckResourceAttrSet(resourceName, "policy"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSESV2EmailIdentityPolicy_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_email_identity_policy.test" + emailIdentity := acctest.RandomEmailAddress(acctest.RandomDomainName()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEmailIdentityPolicyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccEmailIdentityPolicyConfig_basic(emailIdentity, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckEmailIdentityPolicyExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfsesv2.ResourceEmailIdentityPolicy(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckEmailIdentityPolicyDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Client(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sesv2_email_identity_policy" { + continue + } + + _, err := tfsesv2.FindEmailIdentityPolicyByID(ctx, conn, rs.Primary.ID) + + if err != nil { + var nfe *types.NotFoundException + if errors.As(err, &nfe) { + return nil + } + return err + } + + return create.Error(names.SESV2, create.ErrActionCheckingDestroyed, tfsesv2.ResNameEmailIdentityPolicy, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckEmailIdentityPolicyExists(ctx context.Context, name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameEmailIdentityPolicy, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameEmailIdentityPolicy, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Client(ctx) + + _, err := tfsesv2.FindEmailIdentityPolicyByID(ctx, conn, rs.Primary.ID) + + if err != nil { + return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameEmailIdentityPolicy, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccEmailIdentityPolicyConfig_basic(emailIdentity, rName string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +data "aws_partition" "current" {} + +resource "aws_sesv2_email_identity" "test" { + email_identity = %[1]q +} + +resource "aws_sesv2_email_identity_policy" "test" { + email_identity = aws_sesv2_email_identity.test.email_identity + policy_name = %[2]q + + policy = < Date: Sat, 27 Jan 2024 15:59:56 +0100 Subject: [PATCH 3/4] Create docs page --- .../sesv2_email_identity_policy.html.markdown | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 website/docs/r/sesv2_email_identity_policy.html.markdown diff --git a/website/docs/r/sesv2_email_identity_policy.html.markdown b/website/docs/r/sesv2_email_identity_policy.html.markdown new file mode 100644 index 000000000000..87fe55fd9529 --- /dev/null +++ b/website/docs/r/sesv2_email_identity_policy.html.markdown @@ -0,0 +1,78 @@ +--- +subcategory: "SESv2 (Simple Email V2)" +layout: "aws" +page_title: "AWS: aws_sesv2_email_identity_policy" +description: |- + Terraform resource for managing an AWS SESv2 (Simple Email V2) Email Identity Policy. +--- +# Resource: aws_sesv2_email_identity_policy + +Terraform resource for managing an AWS SESv2 (Simple Email V2) Email Identity Policy. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_sesv2_email_identity" "example" { + email_identity = "testing@example.com" +} + +resource "aws_sesv2_email_identity_policy" "example" { + email_identity = aws_sesv2_email_identity.example.email_identity + policy_name = "example" + + policy = < Date: Sat, 27 Jan 2024 16:20:27 +0100 Subject: [PATCH 4/4] Add changelog --- .changelog/35486.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/35486.txt diff --git a/.changelog/35486.txt b/.changelog/35486.txt new file mode 100644 index 000000000000..92b92c351670 --- /dev/null +++ b/.changelog/35486.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_sesv2_email_identity_policy +```