diff --git a/.changelog/27565.txt b/.changelog/27565.txt new file mode 100644 index 00000000000..7333449aa01 --- /dev/null +++ b/.changelog/27565.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_sesv2_configuration_set_event_destination +``` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 3a599e0c48a..03090c061fb 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -2095,6 +2095,7 @@ func New(ctx context.Context) (*schema.Provider, error) { "aws_ses_template": ses.ResourceTemplate(), "aws_sesv2_configuration_set": sesv2.ResourceConfigurationSet(), + "aws_sesv2_configuration_set_event_destination": sesv2.ResourceConfigurationSetEventDestination(), "aws_sesv2_dedicated_ip_assignment": sesv2.ResourceDedicatedIPAssignment(), "aws_sesv2_dedicated_ip_pool": sesv2.ResourceDedicatedIPPool(), "aws_sesv2_email_identity": sesv2.ResourceEmailIdentity(), diff --git a/internal/service/sesv2/configuration_set_event_destination.go b/internal/service/sesv2/configuration_set_event_destination.go new file mode 100644 index 00000000000..02b553739be --- /dev/null +++ b/internal/service/sesv2/configuration_set_event_destination.go @@ -0,0 +1,593 @@ +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/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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func ResourceConfigurationSetEventDestination() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceConfigurationSetEventDestinationCreate, + ReadWithoutTimeout: resourceConfigurationSetEventDestinationRead, + UpdateWithoutTimeout: resourceConfigurationSetEventDestinationUpdate, + DeleteWithoutTimeout: resourceConfigurationSetEventDestinationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "configuration_set_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "event_destination": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cloud_watch_destination": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{ + "event_destination.0.cloud_watch_destination", + "event_destination.0.kinesis_firehose_destination", + "event_destination.0.pinpoint_destination", + "event_destination.0.sns_destination", + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "dimension_configuration": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default_dimension_value": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 256), + }, + "dimension_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 256), + }, + "dimension_value_source": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.DimensionValueSource](), + }, + }, + }, + }, + }, + }, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + }, + "kinesis_firehose_destination": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{ + "event_destination.0.cloud_watch_destination", + "event_destination.0.kinesis_firehose_destination", + "event_destination.0.pinpoint_destination", + "event_destination.0.sns_destination", + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "delivery_stream_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "iam_role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "matching_event_types": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[types.EventType](), + }, + }, + "pinpoint_destination": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{ + "event_destination.0.cloud_watch_destination", + "event_destination.0.kinesis_firehose_destination", + "event_destination.0.pinpoint_destination", + "event_destination.0.sns_destination", + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "application_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "sns_destination": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{ + "event_destination.0.cloud_watch_destination", + "event_destination.0.kinesis_firehose_destination", + "event_destination.0.pinpoint_destination", + "event_destination.0.sns_destination", + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "topic_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + }, + }, + }, + "event_destination_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +const ( + ResNameConfigurationSetEventDestination = "Configuration Set Event Destination" +) + +func resourceConfigurationSetEventDestinationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Client() + + in := &sesv2.CreateConfigurationSetEventDestinationInput{ + ConfigurationSetName: aws.String(d.Get("configuration_set_name").(string)), + EventDestination: expandEventDestination(d.Get("event_destination").([]interface{})[0].(map[string]interface{})), + EventDestinationName: aws.String(d.Get("event_destination_name").(string)), + } + + configurationSetEventDestinationID := FormatConfigurationSetEventDestinationID(d.Get("configuration_set_name").(string), d.Get("event_destination_name").(string)) + + out, err := conn.CreateConfigurationSetEventDestination(ctx, in) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionCreating, ResNameConfigurationSetEventDestination, configurationSetEventDestinationID, err) + } + + if out == nil { + return create.DiagError(names.SESV2, create.ErrActionCreating, ResNameConfigurationSetEventDestination, configurationSetEventDestinationID, errors.New("empty output")) + } + + d.SetId(configurationSetEventDestinationID) + + return resourceConfigurationSetEventDestinationRead(ctx, d, meta) +} + +func resourceConfigurationSetEventDestinationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Client() + + configurationSetName, _, err := ParseConfigurationSetEventDestinationID(d.Id()) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionReading, ResNameConfigurationSetEventDestination, d.Id(), err) + } + + out, err := FindConfigurationSetEventDestinationByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SESV2 ConfigurationSetEventDestination (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionReading, ResNameConfigurationSetEventDestination, d.Id(), err) + } + + d.Set("configuration_set_name", configurationSetName) + d.Set("event_destination_name", out.Name) + + if err := d.Set("event_destination", []interface{}{flattenEventDestination(out)}); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSetEventDestination, d.Id(), err) + } + + return nil +} + +func resourceConfigurationSetEventDestinationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Client() + + configurationSetName, eventDestinationName, err := ParseConfigurationSetEventDestinationID(d.Id()) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionUpdating, ResNameConfigurationSetEventDestination, d.Id(), err) + } + + if d.HasChanges("event_destination") { + in := &sesv2.UpdateConfigurationSetEventDestinationInput{ + ConfigurationSetName: aws.String(configurationSetName), + EventDestination: expandEventDestination(d.Get("event_destination").([]interface{})[0].(map[string]interface{})), + EventDestinationName: aws.String(eventDestinationName), + } + + log.Printf("[DEBUG] Updating SESV2 ConfigurationSetEventDestination (%s): %#v", d.Id(), in) + _, err := conn.UpdateConfigurationSetEventDestination(ctx, in) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionUpdating, ResNameConfigurationSetEventDestination, d.Id(), err) + } + } + + return resourceConfigurationSetEventDestinationRead(ctx, d, meta) +} + +func resourceConfigurationSetEventDestinationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Client() + + log.Printf("[INFO] Deleting SESV2 ConfigurationSetEventDestination %s", d.Id()) + + configurationSetName, eventDestinationName, err := ParseConfigurationSetEventDestinationID(d.Id()) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionReading, ResNameConfigurationSetEventDestination, d.Id(), err) + } + + _, err = conn.DeleteConfigurationSetEventDestination(ctx, &sesv2.DeleteConfigurationSetEventDestinationInput{ + ConfigurationSetName: aws.String(configurationSetName), + EventDestinationName: aws.String(eventDestinationName), + }) + + if err != nil { + var nfe *types.NotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.SESV2, create.ErrActionDeleting, ResNameConfigurationSetEventDestination, d.Id(), err) + } + + return nil +} + +func FindConfigurationSetEventDestinationByID(ctx context.Context, conn *sesv2.Client, id string) (types.EventDestination, error) { + configurationSetName, eventDestinationName, err := ParseConfigurationSetEventDestinationID(id) + if err != nil { + return types.EventDestination{}, err + } + + in := &sesv2.GetConfigurationSetEventDestinationsInput{ + ConfigurationSetName: aws.String(configurationSetName), + } + out, err := conn.GetConfigurationSetEventDestinations(ctx, in) + if err != nil { + var nfe *types.NotFoundException + if errors.As(err, &nfe) { + return types.EventDestination{}, &resource.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return types.EventDestination{}, err + } + + if out == nil { + return types.EventDestination{}, tfresource.NewEmptyResultError(in) + } + + for _, eventDestination := range out.EventDestinations { + if aws.ToString(eventDestination.Name) == eventDestinationName { + return eventDestination, nil + } + } + + return types.EventDestination{}, &resource.NotFoundError{} +} + +func flattenEventDestination(apiObject types.EventDestination) map[string]interface{} { + m := map[string]interface{}{ + "enabled": apiObject.Enabled, + } + + if v := apiObject.CloudWatchDestination; v != nil { + m["cloud_watch_destination"] = []interface{}{flattenCloudWatchDestination(v)} + } + + if v := apiObject.KinesisFirehoseDestination; v != nil { + m["kinesis_firehose_destination"] = []interface{}{flattenKinesisFirehoseDestination(v)} + } + + if v := apiObject.MatchingEventTypes; v != nil { + m["matching_event_types"] = enum.Slice(apiObject.MatchingEventTypes...) + } + + if v := apiObject.PinpointDestination; v != nil { + m["pinpoint_destination"] = []interface{}{flattenPinpointDestination(v)} + } + + if v := apiObject.SnsDestination; v != nil { + m["sns_destination"] = []interface{}{flattenSNSDestination(v)} + } + + return m +} + +func flattenCloudWatchDestination(apiObject *types.CloudWatchDestination) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.DimensionConfigurations; v != nil { + m["dimension_configuration"] = flattenCloudWatchDimensionConfigurations(v) + } + + return m +} + +func flattenKinesisFirehoseDestination(apiObject *types.KinesisFirehoseDestination) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.DeliveryStreamArn; v != nil { + m["delivery_stream_arn"] = aws.ToString(v) + } + + if v := apiObject.IamRoleArn; v != nil { + m["iam_role_arn"] = aws.ToString(v) + } + + return m +} + +func flattenPinpointDestination(apiObject *types.PinpointDestination) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.ApplicationArn; v != nil { + m["application_arn"] = aws.ToString(v) + } + + return m +} + +func flattenSNSDestination(apiObject *types.SnsDestination) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.TopicArn; v != nil { + m["topic_arn"] = aws.ToString(v) + } + + return m +} + +func flattenCloudWatchDimensionConfigurations(apiObjects []types.CloudWatchDimensionConfiguration) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var l []interface{} + + for _, apiObject := range apiObjects { + l = append(l, flattenCloudWatchDimensionConfiguration(apiObject)) + } + + return l +} + +func flattenCloudWatchDimensionConfiguration(apiObject types.CloudWatchDimensionConfiguration) map[string]interface{} { + m := map[string]interface{}{ + "dimension_value_source": string(apiObject.DimensionValueSource), + } + + if v := apiObject.DefaultDimensionValue; v != nil { + m["default_dimension_value"] = aws.ToString(v) + } + + if v := apiObject.DimensionName; v != nil { + m["dimension_name"] = aws.ToString(v) + } + + return m +} + +func expandEventDestination(tfMap map[string]interface{}) *types.EventDestinationDefinition { + if tfMap == nil { + return nil + } + + a := &types.EventDestinationDefinition{} + + if v, ok := tfMap["cloud_watch_destination"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + a.CloudWatchDestination = expandCloudWatchDestination(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["enabled"].(bool); ok { + a.Enabled = v + } + + if v, ok := tfMap["kinesis_firehose_destination"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + a.KinesisFirehoseDestination = expandKinesisFirehoseDestination(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["matching_event_types"].([]interface{}); ok && len(v) > 0 { + a.MatchingEventTypes = stringsToEventTypes(flex.ExpandStringList(v)) + } + + if v, ok := tfMap["pinpoint_destination"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + a.PinpointDestination = expandPinpointDestinaton(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["sns_destination"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + a.SnsDestination = expandSNSDestination(v[0].(map[string]interface{})) + } + + return a +} + +func expandCloudWatchDestination(tfMap map[string]interface{}) *types.CloudWatchDestination { + if tfMap == nil { + return nil + } + + a := &types.CloudWatchDestination{} + + if v, ok := tfMap["dimension_configuration"].([]interface{}); ok && len(v) > 0 { + a.DimensionConfigurations = expandCloudWatchDimensionConfigurations(v) + } + + return a +} + +func expandKinesisFirehoseDestination(tfMap map[string]interface{}) *types.KinesisFirehoseDestination { + if tfMap == nil { + return nil + } + + a := &types.KinesisFirehoseDestination{} + + if v, ok := tfMap["delivery_stream_arn"].(string); ok && v != "" { + a.DeliveryStreamArn = aws.String(v) + } + + if v, ok := tfMap["iam_role_arn"].(string); ok && v != "" { + a.IamRoleArn = aws.String(v) + } + + return a +} + +func expandPinpointDestinaton(tfMap map[string]interface{}) *types.PinpointDestination { + if tfMap == nil { + return nil + } + + a := &types.PinpointDestination{} + + if v, ok := tfMap["application_arn"].(string); ok && v != "" { + a.ApplicationArn = aws.String(v) + } + + return a +} + +func expandSNSDestination(tfMap map[string]interface{}) *types.SnsDestination { + if tfMap == nil { + return nil + } + + a := &types.SnsDestination{} + + if v, ok := tfMap["topic_arn"].(string); ok && v != "" { + a.TopicArn = aws.String(v) + } + + return a +} + +func expandCloudWatchDimensionConfigurations(tfList []interface{}) []types.CloudWatchDimensionConfiguration { + if len(tfList) == 0 { + return nil + } + + var s []types.CloudWatchDimensionConfiguration + + for _, r := range tfList { + m, ok := r.(map[string]interface{}) + + if !ok { + continue + } + + s = append(s, expandCloudWatchDimensionConfiguration(m)) + } + + return s +} + +func expandCloudWatchDimensionConfiguration(tfMap map[string]interface{}) types.CloudWatchDimensionConfiguration { + a := types.CloudWatchDimensionConfiguration{} + + if v, ok := tfMap["default_dimension_value"].(string); ok && v != "" { + a.DefaultDimensionValue = aws.String(v) + } + + if v, ok := tfMap["dimension_name"].(string); ok && v != "" { + a.DimensionName = aws.String(v) + } + + if v, ok := tfMap["dimension_value_source"].(string); ok && v != "" { + a.DimensionValueSource = types.DimensionValueSource(v) + } + + return a +} + +func FormatConfigurationSetEventDestinationID(configurationSetName, eventDestinationName string) string { + return fmt.Sprintf("%s|%s", configurationSetName, eventDestinationName) +} + +func ParseConfigurationSetEventDestinationID(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 CONFIGURATION_SET_NAME|EVENT_DESTINATION_NAME") + } + + return idParts[0], idParts[1], nil +} + +func stringsToEventTypes(values []*string) []types.EventType { + var eventTypes []types.EventType + + for _, eventType := range values { + eventTypes = append(eventTypes, types.EventType(aws.ToString(eventType))) + } + + return eventTypes +} diff --git a/internal/service/sesv2/configuration_set_event_destination_test.go b/internal/service/sesv2/configuration_set_event_destination_test.go new file mode 100644 index 00000000000..76793380e1d --- /dev/null +++ b/internal/service/sesv2/configuration_set_event_destination_test.go @@ -0,0 +1,574 @@ +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 TestAccSESV2ConfigurationSetEventDestination_basic(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set_event_destination.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetEventDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetEventDestinationConfig_basic(rName, "SEND"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "configuration_set_name", rName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.matching_event_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.matching_event_types.0", "SEND"), + resource.TestCheckResourceAttr(resourceName, "event_destination_name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSetEventDestinationConfig_basic(rName, "REJECT"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "configuration_set_name", rName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.matching_event_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.matching_event_types.0", "REJECT"), + resource.TestCheckResourceAttr(resourceName, "event_destination_name", rName), + ), + }, + }, + }) +} + +func TestAccSESV2ConfigurationSetEventDestination_cloudWatchDestination(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set_event_destination.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetEventDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetEventDestinationConfig_cloudWatchDestination(rName, "test1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.0.dimension_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.0.dimension_configuration.0.default_dimension_value", "test1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.0.dimension_configuration.0.dimension_name", "test1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.0.dimension_configuration.0.dimension_value_source", "MESSAGE_TAG"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSetEventDestinationConfig_cloudWatchDestination(rName, "test2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.0.dimension_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.0.dimension_configuration.0.default_dimension_value", "test2"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.0.dimension_configuration.0.dimension_name", "test2"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.cloud_watch_destination.0.dimension_configuration.0.dimension_value_source", "MESSAGE_TAG"), + ), + }, + }, + }) +} + +func TestAccSESV2ConfigurationSetEventDestination_kinesisFirehoseDestination(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set_event_destination.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetEventDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetEventDestinationConfig_kinesisFirehoseDestination1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.kinesis_firehose_destination.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "event_destination.0.kinesis_firehose_destination.0.delivery_stream_arn", "aws_kinesis_firehose_delivery_stream.test1", "arn"), + resource.TestCheckResourceAttrPair(resourceName, "event_destination.0.kinesis_firehose_destination.0.iam_role_arn", "aws_iam_role.delivery_stream", "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSetEventDestinationConfig_kinesisFirehoseDestination2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.kinesis_firehose_destination.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "event_destination.0.kinesis_firehose_destination.0.delivery_stream_arn", "aws_kinesis_firehose_delivery_stream.test2", "arn"), + resource.TestCheckResourceAttrPair(resourceName, "event_destination.0.kinesis_firehose_destination.0.iam_role_arn", "aws_iam_role.delivery_stream", "arn"), + ), + }, + }, + }) +} + +func TestAccSESV2ConfigurationSetEventDestination_pinpointDestination(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set_event_destination.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetEventDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetEventDestinationConfig_pinpointDestination1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.pinpoint_destination.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "event_destination.0.pinpoint_destination.0.application_arn", "aws_pinpoint_app.test1", "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSetEventDestinationConfig_pinpointDestination2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.pinpoint_destination.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "event_destination.0.pinpoint_destination.0.application_arn", "aws_pinpoint_app.test2", "arn"), + ), + }, + }, + }) +} + +func TestAccSESV2ConfigurationSetEventDestination_snsDestination(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set_event_destination.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetEventDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetEventDestinationConfig_snsDestination1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.sns_destination.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "event_destination.0.sns_destination.0.topic_arn", "aws_sns_topic.test1", "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSetEventDestinationConfig_snsDestination2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "event_destination.0.sns_destination.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "event_destination.0.sns_destination.0.topic_arn", "aws_sns_topic.test2", "arn"), + ), + }, + }, + }) +} + +func TestAccSESV2ConfigurationSetEventDestination_disappears(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set_event_destination.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetEventDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetEventDestinationConfig_basic(rName, "SEND"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetEventDestinationExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfsesv2.ResourceConfigurationSetEventDestination(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckConfigurationSetEventDestinationDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Client() + ctx := context.Background() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sesv2_configuration_set_event_destination" { + continue + } + + _, err := tfsesv2.FindConfigurationSetEventDestinationByID(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.ResNameConfigurationSetEventDestination, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccCheckConfigurationSetEventDestinationExists(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.ResNameConfigurationSetEventDestination, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameConfigurationSetEventDestination, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Client() + + _, err := tfsesv2.FindConfigurationSetEventDestinationByID(context.Background(), conn, rs.Primary.ID) + + if err != nil { + return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameConfigurationSetEventDestination, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccConfigurationSetEventDestinationConfig_basic(rName, matchingEventType string) string { + return fmt.Sprintf(` +resource "aws_sesv2_configuration_set" "test" { + configuration_set_name = %[1]q +} + +resource "aws_sesv2_configuration_set_event_destination" "test" { + configuration_set_name = aws_sesv2_configuration_set.test.configuration_set_name + event_destination_name = %[1]q + + event_destination { + cloud_watch_destination { + dimension_configuration { + default_dimension_value = %[1]q + dimension_name = %[1]q + dimension_value_source = "MESSAGE_TAG" + } + } + + matching_event_types = [%[2]q] + } +} +`, rName, matchingEventType) +} + +func testAccConfigurationSetEventDestinationConfig_cloudWatchDestination(rName, dimension string) string { + return fmt.Sprintf(` +resource "aws_sesv2_configuration_set" "test" { + configuration_set_name = %[1]q +} + +resource "aws_sesv2_configuration_set_event_destination" "test" { + configuration_set_name = aws_sesv2_configuration_set.test.configuration_set_name + event_destination_name = %[1]q + + event_destination { + cloud_watch_destination { + dimension_configuration { + default_dimension_value = %[2]q + dimension_name = %[2]q + dimension_value_source = "MESSAGE_TAG" + } + } + + matching_event_types = ["SEND"] + } +} +`, rName, dimension) +} + +func testAccConfigurationSetEventDestinationConfig_kinesisFirehoseDestinationBase(rName string) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "test" { + bucket = %[1]q +} + +resource "aws_s3_bucket_acl" "test" { + bucket = aws_s3_bucket.test.id + acl = "private" +} + +resource "aws_iam_role" "bucket" { + name = "%[1]s2" + + assume_role_policy = <