From c19c3055eca6d1528e8ea847c57d343ea8ae3c3f Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Fri, 30 Sep 2022 22:44:57 +0200 Subject: [PATCH 01/15] Create README.md --- internal/service/sesv2/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 internal/service/sesv2/README.md diff --git a/internal/service/sesv2/README.md b/internal/service/sesv2/README.md new file mode 100644 index 00000000000..9a79b4d2a45 --- /dev/null +++ b/internal/service/sesv2/README.md @@ -0,0 +1,11 @@ +# Terraform AWS Provider SESv2 Package + +This area is primarily for AWS provider contributors and maintainers. For information on _using_ Terraform and the AWS provider, see the links below. + + +## Handy Links + +* [Find out about contributing](https://hashicorp.github.io/terraform-provider-aws/#contribute) to the AWS provider! +* AWS Provider Docs: [Home](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) +* AWS Provider Docs: [One of the SESv2 resources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sesv2_configuration_set) +* AWS Docs: [AWS SDK for Go SESv2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sesv2) From 4e394e52c82f67c555da19c464ca86701c7e67f8 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Fri, 30 Sep 2022 23:02:29 +0200 Subject: [PATCH 02/15] Generate tag code for SESv2 --- internal/service/sesv2/generate.go | 4 ++ internal/service/sesv2/tags_gen.go | 94 ++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 internal/service/sesv2/generate.go create mode 100644 internal/service/sesv2/tags_gen.go diff --git a/internal/service/sesv2/generate.go b/internal/service/sesv2/generate.go new file mode 100644 index 00000000000..d89d2067fd4 --- /dev/null +++ b/internal/service/sesv2/generate.go @@ -0,0 +1,4 @@ +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ServiceTagsSlice -ListTags -UpdateTags +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package sesv2 diff --git a/internal/service/sesv2/tags_gen.go b/internal/service/sesv2/tags_gen.go new file mode 100644 index 00000000000..e4427451499 --- /dev/null +++ b/internal/service/sesv2/tags_gen.go @@ -0,0 +1,94 @@ +// Code generated by internal/generate/tags/main.go; DO NOT EDIT. +package sesv2 + +import ( + "context" + "fmt" + + "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" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +// ListTags lists sesv2 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(ctx context.Context, conn *sesv2.Client, identifier string) (tftags.KeyValueTags, error) { + input := &sesv2.ListTagsForResourceInput{ + ResourceArn: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(ctx, input) + + if err != nil { + return tftags.New(nil), err + } + + return KeyValueTags(output.Tags), nil +} + +// []*SERVICE.Tag handling + +// Tags returns sesv2 service tags. +func Tags(tags tftags.KeyValueTags) []types.Tag { + result := make([]types.Tag, 0, len(tags)) + + for k, v := range tags.Map() { + tag := types.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + + result = append(result, tag) + } + + return result +} + +// KeyValueTags creates tftags.KeyValueTags from sesv2 service tags. +func KeyValueTags(tags []types.Tag) tftags.KeyValueTags { + m := make(map[string]*string, len(tags)) + + for _, tag := range tags { + m[aws.ToString(tag.Key)] = tag.Value + } + + return tftags.New(m) +} + +// UpdateTags updates sesv2 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(ctx context.Context, conn *sesv2.Client, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error { + oldTags := tftags.New(oldTagsMap) + newTags := tftags.New(newTagsMap) + + if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 { + input := &sesv2.UntagResourceInput{ + ResourceArn: aws.String(identifier), + TagKeys: removedTags.IgnoreAWS().Keys(), + } + + _, err := conn.UntagResource(ctx, input) + + if err != nil { + return fmt.Errorf("untagging resource (%s): %w", identifier, err) + } + } + + if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 { + input := &sesv2.TagResourceInput{ + ResourceArn: aws.String(identifier), + Tags: Tags(updatedTags.IgnoreAWS()), + } + + _, err := conn.TagResource(ctx, input) + + if err != nil { + return fmt.Errorf("tagging resource (%s): %w", identifier, err) + } + } + + return nil +} From 59daad8759ad0df8658272350b91171c428aefa1 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 12:13:56 +0200 Subject: [PATCH 03/15] Add initial aws_sesv2_configuration_set implementation --- internal/provider/provider.go | 3 + internal/service/sesv2/configuration_set.go | 596 ++++++++++++++++++++ 2 files changed, 599 insertions(+) create mode 100644 internal/service/sesv2/configuration_set.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c49bd366d3a..b3577732f45 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -170,6 +170,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/servicediscovery" "github.com/hashicorp/terraform-provider-aws/internal/service/servicequotas" "github.com/hashicorp/terraform-provider-aws/internal/service/ses" + "github.com/hashicorp/terraform-provider-aws/internal/service/sesv2" "github.com/hashicorp/terraform-provider-aws/internal/service/sfn" "github.com/hashicorp/terraform-provider-aws/internal/service/shield" "github.com/hashicorp/terraform-provider-aws/internal/service/signer" @@ -2044,6 +2045,8 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_ses_receipt_rule_set": ses.ResourceReceiptRuleSet(), "aws_ses_template": ses.ResourceTemplate(), + "aws_sesv2_configuration_set": sesv2.ResourceConfigurationSet(), + "aws_sfn_activity": sfn.ResourceActivity(), "aws_sfn_state_machine": sfn.ResourceStateMachine(), diff --git a/internal/service/sesv2/configuration_set.go b/internal/service/sesv2/configuration_set.go new file mode 100644 index 00000000000..d05764f1a94 --- /dev/null +++ b/internal/service/sesv2/configuration_set.go @@ -0,0 +1,596 @@ +package sesv2 + +import ( + "context" + "errors" + "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/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 ResourceConfigurationSet() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceConfigurationSetCreate, + ReadWithoutTimeout: resourceConfigurationSetRead, + UpdateWithoutTimeout: resourceConfigurationSetUpdate, + DeleteWithoutTimeout: resourceConfigurationSetDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "configuration_set_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "delivery_options": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sending_pool_name": { + Type: schema.TypeString, + Optional: true, + }, + "tls_policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(tlsPolicyValues(types.TlsPolicy("").Values()), false), + }, + }, + }, + }, + "reputation_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "last_fresh_start": { + Type: schema.TypeString, + Computed: true, + }, + "reputation_metrics_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + "sending_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sending_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + "suppression_options": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "suppressed_reasons": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(suppressionListReasonValues(types.SuppressionListReason("").Values()), false), + }, + }, + }, + }, + }, + // "tags": tftags.TagsSchema(), + // "tags_all": tftags.TagsSchemaComputed(), + "tracking_options": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "custom_redirect_domain": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + + // CustomizeDiff: verify.SetTagsDiff, + } +} + +const ( + ResNameConfigurationSet = "Configuration Set" +) + +func resourceConfigurationSetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Conn + + in := &sesv2.CreateConfigurationSetInput{ + ConfigurationSetName: aws.String(d.Get("configuration_set_name").(string)), + } + + if v, ok := d.GetOk("delivery_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.DeliveryOptions = expandDeliveryOptions(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("reputation_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.ReputationOptions = expandReputationOptions(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("sending_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.SendingOptions = expandSendingOptions(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("suppression_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.SuppressionOptions = expandSuppressionOptions(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("tracking_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + in.TrackingOptions = expandTrackingOptions(v.([]interface{})[0].(map[string]interface{})) + } + + // defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + // tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + // if len(tags) > 0 { + // in.Tags = Tags(tags.IgnoreAWS()) + // } + + out, err := conn.CreateConfigurationSet(ctx, in) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionCreating, ResNameConfigurationSet, d.Get("configuration_set_name").(string), err) + } + + if out == nil { + return create.DiagError(names.SESV2, create.ErrActionCreating, ResNameConfigurationSet, d.Get("configuration_set_name").(string), errors.New("empty output")) + } + + d.SetId(d.Get("configuration_set_name").(string)) + + return resourceConfigurationSetRead(ctx, d, meta) +} + +func resourceConfigurationSetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Conn + + out, err := findConfigurationSetByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SESV2 ConfigurationSet (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionReading, ResNameConfigurationSet, d.Id(), err) + } + + d.Set("configuration_set_name", out.ConfigurationSetName) + + if out.DeliveryOptions != nil { + if err := d.Set("delivery_options", []interface{}{flattenDeliveryOptions(out.DeliveryOptions)}); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) + } + } else { + d.Set("delivery_options", nil) + } + + if out.ReputationOptions != nil { + if err := d.Set("reputation_options", []interface{}{flattenReputationOptions(out.ReputationOptions)}); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) + } + } else { + d.Set("reputation_options", nil) + } + + if out.SendingOptions != nil { + if err := d.Set("sending_options", []interface{}{flattenSendingOptions(out.SendingOptions)}); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) + } + } else { + d.Set("sending_options", nil) + } + + if out.SuppressionOptions != nil { + if err := d.Set("suppression_options", []interface{}{flattenSuppressionOptions(out.SuppressionOptions)}); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) + } + } else { + d.Set("suppression_options", nil) + } + + if out.TrackingOptions != nil { + if err := d.Set("tracking_options", []interface{}{flattenTrackingOptions(out.TrackingOptions)}); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) + } + } else { + d.Set("tracking_options", nil) + } + + // defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + // ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + // tags := KeyValueTags(out.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + // if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + // return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) + // } + + // if err := d.Set("tags_all", tags.Map()); err != nil { + // return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) + // } + + return nil +} + +func resourceConfigurationSetUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Conn + + if d.HasChanges("delivery_options") { + in := &sesv2.PutConfigurationSetDeliveryOptionsInput{ + ConfigurationSetName: aws.String(d.Id()), + } + + if v, ok := d.GetOk("delivery_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) + + if v, ok := tfMap["sending_pool_name"].(string); ok && v != "" { + in.SendingPoolName = aws.String(v) + } + + if v, ok := tfMap["tls_policy"].(string); ok && v != "" { + in.TlsPolicy = types.TlsPolicy(v) + } + } + + log.Printf("[DEBUG] Updating SESV2 ConfigurationSet DeliveryOptions (%s): %#v", d.Id(), in) + _, err := conn.PutConfigurationSetDeliveryOptions(ctx, in) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionUpdating, ResNameConfigurationSet, d.Id(), err) + } + } + + if d.HasChanges("reputation_options") { + log.Printf("[DEBUG] has change kurwa") + in := &sesv2.PutConfigurationSetReputationOptionsInput{ + ConfigurationSetName: aws.String(d.Id()), + } + + if v, ok := d.GetOk("reputation_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) + + if v, ok := tfMap["reputation_metrics_enabled"].(bool); ok { + in.ReputationMetricsEnabled = v + } + } + + log.Printf("[DEBUG] Updating SESV2 ConfigurationSet ReputationOptions (%s): %#v", d.Id(), in) + _, err := conn.PutConfigurationSetReputationOptions(ctx, in) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionUpdating, ResNameConfigurationSet, d.Id(), err) + } + } + + if d.HasChanges("sending_options") { + if v, ok := d.GetOk("sending_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) + + in := &sesv2.PutConfigurationSetSendingOptionsInput{ + ConfigurationSetName: aws.String(d.Id()), + } + + if v, ok := tfMap["sending_enabled"].(bool); ok { + in.SendingEnabled = v + } + + log.Printf("[DEBUG] Updating SESV2 ConfigurationSet SendingOptions (%s): %#v", d.Id(), in) + _, err := conn.PutConfigurationSetSendingOptions(ctx, in) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionUpdating, ResNameConfigurationSet, d.Id(), err) + } + } + } + + if d.HasChanges("suppression_options") { + in := &sesv2.PutConfigurationSetSuppressionOptionsInput{ + ConfigurationSetName: aws.String(d.Id()), + } + + if v, ok := d.GetOk("suppression_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) + + if v, ok := tfMap["suppressed_reasons"].([]interface{}); ok && len(v) > 0 { + in.SuppressedReasons = expandSuppressedReasons(v) + } + } + + log.Printf("[DEBUG] Updating SESV2 ConfigurationSet SuppressionOptions (%s): %#v", d.Id(), in) + _, err := conn.PutConfigurationSetSuppressionOptions(ctx, in) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionUpdating, ResNameConfigurationSet, d.Id(), err) + } + } + + if d.HasChanges("tracking_options") { + in := &sesv2.PutConfigurationSetTrackingOptionsInput{ + ConfigurationSetName: aws.String(d.Id()), + } + + if v, ok := d.GetOk("tracking_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) + + if v, ok := tfMap["custom_redirect_domain"].(string); ok && v != "" { + in.CustomRedirectDomain = aws.String(v) + } + } + + log.Printf("[DEBUG] Updating SESV2 ConfigurationSet TrackingOptions (%s): %#v", d.Id(), in) + _, err := conn.PutConfigurationSetTrackingOptions(ctx, in) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionUpdating, ResNameConfigurationSet, d.Id(), err) + } + } + + return resourceConfigurationSetRead(ctx, d, meta) +} + +func resourceConfigurationSetDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SESV2Conn + + log.Printf("[INFO] Deleting SESV2 ConfigurationSet %s", d.Id()) + + _, err := conn.DeleteConfigurationSet(ctx, &sesv2.DeleteConfigurationSetInput{ + ConfigurationSetName: 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, ResNameConfigurationSet, d.Id(), err) + } + + return nil +} + +func findConfigurationSetByID(ctx context.Context, conn *sesv2.Client, id string) (*sesv2.GetConfigurationSetOutput, error) { + in := &sesv2.GetConfigurationSetInput{ + ConfigurationSetName: aws.String(id), + } + out, err := conn.GetConfigurationSet(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 flattenDeliveryOptions(apiObject *types.DeliveryOptions) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.SendingPoolName; v != nil { + m["sending_pool_name"] = aws.ToString(v) + } + + m["tls_policy"] = string(apiObject.TlsPolicy) + + return m +} + +func flattenReputationOptions(apiObject *types.ReputationOptions) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "reputation_metrics_enabled": apiObject.ReputationMetricsEnabled, + } + + if v := apiObject.LastFreshStart; v != nil { + m["last_fresh_start"] = v.Format(time.RFC3339) + } + + return m +} + +func flattenSendingOptions(apiObject *types.SendingOptions) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "sending_enabled": apiObject.SendingEnabled, + } + + return m +} + +func flattenSuppressionOptions(apiObject *types.SuppressionOptions) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.SuppressedReasons; v != nil { + m["suppressed_reasons"] = flattenSuppressedReasons(apiObject.SuppressedReasons) + } + + return m +} + +func flattenSuppressedReasons(apiObject []types.SuppressionListReason) []string { + var out []string + + for _, v := range apiObject { + out = append(out, string(v)) + } + + return out +} + +func flattenTrackingOptions(apiObject *types.TrackingOptions) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.CustomRedirectDomain; v != nil { + m["custom_redirect_domain"] = aws.ToString(v) + } + + return m +} + +func expandDeliveryOptions(tfMap map[string]interface{}) *types.DeliveryOptions { + if tfMap == nil { + return nil + } + + a := &types.DeliveryOptions{} + + if v, ok := tfMap["sending_pool_name"].(string); ok && v != "" { + a.SendingPoolName = aws.String(v) + } + + if v, ok := tfMap["tls_policy"].(string); ok && v != "" { + a.TlsPolicy = types.TlsPolicy(v) + } + + return a +} + +func expandReputationOptions(tfMap map[string]interface{}) *types.ReputationOptions { + if tfMap == nil { + return nil + } + + a := &types.ReputationOptions{} + + if v, ok := tfMap["reputation_metrics_enabled"].(bool); ok { + a.ReputationMetricsEnabled = v + } + + return a +} + +func expandSendingOptions(tfMap map[string]interface{}) *types.SendingOptions { + if tfMap == nil { + return nil + } + + a := &types.SendingOptions{} + + if v, ok := tfMap["sending_enabled"].(bool); ok { + a.SendingEnabled = v + } + + return a +} + +func expandSuppressionOptions(tfMap map[string]interface{}) *types.SuppressionOptions { + if tfMap == nil { + return nil + } + + a := &types.SuppressionOptions{} + + if v, ok := tfMap["suppressed_reasons"].([]interface{}); ok && len(v) > 0 { + a.SuppressedReasons = expandSuppressedReasons(v) + } + + return a +} + +func expandSuppressedReasons(tfList []interface{}) []types.SuppressionListReason { + var out []types.SuppressionListReason + + for _, v := range tfList { + if v, ok := v.(string); ok && v != "" { + out = append(out, types.SuppressionListReason(v)) + } + } + + return out +} + +func expandTrackingOptions(tfMap map[string]interface{}) *types.TrackingOptions { + if tfMap == nil { + return nil + } + + a := &types.TrackingOptions{} + + if v, ok := tfMap["custom_redirect_domain"].(string); ok && v != "" { + a.CustomRedirectDomain = aws.String(v) + } + + return a +} + +func tlsPolicyValues(in []types.TlsPolicy) []string { + var out []string + + for _, v := range in { + out = append(out, string(v)) + } + + return out +} + +func suppressionListReasonValues(in []types.SuppressionListReason) []string { + var out []string + + for _, v := range in { + out = append(out, string(v)) + } + + return out +} From 6460c7b62ae69f2bb32ac99f9b8b7be5f17a524e Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 12:35:47 +0200 Subject: [PATCH 04/15] Add tags support --- internal/service/sesv2/configuration_set.go | 64 +++++++++++++++------ 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/internal/service/sesv2/configuration_set.go b/internal/service/sesv2/configuration_set.go index d05764f1a94..d5701a27eb2 100644 --- a/internal/service/sesv2/configuration_set.go +++ b/internal/service/sesv2/configuration_set.go @@ -3,19 +3,23 @@ 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-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" + 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" ) @@ -31,6 +35,10 @@ func ResourceConfigurationSet() *schema.Resource { }, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "configuration_set_name": { Type: schema.TypeString, Required: true, @@ -106,8 +114,8 @@ func ResourceConfigurationSet() *schema.Resource { }, }, }, - // "tags": tftags.TagsSchema(), - // "tags_all": tftags.TagsSchemaComputed(), + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), "tracking_options": { Type: schema.TypeList, Optional: true, @@ -123,7 +131,7 @@ func ResourceConfigurationSet() *schema.Resource { }, }, - // CustomizeDiff: verify.SetTagsDiff, + CustomizeDiff: verify.SetTagsDiff, } } @@ -158,12 +166,12 @@ func resourceConfigurationSetCreate(ctx context.Context, d *schema.ResourceData, in.TrackingOptions = expandTrackingOptions(v.([]interface{})[0].(map[string]interface{})) } - // defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - // tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - // if len(tags) > 0 { - // in.Tags = Tags(tags.IgnoreAWS()) - // } + if len(tags) > 0 { + in.Tags = Tags(tags.IgnoreAWS()) + } out, err := conn.CreateConfigurationSet(ctx, in) if err != nil { @@ -194,6 +202,15 @@ func resourceConfigurationSetRead(ctx context.Context, d *schema.ResourceData, m return create.DiagError(names.SESV2, create.ErrActionReading, ResNameConfigurationSet, d.Id(), err) } + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: "sesv2", + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("configuration-set/%s", d.Id()), + }.String() + + d.Set("arn", arn) d.Set("configuration_set_name", out.ConfigurationSetName) if out.DeliveryOptions != nil { @@ -236,17 +253,22 @@ func resourceConfigurationSetRead(ctx context.Context, d *schema.ResourceData, m d.Set("tracking_options", nil) } - // defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - // ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - // tags := KeyValueTags(out.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + tags, err := ListTags(ctx, conn, d.Get("arn").(string)) + if err != nil { + return create.DiagError(names.SESV2, create.ErrActionReading, ResNameConfigurationSet, 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, ResNameConfigurationSet, d.Id(), err) - // } + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) + } - // if err := d.Set("tags_all", tags.Map()); err != nil { - // return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) - // } + if err := d.Set("tags_all", tags.Map()); err != nil { + return create.DiagError(names.SESV2, create.ErrActionSetting, ResNameConfigurationSet, d.Id(), err) + } return nil } @@ -359,6 +381,14 @@ func resourceConfigurationSetUpdate(ctx context.Context, d *schema.ResourceData, } } + 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, ResNameConfigurationSet, d.Id(), err) + } + } + return resourceConfigurationSetRead(ctx, d, meta) } From 7ced20af7b969c5a434489ef312523a2995cbef0 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 14:18:18 +0200 Subject: [PATCH 05/15] Add acceptance tests --- internal/service/sesv2/configuration_set.go | 4 +- .../service/sesv2/configuration_set_test.go | 383 ++++++++++++++++++ 2 files changed, 385 insertions(+), 2 deletions(-) create mode 100644 internal/service/sesv2/configuration_set_test.go diff --git a/internal/service/sesv2/configuration_set.go b/internal/service/sesv2/configuration_set.go index d5701a27eb2..5461e434b63 100644 --- a/internal/service/sesv2/configuration_set.go +++ b/internal/service/sesv2/configuration_set.go @@ -190,7 +190,7 @@ func resourceConfigurationSetCreate(ctx context.Context, d *schema.ResourceData, func resourceConfigurationSetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).SESV2Conn - out, err := findConfigurationSetByID(ctx, conn, d.Id()) + out, err := FindConfigurationSetByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] SESV2 ConfigurationSet (%s) not found, removing from state", d.Id()) @@ -413,7 +413,7 @@ func resourceConfigurationSetDelete(ctx context.Context, d *schema.ResourceData, return nil } -func findConfigurationSetByID(ctx context.Context, conn *sesv2.Client, id string) (*sesv2.GetConfigurationSetOutput, error) { +func FindConfigurationSetByID(ctx context.Context, conn *sesv2.Client, id string) (*sesv2.GetConfigurationSetOutput, error) { in := &sesv2.GetConfigurationSetInput{ ConfigurationSetName: aws.String(id), } diff --git a/internal/service/sesv2/configuration_set_test.go b/internal/service/sesv2/configuration_set_test.go new file mode 100644 index 00000000000..bc011360c36 --- /dev/null +++ b/internal/service/sesv2/configuration_set_test.go @@ -0,0 +1,383 @@ +package sesv2_test + +import ( + "context" + "errors" + "fmt" + "regexp" + "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 TestAccSESV2ConfigurationSet_basic(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "configuration_set_name", rName), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "sesv2", regexp.MustCompile(`configuration-set/.+`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSESV2ConfigurationSet_disappears(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfsesv2.ResourceConfigurationSet(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccSESV2ConfigurationSet_tlsPolicy(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetConfig_tlsPolicy(rName, string(types.TlsPolicyRequire)), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "delivery_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "delivery_options.0.tls_policy", string(types.TlsPolicyRequire)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSetConfig_tlsPolicy(rName, string(types.TlsPolicyOptional)), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "delivery_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "delivery_options.0.tls_policy", string(types.TlsPolicyOptional)), + ), + }, + }, + }) +} + +func TestAccSESV2ConfigurationSet_reputationMetricsEnabled(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetConfig_reputationMetricsEnabled(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "reputation_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "reputation_options.0.reputation_metrics_enabled", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSetConfig_reputationMetricsEnabled(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "reputation_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "reputation_options.0.reputation_metrics_enabled", "false"), + ), + }, + }, + }) +} + +func TestAccSESV2ConfigurationSet_sendingEnabled(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetConfig_sendingEnabled(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "sending_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "sending_options.0.sending_enabled", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSetConfig_sendingEnabled(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "sending_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "sending_options.0.sending_enabled", "false"), + ), + }, + }, + }) +} + +func TestAccSESV2ConfigurationSet_suppressedReasons(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSetConfig_suppressedReasons(rName, string(types.SuppressionListReasonBounce)), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "suppression_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "suppression_options.0.suppressed_reasons.#", "1"), + resource.TestCheckResourceAttr(resourceName, "suppression_options.0.suppressed_reasons.0", string(types.SuppressionListReasonBounce)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSetConfig_suppressedReasons(rName, string(types.SuppressionListReasonComplaint)), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "suppression_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "suppression_options.0.suppressed_reasons.#", "1"), + resource.TestCheckResourceAttr(resourceName, "suppression_options.0.suppressed_reasons.0", string(types.SuppressionListReasonComplaint)), + ), + }, + }, + }) +} + +func TestAccSESV2ConfigurationSet_tags(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sesv2_configuration_set.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SESV2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationSet_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationSet_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccConfigurationSet_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationSetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckConfigurationSetDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sesv2_configuration_set" { + continue + } + + _, err := tfsesv2.FindConfigurationSetByID(context.Background(), 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.ResNameConfigurationSet, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccCheckConfigurationSetExists(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.ResNameConfigurationSet, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameConfigurationSet, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SESV2Conn + + _, err := tfsesv2.FindConfigurationSetByID(context.Background(), conn, rs.Primary.ID) + + if err != nil { + return create.Error(names.SESV2, create.ErrActionCheckingExistence, tfsesv2.ResNameConfigurationSet, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccConfigurationSetConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_sesv2_configuration_set" "test" { + configuration_set_name = %[1]q +} +`, rName) +} + +func testAccConfigurationSetConfig_tlsPolicy(rName, tlsPolicy string) string { + return fmt.Sprintf(` +resource "aws_sesv2_configuration_set" "test" { + configuration_set_name = %[1]q + + delivery_options { + tls_policy = %[2]q + } +} +`, rName, tlsPolicy) +} + +func testAccConfigurationSetConfig_reputationMetricsEnabled(rName string, reputationMetricsEnabled bool) string { + return fmt.Sprintf(` +resource "aws_sesv2_configuration_set" "test" { + configuration_set_name = %[1]q + + reputation_options { + reputation_metrics_enabled = %[2]t + } +} +`, rName, reputationMetricsEnabled) +} + +func testAccConfigurationSetConfig_sendingEnabled(rName string, sendingEnabled bool) string { + return fmt.Sprintf(` +resource "aws_sesv2_configuration_set" "test" { + configuration_set_name = %[1]q + + sending_options { + sending_enabled = %[2]t + } +} +`, rName, sendingEnabled) +} + +func testAccConfigurationSetConfig_suppressedReasons(rName, suppressedReason string) string { + return fmt.Sprintf(` +resource "aws_sesv2_configuration_set" "test" { + configuration_set_name = %[1]q + + suppression_options { + suppressed_reasons = [%[2]q] + } +} +`, rName, suppressedReason) +} + +func testAccConfigurationSet_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_sesv2_configuration_set" "test" { + configuration_set_name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccConfigurationSet_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_sesv2_configuration_set" "test" { + configuration_set_name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} From 28362c6c1f27b58d20950f5a97a42f034e196afd Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 14:48:52 +0200 Subject: [PATCH 06/15] Fix terrafmt issues --- internal/service/sesv2/configuration_set_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/sesv2/configuration_set_test.go b/internal/service/sesv2/configuration_set_test.go index bc011360c36..4928ef3d93f 100644 --- a/internal/service/sesv2/configuration_set_test.go +++ b/internal/service/sesv2/configuration_set_test.go @@ -315,7 +315,7 @@ resource "aws_sesv2_configuration_set" "test" { configuration_set_name = %[1]q delivery_options { - tls_policy = %[2]q + tls_policy = %[2]q } } `, rName, tlsPolicy) @@ -327,7 +327,7 @@ resource "aws_sesv2_configuration_set" "test" { configuration_set_name = %[1]q reputation_options { - reputation_metrics_enabled = %[2]t + reputation_metrics_enabled = %[2]t } } `, rName, reputationMetricsEnabled) @@ -339,7 +339,7 @@ resource "aws_sesv2_configuration_set" "test" { configuration_set_name = %[1]q sending_options { - sending_enabled = %[2]t + sending_enabled = %[2]t } } `, rName, sendingEnabled) @@ -351,7 +351,7 @@ resource "aws_sesv2_configuration_set" "test" { configuration_set_name = %[1]q suppression_options { - suppressed_reasons = [%[2]q] + suppressed_reasons = [%[2]q] } } `, rName, suppressedReason) From 320a9b6d0b09529b9a928f3c390df2768f48f382 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 14:49:09 +0200 Subject: [PATCH 07/15] Add validation for configuration set name --- internal/service/sesv2/configuration_set.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/service/sesv2/configuration_set.go b/internal/service/sesv2/configuration_set.go index 5461e434b63..f4aeb05c9f6 100644 --- a/internal/service/sesv2/configuration_set.go +++ b/internal/service/sesv2/configuration_set.go @@ -40,9 +40,10 @@ func ResourceConfigurationSet() *schema.Resource { Computed: true, }, "configuration_set_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 64), }, "delivery_options": { Type: schema.TypeList, From 5b54a8caaa748de0d5cd8aef3aa026bf5ddca644 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 14:49:18 +0200 Subject: [PATCH 08/15] Add website page --- .../r/sesv2_configuration_set.html.markdown | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 website/docs/r/sesv2_configuration_set.html.markdown diff --git a/website/docs/r/sesv2_configuration_set.html.markdown b/website/docs/r/sesv2_configuration_set.html.markdown new file mode 100644 index 00000000000..25932b4e810 --- /dev/null +++ b/website/docs/r/sesv2_configuration_set.html.markdown @@ -0,0 +1,96 @@ +--- +subcategory: "SESv2 (Simple Email V2)" +layout: "aws" +page_title: "AWS: aws_sesv2_configuration_set" +description: |- + Terraform resource for managing an AWS SESv2 (Simple Email V2) Configuration Set. +--- + +# Resource: aws_sesv2_configuration_set + +Terraform resource for managing an AWS SESv2 (Simple Email V2) Configuration Set. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_sesv2_configuration_set" "example" { + configuration_set_name = "example" + + delivery_options { + tls_policy = "REQUIRE" + } + + reputation_options { + reputation_metrics_enabled = false + } + + sendig_options { + sending_enabled = true + } + + suppression_options { + suppressed_reasons = ["BOUNCE", "COMPLAINT"] + } + + tracking_options { + custom_redirect_domain = "example.com" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `configuration_set_name` - (Required) The name of the configuration set. +* `delivery_options` - (Optional) An object that defines the dedicated IP pool that is used to send emails that you send using the configuration set. +* `reputation_options` - (Optional) An object that defines whether or not Amazon SES collects reputation metrics for the emails that you send that use the configuration set. +* `sending_options` - (Optional) An object that defines whether or not Amazon SES can send email that you send using the configuration set. +* `suppression_options` - (Optional) An object that contains information about the suppression list preferences for your account. +* `tags` - (Optional) A map of tags to assign to the service. 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. +* `tracking_options` - (Optional) An object that defines the open and click tracking options for emails that you send using the configuration set. + +### delivery_options + +The following arguments are supported: + +* `sending_pool_name` - (Optional) The name of the dedicated IP pool to associate with the configuration set. +* `tls_policy` - (Optional) Specifies whether messages that use the configuration set are required to use Transport Layer Security (TLS). Valid values: `REQUIRE`, `OPTIONAL`. + +### reputation_options + +The following arguments are supported; + +* `reputation_metrics_enabled` - (Optional) If `true`, tracking of reputation metrics is enabled for the configuration set. If `false`, tracking of reputation metrics is disabled for the configuration set. + +### sending_options + +The following arguments are supported: + +* `sending_enabled` - (Optional) If `true`, email sending is enabled for the configuration set. If `false`, email sending is disabled for the configuration set. + +### suppression_options + +- `suppressed_reasons` - (Optional) A list that contains the reasons that email addresses are automatically added to the suppression list for your account. Valid vales: `BOUNCE`, `COMPLAINT`. + +## tracking_options + +- `custom_redirect_domain` - (Required) The domain to use for tracking open and click events. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the Configuration Set. +* `reputation_options` - An object that defines whether or not Amazon SES collects reputation metrics for the emails that you send that use the configuration set. + * `last_fresh_start` - The date and time (in Unix time) when the reputation metrics were last given a fresh start. When your account is given a fresh start, your reputation metrics are calculated starting from the date of the fresh start. + +## Import + +SESv2 (Simple Email V2) Configuration Set can be imported using the `configuration_set_name`, e.g., + +``` +$ terraform import aws_sesv2_configuration_set.example example +``` From 0594150274182e1ffc35b20b7b3852c9d1008eeb Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 18:48:31 +0200 Subject: [PATCH 09/15] Fix config func names --- internal/service/sesv2/configuration_set_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/service/sesv2/configuration_set_test.go b/internal/service/sesv2/configuration_set_test.go index 4928ef3d93f..3ed67bb91e6 100644 --- a/internal/service/sesv2/configuration_set_test.go +++ b/internal/service/sesv2/configuration_set_test.go @@ -221,7 +221,7 @@ func TestAccSESV2ConfigurationSet_tags(t *testing.T) { CheckDestroy: testAccCheckConfigurationSetDestroy, Steps: []resource.TestStep{ { - Config: testAccConfigurationSet_tags1(rName, "key1", "value1"), + Config: testAccConfigurationSetConfig_tags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationSetExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -234,7 +234,7 @@ func TestAccSESV2ConfigurationSet_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccConfigurationSet_tags2(rName, "key1", "value1updated", "key2", "value2"), + Config: testAccConfigurationSetConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationSetExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), @@ -243,7 +243,7 @@ func TestAccSESV2ConfigurationSet_tags(t *testing.T) { ), }, { - Config: testAccConfigurationSet_tags1(rName, "key2", "value2"), + Config: testAccConfigurationSetConfig_tags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationSetExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -357,7 +357,7 @@ resource "aws_sesv2_configuration_set" "test" { `, rName, suppressedReason) } -func testAccConfigurationSet_tags1(rName, tagKey1, tagValue1 string) string { +func testAccConfigurationSetConfig_tags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_sesv2_configuration_set" "test" { configuration_set_name = %[1]q @@ -369,7 +369,7 @@ resource "aws_sesv2_configuration_set" "test" { `, rName, tagKey1, tagValue1) } -func testAccConfigurationSet_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { +func testAccConfigurationSetConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_sesv2_configuration_set" "test" { configuration_set_name = %[1]q From 28503a2f111278bc4bb622715330d0e9b0d3ba51 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 18:49:12 +0200 Subject: [PATCH 10/15] Fix indentation in website page --- website/docs/r/sesv2_configuration_set.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/sesv2_configuration_set.html.markdown b/website/docs/r/sesv2_configuration_set.html.markdown index 25932b4e810..414717c50df 100644 --- a/website/docs/r/sesv2_configuration_set.html.markdown +++ b/website/docs/r/sesv2_configuration_set.html.markdown @@ -85,7 +85,7 @@ In addition to all arguments above, the following attributes are exported: * `arn` - ARN of the Configuration Set. * `reputation_options` - An object that defines whether or not Amazon SES collects reputation metrics for the emails that you send that use the configuration set. - * `last_fresh_start` - The date and time (in Unix time) when the reputation metrics were last given a fresh start. When your account is given a fresh start, your reputation metrics are calculated starting from the date of the fresh start. + * `last_fresh_start` - The date and time (in Unix time) when the reputation metrics were last given a fresh start. When your account is given a fresh start, your reputation metrics are calculated starting from the date of the fresh start. ## Import From 6c5272e259f8f826d31f240bbe2a5f1612339a57 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 18:52:17 +0200 Subject: [PATCH 11/15] Run make gen --- .ci/.semgrep-service-name0.yml | 15 +++ .ci/.semgrep-service-name1.yml | 44 +++++--- .ci/.semgrep-service-name2.yml | 73 ++++++++----- .ci/.semgrep-service-name3.yml | 102 ++++++++++-------- .../components/generated/services_all.kt | 1 + 5 files changed, 147 insertions(+), 88 deletions(-) diff --git a/.ci/.semgrep-service-name0.yml b/.ci/.semgrep-service-name0.yml index 1846dd5eaa6..d401bb0a8d5 100644 --- a/.ci/.semgrep-service-name0.yml +++ b/.ci/.semgrep-service-name0.yml @@ -3043,3 +3043,18 @@ rules: - pattern-not-regex: "^TestAccConnect" - pattern-regex: ^TestAcc.* severity: WARNING + - id: connect-in-const-name + languages: + - go + message: Do not use "Connect" in const name inside connect package + paths: + include: + - internal/service/connect + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Connect" + - pattern-not-regex: .*uickConnect.* + severity: WARNING diff --git a/.ci/.semgrep-service-name1.yml b/.ci/.semgrep-service-name1.yml index 23f8bee961e..ff7940844e0 100644 --- a/.ci/.semgrep-service-name1.yml +++ b/.ci/.semgrep-service-name1.yml @@ -1,20 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - id: connect-in-const-name - languages: - - go - message: Do not use "Connect" in const name inside connect package - paths: - include: - - internal/service/connect - patterns: - - pattern: const $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Connect" - - pattern-not-regex: .*uickConnect.* - severity: WARNING - id: connect-in-var-name languages: - go @@ -3030,3 +3015,32 @@ rules: - pattern-regex: "(?i)Inspector" - pattern-not-regex: ^TestAcc.* severity: WARNING + - id: inspector-in-test-name + languages: + - go + message: Include "Inspector" in test name + paths: + include: + - internal/service/inspector/*_test.go + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccInspector" + - pattern-regex: ^TestAcc.* + severity: WARNING + - id: inspector-in-const-name + languages: + - go + message: Do not use "Inspector" in const name inside inspector package + paths: + include: + - internal/service/inspector + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Inspector" + severity: WARNING diff --git a/.ci/.semgrep-service-name2.yml b/.ci/.semgrep-service-name2.yml index 7266fd8a8c8..b40909031f1 100644 --- a/.ci/.semgrep-service-name2.yml +++ b/.ci/.semgrep-service-name2.yml @@ -1,34 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - id: inspector-in-test-name - languages: - - go - message: Include "Inspector" in test name - paths: - include: - - internal/service/inspector/*_test.go - patterns: - - pattern: func $NAME( ... ) { ... } - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-not-regex: "^TestAccInspector" - - pattern-regex: ^TestAcc.* - severity: WARNING - - id: inspector-in-const-name - languages: - - go - message: Do not use "Inspector" in const name inside inspector package - paths: - include: - - internal/service/inspector - patterns: - - pattern: const $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Inspector" - severity: WARNING - id: inspector-in-var-name languages: - go @@ -3040,3 +3011,47 @@ rules: patterns: - pattern-regex: "(?i)RDS" severity: WARNING + - id: redshift-in-func-name + languages: + - go + message: Do not use "Redshift" in func name inside redshift package + paths: + include: + - internal/service/redshift + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Redshift" + - pattern-not-regex: ^TestAcc.* + severity: WARNING + - id: redshift-in-test-name + languages: + - go + message: Include "Redshift" in test name + paths: + include: + - internal/service/redshift/*_test.go + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccRedshift" + - pattern-regex: ^TestAcc.* + severity: WARNING + - id: redshift-in-const-name + languages: + - go + message: Do not use "Redshift" in const name inside redshift package + paths: + include: + - internal/service/redshift + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Redshift" + severity: WARNING diff --git a/.ci/.semgrep-service-name3.yml b/.ci/.semgrep-service-name3.yml index bb92fdd8fab..c0bf089d163 100644 --- a/.ci/.semgrep-service-name3.yml +++ b/.ci/.semgrep-service-name3.yml @@ -1,49 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - id: redshift-in-func-name - languages: - - go - message: Do not use "Redshift" in func name inside redshift package - paths: - include: - - internal/service/redshift - patterns: - - pattern: func $NAME( ... ) { ... } - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Redshift" - - pattern-not-regex: ^TestAcc.* - severity: WARNING - - id: redshift-in-test-name - languages: - - go - message: Include "Redshift" in test name - paths: - include: - - internal/service/redshift/*_test.go - patterns: - - pattern: func $NAME( ... ) { ... } - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-not-regex: "^TestAccRedshift" - - pattern-regex: ^TestAcc.* - severity: WARNING - - id: redshift-in-const-name - languages: - - go - message: Do not use "Redshift" in const name inside redshift package - paths: - include: - - internal/service/redshift - patterns: - - pattern: const $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Redshift" - severity: WARNING - id: redshift-in-var-name languages: - go @@ -1650,6 +1606,64 @@ rules: patterns: - pattern-regex: "(?i)SES" severity: WARNING + - id: sesv2-in-func-name + languages: + - go + message: Do not use "SESV2" in func name inside sesv2 package + paths: + include: + - internal/service/sesv2 + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)SESV2" + - pattern-not-regex: ^TestAcc.* + severity: WARNING + - id: sesv2-in-test-name + languages: + - go + message: Include "SESV2" in test name + paths: + include: + - internal/service/sesv2/*_test.go + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccSESV2" + - pattern-regex: ^TestAcc.* + severity: WARNING + - id: sesv2-in-const-name + languages: + - go + message: Do not use "SESV2" in const name inside sesv2 package + paths: + include: + - internal/service/sesv2 + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)SESV2" + severity: WARNING + - id: sesv2-in-var-name + languages: + - go + message: Do not use "SESV2" in var name inside sesv2 package + paths: + include: + - internal/service/sesv2 + patterns: + - pattern: var $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)SESV2" + severity: WARNING - id: sfn-in-func-name languages: - go diff --git a/.teamcity/components/generated/services_all.kt b/.teamcity/components/generated/services_all.kt index 5e550adca6a..f67a4ed4612 100644 --- a/.teamcity/components/generated/services_all.kt +++ b/.teamcity/components/generated/services_all.kt @@ -155,6 +155,7 @@ val services = mapOf( "servicediscovery" to ServiceSpec("Cloud Map", vpcLock = true), "servicequotas" to ServiceSpec("Service Quotas"), "ses" to ServiceSpec("SES (Simple Email)"), + "sesv2" to ServiceSpec("SESv2 (Simple Email V2)"), "sfn" to ServiceSpec("SFN (Step Functions)"), "shield" to ServiceSpec("Shield"), "signer" to ServiceSpec("Signer"), From 0564865b8f57e1a6af95e9f8b2e4db2c5a3c82b8 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sun, 2 Oct 2022 19:12:55 +0200 Subject: [PATCH 12/15] Fix imports --- internal/service/sesv2/configuration_set_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/sesv2/configuration_set_test.go b/internal/service/sesv2/configuration_set_test.go index 3ed67bb91e6..d5b17ce4f0b 100644 --- a/internal/service/sesv2/configuration_set_test.go +++ b/internal/service/sesv2/configuration_set_test.go @@ -14,7 +14,6 @@ import ( "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" ) From 86971962c428480543eb460872bbccd1565faa21 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Fri, 14 Oct 2022 09:41:18 -0400 Subject: [PATCH 13/15] r/aws_sesv2_configuration_set: add changelog entry --- .changelog/27056.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/27056.txt diff --git a/.changelog/27056.txt b/.changelog/27056.txt new file mode 100644 index 00000000000..5d657a0e1dc --- /dev/null +++ b/.changelog/27056.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_sesv2_configuration_set +``` From b993df610736e173044fc7f59d91668d0f411a29 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Fri, 14 Oct 2022 09:43:45 -0400 Subject: [PATCH 14/15] r/aws_sesv2_configuration_set: use ses in arn service section --- internal/service/sesv2/configuration_set.go | 2 +- internal/service/sesv2/configuration_set_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/sesv2/configuration_set.go b/internal/service/sesv2/configuration_set.go index f4aeb05c9f6..a860c8e9687 100644 --- a/internal/service/sesv2/configuration_set.go +++ b/internal/service/sesv2/configuration_set.go @@ -205,7 +205,7 @@ func resourceConfigurationSetRead(ctx context.Context, d *schema.ResourceData, m arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, - Service: "sesv2", + Service: "ses", Region: meta.(*conns.AWSClient).Region, AccountID: meta.(*conns.AWSClient).AccountID, Resource: fmt.Sprintf("configuration-set/%s", d.Id()), diff --git a/internal/service/sesv2/configuration_set_test.go b/internal/service/sesv2/configuration_set_test.go index d5b17ce4f0b..b862616eaa8 100644 --- a/internal/service/sesv2/configuration_set_test.go +++ b/internal/service/sesv2/configuration_set_test.go @@ -33,7 +33,7 @@ func TestAccSESV2ConfigurationSet_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationSetExists(resourceName), resource.TestCheckResourceAttr(resourceName, "configuration_set_name", rName), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "sesv2", regexp.MustCompile(`configuration-set/.+`)), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ses", regexp.MustCompile(`configuration-set/.+`)), ), }, { From 753ff2aacae8a69f897b64564211b728ad2c3d8c Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Fri, 14 Oct 2022 10:12:11 -0400 Subject: [PATCH 15/15] fix markdownlint no-multiple-blanks finding --- internal/service/sesv2/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/sesv2/README.md b/internal/service/sesv2/README.md index 9a79b4d2a45..c007f0f0470 100644 --- a/internal/service/sesv2/README.md +++ b/internal/service/sesv2/README.md @@ -2,7 +2,6 @@ This area is primarily for AWS provider contributors and maintainers. For information on _using_ Terraform and the AWS provider, see the links below. - ## Handy Links * [Find out about contributing](https://hashicorp.github.io/terraform-provider-aws/#contribute) to the AWS provider!