-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27361 from hashicorp/f-aws_sesv2_dedicated_ip
New Resource `aws_sesv2_dedicated_ip_assignment`
- Loading branch information
Showing
8 changed files
with
394 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:new-resource | ||
aws_sesv2_dedicated_ip_assignment | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package sesv2 | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"log" | ||
"strings" | ||
"time" | ||
|
||
"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/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/create" | ||
"github.com/hashicorp/terraform-provider-aws/internal/tfresource" | ||
"github.com/hashicorp/terraform-provider-aws/names" | ||
) | ||
|
||
func ResourceDedicatedIPAssignment() *schema.Resource { | ||
return &schema.Resource{ | ||
CreateWithoutTimeout: resourceDedicatedIPAssignmentCreate, | ||
ReadWithoutTimeout: resourceDedicatedIPAssignmentRead, | ||
DeleteWithoutTimeout: resourceDedicatedIPAssignmentDelete, | ||
|
||
Importer: &schema.ResourceImporter{ | ||
StateContext: schema.ImportStatePassthroughContext, | ||
}, | ||
|
||
Timeouts: &schema.ResourceTimeout{ | ||
Create: schema.DefaultTimeout(30 * time.Minute), | ||
Delete: schema.DefaultTimeout(30 * time.Minute), | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"destination_pool_name": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"ip": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateFunc: validation.IsIPAddress, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
const ( | ||
ResNameDedicatedIPAssignment = "Dedicated IP Assignment" | ||
) | ||
|
||
func resourceDedicatedIPAssignmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
conn := meta.(*conns.AWSClient).SESV2Conn | ||
|
||
in := &sesv2.PutDedicatedIpInPoolInput{ | ||
Ip: aws.String(d.Get("ip").(string)), | ||
DestinationPoolName: aws.String(d.Get("destination_pool_name").(string)), | ||
} | ||
|
||
_, err := conn.PutDedicatedIpInPool(ctx, in) | ||
if err != nil { | ||
return create.DiagError(names.SESV2, create.ErrActionCreating, ResNameDedicatedIPAssignment, d.Get("ip").(string), err) | ||
} | ||
|
||
id := toID(d.Get("ip").(string), d.Get("destination_pool_name").(string)) | ||
d.SetId(id) | ||
|
||
return resourceDedicatedIPAssignmentRead(ctx, d, meta) | ||
} | ||
|
||
func resourceDedicatedIPAssignmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
conn := meta.(*conns.AWSClient).SESV2Conn | ||
|
||
out, err := FindDedicatedIPAssignmentByID(ctx, conn, d.Id()) | ||
if !d.IsNewResource() && tfresource.NotFound(err) { | ||
log.Printf("[WARN] SESV2 DedicatedIPAssignment (%s) not found, removing from state", d.Id()) | ||
d.SetId("") | ||
return nil | ||
} | ||
if err != nil { | ||
return create.DiagError(names.SESV2, create.ErrActionReading, ResNameDedicatedIPAssignment, d.Id(), err) | ||
} | ||
|
||
d.Set("ip", aws.ToString(out.Ip)) | ||
d.Set("destination_pool_name", aws.ToString(out.PoolName)) | ||
|
||
return nil | ||
} | ||
|
||
func resourceDedicatedIPAssignmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
conn := meta.(*conns.AWSClient).SESV2Conn | ||
ip, _ := splitID(d.Id()) | ||
|
||
log.Printf("[INFO] Deleting SESV2 DedicatedIPAssignment %s", d.Id()) | ||
_, err := conn.PutDedicatedIpInPool(ctx, &sesv2.PutDedicatedIpInPoolInput{ | ||
Ip: aws.String(ip), | ||
DestinationPoolName: aws.String(defaultDedicatedPoolName), | ||
}) | ||
|
||
if err != nil { | ||
var nfe *types.NotFoundException | ||
if errors.As(err, &nfe) { | ||
return nil | ||
} | ||
|
||
return create.DiagError(names.SESV2, create.ErrActionDeleting, ResNameDedicatedIPAssignment, d.Id(), err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
const ( | ||
// defaultDedicatedPoolName contains the name of the standard pool managed by AWS | ||
// where dedicated IP addresses with an assignment are stored | ||
// | ||
// When an assignment resource is removed from state, the delete function will re-assign | ||
// the relevant IP to this pool. | ||
defaultDedicatedPoolName = "ses-default-dedicated-pool" | ||
) | ||
|
||
// ErrIncorrectPoolAssignment is returned when an IP is assigned to a pool different | ||
// from what is specified in state | ||
var ErrIncorrectPoolAssignment = errors.New("incorrect pool assignment") | ||
|
||
func FindDedicatedIPAssignmentByID(ctx context.Context, conn *sesv2.Client, id string) (*types.DedicatedIp, error) { | ||
ip, destinationPoolName := splitID(id) | ||
|
||
in := &sesv2.GetDedicatedIpInput{ | ||
Ip: aws.String(ip), | ||
} | ||
out, err := conn.GetDedicatedIp(ctx, in) | ||
if err != nil { | ||
var nfe *types.NotFoundException | ||
if errors.As(err, &nfe) { | ||
return nil, &resource.NotFoundError{ | ||
LastError: err, | ||
LastRequest: in, | ||
} | ||
} | ||
|
||
return nil, err | ||
} | ||
|
||
if out == nil || out.DedicatedIp == nil { | ||
return nil, tfresource.NewEmptyResultError(in) | ||
} | ||
if out.DedicatedIp.PoolName == nil || aws.ToString(out.DedicatedIp.PoolName) != destinationPoolName { | ||
return nil, &resource.NotFoundError{ | ||
LastError: ErrIncorrectPoolAssignment, | ||
LastRequest: in, | ||
} | ||
} | ||
|
||
return out.DedicatedIp, nil | ||
} | ||
|
||
func toID(ip, destinationPoolName string) string { | ||
return fmt.Sprintf("%s,%s", ip, destinationPoolName) | ||
} | ||
|
||
func splitID(id string) (string, string) { | ||
items := strings.Split(id, ",") | ||
if len(items) == 2 { | ||
return items[0], items[1] | ||
} | ||
return "", "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package sesv2_test | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"testing" | ||
|
||
"github.com/aws/aws-sdk-go-v2/service/sesv2/types" | ||
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" | ||
"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 TestAccSESV2DedicatedIPAssignment_serial(t *testing.T) { | ||
testCases := map[string]func(t *testing.T){ | ||
"basic": testAccSESV2DedicatedIPAssignment_basic, | ||
"disappears": testAccSESV2DedicatedIPAssignment_disappears, | ||
} | ||
|
||
for name, tc := range testCases { | ||
tc := tc | ||
t.Run(name, func(t *testing.T) { | ||
tc(t) | ||
}) | ||
} | ||
} | ||
|
||
func testAccSESV2DedicatedIPAssignment_basic(t *testing.T) { // nosemgrep:ci.sesv2-in-func-name | ||
if os.Getenv("SES_DEDICATED_IP") == "" { | ||
t.Skip("Environment variable SES_DEDICATED_IP is not set") | ||
} | ||
|
||
ip := os.Getenv("SES_DEDICATED_IP") | ||
poolName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) | ||
resourceName := "aws_sesv2_dedicated_ip_assignment.test" | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { acctest.PreCheck(t) }, | ||
ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), | ||
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, | ||
CheckDestroy: testAccCheckDedicatedIPAssignmentDestroy, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccDedicatedIPAssignmentConfig_basic(ip, poolName), | ||
Check: resource.ComposeTestCheckFunc( | ||
testAccCheckDedicatedIPAssignmentExists(resourceName), | ||
resource.TestCheckResourceAttr(resourceName, "ip", ip), | ||
resource.TestCheckResourceAttr(resourceName, "destination_pool_name", poolName), | ||
), | ||
}, | ||
{ | ||
ResourceName: resourceName, | ||
ImportState: true, | ||
ImportStateVerify: true, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccSESV2DedicatedIPAssignment_disappears(t *testing.T) { // nosemgrep:ci.sesv2-in-func-name | ||
if os.Getenv("SES_DEDICATED_IP") == "" { | ||
t.Skip("Environment variable SES_DEDICATED_IP is not set") | ||
} | ||
|
||
ip := os.Getenv("SES_DEDICATED_IP") | ||
poolName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) | ||
resourceName := "aws_sesv2_dedicated_ip_assignment.test" | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { acctest.PreCheck(t) }, | ||
ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), | ||
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, | ||
CheckDestroy: testAccCheckDedicatedIPAssignmentDestroy, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccDedicatedIPAssignmentConfig_basic(ip, poolName), | ||
Check: resource.ComposeTestCheckFunc( | ||
testAccCheckDedicatedIPAssignmentExists(resourceName), | ||
acctest.CheckResourceDisappears(acctest.Provider, tfsesv2.ResourceDedicatedIPAssignment(), resourceName), | ||
), | ||
ExpectNonEmptyPlan: true, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccCheckDedicatedIPAssignmentDestroy(s *terraform.State) error { | ||
conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Conn | ||
ctx := context.Background() | ||
|
||
for _, rs := range s.RootModule().Resources { | ||
if rs.Type != "aws_sesv2_dedicated_ip_assignment" { | ||
continue | ||
} | ||
|
||
_, err := tfsesv2.FindDedicatedIPAssignmentByID(ctx, conn, rs.Primary.ID) | ||
if err != nil { | ||
var nfe *types.NotFoundException | ||
if errors.As(err, &nfe) { | ||
return nil | ||
} | ||
if errors.Is(err, tfsesv2.ErrIncorrectPoolAssignment) { | ||
return nil | ||
} | ||
return err | ||
} | ||
|
||
return create.Error(names.SESV2, create.ErrActionCheckingDestroyed, tfsesv2.ResNameDedicatedIPAssignment, rs.Primary.ID, errors.New("not destroyed")) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func testAccCheckDedicatedIPAssignmentExists(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.ResNameDedicatedIPAssignment, name, errors.New("not found")) | ||
} | ||
|
||
if rs.Primary.ID == "" { | ||
return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameDedicatedIPAssignment, name, errors.New("not set")) | ||
} | ||
|
||
conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Conn | ||
ctx := context.Background() | ||
_, err := tfsesv2.FindDedicatedIPAssignmentByID(ctx, conn, rs.Primary.ID) | ||
if err != nil { | ||
return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameDedicatedIPAssignment, rs.Primary.ID, err) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
func testAccDedicatedIPAssignmentConfig_basic(ip, poolName string) string { | ||
return fmt.Sprintf(` | ||
resource "aws_sesv2_dedicated_ip_pool" "test" { | ||
pool_name = %[2]q | ||
} | ||
resource "aws_sesv2_dedicated_ip_assignment" "test" { | ||
ip = %[1]q | ||
destination_pool_name = aws_sesv2_dedicated_ip_pool.test.pool_name | ||
} | ||
`, ip, poolName) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.