diff --git a/.changelog/30094.txt b/.changelog/30094.txt new file mode 100644 index 000000000000..dbf2024f65ed --- /dev/null +++ b/.changelog/30094.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_sesv2_contact_list +``` diff --git a/internal/service/sesv2/contact_list.go b/internal/service/sesv2/contact_list.go new file mode 100644 index 000000000000..5285d82192ba --- /dev/null +++ b/internal/service/sesv2/contact_list.go @@ -0,0 +1,347 @@ +package sesv2 + +import ( + "context" + "errors" + "fmt" + "log" + "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/aws/aws-sdk-go/aws/arn" + "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-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + 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" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_sesv2_contact_list") +func ResourceContactList() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceContactListCreate, + ReadWithoutTimeout: resourceContactListRead, + UpdateWithoutTimeout: resourceContactListUpdate, + DeleteWithoutTimeout: resourceContactListDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "contact_list_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "created_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "last_updated_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "topic": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default_subscription_status": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.SubscriptionStatus](), + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "display_name": { + Type: schema.TypeString, + Required: true, + }, + "topic_name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +const ( + ResNameContactList = "Contact List" +) + +func resourceContactListCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Client() + + in := &sesv2.CreateContactListInput{ + ContactListName: aws.String(d.Get("contact_list_name").(string)), + } + + if v, ok := d.GetOk("description"); ok { + in.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("topic"); ok && v.(*schema.Set).Len() > 0 { + in.Topics = expandTopics(v.(*schema.Set).List()) + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) + + if len(tags) > 0 { + in.Tags = Tags(tags.IgnoreAWS()) + } + + out, err := conn.CreateContactList(ctx, in) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionCreating, ResNameContactList, d.Get("contact_list_name").(string), err) + } + + if out == nil { + return create.DiagError(names.SESV2, create.ErrActionCreating, ResNameContactList, d.Get("contact_list_name").(string), errors.New("empty output")) + } + + d.SetId(d.Get("contact_list_name").(string)) + + return resourceContactListRead(ctx, d, meta) +} + +func resourceContactListRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Client() + + out, err := FindContactListByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SESV2 ContactList (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionReading, ResNameContactList, d.Id(), err) + } + + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: "ses", + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("contact-list/%s", d.Id()), + }.String() + + d.Set("arn", arn) + d.Set("contact_list_name", out.ContactListName) + d.Set("created_timestamp", aws.ToTime(out.CreatedTimestamp).Format(time.RFC3339)) + d.Set("description", out.Description) + d.Set("last_updated_timestamp", aws.ToTime(out.LastUpdatedTimestamp).Format(time.RFC3339)) + + if err := d.Set("topic", flattenTopics(out.Topics)); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameContactList, d.Id(), err) + } + + tags, err := ListTags(ctx, conn, d.Get("arn").(string)) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionReading, ResNameEmailIdentity, d.Id(), err) + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameContactList, d.Id(), err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameContactList, d.Id(), err) + } + + return nil +} + +func resourceContactListUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Client() + + in := &sesv2.UpdateContactListInput{ + ContactListName: aws.String(d.Id()), + } + + if d.HasChanges("description", "topic") { + in.Description = aws.String(d.Get("description").(string)) + in.Topics = expandTopics(d.Get("topic").(*schema.Set).List()) + + log.Printf("[DEBUG] Updating SESV2 ContactList (%s): %#v", d.Id(), in) + if _, err := conn.UpdateContactList(ctx, in); err != nil { + return create.DiagError(names.SESV2, create.ErrActionUpdating, ResNameContactList, d.Id(), err) + } + } + + if d.HasChanges("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(ctx, conn, d.Get("arn").(string), o, n); err != nil { + return create.DiagError(names.SESV2, create.ErrActionUpdating, ResNameEmailIdentity, d.Id(), err) + } + } + + return resourceContactListRead(ctx, d, meta) +} + +func resourceContactListDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Client() + + log.Printf("[INFO] Deleting SESV2 ContactList %s", d.Id()) + + _, err := conn.DeleteContactList(ctx, &sesv2.DeleteContactListInput{ + ContactListName: aws.String(d.Id()), + }) + + if err != nil { + var nfe *types.NotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.SESV2, create.ErrActionDeleting, ResNameContactList, d.Id(), err) + } + + return nil +} + +func FindContactListByID(ctx context.Context, conn *sesv2.Client, id string) (*sesv2.GetContactListOutput, error) { + in := &sesv2.GetContactListInput{ + ContactListName: aws.String(id), + } + out, err := conn.GetContactList(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 { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} + +func expandTopics(tfList []interface{}) []types.Topic { + if len(tfList) == 0 { + return nil + } + + var apiObjects []types.Topic + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandTopic(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, *apiObject) + } + + return apiObjects +} + +func expandTopic(tfMap map[string]interface{}) *types.Topic { + if tfMap == nil { + return nil + } + + apiObject := &types.Topic{} + + if v, ok := tfMap["default_subscription_status"].(string); ok && v != "" { + apiObject.DefaultSubscriptionStatus = types.SubscriptionStatus(v) + } + + if v, ok := tfMap["description"].(string); ok && v != "" { + apiObject.Description = aws.String(v) + } + + if v, ok := tfMap["display_name"].(string); ok && v != "" { + apiObject.DisplayName = aws.String(v) + } + + if v, ok := tfMap["topic_name"].(string); ok && v != "" { + apiObject.TopicName = aws.String(v) + } + + return apiObject +} + +func flattenTopics(apiObjects []types.Topic) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + tfList = append(tfList, flattenTopic(&apiObject)) + } + + return tfList +} + +func flattenTopic(apiObject *types.Topic) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "default_subscription_status": string(apiObject.DefaultSubscriptionStatus), + } + + if v := apiObject.Description; v != nil { + tfMap["description"] = aws.ToString(v) + } + + if v := apiObject.DisplayName; v != nil { + tfMap["display_name"] = aws.ToString(v) + } + + if v := apiObject.TopicName; v != nil { + tfMap["topic_name"] = aws.ToString(v) + } + + return tfMap +} diff --git a/internal/service/sesv2/contact_list_test.go b/internal/service/sesv2/contact_list_test.go new file mode 100644 index 000000000000..319e64a75d7c --- /dev/null +++ b/internal/service/sesv2/contact_list_test.go @@ -0,0 +1,310 @@ +package sesv2_test + +import ( + "context" + "errors" + "fmt" + "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 TestAccSESV2ContactList_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_contact_list.test" + + // Only one contact list is allowed per AWS account. + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactListDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccContactListConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactListExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "contact_list_name", rName), + acctest.CheckResourceAttrRFC3339(resourceName, "created_timestamp"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + acctest.CheckResourceAttrRFC3339(resourceName, "last_updated_timestamp"), + resource.TestCheckResourceAttr(resourceName, "topic.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSESV2ContactList_description(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_contact_list.test" + + // Only one contact list is allowed per AWS account. + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactListDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccContactListConfig_description(rName, "description1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactListExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactListConfig_description(rName, "description2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactListExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + ), + }, + }, + }) +} + +func TestAccSESV2ContactList_topic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_contact_list.test" + + // Only one contact list is allowed per AWS account. + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactListDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccContactListConfig_topic1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactListExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "topic.#", "1"), + resource.TestCheckResourceAttr(resourceName, "topic.0.default_subscription_status", "OPT_IN"), + resource.TestCheckResourceAttr(resourceName, "topic.0.description", ""), + resource.TestCheckResourceAttr(resourceName, "topic.0.display_name", "topic1"), + resource.TestCheckResourceAttr(resourceName, "topic.0.topic_name", "topic1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactListConfig_topic2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactListExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "topic.#", "1"), + resource.TestCheckResourceAttr(resourceName, "topic.0.default_subscription_status", "OPT_OUT"), + resource.TestCheckResourceAttr(resourceName, "topic.0.description", "description"), + resource.TestCheckResourceAttr(resourceName, "topic.0.display_name", "topic2"), + resource.TestCheckResourceAttr(resourceName, "topic.0.topic_name", "topic2"), + ), + }, + }, + }) +} + +func TestAccSESV2ContactList_tags(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_contact_list.test" + + // Only one contact list is allowed per AWS account. + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactListDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccContactListConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactListExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactListConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactListExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccSESV2ContactList_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_contact_list.test" + + // Only one contact list is allowed per AWS account. + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactListDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccContactListConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactListExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfsesv2.ResourceContactList(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckContactListDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Client() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sesv2_contact_list" { + continue + } + + _, err := tfsesv2.FindContactListByID(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.ResNameContactList, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckContactListExists(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.ResNameContactList, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameContactList, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Client() + + _, err := tfsesv2.FindContactListByID(ctx, conn, rs.Primary.ID) + + if err != nil { + return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameContactList, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccContactListConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_sesv2_contact_list" "test" { + contact_list_name = %[1]q +} +`, rName) +} + +func testAccContactListConfig_description(rName, description string) string { + return fmt.Sprintf(` +resource "aws_sesv2_contact_list" "test" { + contact_list_name = %[1]q + description = %[2]q +} +`, rName, description) +} + +func testAccContactListConfig_topic1(rName string) string { + return fmt.Sprintf(` +resource "aws_sesv2_contact_list" "test" { + contact_list_name = %[1]q + + topic { + default_subscription_status = "OPT_IN" + display_name = "topic1" + topic_name = "topic1" + } +} +`, rName) +} + +func testAccContactListConfig_topic2(rName string) string { + return fmt.Sprintf(` +resource "aws_sesv2_contact_list" "test" { + contact_list_name = %[1]q + + topic { + default_subscription_status = "OPT_OUT" + description = "description" + display_name = "topic2" + topic_name = "topic2" + } +} +`, rName) +} + +func testAccContactListConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_sesv2_contact_list" "test" { + contact_list_name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccContactListConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_sesv2_contact_list" "test" { + contact_list_name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/sesv2/list.go b/internal/service/sesv2/list.go index 71bb0ff5ef4e..9f5389386b4e 100644 --- a/internal/service/sesv2/list.go +++ b/internal/service/sesv2/list.go @@ -24,3 +24,21 @@ func ListConfigurationSetsPages(ctx context.Context, conn *sesv2.Client, in *ses return nil } + +func ListContactListsPages(ctx context.Context, conn *sesv2.Client, in *sesv2.ListContactListsInput, fn func(*sesv2.ListContactListsOutput, bool) bool) error { + for { + out, err := conn.ListContactLists(ctx, in) + if err != nil { + return err + } + + lastPage := aws.ToString(out.NextToken) == "" + if !fn(out, lastPage) || lastPage { + break + } + + in.NextToken = out.NextToken + } + + return nil +} diff --git a/internal/service/sesv2/service_package_gen.go b/internal/service/sesv2/service_package_gen.go index 990222d51a91..9ef502e10cf4 100644 --- a/internal/service/sesv2/service_package_gen.go +++ b/internal/service/sesv2/service_package_gen.go @@ -38,6 +38,10 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceConfigurationSetEventDestination, TypeName: "aws_sesv2_configuration_set_event_destination", }, + { + Factory: ResourceContactList, + TypeName: "aws_sesv2_contact_list", + }, { Factory: ResourceDedicatedIPAssignment, TypeName: "aws_sesv2_dedicated_ip_assignment", diff --git a/internal/service/sesv2/sweep.go b/internal/service/sesv2/sweep.go index 70f42f57d877..7a8bb57ee881 100644 --- a/internal/service/sesv2/sweep.go +++ b/internal/service/sesv2/sweep.go @@ -7,6 +7,7 @@ import ( "fmt" "log" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/sesv2" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -19,6 +20,11 @@ func init() { Name: "aws_sesv2_configuration_set", F: sweepConfigurationSets, }) + + resource.AddTestSweepers("aws_sesv2_contact_list", &resource.Sweeper{ + Name: "aws_sesv2_contact_list", + F: sweepContactLists, + }) } func sweepConfigurationSets(region string) error { @@ -67,3 +73,50 @@ func sweepConfigurationSets(region string) error { return errs.ErrorOrNil() } + +func sweepContactLists(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(region) + + if err != nil { + return fmt.Errorf("getting client: %w", err) + } + + conn := client.(*conns.AWSClient).SESV2Client() + sweepResources := make([]sweep.Sweepable, 0) + var errs *multierror.Error + + input := &sesv2.ListContactListsInput{} + + err = ListContactListsPages(ctx, conn, input, func(page *sesv2.ListContactListsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, contactList := range page.ContactLists { + r := ResourceContactList() + d := r.Data(nil) + + d.SetId(aws.ToString(contactList.ContactListName)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("listing Contact Lists for %s: %w", region, err)) + } + + if err := sweep.SweepOrchestratorWithContext(ctx, sweepResources); err != nil { + errs = multierror.Append(errs, fmt.Errorf("sweeping Contact Lists for %s: %w", region, err)) + } + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Contact Lists sweep for %s: %s", region, errs) + return nil + } + + return errs.ErrorOrNil() +} diff --git a/website/docs/r/sesv2_contact_list.html.markdown b/website/docs/r/sesv2_contact_list.html.markdown new file mode 100644 index 000000000000..076d859fa4d3 --- /dev/null +++ b/website/docs/r/sesv2_contact_list.html.markdown @@ -0,0 +1,76 @@ +--- +subcategory: "SESv2 (Simple Email V2)" +layout: "aws" +page_title: "AWS: aws_sesv2_contact_list" +description: |- + Terraform resource for managing an AWS SESv2 (Simple Email V2) Contact List. +--- + +# Resource: aws_sesv2_contact_list + +Terraform resource for managing an AWS SESv2 (Simple Email V2) Contact List. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_sesv2_contact_list" "example" { + contact_list_name = "example" +} +``` + +### Extended Usage + +```terraform +resource "aws_sesv2_contact_list" "example" { + contact_list_name = "example" + description = "description" + + topic { + default_subscription_status = "OPT_IN" + description = "topic description" + display_name = "Example Topic" + topic_name = "example-topic" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `contact_list_name` - (Required) The name of the contact list. + +The following arguments are optional: + +* `description` - (Optional) A description of what the contact list is about. +* `tags` - (Optional) Key-value map of resource tags for the contact list. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +* `topic` - (Optional) Configuration block(s) with topic for the contact list. Detailed below. + +### topic + +The following arguments are required: + +* `default_subscription_status` - (Required) The default subscription status to be applied to a contact if the contact has not noted their preference for subscribing to a topic. +* `display_name` - (Required) The name of the topic the contact will see. +* `topic_name` - (Required) The name of the topic. + +The following arguments are optional: + +* `description` - (Optional) A description of what the topic is about, which the contact will see. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `created_timestamp` - A timestamp noting when the contact list was created in ISO 8601 format. +* `last_updated_timestamp` - A timestamp noting the last time the contact list was updated in ISO 8601 format. + +## Import + +SESv2 (Simple Email V2) Contact List can be imported using the `example_id_arg`, e.g., + +``` +$ terraform import aws_sesv2_contact_list.example example +```