From b9c511871f19603646688716f57b93972d3d9e91 Mon Sep 17 00:00:00 2001 From: Michael Chen Date: Wed, 12 Apr 2023 10:28:12 +1000 Subject: [PATCH 01/20] Implement AWS SSM Contacts resources --- .ci/.semgrep-service-name0.yml | 15 + .ci/.semgrep-service-name1.yml | 58 +- .ci/.semgrep-service-name2.yml | 101 ++- .ci/.semgrep-service-name3.yml | 116 +-- .../components/generated/services_all.kt | 1 + internal/provider/service_packages_gen.go | 2 + internal/service/ssmcontacts/contact.go | 186 ++++ .../service/ssmcontacts/contact_channel.go | 174 ++++ .../contact_channel_data_source.go | 77 ++ .../contact_channel_data_source_test.go | 79 ++ .../ssmcontacts/contact_channel_test.go | 470 ++++++++++ .../ssmcontacts/contact_data_source.go | 75 ++ .../ssmcontacts/contact_data_source_test.go | 83 ++ internal/service/ssmcontacts/contact_test.go | 531 ++++++++++++ internal/service/ssmcontacts/find.go | 60 ++ internal/service/ssmcontacts/flex.go | 197 +++++ internal/service/ssmcontacts/generate.go | 4 + internal/service/ssmcontacts/helper.go | 44 + internal/service/ssmcontacts/plan.go | 197 +++++ .../service/ssmcontacts/plan_data_source.go | 101 +++ .../ssmcontacts/plan_data_source_test.go | 251 ++++++ internal/service/ssmcontacts/plan_test.go | 809 ++++++++++++++++++ .../ssmcontacts/service_package_gen.go | 60 ++ .../service/ssmcontacts/ssmcontacts_test.go | 54 ++ internal/service/ssmcontacts/tags_gen.go | 138 +++ names/names.go | 2 +- names/names_data.csv | 2 +- website/allowed-subcategories.txt | 2 +- .../docs/d/ssmcontacts_contact.html.markdown | 37 + .../ssmcontacts_contact_channel.html.markdown | 41 + website/docs/d/ssmcontacts_plan.html.markdown | 33 + .../docs/r/ssmcontacts_contact.html.markdown | 72 ++ .../ssmcontacts_contact_channel.html.markdown | 78 ++ website/docs/r/ssmcontacts_plan.html.markdown | 92 ++ 34 files changed, 4123 insertions(+), 119 deletions(-) create mode 100644 internal/service/ssmcontacts/contact.go create mode 100644 internal/service/ssmcontacts/contact_channel.go create mode 100644 internal/service/ssmcontacts/contact_channel_data_source.go create mode 100644 internal/service/ssmcontacts/contact_channel_data_source_test.go create mode 100644 internal/service/ssmcontacts/contact_channel_test.go create mode 100644 internal/service/ssmcontacts/contact_data_source.go create mode 100644 internal/service/ssmcontacts/contact_data_source_test.go create mode 100644 internal/service/ssmcontacts/contact_test.go create mode 100644 internal/service/ssmcontacts/find.go create mode 100644 internal/service/ssmcontacts/flex.go create mode 100644 internal/service/ssmcontacts/generate.go create mode 100644 internal/service/ssmcontacts/helper.go create mode 100644 internal/service/ssmcontacts/plan.go create mode 100644 internal/service/ssmcontacts/plan_data_source.go create mode 100644 internal/service/ssmcontacts/plan_data_source_test.go create mode 100644 internal/service/ssmcontacts/plan_test.go create mode 100644 internal/service/ssmcontacts/service_package_gen.go create mode 100644 internal/service/ssmcontacts/ssmcontacts_test.go create mode 100644 internal/service/ssmcontacts/tags_gen.go create mode 100644 website/docs/d/ssmcontacts_contact.html.markdown create mode 100644 website/docs/d/ssmcontacts_contact_channel.html.markdown create mode 100644 website/docs/d/ssmcontacts_plan.html.markdown create mode 100644 website/docs/r/ssmcontacts_contact.html.markdown create mode 100644 website/docs/r/ssmcontacts_contact_channel.html.markdown create mode 100644 website/docs/r/ssmcontacts_plan.html.markdown diff --git a/.ci/.semgrep-service-name0.yml b/.ci/.semgrep-service-name0.yml index bb4fb26b932..10e20096620 100644 --- a/.ci/.semgrep-service-name0.yml +++ b/.ci/.semgrep-service-name0.yml @@ -3390,3 +3390,18 @@ rules: patterns: - pattern-regex: "(?i)ComputeOptimizer" severity: WARNING + - id: configservice-in-func-name + languages: + - go + message: Do not use "ConfigService" in func name inside configservice package + paths: + include: + - internal/service/configservice + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)ConfigService" + - pattern-not-regex: ^TestAcc.* + severity: WARNING diff --git a/.ci/.semgrep-service-name1.yml b/.ci/.semgrep-service-name1.yml index 3128ac29f78..45148c64670 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: configservice-in-func-name - languages: - - go - message: Do not use "ConfigService" in func name inside configservice package - paths: - include: - - internal/service/configservice - patterns: - - pattern: func $NAME( ... ) { ... } - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)ConfigService" - - pattern-not-regex: ^TestAcc.* - severity: WARNING - id: configservice-in-test-name languages: - go @@ -3366,3 +3351,46 @@ rules: - pattern-not-regex: "^TestAccInspector2" - pattern-regex: ^TestAcc.* severity: WARNING + - id: inspector2-in-const-name + languages: + - go + message: Do not use "Inspector2" in const name inside inspector2 package + paths: + include: + - internal/service/inspector2 + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Inspector2" + severity: WARNING + - id: inspector2-in-var-name + languages: + - go + message: Do not use "Inspector2" in var name inside inspector2 package + paths: + include: + - internal/service/inspector2 + patterns: + - pattern: var $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Inspector2" + severity: WARNING + - id: inspectorv2-in-func-name + languages: + - go + message: Do not use "inspectorv2" in func name inside inspector2 package + paths: + include: + - internal/service/inspector2 + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)inspectorv2" + - pattern-not-regex: ^TestAcc.* + severity: WARNING diff --git a/.ci/.semgrep-service-name2.yml b/.ci/.semgrep-service-name2.yml index 4c5a2e2d134..ac2684c171d 100644 --- a/.ci/.semgrep-service-name2.yml +++ b/.ci/.semgrep-service-name2.yml @@ -1,48 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - id: inspector2-in-const-name - languages: - - go - message: Do not use "Inspector2" in const name inside inspector2 package - paths: - include: - - internal/service/inspector2 - patterns: - - pattern: const $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Inspector2" - severity: WARNING - - id: inspector2-in-var-name - languages: - - go - message: Do not use "Inspector2" in var name inside inspector2 package - paths: - include: - - internal/service/inspector2 - patterns: - - pattern: var $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Inspector2" - severity: WARNING - - id: inspectorv2-in-func-name - languages: - - go - message: Do not use "inspectorv2" in func name inside inspector2 package - paths: - include: - - internal/service/inspector2 - patterns: - - pattern: func $NAME( ... ) { ... } - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)inspectorv2" - - pattern-not-regex: ^TestAcc.* - severity: WARNING - id: inspectorv2-in-const-name languages: - go @@ -3377,3 +3334,61 @@ rules: - 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 + message: Do not use "Redshift" in var name inside redshift package + paths: + include: + - internal/service/redshift + patterns: + - pattern: var $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Redshift" + severity: WARNING + - id: redshiftdata-in-func-name + languages: + - go + message: Do not use "RedshiftData" in func name inside redshiftdata package + paths: + include: + - internal/service/redshiftdata + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)RedshiftData" + - pattern-not-regex: ^TestAcc.* + severity: WARNING diff --git a/.ci/.semgrep-service-name3.yml b/.ci/.semgrep-service-name3.yml index 07b0e073fa9..6d40269a840 100644 --- a/.ci/.semgrep-service-name3.yml +++ b/.ci/.semgrep-service-name3.yml @@ -1,63 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - 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 - message: Do not use "Redshift" in var name inside redshift package - paths: - include: - - internal/service/redshift - patterns: - - pattern: var $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Redshift" - severity: WARNING - - id: redshiftdata-in-func-name - languages: - - go - message: Do not use "RedshiftData" in func name inside redshiftdata package - paths: - include: - - internal/service/redshiftdata - patterns: - - pattern: func $NAME( ... ) { ... } - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)RedshiftData" - - pattern-not-regex: ^TestAcc.* - severity: WARNING - id: redshiftdata-in-test-name languages: - go @@ -2275,6 +2217,64 @@ rules: patterns: - pattern-regex: "(?i)SSM" severity: WARNING + - id: ssmcontacts-in-func-name + languages: + - go + message: Do not use "SSMContacts" in func name inside ssmcontacts package + paths: + include: + - internal/service/ssmcontacts + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)SSMContacts" + - pattern-not-regex: ^TestAcc.* + severity: WARNING + - id: ssmcontacts-in-test-name + languages: + - go + message: Include "SSMContacts" in test name + paths: + include: + - internal/service/ssmcontacts/*_test.go + patterns: + - pattern: func $NAME( ... ) { ... } + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccSSMContacts" + - pattern-regex: ^TestAcc.* + severity: WARNING + - id: ssmcontacts-in-const-name + languages: + - go + message: Do not use "SSMContacts" in const name inside ssmcontacts package + paths: + include: + - internal/service/ssmcontacts + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)SSMContacts" + severity: WARNING + - id: ssmcontacts-in-var-name + languages: + - go + message: Do not use "SSMContacts" in var name inside ssmcontacts package + paths: + include: + - internal/service/ssmcontacts + patterns: + - pattern: var $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)SSMContacts" + severity: WARNING - id: ssmincidents-in-func-name languages: - go diff --git a/.teamcity/components/generated/services_all.kt b/.teamcity/components/generated/services_all.kt index c15ae757fc0..1881246c44f 100644 --- a/.teamcity/components/generated/services_all.kt +++ b/.teamcity/components/generated/services_all.kt @@ -181,6 +181,7 @@ val services = mapOf( "sns" to ServiceSpec("SNS (Simple Notification)"), "sqs" to ServiceSpec("SQS (Simple Queue)"), "ssm" to ServiceSpec("SSM (Systems Manager)", vpcLock = true), + "ssmcontacts" to ServiceSpec("SSM Contacts"), "ssmincidents" to ServiceSpec("SSM Incident Manager Incidents"), "ssoadmin" to ServiceSpec("SSO Admin"), "storagegateway" to ServiceSpec("Storage Gateway", vpcLock = true), diff --git a/internal/provider/service_packages_gen.go b/internal/provider/service_packages_gen.go index 4c52ebc9337..51beae82b35 100644 --- a/internal/provider/service_packages_gen.go +++ b/internal/provider/service_packages_gen.go @@ -190,6 +190,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/sns" "github.com/hashicorp/terraform-provider-aws/internal/service/sqs" "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" + "github.com/hashicorp/terraform-provider-aws/internal/service/ssmcontacts" "github.com/hashicorp/terraform-provider-aws/internal/service/ssmincidents" "github.com/hashicorp/terraform-provider-aws/internal/service/ssoadmin" "github.com/hashicorp/terraform-provider-aws/internal/service/storagegateway" @@ -395,6 +396,7 @@ func servicePackages(context.Context) []conns.ServicePackage { sns.ServicePackage, sqs.ServicePackage, ssm.ServicePackage, + ssmcontacts.ServicePackage, ssmincidents.ServicePackage, ssoadmin.ServicePackage, storagegateway.ServicePackage, diff --git a/internal/service/ssmcontacts/contact.go b/internal/service/ssmcontacts/contact.go new file mode 100644 index 00000000000..53360193cfc --- /dev/null +++ b/internal/service/ssmcontacts/contact.go @@ -0,0 +1,186 @@ +package ssmcontacts + +import ( + "context" + "errors" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_ssmcontacts_contact") +func ResourceContact() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceContactCreate, + ReadWithoutTimeout: resourceContactRead, + UpdateWithoutTimeout: resourceContactUpdate, + DeleteWithoutTimeout: resourceContactDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "alias": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +const ( + ResNameContact = "Contact" +) + +func resourceContactCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*conns.AWSClient).SSMContactsClient() + + input := &ssmcontacts.CreateContactInput{ + Alias: aws.String(d.Get("alias").(string)), + Type: types.ContactType(d.Get("type").(string)), + Plan: &types.Plan{Stages: []types.Stage{}}, + DisplayName: aws.String(d.Get("display_name").(string)), + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + output, err := client.CreateContact(ctx, input) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameContact, d.Get("alias").(string), err) + } + + if output == nil { + return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameContact, d.Get("alias").(string), errors.New("empty output")) + } + + d.SetId(aws.ToString(output.ContactArn)) + + return resourceContactRead(ctx, d, meta) +} + +func resourceContactRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + out, err := findContactByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSMContacts Contact (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNameContact, d.Id(), err) + } + + if err := setContactResourceData(d, out); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionSetting, ResNameContact, d.Id(), err) + } + + tags, err := ListTags(ctx, conn, d.Id()) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNameContact, 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.SSMContacts, create.ErrActionSetting, ResNameContact, d.Id(), err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionSetting, ResNameContact, d.Id(), err) + } + + return nil +} + +func resourceContactUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + update := false + + in := &ssmcontacts.UpdateContactInput{ + ContactId: aws.String(d.Id()), + } + + if d.HasChanges("display_name") { + in.DisplayName = aws.String(d.Get("display_name").(string)) + update = true + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := UpdateTags(ctx, conn, d.Id(), o, n); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNameContact, d.Id(), err) + } + } + + if !update { + return nil + } + + log.Printf("[DEBUG] Updating SSMContacts Contact (%s): %#v", d.Id(), in) + _, err := conn.UpdateContact(ctx, in) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNameContact, d.Id(), err) + } + + return resourceContactRead(ctx, d, meta) +} + +func resourceContactDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + log.Printf("[INFO] Deleting SSMContacts Contact %s", d.Id()) + + _, err := conn.DeleteContact(ctx, &ssmcontacts.DeleteContactInput{ + ContactId: aws.String(d.Id()), + }) + + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.SSMContacts, create.ErrActionDeleting, ResNameContact, d.Id(), err) + } + return nil +} diff --git a/internal/service/ssmcontacts/contact_channel.go b/internal/service/ssmcontacts/contact_channel.go new file mode 100644 index 00000000000..72cbadce874 --- /dev/null +++ b/internal/service/ssmcontacts/contact_channel.go @@ -0,0 +1,174 @@ +package ssmcontacts + +import ( + "context" + "errors" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_ssmcontacts_contact_channel") +func ResourceContactChannel() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceContactChannelCreate, + ReadWithoutTimeout: resourceContactChannelRead, + UpdateWithoutTimeout: resourceContactChannelUpdate, + DeleteWithoutTimeout: resourceContactChannelDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "activation_status": { + Type: schema.TypeString, + Computed: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "contact_id": { + ForceNew: true, + Type: schema.TypeString, + Required: true, + }, + "delivery_address": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "simple_address": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "type": { + ForceNew: true, + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +const ( + ResNameContactChannel = "Contact Channel" +) + +func resourceContactChannelCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + delivery_address := expandContactChannelAddress(d.Get("delivery_address").([]interface{})) + in := &ssmcontacts.CreateContactChannelInput{ + ContactId: aws.String(d.Get("contact_id").(string)), + DeferActivation: aws.Bool(true), + DeliveryAddress: delivery_address, + Name: aws.String(d.Get("name").(string)), + Type: types.ChannelType(d.Get("type").(string)), + } + + out, err := conn.CreateContactChannel(ctx, in) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameContactChannel, d.Get("name").(string), err) + } + + if out == nil { + return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameContactChannel, d.Get("name").(string), errors.New("empty output")) + } + + d.SetId(aws.ToString(out.ContactChannelArn)) + + return resourceContactChannelRead(ctx, d, meta) +} + +func resourceContactChannelRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + out, err := findContactChannelByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSMContacts ContactChannel (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNameContactChannel, d.Id(), err) + } + + if err := setContactChannelResourceData(d, out); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionSetting, ResNameContactChannel, d.Id(), err) + } + + return nil +} + +func resourceContactChannelUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + update := false + + in := &ssmcontacts.UpdateContactChannelInput{ + ContactChannelId: aws.String(d.Id()), + } + + if d.HasChanges("delivery_address") { + in.DeliveryAddress = expandContactChannelAddress(d.Get("delivery_address").([]interface{})) + update = true + } + + if d.HasChanges("name") { + in.Name = aws.String(d.Get("name").(string)) + update = true + } + + if !update { + return nil + } + + log.Printf("[DEBUG] Updating SSMContacts ContactChannel (%s): %#v", d.Id(), in) + _, err := conn.UpdateContactChannel(ctx, in) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNameContactChannel, d.Id(), err) + } + + return resourceContactChannelRead(ctx, d, meta) +} + +func resourceContactChannelDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + log.Printf("[INFO] Deleting SSMContacts ContactChannel %s", d.Id()) + + _, err := conn.DeleteContactChannel(ctx, &ssmcontacts.DeleteContactChannelInput{ + ContactChannelId: aws.String(d.Id()), + }) + + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.SSMContacts, create.ErrActionDeleting, ResNameContactChannel, d.Id(), err) + } + + return nil +} diff --git a/internal/service/ssmcontacts/contact_channel_data_source.go b/internal/service/ssmcontacts/contact_channel_data_source.go new file mode 100644 index 00000000000..2fefef64e57 --- /dev/null +++ b/internal/service/ssmcontacts/contact_channel_data_source.go @@ -0,0 +1,77 @@ +package ssmcontacts + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKDataSource("aws_ssmcontacts_contact_channel") +func DataSourceContactChannel() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceContactChannelRead, + + Schema: map[string]*schema.Schema{ + "activation_status": { + Type: schema.TypeString, + Computed: true, + }, + "arn": { + Type: schema.TypeString, + Required: true, + }, + "contact_id": { + Type: schema.TypeString, + Computed: true, + }, + "delivery_address": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "simple_address": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +const ( + DSNameContactChannel = "Contact Channel Data Source" +) + +func dataSourceContactChannelRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + arn := d.Get("arn").(string) + + out, err := findContactChannelByID(ctx, conn, arn) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNameContactChannel, arn, err) + } + + d.SetId(aws.ToString(out.ContactChannelArn)) + + if err := setContactChannelResourceData(d, out); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionSetting, ResNameContactChannel, d.Id(), err) + } + + return nil +} diff --git a/internal/service/ssmcontacts/contact_channel_data_source_test.go b/internal/service/ssmcontacts/contact_channel_data_source_test.go new file mode 100644 index 00000000000..0c311069ee9 --- /dev/null +++ b/internal/service/ssmcontacts/contact_channel_data_source_test.go @@ -0,0 +1,79 @@ +package ssmcontacts_test + +import ( + "context" + "fmt" + "testing" + + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testContactChannelDataSource_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + contactChannelResourceName := "aws_ssmcontacts_contact_channel.test" + dataSourceName := "data.aws_ssmcontacts_contact_channel.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactChannelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactChannelDataSourceConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactChannelExists(dataSourceName), + resource.TestCheckResourceAttrPair(dataSourceName, "activation_status", contactChannelResourceName, "activation_status"), + resource.TestCheckResourceAttrPair(dataSourceName, "delivery_address.#", contactChannelResourceName, "delivery_address.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "delivery_address.0.simple_address", contactChannelResourceName, "delivery_address.0.simple_address"), + resource.TestCheckResourceAttrPair(dataSourceName, "name", contactChannelResourceName, "name"), + resource.TestCheckResourceAttrPair(dataSourceName, "type", contactChannelResourceName, "type"), + resource.TestCheckResourceAttrPair(dataSourceName, "contact_id", contactChannelResourceName, "contact_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "arn", contactChannelResourceName, "arn"), + ), + }, + }, + }) +} + +func testAccContactChannelDataSourceConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_ssmincidents_replication_set" "test" { + region { + name = %[1]q + } +} + +resource "aws_ssmcontacts_contact" "test" { + alias = "test-contact-for-%[2]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact_channel" "test" { + contact_id = aws_ssmcontacts_contact.test.arn + delivery_address { + simple_address = "default@example.com" + } + name = "%[2]s" + type = "EMAIL" +} + +data "aws_ssmcontacts_contact_channel" "test" { + arn = aws_ssmcontacts_contact_channel.test.arn +} +`, acctest.Region(), rName) +} diff --git a/internal/service/ssmcontacts/contact_channel_test.go b/internal/service/ssmcontacts/contact_channel_test.go new file mode 100644 index 00000000000..c13bbfd61db --- /dev/null +++ b/internal/service/ssmcontacts/contact_channel_test.go @@ -0,0 +1,470 @@ +package ssmcontacts_test + +import ( + "context" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/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" + tfssmcontacts "github.com/hashicorp/terraform-provider-aws/internal/service/ssmcontacts" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testContactChannel_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + contactResourceName := "aws_ssmcontacts_contact.test" + channelResourceName := "aws_ssmcontacts_contact_channel.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactChannelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactChannelConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "default@example.com"), + resource.TestCheckResourceAttr(channelResourceName, "name", rName), + resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), + resource.TestCheckResourceAttrPair(channelResourceName, "contact_id", contactResourceName, "arn"), + acctest.MatchResourceAttrRegionalARN(channelResourceName, "arn", "ssm-contacts", regexp.MustCompile("contact-channel/test-contact-for-"+rName+"/.")), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // We need to explicitly test destroying this resource instead of just using CheckDestroy, + // because CheckDestroy will run after the replication set has been destroyed and destroying + // the replication set will destroy all other resources. + Config: testAccContactChannelConfig_none(), + Check: testAccCheckContactChannelDestroy, + }, + }, + }) +} + +func testContactChannel_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + channelResourceName := "aws_ssmcontacts_contact_channel.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactChannelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactChannelConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactChannelExists(channelResourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssmcontacts.ResourceContactChannel(), channelResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testContactChannel_contactId(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + testContactOneResourceName := "aws_ssmcontacts_contact.test_contact_one" + testContactTwoResourceName := "aws_ssmcontacts_contact.test_contact_two" + channelResourceName := "aws_ssmcontacts_contact_channel.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactChannelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactChannelConfig_withTwoContacts(rName, testContactOneResourceName+".arn"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(testContactOneResourceName), + testAccCheckContactExists(testContactTwoResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttrPair(channelResourceName, "contact_id", testContactOneResourceName, "arn"), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactChannelConfig_withTwoContacts(rName, testContactTwoResourceName+".arn"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(testContactOneResourceName), + testAccCheckContactExists(testContactTwoResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttrPair(channelResourceName, "contact_id", testContactTwoResourceName, "arn"), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testContactChannel_deliveryAddress(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + contactResourceName := "aws_ssmcontacts_contact.test" + channelResourceName := "aws_ssmcontacts_contact_channel.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactChannelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactChannelConfig(rName, rName, "EMAIL", "first@example.com"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "first@example.com"), + resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactChannelConfig(rName, rName, "EMAIL", "second@example.com"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "second@example.com"), + resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testContactChannel_name(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix + "1") + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix + "2") + contactResourceName := "aws_ssmcontacts_contact.test" + channelResourceName := "aws_ssmcontacts_contact_channel.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactChannelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactChannelConfig(rName1, "update-name-test", "EMAIL", "test@example.com"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttr(channelResourceName, "name", rName1), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactChannelConfig(rName2, "update-name-test", "EMAIL", "test@example.com"), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttr(channelResourceName, "name", rName2), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testContactChannel_type(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + contactResourceName := "aws_ssmcontacts_contact.test" + channelResourceName := "aws_ssmcontacts_contact_channel.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactChannelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactChannelConfig_defaultEmail(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "default@example.com"), + resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactChannelConfig_defaultSms(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "+12065550100"), + resource.TestCheckResourceAttr(channelResourceName, "type", "SMS"), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactChannelConfig_defaultVoice(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckContactChannelExists(channelResourceName), + resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "+12065550199"), + resource.TestCheckResourceAttr(channelResourceName, "type", "VOICE"), + ), + }, + { + ResourceName: channelResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckContactChannelDestroy(s *terraform.State) error { + ctx := context.Background() + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssmcontacts_contact_channel" { + continue + } + + input := &ssmcontacts.GetContactChannelInput{ + ContactChannelId: aws.String(rs.Primary.ID), + } + _, err := conn.GetContactChannel(ctx, input) + + if err != nil { + // Getting resources may return validation exception when the replication set has been destroyed + var ve *types.ValidationException + if errors.As(err, &ve) { + continue + } + + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + continue + } + + return err + } + + return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNameContactChannel, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccCheckContactChannelExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ctx := context.Background() + + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameContactChannel, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameContactChannel, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() + _, err := conn.GetContactChannel(ctx, &ssmcontacts.GetContactChannelInput{ + ContactChannelId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameContactChannel, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccContactChannelConfig_basic(rName string) string { + return testAccContactChannelConfig_defaultEmail(rName) +} + +func testAccContactChannelConfig_none() string { + return testAccContactChannelConfigBase() +} + +func testAccContactChannelConfig_defaultEmail(rName string) string { + return testAccContactChannelConfig(rName, rName, "EMAIL", "default@example.com") +} + +func testAccContactChannelConfig_defaultSms(rName string) string { + return testAccContactChannelConfig(rName, rName, "SMS", "+12065550100") +} + +func testAccContactChannelConfig_defaultVoice(rName string) string { + return testAccContactChannelConfig(rName, rName, "VOICE", "+12065550199") +} + +func testAccContactChannelConfig(rName string, contactAliasDisambiguator string, channelType string, address string) string { + return acctest.ConfigCompose( + testAccContactChannelConfigBase(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "test" { + alias = "test-contact-for-%[2]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact_channel" "test" { + contact_id = aws_ssmcontacts_contact.test.arn + delivery_address { + simple_address = "%[4]s" + } + name = "%[1]s" + type = "%[3]s" +} +`, rName, contactAliasDisambiguator, channelType, address)) +} + +func testAccContactChannelConfig_withTwoContacts(rName, contactArn string) string { + return acctest.ConfigCompose( + testAccContactChannelConfigBase(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "test_contact_one" { + alias = "test-contact-one-for-%[1]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact" "test_contact_two" { + alias = "test-contact-two-for-%[1]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact_channel" "test" { + contact_id = %[2]s + delivery_address { + simple_address = "test@example.com" + } + name = "%[1]s" + type = "EMAIL" +} +`, rName, contactArn)) +} + +func testAccContactChannelConfigBase() string { + return fmt.Sprintf(` +resource "aws_ssmincidents_replication_set" "test" { + region { + name = %[1]q + } +} +`, acctest.Region()) +} diff --git a/internal/service/ssmcontacts/contact_data_source.go b/internal/service/ssmcontacts/contact_data_source.go new file mode 100644 index 00000000000..97149879c7a --- /dev/null +++ b/internal/service/ssmcontacts/contact_data_source.go @@ -0,0 +1,75 @@ +package ssmcontacts + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKDataSource("aws_ssmcontacts_contact") +func DataSourceContact() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceContactRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Required: true, + }, + "alias": { + Type: schema.TypeString, + Computed: true, + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchemaComputed(), + }, + } +} + +const ( + DSNameContact = "Contact Data Source" +) + +func dataSourceContactRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + arn := d.Get("arn").(string) + + out, err := findContactByID(ctx, conn, arn) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNameContact, arn, err) + } + + d.SetId(aws.ToString(out.ContactArn)) + + if err := setContactResourceData(d, out); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameContact, d.Id(), err) + } + + tags, err := ListTags(ctx, conn, d.Id()) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNameContact, d.Id(), err) + } + + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + //lintignore:AWSR002 + if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameContact, d.Id(), err) + } + + return nil +} diff --git a/internal/service/ssmcontacts/contact_data_source_test.go b/internal/service/ssmcontacts/contact_data_source_test.go new file mode 100644 index 00000000000..3af9ef63572 --- /dev/null +++ b/internal/service/ssmcontacts/contact_data_source_test.go @@ -0,0 +1,83 @@ +package ssmcontacts_test + +import ( + "context" + "fmt" + "testing" + + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testContactDataSource_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_contact.contact_one" + dataSourceName := "data.aws_ssmcontacts_contact.contact_one" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactDataSourceConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(dataSourceName), + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceName, "arn"), + resource.TestCheckResourceAttrPair(resourceName, "alias", dataSourceName, "alias"), + resource.TestCheckResourceAttrPair(resourceName, "type", dataSourceName, "type"), + resource.TestCheckResourceAttrPair(resourceName, "display_name", dataSourceName, "display_name"), + resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), + resource.TestCheckResourceAttrPair(resourceName, "tags.key1", dataSourceName, "tags.key1"), + resource.TestCheckResourceAttrPair(resourceName, "tags.key2", dataSourceName, "tags.key2"), + ), + }, + }, + }) +} + +func testAccContactDataSourceConfigBase() string { + return fmt.Sprintf(` +resource "aws_ssmincidents_replication_set" "test" { + region { + name = %[1]q + } +} +`, acctest.Region()) +} + +func testAccContactDataSourceConfig_basic(alias string) string { + return acctest.ConfigCompose( + testAccContactDataSourceConfigBase(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "contact_one" { + alias = %[1]q + display_name = %[1]q + type = "PERSONAL" + + tags = { + key1 = "tag1" + key2 = "tag2" + } + + depends_on = [aws_ssmincidents_replication_set.test] +} + +data "aws_ssmcontacts_contact" "contact_one" { + arn = aws_ssmcontacts_contact.contact_one.arn +} +`, alias)) +} diff --git a/internal/service/ssmcontacts/contact_test.go b/internal/service/ssmcontacts/contact_test.go new file mode 100644 index 00000000000..25cbf8d6059 --- /dev/null +++ b/internal/service/ssmcontacts/contact_test.go @@ -0,0 +1,531 @@ +package ssmcontacts_test + +import ( + "context" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/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" + tfssmcontacts "github.com/hashicorp/terraform-provider-aws/internal/service/ssmcontacts" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testContact_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_contact.contact_one" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alias", rName), + resource.TestCheckResourceAttr(resourceName, "type", "PERSONAL"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ssm-contacts", regexp.MustCompile(`contact/+.`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // We need to explicitly test destroying this resource instead of just using CheckDestroy, + // because CheckDestroy will run after the replication set has been destroyed and destroying + // the replication set will destroy all other resources. + Config: testAccContactConfig_none(), + Check: testAccCheckContactDestroy, + }, + }, + }) +} + +func testContact_updateAlias(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + oldAlias := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + newAlias := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resourceName := "aws_ssmcontacts_contact.contact_one" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactConfig_alias(oldAlias), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alias", oldAlias), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactConfig_alias(newAlias), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alias", newAlias), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testContact_updateType(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + personalType := "PERSONAL" + escalationType := "ESCALATION" + + resourceName := "aws_ssmcontacts_contact.contact_one" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactConfig_type(name, personalType), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "type", personalType), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactConfig_type(name, escalationType), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "type", escalationType), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testContact_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_contact.contact_one" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssmcontacts.ResourceContact(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testContact_updateDisplayName(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + oldDisplayName := sdkacctest.RandString(26) + newDisplayName := sdkacctest.RandString(26) + resourceName := "aws_ssmcontacts_contact.contact_one" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactConfig_displayName(rName, oldDisplayName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", oldDisplayName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactConfig_displayName(rName, newDisplayName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "display_name", newDisplayName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testContact_updateTags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_contact.contact_one" + + rKey1 := sdkacctest.RandString(26) + rVal1 := sdkacctest.RandString(26) + rVal1Updated := sdkacctest.RandString(26) + rKey2 := sdkacctest.RandString(26) + rVal2 := sdkacctest.RandString(26) + rVal2Updated := sdkacctest.RandString(26) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckContactDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContactConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactConfig_oneTag(rName, rKey1, rVal1), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags."+rKey1, rVal1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactConfig_twoTags(rName, rKey1, rVal1, rKey2, rVal2), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags."+rKey1, rVal1), + resource.TestCheckResourceAttr(resourceName, "tags."+rKey2, rVal2), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactConfig_twoTags(rName, rKey1, rVal1Updated, rKey2, rVal2Updated), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags."+rKey1, rVal1Updated), + resource.TestCheckResourceAttr(resourceName, "tags."+rKey2, rVal2Updated), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactConfig_oneTag(rName, rKey1, rVal1Updated), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags."+rKey1, rVal1Updated), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContactConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckContactDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() + ctx := context.Background() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssmcontacts_contact" { + continue + } + + input := &ssmcontacts.GetContactInput{ + ContactId: aws.String(rs.Primary.ID), + } + _, err := conn.GetContact(ctx, input) + + if err != nil { + // Getting resources may return validation exception when the replication set has been destroyed + var ve *types.ValidationException + if errors.As(err, &ve) { + continue + } + + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + continue + } + + return err + } + + return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNameContact, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccCheckContactExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameContact, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameContact, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() + ctx := context.Background() + + _, err := conn.GetContact(ctx, &ssmcontacts.GetContactInput{ + ContactId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameContact, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccContactPreCheck(t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() + ctx := context.Background() + + input := &ssmcontacts.ListContactsInput{} + _, err := conn.ListContacts(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccContactConfigBase() string { + return fmt.Sprintf(` +resource "aws_ssmincidents_replication_set" "test" { + region { + name = %[1]q + } +} +`, acctest.Region()) +} + +func testAccContactConfig_basic(alias string) string { + return acctest.ConfigCompose( + testAccContactConfigBase(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "contact_one" { + alias = %[1]q + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, alias)) +} + +func testAccContactConfig_none() string { + return testAccContactConfigBase() +} + +func testAccContactConfig_alias(alias string) string { + return acctest.ConfigCompose( + testAccContactConfigBase(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "contact_one" { + alias = %[1]q + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, alias)) +} + +func testAccContactConfig_type(name, typeValue string) string { + return acctest.ConfigCompose( + testAccContactConfigBase(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "contact_one" { + alias = %[1]q + type = %[2]q + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, name, typeValue)) +} + +func testAccContactConfig_displayName(alias, displayName string) string { + return acctest.ConfigCompose( + testAccContactConfigBase(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "contact_one" { + alias = %[1]q + display_name = %[2]q + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, alias, displayName)) +} + +func testAccContactConfig_oneTag(alias, tagKey, tagVal string) string { + return acctest.ConfigCompose( + testAccContactConfigBase(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "contact_one" { + alias = %[1]q + type = "PERSONAL" + + tags = { + %[2]q = %[3]q + } + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, alias, tagKey, tagVal)) +} + +func testAccContactConfig_twoTags(alias, tagKey1, tagVal1, tagKey2, tagVal2 string) string { + return acctest.ConfigCompose( + testAccContactConfigBase(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "contact_one" { + alias = %[1]q + type = "PERSONAL" + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, alias, tagKey1, tagVal1, tagKey2, tagVal2)) +} diff --git a/internal/service/ssmcontacts/find.go b/internal/service/ssmcontacts/find.go new file mode 100644 index 00000000000..bc72700d52a --- /dev/null +++ b/internal/service/ssmcontacts/find.go @@ -0,0 +1,60 @@ +package ssmcontacts + +import ( + "context" + "errors" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func findContactByID(ctx context.Context, conn *ssmcontacts.Client, id string) (*ssmcontacts.GetContactOutput, error) { + in := &ssmcontacts.GetContactInput{ + ContactId: aws.String(id), + } + out, err := conn.GetContact(ctx, in) + if err != nil { + var nfe *types.ResourceNotFoundException + 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 findContactChannelByID(ctx context.Context, conn *ssmcontacts.Client, id string) (*ssmcontacts.GetContactChannelOutput, error) { + in := &ssmcontacts.GetContactChannelInput{ + ContactChannelId: aws.String(id), + } + out, err := conn.GetContactChannel(ctx, in) + if err != nil { + var nfe *types.ResourceNotFoundException + 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 +} diff --git a/internal/service/ssmcontacts/flex.go b/internal/service/ssmcontacts/flex.go new file mode 100644 index 00000000000..109407b29de --- /dev/null +++ b/internal/service/ssmcontacts/flex.go @@ -0,0 +1,197 @@ +package ssmcontacts + +import ( + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" +) + +func expandContactChannelAddress(deliveryAddress []interface{}) *types.ContactChannelAddress { + if len(deliveryAddress) == 0 || deliveryAddress[0] == nil { + return nil + } + + m := deliveryAddress[0].(map[string]interface{}) + + contactChannelAddress := &types.ContactChannelAddress{} + + if v, ok := m["simple_address"].(string); ok { + contactChannelAddress.SimpleAddress = aws.String(v) + } + + return contactChannelAddress +} + +func flattenContactChannelAddress(contactChannelAddress *types.ContactChannelAddress) []interface{} { + m := map[string]interface{}{} + + if v := contactChannelAddress.SimpleAddress; v != nil { + m["simple_address"] = aws.ToString(v) + } + + return []interface{}{m} +} + +func expandStages(stages []interface{}) []types.Stage { + var stageList []types.Stage + + for _, stage := range stages { + s := types.Stage{} + + stageData := stage.(map[string]interface{}) + + if v, ok := stageData["duration_in_minutes"].(int); ok { + s.DurationInMinutes = aws.Int32(int32(v)) + } + + if v, ok := stageData["target"].([]interface{}); ok { + s.Targets = expandTargets(v) + } + + stageList = append(stageList, s) + } + + return stageList +} + +func flattenStages(stages []types.Stage) []interface{} { + var result []interface{} + + for _, stage := range stages { + s := map[string]interface{}{} + + if v := stage.DurationInMinutes; v != nil { + s["duration_in_minutes"] = aws.ToInt32(v) + } + + if v := stage.Targets; v != nil { + s["target"] = flattenTargets(v) + } + + result = append(result, s) + } + + return result +} + +func expandTargets(targets []interface{}) []types.Target { + targetList := make([]types.Target, 0) + + for _, target := range targets { + + if target == nil { + continue + } + + t := types.Target{} + + targetData := target.(map[string]interface{}) + + if v, ok := targetData["channel_target_info"].([]interface{}); ok { + t.ChannelTargetInfo = expandChannelTargetInfo(v) + } + + if v, ok := targetData["contact_target_info"].([]interface{}); ok { + t.ContactTargetInfo = expandContactTargetInfo(v) + } + + targetList = append(targetList, t) + } + + return targetList +} + +func flattenTargets(targets []types.Target) []interface{} { + result := make([]interface{}, 0) + + for _, target := range targets { + t := map[string]interface{}{} + + if v := target.ChannelTargetInfo; v != nil { + t["channel_target_info"] = flattenChannelTargetInfo(v) + } + + if v := target.ContactTargetInfo; v != nil { + t["contact_target_info"] = flattenContactTargetInfo(v) + } + + result = append(result, t) + } + + return result +} + +func expandChannelTargetInfo(channelTargetInfo []interface{}) *types.ChannelTargetInfo { + if len(channelTargetInfo) == 0 { + return nil + } + + c := &types.ChannelTargetInfo{} + + channelTargetInfoData := channelTargetInfo[0].(map[string]interface{}) + + if v, ok := channelTargetInfoData["contact_channel_id"].(string); ok && v != "" { + c.ContactChannelId = aws.String(v) + } + + if v, ok := channelTargetInfoData["retry_interval_in_minutes"].(int); ok { + c.RetryIntervalInMinutes = aws.Int32(int32(v)) + } + + return c +} + +func flattenChannelTargetInfo(channelTargetInfo *types.ChannelTargetInfo) []interface{} { + var result []interface{} + + c := make(map[string]interface{}) + + if v := channelTargetInfo.ContactChannelId; v != nil { + c["contact_channel_id"] = aws.ToString(v) + } + + if v := channelTargetInfo.RetryIntervalInMinutes; v != nil { + c["retry_interval_in_minutes"] = aws.ToInt32(v) + } + + result = append(result, c) + + return result +} + +func expandContactTargetInfo(contactTargetInfo []interface{}) *types.ContactTargetInfo { + if len(contactTargetInfo) == 0 { + return nil + } + + c := &types.ContactTargetInfo{} + + contactTargetInfoData := contactTargetInfo[0].(map[string]interface{}) + + if v, ok := contactTargetInfoData["is_essential"].(bool); ok { + c.IsEssential = aws.Bool(v) + } + + if v, ok := contactTargetInfoData["contact_id"].(string); ok && v != "" { + c.ContactId = aws.String(v) + } + + return c +} + +func flattenContactTargetInfo(contactTargetInfo *types.ContactTargetInfo) []interface{} { + var result []interface{} + + c := make(map[string]interface{}) + + if v := contactTargetInfo.IsEssential; v != nil { + c["is_essential"] = aws.ToBool(v) + } + + if v := contactTargetInfo.ContactId; v != nil { + c["contact_id"] = aws.ToString(v) + } + + result = append(result, c) + + return result +} diff --git a/internal/service/ssmcontacts/generate.go b/internal/service/ssmcontacts/generate.go new file mode 100644 index 00000000000..d7d1ec99216 --- /dev/null +++ b/internal/service/ssmcontacts/generate.go @@ -0,0 +1,4 @@ +//go:generate go run ../../generate/tags/main.go -ServiceTagsSlice -ListTags -UpdateTags -AWSSDKVersion=2 -TagInIDElem=ResourceARN -ListTagsInIDElem=ResourceARN +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package ssmcontacts diff --git a/internal/service/ssmcontacts/helper.go b/internal/service/ssmcontacts/helper.go new file mode 100644 index 00000000000..b40430e806f --- /dev/null +++ b/internal/service/ssmcontacts/helper.go @@ -0,0 +1,44 @@ +package ssmcontacts + +import ( + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func setContactResourceData(d *schema.ResourceData, getContactOutput *ssmcontacts.GetContactOutput) error { + if err := d.Set("arn", getContactOutput.ContactArn); err != nil { + return err + } + if err := d.Set("alias", getContactOutput.Alias); err != nil { + return err + } + if err := d.Set("type", getContactOutput.Type); err != nil { + return err + } + if err := d.Set("display_name", getContactOutput.DisplayName); err != nil { + return err + } + return nil +} + +func setContactChannelResourceData(d *schema.ResourceData, out *ssmcontacts.GetContactChannelOutput) error { + d.Set("activation_status", out.ActivationStatus) + d.Set("arn", out.ContactChannelArn) + d.Set("contact_id", out.ContactArn) + d.Set("name", out.Name) + d.Set("type", out.Type) + if err := d.Set("delivery_address", flattenContactChannelAddress(out.DeliveryAddress)); err != nil { + return err + } + return nil +} + +func setPlanResourceData(d *schema.ResourceData, getContactOutput *ssmcontacts.GetContactOutput) error { + if err := d.Set("contact_id", getContactOutput.ContactArn); err != nil { + return err + } + if err := d.Set("stage", flattenStages(getContactOutput.Plan.Stages)); err != nil { + return err + } + return nil +} diff --git a/internal/service/ssmcontacts/plan.go b/internal/service/ssmcontacts/plan.go new file mode 100644 index 00000000000..5f8d19a7dfd --- /dev/null +++ b/internal/service/ssmcontacts/plan.go @@ -0,0 +1,197 @@ +package ssmcontacts + +import ( + "context" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_ssmcontacts_plan") +func ResourcePlan() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourcePlanCreate, + ReadWithoutTimeout: resourcePlanRead, + UpdateWithoutTimeout: resourcePlanUpdate, + DeleteWithoutTimeout: resourcePlanDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "contact_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "stage": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "duration_in_minutes": { + Type: schema.TypeInt, + Required: true, + }, + "target": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "channel_target_info": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "contact_channel_id": { + Type: schema.TypeString, + Required: true, + }, + "retry_interval_in_minutes": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "contact_target_info": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_essential": { + Type: schema.TypeBool, + Required: true, + }, + "contact_id": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +const ( + ResNamePlan = "Plan" +) + +func resourcePlanCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + contactId := d.Get("contact_id").(string) + stages := expandStages(d.Get("stage").([]interface{})) + plan := &types.Plan{ + Stages: stages, + } + + in := &ssmcontacts.UpdateContactInput{ + ContactId: aws.String(contactId), + Plan: plan, + } + + _, err := conn.UpdateContact(ctx, in) + if err != nil { + return create.DiagError( + names.SSMContacts, + create.ErrActionCreating, + ResNamePlan, + contactId, + err, + ) + } + + d.SetId(contactId) + + return resourcePlanRead(ctx, d, meta) +} + +func resourcePlanRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + out, err := findContactByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSMContacts Plan (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNamePlan, d.Id(), err) + } + + if err := setPlanResourceData(d, out); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNamePlan, d.Id(), err) + } + + return nil +} + +func resourcePlanUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + update := false + + in := &ssmcontacts.UpdateContactInput{ + ContactId: aws.String(d.Id()), + } + + if d.HasChanges("stage") { + stages := expandStages(d.Get("stage").([]interface{})) + in.Plan = &types.Plan{ + Stages: stages, + } + update = true + } + + if !update { + return nil + } + + log.Printf("[DEBUG] Updating SSMContacts Plan (%s): %#v", d.Id(), in) + _, err := conn.UpdateContact(ctx, in) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNamePlan, d.Id(), err) + } + + return resourcePlanRead(ctx, d, meta) +} + +func resourcePlanDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + log.Printf("[INFO] Deleting SSMContacts Plan %s", d.Id()) + + _, err := conn.UpdateContact(ctx, &ssmcontacts.UpdateContactInput{ + ContactId: aws.String(d.Id()), + Plan: &types.Plan{ + Stages: []types.Stage{}, + }, + }) + + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionDeleting, ResNamePlan, d.Id(), err) + } + + return nil +} diff --git a/internal/service/ssmcontacts/plan_data_source.go b/internal/service/ssmcontacts/plan_data_source.go new file mode 100644 index 00000000000..c0527ceba7e --- /dev/null +++ b/internal/service/ssmcontacts/plan_data_source.go @@ -0,0 +1,101 @@ +package ssmcontacts + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKDataSource("aws_ssmcontacts_plan") +func DataSourcePlan() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourcePlanRead, + + Schema: map[string]*schema.Schema{ + "contact_id": { + Type: schema.TypeString, + Required: true, + }, + "stage": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "duration_in_minutes": { + Type: schema.TypeInt, + Computed: true, + }, + "target": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "channel_target_info": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "contact_channel_id": { + Type: schema.TypeString, + Computed: true, + }, + "retry_interval_in_minutes": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "contact_target_info": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_essential": { + Type: schema.TypeBool, + Computed: true, + }, + "contact_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +const ( + DSNamePlan = "Plan Data Source" +) + +func dataSourcePlanRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient() + + contactId := d.Get("contact_id").(string) + + out, err := findContactByID(ctx, conn, contactId) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNamePlan, contactId, err) + } + + d.SetId(aws.ToString(out.ContactArn)) + + if err := setPlanResourceData(d, out); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNamePlan, contactId, err) + } + + return nil +} diff --git a/internal/service/ssmcontacts/plan_data_source_test.go b/internal/service/ssmcontacts/plan_data_source_test.go new file mode 100644 index 00000000000..f787c3e5b51 --- /dev/null +++ b/internal/service/ssmcontacts/plan_data_source_test.go @@ -0,0 +1,251 @@ +package ssmcontacts_test + +import ( + "context" + "fmt" + "testing" + + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testPlanDataSource_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + planDataSourceName := "data.aws_ssmcontacts_plan.test" + planResourceName := "aws_ssmcontacts_plan.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanDataSourceConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPlanExists(planDataSourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttrPair( + planDataSourceName, "contact_id", + planResourceName, "contact_id", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, "stage.#", + planResourceName, "stage.#", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, "stage.0.duration_in_minutes", + planResourceName, "stage.0.duration_in_minutes", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, "stage.0.target.#", + planResourceName, "stage.0.target.#", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, "stage.0.target.0.contact_target_info.is_essential", + planResourceName, "stage.0.target.0.contact_target_info.contact_id", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, "stage.0.target.1.contact_target_info.is_essential", + planResourceName, "stage.0.target.1.contact_target_info.contact_id", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, "stage.1.duration_in_minutes", + planResourceName, "stage.1.duration_in_minutes", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, "stage.1.target.#", + planResourceName, "stage.1.target.#", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, "stage.1.target.0.contact_target_info.is_essential", + planResourceName, "stage.1.target.0.contact_target_info.contact_id", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, "stage.1.target.1.contact_target_info.is_essential", + planResourceName, "stage.1.target.1.contact_target_info.contact_id", + ), + ), + }, + }, + }) +} + +func testPlanDataSource_channelTargetInfo(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + planDataSourceName := "data.aws_ssmcontacts_plan.test" + planResourceName := "aws_ssmcontacts_plan.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanDataSourceConfig_channelTargetInfo(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPlanExists(planDataSourceName), + resource.TestCheckResourceAttrPair(planDataSourceName, "stage.0.target.#", planResourceName, "stage.0.target.#"), + resource.TestCheckResourceAttrPair( + planDataSourceName, + "stage.0.target.0.channel_target_info.0.contact_channel_id", + planResourceName, + "stage.0.target.0.channel_target_info.0.contact_channel_id", + ), + resource.TestCheckResourceAttrPair( + planDataSourceName, + "stage.0.target.0.contact_target_info.0.retry_interval_in_minutes", + planResourceName, + "stage.0.target.0.contact_target_info.0.retry_interval_in_minutes", + ), + ), + }, + }, + }) +} + +func testAccPlanDataSourceConfig_basic(rName string) string { + return acctest.ConfigCompose( + testAccPlanDataSourceConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + stage { + duration_in_minutes = 1 + target { + contact_target_info { + is_essential = false + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + } + } + target { + contact_target_info { + is_essential = true + contact_id = aws_ssmcontacts_contact.test_contact_two.arn + } + } + } + stage { + duration_in_minutes = 0 + target { + contact_target_info { + is_essential = false + contact_id = aws_ssmcontacts_contact.test_contact_three.arn + } + } + target { + contact_target_info { + is_essential = true + contact_id = aws_ssmcontacts_contact.test_contact_four.arn + } + } + } +} + +data "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + + depends_on = [aws_ssmcontacts_plan.test] +} +`)) +} + +func testAccPlanDataSourceConfig_channelTargetInfo(rName string) string { + return acctest.ConfigCompose( + testAccPlanDataSourceConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact_channel" "test" { + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + delivery_address { + simple_address = "email@example.com" + } + name = "Test Contact Channel for %[1]s" + type = "EMAIL" +} + +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + stage { + duration_in_minutes = 1 + target { + channel_target_info { + contact_channel_id = aws_ssmcontacts_contact_channel.test.arn + retry_interval_in_minutes = 1 + } + } + } +} + +data "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + + depends_on = [aws_ssmcontacts_plan.test] +} +`, rName)) +} + +func testAccPlanDataSourceConfig_base(alias string) string { + return fmt.Sprintf(` +resource "aws_ssmincidents_replication_set" "test" { + region { + name = %[1]q + } +} + +resource "aws_ssmcontacts_contact" "test_contact_one" { + alias = "test-contact-one-for-%[2]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact" "test_contact_two" { + alias = "test-contact-two-for-%[2]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact" "test_contact_three" { + alias = "test-contact-three-for-%[2]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact" "test_contact_four" { + alias = "test-contact-four-for-%[2]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact" "test_escalation_plan_one" { + alias = "test-escalation-plan-for-%[2]s" + type = "ESCALATION" + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, acctest.Region(), alias) +} diff --git a/internal/service/ssmcontacts/plan_test.go b/internal/service/ssmcontacts/plan_test.go new file mode 100644 index 00000000000..8278c466dd7 --- /dev/null +++ b/internal/service/ssmcontacts/plan_test.go @@ -0,0 +1,809 @@ +package ssmcontacts_test + +import ( + "context" + "errors" + "fmt" + "strconv" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/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" + tfssmcontacts "github.com/hashicorp/terraform-provider-aws/internal/service/ssmcontacts" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testPlan_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + contactResourceName := "aws_ssmcontacts_contact.test_contact_one" + planResourceName := "aws_ssmcontacts_plan.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_oneStage(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.#", "1"), + resource.TestCheckResourceAttr(planResourceName, "stage.0.duration_in_minutes", "1"), + acctest.CheckResourceAttrRegionalARN( + planResourceName, + "contact_id", + "ssm-contacts", + "contact/test-contact-one-for-"+rName, + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // We need to explicitly test destroying this resource instead of just using CheckDestroy, + // because CheckDestroy will run after the replication set has been destroyed and destroying + // the replication set will destroy all other resources. + Config: testAccPlanConfig_none(rName), + Check: testAccCheckPlanDestroy, + }, + }, + }) +} + +func testPlan_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + contactResourceName := "aws_ssmcontacts_contact.test_contact_one" + planResourceName := "aws_ssmcontacts_plan.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_oneStage(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckPlanExists(planResourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssmcontacts.ResourcePlan(), planResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testPlan_updateContactId(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + contactOneResourceName := "aws_ssmcontacts_contact.test_contact_one" + contactTwoResourceName := "aws_ssmcontacts_contact.test_contact_two" + + planResourceName := "aws_ssmcontacts_plan.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_contactId(rName, contactOneResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactOneResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckTypeSetElemAttrPair(planResourceName, "contact_id", contactOneResourceName, "arn"), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPlanConfig_contactId(rName, contactTwoResourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactTwoResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckTypeSetElemAttrPair(planResourceName, "contact_id", contactTwoResourceName, "arn"), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testPlan_updateStages(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + contactResourceName := "aws_ssmcontacts_contact.test_contact_one" + planResourceName := "aws_ssmcontacts_plan.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_oneStage(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.#", "1"), + resource.TestCheckResourceAttr(planResourceName, "stage.0.duration_in_minutes", "1"), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "0"), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPlanConfig_twoStages(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.#", "2"), + resource.TestCheckResourceAttr(planResourceName, "stage.0.duration_in_minutes", "1"), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "0"), + resource.TestCheckResourceAttr(planResourceName, "stage.1.duration_in_minutes", "2"), + resource.TestCheckResourceAttr(planResourceName, "stage.1.target.#", "0"), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPlanConfig_oneStage(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.#", "1"), + resource.TestCheckResourceAttr(planResourceName, "stage.0.duration_in_minutes", "1"), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "0"), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testPlan_updateDurationInMinutes(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + contactResourceName := "aws_ssmcontacts_contact.test_contact_one" + planResourceName := "aws_ssmcontacts_plan.test" + oldDurationInMinutes := 1 + newDurationInMinutes := 2 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_durationInMinutes(rName, oldDurationInMinutes), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.duration_in_minutes", + strconv.Itoa(oldDurationInMinutes), + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPlanConfig_durationInMinutes(rName, newDurationInMinutes), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(contactResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.duration_in_minutes", + strconv.Itoa(newDurationInMinutes), + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testPlan_updateTargets(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + escalationPlanResourceName := "aws_ssmcontacts_contact.test_escalation_plan_one" + planResourceName := "aws_ssmcontacts_plan.test" + testContactOneResourceArn := "aws_ssmcontacts_contact.test_contact_one.arn" + testContactTwoResourceArn := "aws_ssmcontacts_contact.test_contact_two.arn" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_oneTarget(rName, testContactOneResourceArn), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(escalationPlanResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.target.0.contact_target_info.0.is_essential", + "false", + ), + acctest.CheckResourceAttrRegionalARN( + planResourceName, + "stage.0.target.0.contact_target_info.0.contact_id", + "ssm-contacts", + "contact/test-contact-one-for-"+rName, + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPlanConfig_twoTargets(rName, testContactOneResourceArn, testContactTwoResourceArn), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(escalationPlanResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "2"), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.target.0.contact_target_info.0.is_essential", + "false", + ), + acctest.CheckResourceAttrRegionalARN( + planResourceName, + "stage.0.target.0.contact_target_info.0.contact_id", + "ssm-contacts", + "contact/test-contact-one-for-"+rName, + ), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.target.1.contact_target_info.0.is_essential", + "true", + ), + acctest.CheckResourceAttrRegionalARN( + planResourceName, + "stage.0.target.1.contact_target_info.0.contact_id", + "ssm-contacts", + "contact/test-contact-two-for-"+rName, + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPlanConfig_oneTarget(rName, testContactOneResourceArn), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(escalationPlanResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.target.0.contact_target_info.0.is_essential", + "false", + ), + acctest.CheckResourceAttrRegionalARN( + planResourceName, + "stage.0.target.0.contact_target_info.0.contact_id", + "ssm-contacts", + "contact/test-contact-one-for-"+rName, + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testPlan_updateContactTargetInfo(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + escalationPlanResourceName := "aws_ssmcontacts_contact.test_escalation_plan_one" + planResourceName := "aws_ssmcontacts_plan.test" + testContactOneResourceArn := "aws_ssmcontacts_contact.test_contact_one.arn" + testContactTwoResourceArn := "aws_ssmcontacts_contact.test_contact_two.arn" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_contactTargetInfo(rName, false, testContactOneResourceArn), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(escalationPlanResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.target.0.contact_target_info.0.is_essential", + "false", + ), + acctest.CheckResourceAttrRegionalARN( + planResourceName, + "stage.0.target.0.contact_target_info.0.contact_id", + "ssm-contacts", + "contact/test-contact-one-for-"+rName, + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPlanConfig_contactTargetInfo(rName, true, testContactTwoResourceArn), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(escalationPlanResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.target.0.contact_target_info.0.is_essential", + "true", + ), + acctest.CheckResourceAttrRegionalARN( + planResourceName, + "stage.0.target.0.contact_target_info.0.contact_id", + "ssm-contacts", + "contact/test-contact-two-for-"+rName, + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testPlan_updateChannelTargetInfo(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + escalationPlanResourceName := "aws_ssmcontacts_contact.test_escalation_plan_one" + planResourceName := "aws_ssmcontacts_plan.test" + contactChannelOneResourceName := "aws_ssmcontacts_contact_channel.test_channel_one" + contactChannelTwoResourceName := "aws_ssmcontacts_contact_channel.test_channel_two" + + oldRetryIntervalInMinutes := 3 + newRetryIntervalInMinutes := 5 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccContactPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_channelTargetInfo( + rName, + contactChannelOneResourceName, + oldRetryIntervalInMinutes, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(escalationPlanResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), + resource.TestCheckResourceAttrPair( + planResourceName, + "stage.0.target.0.channel_target_info.0.contact_channel_id", + contactChannelOneResourceName, + "arn", + ), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.target.0.channel_target_info.0.retry_interval_in_minutes", + strconv.Itoa(oldRetryIntervalInMinutes), + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPlanConfig_channelTargetInfo( + rName, + contactChannelTwoResourceName, + newRetryIntervalInMinutes, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(escalationPlanResourceName), + testAccCheckPlanExists(planResourceName), + resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), + resource.TestCheckResourceAttrPair( + planResourceName, + "stage.0.target.0.channel_target_info.0.contact_channel_id", + contactChannelTwoResourceName, + "arn", + ), + resource.TestCheckResourceAttr( + planResourceName, + "stage.0.target.0.channel_target_info.0.retry_interval_in_minutes", + strconv.Itoa(newRetryIntervalInMinutes), + ), + ), + }, + { + ResourceName: planResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckPlanExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNamePlan, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNamePlan, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() + ctx := context.Background() + output, err := conn.GetContact(ctx, &ssmcontacts.GetContactInput{ + ContactId: aws.String(rs.Primary.ID), + }) + + if err != nil || len(output.Plan.Stages) == 0 { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNamePlan, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccCheckPlanDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() + ctx := context.Background() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssmcontacts_plan" { + continue + } + + input := &ssmcontacts.GetContactInput{ + ContactId: aws.String(rs.Primary.ID), + } + output, err := conn.GetContact(ctx, input) + if err != nil { + // Getting resources may return validation exception when the replication set has been destroyed + var ve *types.ValidationException + if errors.As(err, &ve) { + continue + } + + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + continue + } + + return err + } + + if len(output.Plan.Stages) == 0 { + return nil + } + + return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNamePlan, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccPlanConfig_contactId(rName, contactName string) string { + return acctest.ConfigCompose( + testAccPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_plan" "test" { + contact_id = %[1]s + stage { + duration_in_minutes = 1 + } +} +`, contactName+".arn")) +} + +func testAccPlanConfig_none(rName string) string { + return testAccPlanConfig_base(rName) +} + +func testAccPlanConfig_oneStage(rName string) string { + return acctest.ConfigCompose( + testAccPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + stage { + duration_in_minutes = 1 + } +} +`)) +} + +func testAccPlanConfig_twoStages(rName string) string { + return acctest.ConfigCompose( + testAccPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + stage { + duration_in_minutes = 1 + } + stage { + duration_in_minutes = 2 + } +} +`)) +} + +func testAccPlanConfig_durationInMinutes(rName string, durationInMinutes int) string { + return acctest.ConfigCompose( + testAccPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + stage { + duration_in_minutes = %[1]d + } +} +`, durationInMinutes)) +} + +func testAccPlanConfig_oneTarget(rName, contactOneArn string) string { + return acctest.ConfigCompose( + testAccPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + stage { + duration_in_minutes = 0 + target { + contact_target_info { + is_essential = false + contact_id = %[1]s + } + } + } +} +`, contactOneArn)) +} + +func testAccPlanConfig_twoTargets(rName, contactOneArn, contactTwoArn string) string { + return acctest.ConfigCompose( + testAccPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + stage { + duration_in_minutes = 0 + target { + contact_target_info { + is_essential = false + contact_id = %[1]s + } + } + target { + contact_target_info { + is_essential = true + contact_id = %[2]s + } + } + } +} +`, contactOneArn, contactTwoArn)) +} + +func testAccPlanConfig_contactTargetInfo(rName string, isEssential bool, contactId string) string { + return acctest.ConfigCompose( + testAccPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + stage { + duration_in_minutes = 0 + target { + contact_target_info { + is_essential = %[1]t + contact_id = %[2]s + } + } + } +} +`, isEssential, contactId)) +} + +func testAccPlanConfig_channelTargetInfo(rName, contactChannelResourceName string, retryIntervalInMinutes int) string { + return acctest.ConfigCompose( + testAccPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact_channel" "test_channel_one" { + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + delivery_address { + simple_address = "email1@example.com" + } + name = "Test Contact Channel 1" + type = "EMAIL" +} + +resource "aws_ssmcontacts_contact_channel" "test_channel_two" { + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + delivery_address { + simple_address = "email2@example.com" + } + name = "Test Contact Channel 2" + type = "EMAIL" +} + +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + stage { + duration_in_minutes = 1 + target { + channel_target_info { + contact_channel_id = %[1]s.arn + retry_interval_in_minutes = %[2]d + } + } + } +} +`, contactChannelResourceName, retryIntervalInMinutes)) +} + +func testAccPlanConfig_base(alias string) string { + return fmt.Sprintf(` +resource "aws_ssmincidents_replication_set" "test" { + region { + name = %[1]q + } +} + +resource "aws_ssmcontacts_contact" "test_contact_one" { + alias = "test-contact-one-for-%[2]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact" "test_contact_two" { + alias = "test-contact-two-for-%[2]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact" "test_escalation_plan_one" { + alias = "test-escalation-plan-for-%[2]s" + type = "ESCALATION" + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, acctest.Region(), alias) +} diff --git a/internal/service/ssmcontacts/service_package_gen.go b/internal/service/ssmcontacts/service_package_gen.go new file mode 100644 index 00000000000..31afbdac360 --- /dev/null +++ b/internal/service/ssmcontacts/service_package_gen.go @@ -0,0 +1,60 @@ +// Code generated by internal/generate/servicepackages/main.go; DO NOT EDIT. + +package ssmcontacts + +import ( + "context" + + "github.com/hashicorp/terraform-provider-aws/internal/types" + "github.com/hashicorp/terraform-provider-aws/names" +) + +type servicePackage struct{} + +func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.ServicePackageFrameworkDataSource { + return []*types.ServicePackageFrameworkDataSource{} +} + +func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { + return []*types.ServicePackageFrameworkResource{} +} + +func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { + return []*types.ServicePackageSDKDataSource{ + { + Factory: DataSourceContact, + TypeName: "aws_ssmcontacts_contact", + }, + { + Factory: DataSourceContactChannel, + TypeName: "aws_ssmcontacts_contact_channel", + }, + { + Factory: DataSourcePlan, + TypeName: "aws_ssmcontacts_plan", + }, + } +} + +func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { + return []*types.ServicePackageSDKResource{ + { + Factory: ResourceContact, + TypeName: "aws_ssmcontacts_contact", + }, + { + Factory: ResourceContactChannel, + TypeName: "aws_ssmcontacts_contact_channel", + }, + { + Factory: ResourcePlan, + TypeName: "aws_ssmcontacts_plan", + }, + } +} + +func (p *servicePackage) ServicePackageName() string { + return names.SSMContacts +} + +var ServicePackage = &servicePackage{} diff --git a/internal/service/ssmcontacts/ssmcontacts_test.go b/internal/service/ssmcontacts/ssmcontacts_test.go new file mode 100644 index 00000000000..d96b6e5236f --- /dev/null +++ b/internal/service/ssmcontacts/ssmcontacts_test.go @@ -0,0 +1,54 @@ +package ssmcontacts_test + +import ( + "testing" + + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +// SSMContacts resources depend on a replication set existing and +// only one replication set resource can be active at once, so we must have serialised tests +func TestAccSSMContacts_serial(t *testing.T) { + t.Parallel() + + testCases := map[string]map[string]func(t *testing.T){ + "Contact Resource Tests": { + "basic": testContact_basic, + "disappears": testContact_disappears, + "updateAlias": testContact_updateAlias, + "updateDisplayName": testContact_updateDisplayName, + "updateTags": testContact_updateTags, + "updateType": testContact_updateType, + }, + "Contact Data Source Tests": { + "basic": testContactDataSource_basic, + }, + "Contact Channel Resource Tests": { + "basic": testContactChannel_basic, + "contactId": testContactChannel_contactId, + "deliveryAddress": testContactChannel_deliveryAddress, + "disappears": testContactChannel_disappears, + "name": testContactChannel_name, + "type": testContactChannel_type, + }, + "Contact Channel Data Source Tests": { + "basic": testContactChannelDataSource_basic, + }, + "Plan Resource Tests": { + "basic": testPlan_basic, + "disappears": testPlan_disappears, + "updateChannelTargetInfo": testPlan_updateChannelTargetInfo, + "updateContactId": testPlan_updateContactId, + "updateContactTargetInfo": testPlan_updateContactTargetInfo, + "updateDurationInMinutes": testPlan_updateDurationInMinutes, + "updateStages": testPlan_updateStages, + "updateTargets": testPlan_updateTargets, + }, + "Plan Data Source Tests": { + "basic": testPlanDataSource_basic, + "channelTargetInfo": testPlanDataSource_channelTargetInfo, + }, + } + + acctest.RunSerialTests2Levels(t, testCases, 0) +} diff --git a/internal/service/ssmcontacts/tags_gen.go b/internal/service/ssmcontacts/tags_gen.go new file mode 100644 index 00000000000..2dc2ccadd02 --- /dev/null +++ b/internal/service/ssmcontacts/tags_gen.go @@ -0,0 +1,138 @@ +// Code generated by internal/generate/tags/main.go; DO NOT EDIT. +package ssmcontacts + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/types" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// ListTags lists ssmcontacts 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 *ssmcontacts.Client, identifier string) (tftags.KeyValueTags, error) { + input := &ssmcontacts.ListTagsForResourceInput{ + ResourceARN: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(ctx, input) + + if err != nil { + return tftags.New(ctx, nil), err + } + + return KeyValueTags(ctx, output.Tags), nil +} + +// ListTags lists ssmcontacts service tags and set them in Context. +// It is called from outside this package. +func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier string) error { + tags, err := ListTags(ctx, meta.(*conns.AWSClient).SSMContactsClient(), identifier) + + if err != nil { + return err + } + + if inContext, ok := tftags.FromContext(ctx); ok { + inContext.TagsOut = types.Some(tags) + } + + return nil +} + +// []*SERVICE.Tag handling + +// Tags returns ssmcontacts service tags. +func Tags(tags tftags.KeyValueTags) []awstypes.Tag { + result := make([]awstypes.Tag, 0, len(tags)) + + for k, v := range tags.Map() { + tag := awstypes.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + + result = append(result, tag) + } + + return result +} + +// KeyValueTags creates tftags.KeyValueTags from ssmcontacts service tags. +func KeyValueTags(ctx context.Context, tags []awstypes.Tag) tftags.KeyValueTags { + m := make(map[string]*string, len(tags)) + + for _, tag := range tags { + m[aws.ToString(tag.Key)] = tag.Value + } + + return tftags.New(ctx, m) +} + +// GetTagsIn returns ssmcontacts service tags from Context. +// nil is returned if there are no input tags. +func GetTagsIn(ctx context.Context) []awstypes.Tag { + if inContext, ok := tftags.FromContext(ctx); ok { + if tags := Tags(inContext.TagsIn.UnwrapOrDefault()); len(tags) > 0 { + return tags + } + } + + return nil +} + +// SetTagsOut sets ssmcontacts service tags in Context. +func SetTagsOut(ctx context.Context, tags []awstypes.Tag) { + if inContext, ok := tftags.FromContext(ctx); ok { + inContext.TagsOut = types.Some(KeyValueTags(ctx, tags)) + } +} + +// UpdateTags updates ssmcontacts 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 *ssmcontacts.Client, identifier string, oldTagsMap, newTagsMap any) error { + oldTags := tftags.New(ctx, oldTagsMap) + newTags := tftags.New(ctx, newTagsMap) + + if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 { + input := &ssmcontacts.UntagResourceInput{ + ResourceARN: aws.String(identifier), + TagKeys: removedTags.IgnoreSystem(names.SSMContacts).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 := &ssmcontacts.TagResourceInput{ + ResourceARN: aws.String(identifier), + Tags: Tags(updatedTags.IgnoreSystem(names.SSMContacts)), + } + + _, err := conn.TagResource(ctx, input) + + if err != nil { + return fmt.Errorf("tagging resource (%s): %w", identifier, err) + } + } + + return nil +} + +// UpdateTags updates ssmcontacts service tags. +// It is called from outside this package. +func (p *servicePackage) UpdateTags(ctx context.Context, meta any, identifier string, oldTags, newTags any) error { + return UpdateTags(ctx, meta.(*conns.AWSClient).SSMContactsClient(), identifier, oldTags, newTags) +} diff --git a/names/names.go b/names/names.go index 5f1d1651b5d..07af87cca5a 100644 --- a/names/names.go +++ b/names/names.go @@ -41,7 +41,7 @@ const ( SchedulerEndpointID = "scheduler" SESV2EndpointID = "sesv2" SSMEndpointID = "ssm" - SSMContactsEndpointId = "ssm-contacts" + SSMContactsEndpointID = "ssm-contacts" SSMIncidentsEndpointID = "ssm-incidents" TranscribeEndpointID = "transcribe" VPCLatticeEndpointID = "vpc-lattice" diff --git a/names/names_data.csv b/names/names_data.csv index 2a97728c5dd..b134767698f 100644 --- a/names/names_data.csv +++ b/names/names_data.csv @@ -333,7 +333,7 @@ snowball,snowball,snowball,snowball,,snowball,,,Snowball,Snowball,,1,,,aws_snowb sns,sns,sns,sns,,sns,,,SNS,SNS,,1,,,aws_sns_,,sns_,SNS (Simple Notification),Amazon,,,,, sqs,sqs,sqs,sqs,,sqs,,,SQS,SQS,,1,,,aws_sqs_,,sqs_,SQS (Simple Queue),Amazon,,,,, ssm,ssm,ssm,ssm,,ssm,,,SSM,SSM,,1,2,,aws_ssm_,,ssm_,SSM (Systems Manager),AWS,,,,, -ssm-contacts,ssmcontacts,ssmcontacts,ssmcontacts,,ssmcontacts,,,SSMContacts,SSMContacts,,,2,,aws_ssmcontacts_,,ssmcontacts_,SSM Incident Manager Contacts,AWS,,,,, +ssm-contacts,ssmcontacts,ssmcontacts,ssmcontacts,,ssmcontacts,,,SSMContacts,SSMContacts,,,2,,aws_ssmcontacts_,,ssmcontacts_,SSM Contacts,AWS,,,,, ssm-incidents,ssmincidents,ssmincidents,ssmincidents,,ssmincidents,,,SSMIncidents,SSMIncidents,,,2,,aws_ssmincidents_,,ssmincidents_,SSM Incident Manager Incidents,AWS,,,,, sso,sso,sso,sso,,sso,,,SSO,SSO,,1,,,aws_sso_,,sso_,SSO (Single Sign-On),AWS,,,,, sso-admin,ssoadmin,ssoadmin,ssoadmin,,ssoadmin,,,SSOAdmin,SSOAdmin,,1,,,aws_ssoadmin_,,ssoadmin_,SSO Admin,AWS,,,,, diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt index e39035a3615..8383bdd01d8 100644 --- a/website/allowed-subcategories.txt +++ b/website/allowed-subcategories.txt @@ -270,7 +270,7 @@ SMS (Server Migration) SNS (Simple Notification) SQS (Simple Queue) SSM (Systems Manager) -SSM Incident Manager Contacts +SSM Contacts SSM Incident Manager Incidents SSO (Single Sign-On) SSO Admin diff --git a/website/docs/d/ssmcontacts_contact.html.markdown b/website/docs/d/ssmcontacts_contact.html.markdown new file mode 100644 index 00000000000..a6848f027d8 --- /dev/null +++ b/website/docs/d/ssmcontacts_contact.html.markdown @@ -0,0 +1,37 @@ +--- +subcategory: "SSM Contacts" +layout: "aws" +page_title: "AWS: aws_ssmcontacts_contact" +description: |- + Terraform data source for managing an AWS SSM Contact. +--- + +# Data Source: aws_ssmcontacts_contact + +Terraform data source for managing an AWS SSM Contact. + +## Example Usage + +### Basic Usage + +```terraform +data "aws_ssmcontacts_contact" "example" { + arn = "arn:aws:ssm-contacts:us-west-2:123456789012:contact/contactalias" +} +``` + +## Argument Reference + +The following arguments are required: + +* `arn` - (Required) The Amazon Resource Name (ARN) of the contact or escalation plan. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `alias` - A unique and identifiable alias of the contact or escalation plan. +* `type` - The type of contact engaged. A single contact is type PERSONAL and an escalation plan is type + ESCALATION. +* `display_name` - Full friendly name of the contact or escalation plan. +* `tags` - Map of tags to assign to the resource. diff --git a/website/docs/d/ssmcontacts_contact_channel.html.markdown b/website/docs/d/ssmcontacts_contact_channel.html.markdown new file mode 100644 index 00000000000..b3d71e9fba6 --- /dev/null +++ b/website/docs/d/ssmcontacts_contact_channel.html.markdown @@ -0,0 +1,41 @@ +--- +subcategory: "SSM Contacts" +layout: "aws" +page_title: "AWS: aws_ssmcontacts_contact_channel" +description: |- + Terraform data source for managing an AWS SSM Contacts Contact Channel. +--- + +# Data Source: aws_ssmcontacts_contact_channel + +Terraform data source for managing an AWS SSM Contacts Contact Channel. + +## Example Usage + +### Basic Usage + +```terraform +data "aws_ssmcontacts_contact_channel" "example" { + arn = "arn:aws:ssm-contacts:us-west-2:123456789012:contact-channel/example" +} +``` + +## Argument Reference + +The following arguments are required: + +- `arn` - Amazon Resource Name (ARN) of the contact channel. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +- `activation_status` - Whether the contact channel is activated. + +- `contact_id` - Amazon Resource Name (ARN) of the AWS SSM Contact that the contact channel belongs to. + +- `delivery_address` - Details used to engage the contact channel. + +- `name` - Name of the contact channel. + +- `type` - Type of the contact channel. diff --git a/website/docs/d/ssmcontacts_plan.html.markdown b/website/docs/d/ssmcontacts_plan.html.markdown new file mode 100644 index 00000000000..96e1f8916f0 --- /dev/null +++ b/website/docs/d/ssmcontacts_plan.html.markdown @@ -0,0 +1,33 @@ +--- +subcategory: "SSM Contacts" +layout: "aws" +page_title: "AWS: aws_ssmcontacts_plan" +description: |- + Terraform data source for managing an AWS SSM Contact Plan. +--- + +# Data Source: aws_ssmcontacts_plan + +Terraform data source for managing a Plan of an AWS SSM Contact. + +## Example Usage + +### Basic Usage + +```terraform +data "aws_ssmcontacts_plan" "test" { + contact_id = "arn:aws:ssm-contacts:us-west-2:123456789012:contact/contactalias" +} +``` + +## Argument Reference + +The following arguments are required: + +* `contact_id` - (Required) The Amazon Resource Name (ARN) of the contact or escalation plan. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `stage` - List of stages. A contact has an engagement plan with stages that contact specified contact channels. An escalation plan uses stages that contact specified contacts. diff --git a/website/docs/r/ssmcontacts_contact.html.markdown b/website/docs/r/ssmcontacts_contact.html.markdown new file mode 100644 index 00000000000..0443f138cd1 --- /dev/null +++ b/website/docs/r/ssmcontacts_contact.html.markdown @@ -0,0 +1,72 @@ +--- +subcategory: "SSM Contacts" +layout: "aws" +page_title: "AWS: aws_ssmcontacts_contact" +description: |- + Terraform resource for managing an AWS SSM Contact. +--- + +# Resource: aws_ssmcontacts_contact + +Terraform resource for managing an AWS SSM Contact. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_ssmcontacts_contact" "example" { + alias = "alias" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.example] +} +``` + +### Usage With All Fields + +```terraform +resource "aws_ssmcontacts_contact" "example" { + alias = "alias" + display_name = "displayName" + type = "ESCALATION" + tags = { + key = "value" + } + + depends_on = [aws_ssmincidents_replication_set.example] +} +``` + +## Argument Reference + +~> **NOTE:** A contact implicitly depends on a replication set. If you configured your replication set in Terraform, we recommend you add it to the `depends_on` argument for the Terraform Contact Resource. + +The following arguments are required: + +- `alias` - (Required) A unique and identifiable alias for the contact or escalation plan. + +- `type` - (Required) The type of contact engaged. A single contact is type PERSONAL and an escalation + plan is type ESCALATION. + +The following arguments are optional: + +- `display_name` - (Optional) Full friendly name of the contact or escalation plan. + +- `tags` - (Optional) Map of tags to assign to the resource. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +- `arn` - The Amazon Resource Name (ARN) of the contact or escalation plan. + +- `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Import + +Import SSM Contact using the `ARN`. For example: + +``` +$ terraform import aws_ssmcontacts_contact.example {ARNValue} +``` diff --git a/website/docs/r/ssmcontacts_contact_channel.html.markdown b/website/docs/r/ssmcontacts_contact_channel.html.markdown new file mode 100644 index 00000000000..516bb6183db --- /dev/null +++ b/website/docs/r/ssmcontacts_contact_channel.html.markdown @@ -0,0 +1,78 @@ +--- +subcategory: "SSM Contacts" +layout: "aws" +page_title: "AWS: aws_ssmcontacts_contact_channel" +description: |- + Terraform resource for managing an AWS SSM Contacts Contact Channel. +--- + +# Resource: aws_ssmcontacts_contact_channel + +Terraform resource for managing an AWS SSM Contacts Contact Channel. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_ssmcontacts_contact_channel" "example" { + contact_id = "arn:aws:ssm-contacts:us-west-2:123456789012:contact/contactalias" + delivery_address { + simple_address = "email@example.com" + } + name = "Example contact channel" + type = "EMAIL" +} +``` + +### Usage with SSM Contact + +```terraform +resource "aws_ssmcontacts_contact" "example_contact" { + alias = "example_contact" + type = "PERSONAL" +} + +resource "aws_ssmcontacts_contact_channel" "example" { + contact_id = aws_ssmcontacts_contact.example_contact.arn + delivery_address { + simple_address = "email@example.com" + } + name = "Example contact channel" + type = "EMAIL" +} +``` + +## Argument Reference + +~> **NOTE:** The contact channel needs to be activated in the AWS Systems Manager console, otherwise it can't be used to engage the contact. See the [Contacts section of the Incident Manager User Guide](https://docs.aws.amazon.com/incident-manager/latest/userguide/contacts.html) for more information. + +The following arguments are required: + +- `contact_id` - (Required) Amazon Resource Name (ARN) of the AWS SSM Contact that the contact channel belongs to. + +- `delivery_address` - (Required) Block that contains contact engagement details. See details below. + +- `name` - (Required) Name of the contact channel. + +- `type` - (Required) Type of the contact channel. One of `SMS`, `VOICE` or `EMAIL`. + +### delivery_address + +- `simple_address` - (Required) Details to engage this contact channel. The expected format depends on the contact channel type and is described in the [`ContactChannelAddress` section of the SSM Contacts API Reference](https://docs.aws.amazon.com/incident-manager/latest/APIReference/API_SSMContacts_ContactChannelAddress.html). + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +- `activation_status` - Whether the contact channel is activated. The contact channel must be activated to use it to engage the contact. One of `ACTIVATED` or `NOT_ACTIVATED`. + +- `arn` - Amazon Resource Name (ARN) of the contact channel. + +## Import + +Import SSM Contact Channel using the `ARN`, e.g., + +``` +$ terraform import aws_ssmcontacts_contact_channel.example arn:aws:ssm-contacts:us-west-2:123456789012:contact-channel/example +``` diff --git a/website/docs/r/ssmcontacts_plan.html.markdown b/website/docs/r/ssmcontacts_plan.html.markdown new file mode 100644 index 00000000000..d9939a69e1d --- /dev/null +++ b/website/docs/r/ssmcontacts_plan.html.markdown @@ -0,0 +1,92 @@ +--- +subcategory: "SSM Contacts" +layout: "aws" +page_title: "AWS: aws_ssmcontacts_plan" +description: |- + Terraform resource for managing an AWS SSM Contact Plan. +--- + +# Resource: aws_ssmcontacts_plan + +Terraform resource for managing an AWS SSM Contact Plan. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_ssmcontacts_plan" "example" { + contact_id = "arn:aws:ssm-contacts:us-west-2:123456789012:contact/contactalias" + stage { + duration_in_minutes = 1 + } +} +``` + +### Usage with SSM Contact + +```terraform +resource "aws_ssmcontacts_contact" "contact" { + alias = "alias" + type = "PERSONAL" +} + +resource "aws_ssmcontacts_plan" "plan" { + contact_id = aws_ssmcontacts_contact.contact.arn + stage { + duration_in_minutes = 1 + } +} +``` + +### Usage With All Fields +```terraform +resource "aws_ssmcontacts_contact" "escalation_plan" { + alias = "escalation-plan-alias" + type = "ESCALATION" +} + +resource "aws_ssmcontacts_contact" "contact_one" { + alias = "alias" + type = "PERSONAL" +} + +resource "aws_ssmcontacts_contact" "contact_two" { + alias = "alias" + type = "PERSONAL" +} + +resource "aws_ssmcontacts_plan" "test" { + contact_id = aws_ssmcontacts_contact.escalation_plan.arn + stage { + duration_in_minutes = 0 + target { + contact_target_info { + is_essential = false + contact_id = aws_ssmcontacts_contact.contact_one.arn + } + } + target { + contact_target_info { + is_essential = true + contact_id = aws_ssmcontacts_contact.contact_two.arn + } + } + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `contact_id` - (Required) The Amazon Resource Name (ARN) of the contact or escalation plan. +* `stage` - (Required) List of stages. A contact has an engagement plan with stages that contact specified contact channels. An escalation plan uses stages that contact specified contacts. + +## Import + +Import SSM Contact Plan using the Contact ARN. For example: + +``` +$ terraform import aws_ssmcontacts_plan.example {ARNValue} +``` From d7c6eb2f979a693a7171cbefe28db30fd2337323 Mon Sep 17 00:00:00 2001 From: Michael Chen Date: Tue, 18 Apr 2023 09:40:39 +1000 Subject: [PATCH 02/20] Add changelog --- .changelog/30667.txt | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .changelog/30667.txt diff --git a/.changelog/30667.txt b/.changelog/30667.txt new file mode 100644 index 00000000000..e207cc727b6 --- /dev/null +++ b/.changelog/30667.txt @@ -0,0 +1,23 @@ +```release-note:new-resource +aws_ssmcontacts_contact +``` + +```release-note:new-data-source +aws_ssmcontacts_contact +``` + +```release-note:new-resource +aws_ssmcontacts_contact_channel +``` + +```release-note:new-data-source +aws_ssmcontacts_contact_channel +``` + +```release-note:new-resource +aws_ssmcontacts_plan +``` + +```release-note:new-data-source +aws_ssmcontacts_plan +``` From 00b450e5f93b5e6cee1e4ab9951c27792f44966e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 11:32:34 -0400 Subject: [PATCH 03/20] r/aws_ssmcontacts_contact: Transparent tagging. --- internal/service/ssmcontacts/contact.go | 64 ++++--------------- .../ssmcontacts/service_package_gen.go | 4 ++ 2 files changed, 17 insertions(+), 51 deletions(-) diff --git a/internal/service/ssmcontacts/contact.go b/internal/service/ssmcontacts/contact.go index 53360193cfc..6698ab7d17e 100644 --- a/internal/service/ssmcontacts/contact.go +++ b/internal/service/ssmcontacts/contact.go @@ -18,7 +18,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_ssmcontacts_contact") +// @SDKResource("aws_ssmcontacts_contact", name="Context") +// @Tags(identifierAttribute="id") func ResourceContact() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceContactCreate, @@ -49,8 +50,8 @@ func ResourceContact() *schema.Resource { Required: true, ForceNew: true, }, - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, @@ -66,16 +67,10 @@ func resourceContactCreate(ctx context.Context, d *schema.ResourceData, meta int input := &ssmcontacts.CreateContactInput{ Alias: aws.String(d.Get("alias").(string)), - Type: types.ContactType(d.Get("type").(string)), - Plan: &types.Plan{Stages: []types.Stage{}}, DisplayName: aws.String(d.Get("display_name").(string)), - } - - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) - - if len(tags) > 0 { - input.Tags = Tags(tags.IgnoreAWS()) + Plan: &types.Plan{Stages: []types.Stage{}}, + Tags: GetTagsIn(ctx), + Type: types.ContactType(d.Get("type").(string)), } output, err := client.CreateContact(ctx, input) @@ -111,57 +106,24 @@ func resourceContactRead(ctx context.Context, d *schema.ResourceData, meta inter return create.DiagError(names.SSMContacts, create.ErrActionSetting, ResNameContact, d.Id(), err) } - tags, err := ListTags(ctx, conn, d.Id()) - if err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNameContact, 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.SSMContacts, create.ErrActionSetting, ResNameContact, d.Id(), err) - } - - if err := d.Set("tags_all", tags.Map()); err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionSetting, ResNameContact, d.Id(), err) - } - return nil } func resourceContactUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).SSMContactsClient() - update := false - - in := &ssmcontacts.UpdateContactInput{ - ContactId: aws.String(d.Id()), - } - if d.HasChanges("display_name") { - in.DisplayName = aws.String(d.Get("display_name").(string)) - update = true - } + in := &ssmcontacts.UpdateContactInput{ + ContactId: aws.String(d.Id()), + DisplayName: aws.String(d.Get("display_name").(string)), + } - if d.HasChange("tags_all") { - o, n := d.GetChange("tags_all") - if err := UpdateTags(ctx, conn, d.Id(), o, n); err != nil { + _, err := conn.UpdateContact(ctx, in) + if err != nil { return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNameContact, d.Id(), err) } } - if !update { - return nil - } - - log.Printf("[DEBUG] Updating SSMContacts Contact (%s): %#v", d.Id(), in) - _, err := conn.UpdateContact(ctx, in) - if err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNameContact, d.Id(), err) - } - return resourceContactRead(ctx, d, meta) } diff --git a/internal/service/ssmcontacts/service_package_gen.go b/internal/service/ssmcontacts/service_package_gen.go index 31afbdac360..a86be578e78 100644 --- a/internal/service/ssmcontacts/service_package_gen.go +++ b/internal/service/ssmcontacts/service_package_gen.go @@ -41,6 +41,10 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: ResourceContact, TypeName: "aws_ssmcontacts_contact", + Name: "Context", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "id", + }, }, { Factory: ResourceContactChannel, From 84e25144776da5df5349c1364bf61da1654c4da9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 11:35:13 -0400 Subject: [PATCH 04/20] r/aws_ssmcontacts_contact_channel: Cosmetics. --- internal/service/ssmcontacts/contact_channel.go | 2 +- internal/service/ssmcontacts/service_package_gen.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/service/ssmcontacts/contact_channel.go b/internal/service/ssmcontacts/contact_channel.go index 72cbadce874..783289954a0 100644 --- a/internal/service/ssmcontacts/contact_channel.go +++ b/internal/service/ssmcontacts/contact_channel.go @@ -16,7 +16,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_ssmcontacts_contact_channel") +// @SDKResource("aws_ssmcontacts_contact_channel", name="Contact Channel") func ResourceContactChannel() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceContactChannelCreate, diff --git a/internal/service/ssmcontacts/service_package_gen.go b/internal/service/ssmcontacts/service_package_gen.go index a86be578e78..28917cb3065 100644 --- a/internal/service/ssmcontacts/service_package_gen.go +++ b/internal/service/ssmcontacts/service_package_gen.go @@ -49,6 +49,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: ResourceContactChannel, TypeName: "aws_ssmcontacts_contact_channel", + Name: "Contact Channel", }, { Factory: ResourcePlan, From b6b92201c70d63b23315a3beca1da42ffa3cecab Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 11:36:36 -0400 Subject: [PATCH 05/20] r/aws_ssmcontacts_plan: Cosmetics. --- internal/service/ssmcontacts/plan.go | 2 +- internal/service/ssmcontacts/service_package_gen.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/service/ssmcontacts/plan.go b/internal/service/ssmcontacts/plan.go index 5f8d19a7dfd..7e821ef8f54 100644 --- a/internal/service/ssmcontacts/plan.go +++ b/internal/service/ssmcontacts/plan.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_ssmcontacts_plan") +// @SDKResource("aws_ssmcontacts_plan", name="Plan") func ResourcePlan() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourcePlanCreate, diff --git a/internal/service/ssmcontacts/service_package_gen.go b/internal/service/ssmcontacts/service_package_gen.go index 28917cb3065..d24f8b62828 100644 --- a/internal/service/ssmcontacts/service_package_gen.go +++ b/internal/service/ssmcontacts/service_package_gen.go @@ -54,6 +54,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: ResourcePlan, TypeName: "aws_ssmcontacts_plan", + Name: "Plan", }, } } From 99ea42ee69916703c6388666ab45635339055598 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 11:44:38 -0400 Subject: [PATCH 06/20] Run 'make gen'. --- internal/service/ssmcontacts/tags_gen.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/service/ssmcontacts/tags_gen.go b/internal/service/ssmcontacts/tags_gen.go index 2dc2ccadd02..78435c6a953 100644 --- a/internal/service/ssmcontacts/tags_gen.go +++ b/internal/service/ssmcontacts/tags_gen.go @@ -102,10 +102,12 @@ func UpdateTags(ctx context.Context, conn *ssmcontacts.Client, identifier string oldTags := tftags.New(ctx, oldTagsMap) newTags := tftags.New(ctx, newTagsMap) - if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 { + removedTags := oldTags.Removed(newTags) + removedTags = removedTags.IgnoreSystem(names.SSMContacts) + if len(removedTags) > 0 { input := &ssmcontacts.UntagResourceInput{ ResourceARN: aws.String(identifier), - TagKeys: removedTags.IgnoreSystem(names.SSMContacts).Keys(), + TagKeys: removedTags.Keys(), } _, err := conn.UntagResource(ctx, input) @@ -115,10 +117,12 @@ func UpdateTags(ctx context.Context, conn *ssmcontacts.Client, identifier string } } - if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 { + updatedTags := oldTags.Updated(newTags) + updatedTags = updatedTags.IgnoreSystem(names.SSMContacts) + if len(updatedTags) > 0 { input := &ssmcontacts.TagResourceInput{ ResourceARN: aws.String(identifier), - Tags: Tags(updatedTags.IgnoreSystem(names.SSMContacts)), + Tags: Tags(updatedTags), } _, err := conn.TagResource(ctx, input) From a7ae2cf64bfff4bcc52d15b5d91feaa8992d7266 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 14:17:27 -0400 Subject: [PATCH 07/20] Fix tfproviderdocs 'missing attributes section: ## Attributes Reference'. --- website/docs/r/ssmcontacts_plan.html.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/r/ssmcontacts_plan.html.markdown b/website/docs/r/ssmcontacts_plan.html.markdown index d9939a69e1d..5fe03b2e811 100644 --- a/website/docs/r/ssmcontacts_plan.html.markdown +++ b/website/docs/r/ssmcontacts_plan.html.markdown @@ -83,6 +83,10 @@ The following arguments are required: * `contact_id` - (Required) The Amazon Resource Name (ARN) of the contact or escalation plan. * `stage` - (Required) List of stages. A contact has an engagement plan with stages that contact specified contact channels. An escalation plan uses stages that contact specified contacts. +## Attributes Reference + +No additional attributes are exported. + ## Import Import SSM Contact Plan using the Contact ARN. For example: From fb3cb5e9c59d622d30bf648580f7072f82469642 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 14:21:08 -0400 Subject: [PATCH 08/20] Fix website terrafmt errors. --- .../docs/r/ssmcontacts_contact.html.markdown | 9 +++--- .../ssmcontacts_contact_channel.html.markdown | 32 +++++++++++-------- website/docs/r/ssmcontacts_plan.html.markdown | 7 ++-- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/website/docs/r/ssmcontacts_contact.html.markdown b/website/docs/r/ssmcontacts_contact.html.markdown index 0443f138cd1..cab55f11002 100644 --- a/website/docs/r/ssmcontacts_contact.html.markdown +++ b/website/docs/r/ssmcontacts_contact.html.markdown @@ -16,8 +16,8 @@ Terraform resource for managing an AWS SSM Contact. ```terraform resource "aws_ssmcontacts_contact" "example" { - alias = "alias" - type = "PERSONAL" + alias = "alias" + type = "PERSONAL" depends_on = [aws_ssmincidents_replication_set.example] } @@ -30,11 +30,12 @@ resource "aws_ssmcontacts_contact" "example" { alias = "alias" display_name = "displayName" type = "ESCALATION" - tags = { + + tags = { key = "value" } - depends_on = [aws_ssmincidents_replication_set.example] + depends_on = [aws_ssmincidents_replication_set.example] } ``` diff --git a/website/docs/r/ssmcontacts_contact_channel.html.markdown b/website/docs/r/ssmcontacts_contact_channel.html.markdown index 516bb6183db..bba36cba46d 100644 --- a/website/docs/r/ssmcontacts_contact_channel.html.markdown +++ b/website/docs/r/ssmcontacts_contact_channel.html.markdown @@ -16,12 +16,14 @@ Terraform resource for managing an AWS SSM Contacts Contact Channel. ```terraform resource "aws_ssmcontacts_contact_channel" "example" { - contact_id = "arn:aws:ssm-contacts:us-west-2:123456789012:contact/contactalias" - delivery_address { - simple_address = "email@example.com" - } - name = "Example contact channel" - type = "EMAIL" + contact_id = "arn:aws:ssm-contacts:us-west-2:123456789012:contact/contactalias" + + delivery_address { + simple_address = "email@example.com" + } + + name = "Example contact channel" + type = "EMAIL" } ``` @@ -29,17 +31,19 @@ resource "aws_ssmcontacts_contact_channel" "example" { ```terraform resource "aws_ssmcontacts_contact" "example_contact" { - alias = "example_contact" - type = "PERSONAL" + alias = "example_contact" + type = "PERSONAL" } resource "aws_ssmcontacts_contact_channel" "example" { - contact_id = aws_ssmcontacts_contact.example_contact.arn - delivery_address { - simple_address = "email@example.com" - } - name = "Example contact channel" - type = "EMAIL" + contact_id = aws_ssmcontacts_contact.example_contact.arn + + delivery_address { + simple_address = "email@example.com" + } + + name = "Example contact channel" + type = "EMAIL" } ``` diff --git a/website/docs/r/ssmcontacts_plan.html.markdown b/website/docs/r/ssmcontacts_plan.html.markdown index 5fe03b2e811..60f460893ff 100644 --- a/website/docs/r/ssmcontacts_plan.html.markdown +++ b/website/docs/r/ssmcontacts_plan.html.markdown @@ -58,18 +58,21 @@ resource "aws_ssmcontacts_contact" "contact_two" { resource "aws_ssmcontacts_plan" "test" { contact_id = aws_ssmcontacts_contact.escalation_plan.arn + stage { duration_in_minutes = 0 + target { contact_target_info { is_essential = false - contact_id = aws_ssmcontacts_contact.contact_one.arn + contact_id = aws_ssmcontacts_contact.contact_one.arn } } + target { contact_target_info { is_essential = true - contact_id = aws_ssmcontacts_contact.contact_two.arn + contact_id = aws_ssmcontacts_contact.contact_two.arn } } } From b984249f3aa837c99a16f56ff3163a7b288c95ce Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 14:23:56 -0400 Subject: [PATCH 09/20] Fix markdownlint 'MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1]'. --- website/docs/d/ssmcontacts_contact.html.markdown | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/docs/d/ssmcontacts_contact.html.markdown b/website/docs/d/ssmcontacts_contact.html.markdown index a6848f027d8..61f393d55c2 100644 --- a/website/docs/d/ssmcontacts_contact.html.markdown +++ b/website/docs/d/ssmcontacts_contact.html.markdown @@ -31,7 +31,6 @@ The following arguments are required: In addition to all arguments above, the following attributes are exported: * `alias` - A unique and identifiable alias of the contact or escalation plan. -* `type` - The type of contact engaged. A single contact is type PERSONAL and an escalation plan is type - ESCALATION. +* `type` - The type of contact engaged. A single contact is type `PERSONAL` and an escalation plan is type `ESCALATION`. * `display_name` - Full friendly name of the contact or escalation plan. * `tags` - Map of tags to assign to the resource. From 52ffa00ba0ff84e296562ae394eb9abbb9ceca3f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 14:24:36 -0400 Subject: [PATCH 10/20] Fix markdownlint 'MD031/blanks-around-fences Fenced code blocks should be surrounded by blank lines'. --- website/docs/r/ssmcontacts_plan.html.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/r/ssmcontacts_plan.html.markdown b/website/docs/r/ssmcontacts_plan.html.markdown index 60f460893ff..dbd28e2cd21 100644 --- a/website/docs/r/ssmcontacts_plan.html.markdown +++ b/website/docs/r/ssmcontacts_plan.html.markdown @@ -40,6 +40,7 @@ resource "aws_ssmcontacts_plan" "plan" { ``` ### Usage With All Fields + ```terraform resource "aws_ssmcontacts_contact" "escalation_plan" { alias = "escalation-plan-alias" From 9b36255b454b65d81e9f47c327f36c6af3efc41c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 14:45:23 -0400 Subject: [PATCH 11/20] Fix acceptance test terrafmt errors. --- .../contact_channel_data_source_test.go | 28 +-- .../ssmcontacts/contact_channel_test.go | 54 ++--- .../ssmcontacts/contact_data_source_test.go | 22 +- internal/service/ssmcontacts/contact_test.go | 68 +++--- .../ssmcontacts/plan_data_source_test.go | 148 ++++++------ internal/service/ssmcontacts/plan_test.go | 210 ++++++++++-------- 6 files changed, 282 insertions(+), 248 deletions(-) diff --git a/internal/service/ssmcontacts/contact_channel_data_source_test.go b/internal/service/ssmcontacts/contact_channel_data_source_test.go index 0c311069ee9..9b8a058bb2f 100644 --- a/internal/service/ssmcontacts/contact_channel_data_source_test.go +++ b/internal/service/ssmcontacts/contact_channel_data_source_test.go @@ -51,29 +51,31 @@ func testContactChannelDataSource_basic(t *testing.T) { func testAccContactChannelDataSourceConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_ssmincidents_replication_set" "test" { - region { - name = %[1]q - } + region { + name = %[1]q + } } resource "aws_ssmcontacts_contact" "test" { - alias = "test-contact-for-%[2]s" - type = "PERSONAL" + alias = "test-contact-for-%[2]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact_channel" "test" { - contact_id = aws_ssmcontacts_contact.test.arn - delivery_address { - simple_address = "default@example.com" - } - name = "%[2]s" - type = "EMAIL" + contact_id = aws_ssmcontacts_contact.test.arn + + delivery_address { + simple_address = "default@example.com" + } + + name = %[2]q + type = "EMAIL" } data "aws_ssmcontacts_contact_channel" "test" { - arn = aws_ssmcontacts_contact_channel.test.arn + arn = aws_ssmcontacts_contact_channel.test.arn } `, acctest.Region(), rName) } diff --git a/internal/service/ssmcontacts/contact_channel_test.go b/internal/service/ssmcontacts/contact_channel_test.go index c13bbfd61db..04018ea7e1f 100644 --- a/internal/service/ssmcontacts/contact_channel_test.go +++ b/internal/service/ssmcontacts/contact_channel_test.go @@ -393,7 +393,7 @@ func testAccContactChannelConfig_basic(rName string) string { } func testAccContactChannelConfig_none() string { - return testAccContactChannelConfigBase() + return testAccContactChannelConfig_base() } func testAccContactChannelConfig_defaultEmail(rName string) string { @@ -410,56 +410,60 @@ func testAccContactChannelConfig_defaultVoice(rName string) string { func testAccContactChannelConfig(rName string, contactAliasDisambiguator string, channelType string, address string) string { return acctest.ConfigCompose( - testAccContactChannelConfigBase(), + testAccContactChannelConfig_base(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "test" { - alias = "test-contact-for-%[2]s" - type = "PERSONAL" + alias = "test-contact-for-%[1]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact_channel" "test" { - contact_id = aws_ssmcontacts_contact.test.arn - delivery_address { - simple_address = "%[4]s" - } - name = "%[1]s" - type = "%[3]s" + contact_id = aws_ssmcontacts_contact.test.arn + + delivery_address { + simple_address = %[4]q + } + + name = %[1]q + type = %[3]q } `, rName, contactAliasDisambiguator, channelType, address)) } func testAccContactChannelConfig_withTwoContacts(rName, contactArn string) string { return acctest.ConfigCompose( - testAccContactChannelConfigBase(), + testAccContactChannelConfig_base(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "test_contact_one" { - alias = "test-contact-one-for-%[1]s" - type = "PERSONAL" + alias = "test-contact-one-for-%[1]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact" "test_contact_two" { - alias = "test-contact-two-for-%[1]s" - type = "PERSONAL" + alias = "test-contact-two-for-%[1]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact_channel" "test" { - contact_id = %[2]s - delivery_address { - simple_address = "test@example.com" - } - name = "%[1]s" - type = "EMAIL" + contact_id = %[2]s + + delivery_address { + simple_address = "test@example.com" + } + + name = %[1]q + type = "EMAIL" } `, rName, contactArn)) } -func testAccContactChannelConfigBase() string { +func testAccContactChannelConfig_base() string { return fmt.Sprintf(` resource "aws_ssmincidents_replication_set" "test" { region { diff --git a/internal/service/ssmcontacts/contact_data_source_test.go b/internal/service/ssmcontacts/contact_data_source_test.go index 3af9ef63572..903fb8f9eff 100644 --- a/internal/service/ssmcontacts/contact_data_source_test.go +++ b/internal/service/ssmcontacts/contact_data_source_test.go @@ -49,7 +49,7 @@ func testContactDataSource_basic(t *testing.T) { }) } -func testAccContactDataSourceConfigBase() string { +func testAccContactDataSourceConfig_base() string { return fmt.Sprintf(` resource "aws_ssmincidents_replication_set" "test" { region { @@ -61,23 +61,23 @@ resource "aws_ssmincidents_replication_set" "test" { func testAccContactDataSourceConfig_basic(alias string) string { return acctest.ConfigCompose( - testAccContactDataSourceConfigBase(), + testAccContactDataSourceConfig_base(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "contact_one" { - alias = %[1]q - display_name = %[1]q - type = "PERSONAL" + alias = %[1]q + display_name = %[1]q + type = "PERSONAL" - tags = { - key1 = "tag1" - key2 = "tag2" - } + tags = { + key1 = "tag1" + key2 = "tag2" + } - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } data "aws_ssmcontacts_contact" "contact_one" { - arn = aws_ssmcontacts_contact.contact_one.arn + arn = aws_ssmcontacts_contact.contact_one.arn } `, alias)) } diff --git a/internal/service/ssmcontacts/contact_test.go b/internal/service/ssmcontacts/contact_test.go index 25cbf8d6059..119cd67571d 100644 --- a/internal/service/ssmcontacts/contact_test.go +++ b/internal/service/ssmcontacts/contact_test.go @@ -428,7 +428,7 @@ func testAccContactPreCheck(t *testing.T) { } } -func testAccContactConfigBase() string { +func testAccContactConfig_base() string { return fmt.Sprintf(` resource "aws_ssmincidents_replication_set" "test" { region { @@ -440,92 +440,92 @@ resource "aws_ssmincidents_replication_set" "test" { func testAccContactConfig_basic(alias string) string { return acctest.ConfigCompose( - testAccContactConfigBase(), + testAccContactConfig_base(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "contact_one" { - alias = %[1]q - type = "PERSONAL" + alias = %[1]q + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } `, alias)) } func testAccContactConfig_none() string { - return testAccContactConfigBase() + return testAccContactConfig_base() } func testAccContactConfig_alias(alias string) string { return acctest.ConfigCompose( - testAccContactConfigBase(), + testAccContactConfig_base(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "contact_one" { - alias = %[1]q - type = "PERSONAL" + alias = %[1]q + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } `, alias)) } func testAccContactConfig_type(name, typeValue string) string { return acctest.ConfigCompose( - testAccContactConfigBase(), + testAccContactConfig_base(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "contact_one" { - alias = %[1]q - type = %[2]q + alias = %[1]q + type = %[2]q - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } `, name, typeValue)) } func testAccContactConfig_displayName(alias, displayName string) string { return acctest.ConfigCompose( - testAccContactConfigBase(), + testAccContactConfig_base(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "contact_one" { - alias = %[1]q - display_name = %[2]q - type = "PERSONAL" + alias = %[1]q + display_name = %[2]q + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } `, alias, displayName)) } func testAccContactConfig_oneTag(alias, tagKey, tagVal string) string { return acctest.ConfigCompose( - testAccContactConfigBase(), + testAccContactConfig_base(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "contact_one" { - alias = %[1]q - type = "PERSONAL" + alias = %[1]q + type = "PERSONAL" - tags = { - %[2]q = %[3]q - } + tags = { + %[2]q = %[3]q + } - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } `, alias, tagKey, tagVal)) } func testAccContactConfig_twoTags(alias, tagKey1, tagVal1, tagKey2, tagVal2 string) string { return acctest.ConfigCompose( - testAccContactConfigBase(), + testAccContactConfig_base(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "contact_one" { - alias = %[1]q - type = "PERSONAL" + alias = %[1]q + type = "PERSONAL" - tags = { - %[2]q = %[3]q - %[4]q = %[5]q - } + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } `, alias, tagKey1, tagVal1, tagKey2, tagVal2)) } diff --git a/internal/service/ssmcontacts/plan_data_source_test.go b/internal/service/ssmcontacts/plan_data_source_test.go index f787c3e5b51..11b9d814f2e 100644 --- a/internal/service/ssmcontacts/plan_data_source_test.go +++ b/internal/service/ssmcontacts/plan_data_source_test.go @@ -130,43 +130,49 @@ func testAccPlanDataSourceConfig_basic(rName string) string { testAccPlanDataSourceConfig_base(rName), fmt.Sprintf(` resource "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn - stage { - duration_in_minutes = 1 - target { - contact_target_info { - is_essential = false - contact_id = aws_ssmcontacts_contact.test_contact_one.arn - } - } - target { - contact_target_info { - is_essential = true - contact_id = aws_ssmcontacts_contact.test_contact_two.arn - } - } - } - stage { - duration_in_minutes = 0 - target { - contact_target_info { - is_essential = false - contact_id = aws_ssmcontacts_contact.test_contact_three.arn - } - } - target { - contact_target_info { - is_essential = true - contact_id = aws_ssmcontacts_contact.test_contact_four.arn - } - } - } + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + + stage { + duration_in_minutes = 1 + + target { + contact_target_info { + is_essential = false + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + } + } + + target { + contact_target_info { + is_essential = true + contact_id = aws_ssmcontacts_contact.test_contact_two.arn + } + } + } + + stage { + duration_in_minutes = 0 + + target { + contact_target_info { + is_essential = false + contact_id = aws_ssmcontacts_contact.test_contact_three.arn + } + } + + target { + contact_target_info { + is_essential = true + contact_id = aws_ssmcontacts_contact.test_contact_four.arn + } + } + } } data "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn - depends_on = [aws_ssmcontacts_plan.test] + depends_on = [aws_ssmcontacts_plan.test] } `)) } @@ -176,31 +182,35 @@ func testAccPlanDataSourceConfig_channelTargetInfo(rName string) string { testAccPlanDataSourceConfig_base(rName), fmt.Sprintf(` resource "aws_ssmcontacts_contact_channel" "test" { - contact_id = aws_ssmcontacts_contact.test_contact_one.arn - delivery_address { - simple_address = "email@example.com" - } - name = "Test Contact Channel for %[1]s" - type = "EMAIL" + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + + delivery_address { + simple_address = "email@example.com" + } + + name = "Test Contact Channel for %[1]s" + type = "EMAIL" } resource "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_contact_one.arn - stage { - duration_in_minutes = 1 - target { - channel_target_info { - contact_channel_id = aws_ssmcontacts_contact_channel.test.arn - retry_interval_in_minutes = 1 - } - } - } + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + + stage { + duration_in_minutes = 1 + + target { + channel_target_info { + contact_channel_id = aws_ssmcontacts_contact_channel.test.arn + retry_interval_in_minutes = 1 + } + } + } } data "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_contact_one.arn + contact_id = aws_ssmcontacts_contact.test_contact_one.arn - depends_on = [aws_ssmcontacts_plan.test] + depends_on = [aws_ssmcontacts_plan.test] } `, rName)) } @@ -208,44 +218,44 @@ data "aws_ssmcontacts_plan" "test" { func testAccPlanDataSourceConfig_base(alias string) string { return fmt.Sprintf(` resource "aws_ssmincidents_replication_set" "test" { - region { - name = %[1]q - } + region { + name = %[1]q + } } resource "aws_ssmcontacts_contact" "test_contact_one" { - alias = "test-contact-one-for-%[2]s" - type = "PERSONAL" + alias = "test-contact-one-for-%[2]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact" "test_contact_two" { - alias = "test-contact-two-for-%[2]s" - type = "PERSONAL" + alias = "test-contact-two-for-%[2]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact" "test_contact_three" { - alias = "test-contact-three-for-%[2]s" - type = "PERSONAL" + alias = "test-contact-three-for-%[2]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact" "test_contact_four" { - alias = "test-contact-four-for-%[2]s" - type = "PERSONAL" + alias = "test-contact-four-for-%[2]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact" "test_escalation_plan_one" { - alias = "test-escalation-plan-for-%[2]s" - type = "ESCALATION" + alias = "test-escalation-plan-for-%[2]s" + type = "ESCALATION" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } `, acctest.Region(), alias) } diff --git a/internal/service/ssmcontacts/plan_test.go b/internal/service/ssmcontacts/plan_test.go index 8278c466dd7..82a98ef3f95 100644 --- a/internal/service/ssmcontacts/plan_test.go +++ b/internal/service/ssmcontacts/plan_test.go @@ -127,7 +127,7 @@ func testPlan_updateContactId(t *testing.T) { CheckDestroy: testAccCheckPlanDestroy, Steps: []resource.TestStep{ { - Config: testAccPlanConfig_contactId(rName, contactOneResourceName), + Config: testAccPlanConfig_contactID(rName, contactOneResourceName), Check: resource.ComposeTestCheckFunc( testAccCheckContactExists(contactOneResourceName), testAccCheckPlanExists(planResourceName), @@ -140,7 +140,7 @@ func testPlan_updateContactId(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccPlanConfig_contactId(rName, contactTwoResourceName), + Config: testAccPlanConfig_contactID(rName, contactTwoResourceName), Check: resource.ComposeTestCheckFunc( testAccCheckContactExists(contactTwoResourceName), testAccCheckPlanExists(planResourceName), @@ -618,15 +618,16 @@ func testAccCheckPlanDestroy(s *terraform.State) error { return nil } -func testAccPlanConfig_contactId(rName, contactName string) string { +func testAccPlanConfig_contactID(rName, contactName string) string { return acctest.ConfigCompose( testAccPlanConfig_base(rName), fmt.Sprintf(` resource "aws_ssmcontacts_plan" "test" { - contact_id = %[1]s - stage { - duration_in_minutes = 1 - } + contact_id = %[1]s + + stage { + duration_in_minutes = 1 + } } `, contactName+".arn")) } @@ -638,30 +639,33 @@ func testAccPlanConfig_none(rName string) string { func testAccPlanConfig_oneStage(rName string) string { return acctest.ConfigCompose( testAccPlanConfig_base(rName), - fmt.Sprintf(` + ` resource "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_contact_one.arn - stage { - duration_in_minutes = 1 - } + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + + stage { + duration_in_minutes = 1 + } } -`)) +`) } func testAccPlanConfig_twoStages(rName string) string { return acctest.ConfigCompose( testAccPlanConfig_base(rName), - fmt.Sprintf(` + ` resource "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_contact_one.arn - stage { - duration_in_minutes = 1 - } - stage { - duration_in_minutes = 2 - } + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + + stage { + duration_in_minutes = 1 + } + + stage { + duration_in_minutes = 2 + } } -`)) +`) } func testAccPlanConfig_durationInMinutes(rName string, durationInMinutes int) string { @@ -669,10 +673,11 @@ func testAccPlanConfig_durationInMinutes(rName string, durationInMinutes int) st testAccPlanConfig_base(rName), fmt.Sprintf(` resource "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_contact_one.arn - stage { - duration_in_minutes = %[1]d - } + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + + stage { + duration_in_minutes = %[1]d + } } `, durationInMinutes)) } @@ -682,16 +687,18 @@ func testAccPlanConfig_oneTarget(rName, contactOneArn string) string { testAccPlanConfig_base(rName), fmt.Sprintf(` resource "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn - stage { - duration_in_minutes = 0 - target { - contact_target_info { - is_essential = false - contact_id = %[1]s - } - } - } + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + + stage { + duration_in_minutes = 0 + + target { + contact_target_info { + is_essential = false + contact_id = %[1]s + } + } + } } `, contactOneArn)) } @@ -701,22 +708,25 @@ func testAccPlanConfig_twoTargets(rName, contactOneArn, contactTwoArn string) st testAccPlanConfig_base(rName), fmt.Sprintf(` resource "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn - stage { - duration_in_minutes = 0 - target { - contact_target_info { - is_essential = false - contact_id = %[1]s - } - } - target { - contact_target_info { - is_essential = true - contact_id = %[2]s - } - } - } + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + + stage { + duration_in_minutes = 0 + + target { + contact_target_info { + is_essential = false + contact_id = %[1]s + } + } + + target { + contact_target_info { + is_essential = true + contact_id = %[2]s + } + } + } } `, contactOneArn, contactTwoArn)) } @@ -726,16 +736,18 @@ func testAccPlanConfig_contactTargetInfo(rName string, isEssential bool, contact testAccPlanConfig_base(rName), fmt.Sprintf(` resource "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn - stage { - duration_in_minutes = 0 - target { - contact_target_info { - is_essential = %[1]t - contact_id = %[2]s - } - } - } + contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn + + stage { + duration_in_minutes = 0 + + target { + contact_target_info { + is_essential = %[1]t + contact_id = %[2]s + } + } + } } `, isEssential, contactId)) } @@ -745,34 +757,40 @@ func testAccPlanConfig_channelTargetInfo(rName, contactChannelResourceName strin testAccPlanConfig_base(rName), fmt.Sprintf(` resource "aws_ssmcontacts_contact_channel" "test_channel_one" { - contact_id = aws_ssmcontacts_contact.test_contact_one.arn - delivery_address { - simple_address = "email1@example.com" - } - name = "Test Contact Channel 1" - type = "EMAIL" + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + + delivery_address { + simple_address = "email1@example.com" + } + + name = "Test Contact Channel 1" + type = "EMAIL" } resource "aws_ssmcontacts_contact_channel" "test_channel_two" { - contact_id = aws_ssmcontacts_contact.test_contact_one.arn - delivery_address { - simple_address = "email2@example.com" - } - name = "Test Contact Channel 2" - type = "EMAIL" + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + + delivery_address { + simple_address = "email2@example.com" + } + + name = "Test Contact Channel 2" + type = "EMAIL" } resource "aws_ssmcontacts_plan" "test" { - contact_id = aws_ssmcontacts_contact.test_contact_one.arn - stage { - duration_in_minutes = 1 - target { - channel_target_info { - contact_channel_id = %[1]s.arn - retry_interval_in_minutes = %[2]d - } - } - } + contact_id = aws_ssmcontacts_contact.test_contact_one.arn + + stage { + duration_in_minutes = 1 + + target { + channel_target_info { + contact_channel_id = %[1]s.arn + retry_interval_in_minutes = %[2]d + } + } + } } `, contactChannelResourceName, retryIntervalInMinutes)) } @@ -780,30 +798,30 @@ resource "aws_ssmcontacts_plan" "test" { func testAccPlanConfig_base(alias string) string { return fmt.Sprintf(` resource "aws_ssmincidents_replication_set" "test" { - region { - name = %[1]q - } + region { + name = %[1]q + } } resource "aws_ssmcontacts_contact" "test_contact_one" { - alias = "test-contact-one-for-%[2]s" - type = "PERSONAL" + alias = "test-contact-one-for-%[2]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact" "test_contact_two" { - alias = "test-contact-two-for-%[2]s" - type = "PERSONAL" + alias = "test-contact-two-for-%[2]s" + type = "PERSONAL" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_contact" "test_escalation_plan_one" { - alias = "test-escalation-plan-for-%[2]s" - type = "ESCALATION" + alias = "test-escalation-plan-for-%[2]s" + type = "ESCALATION" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] } `, acctest.Region(), alias) } From bc41a381633546e30458241baef1247251f29853 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 14:49:26 -0400 Subject: [PATCH 12/20] Fix semgrep 'ci.bare-error-returns'. --- internal/service/ssmcontacts/helper.go | 33 +++++++++++--------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/internal/service/ssmcontacts/helper.go b/internal/service/ssmcontacts/helper.go index b40430e806f..67b117d2004 100644 --- a/internal/service/ssmcontacts/helper.go +++ b/internal/service/ssmcontacts/helper.go @@ -1,23 +1,18 @@ package ssmcontacts import ( + "fmt" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func setContactResourceData(d *schema.ResourceData, getContactOutput *ssmcontacts.GetContactOutput) error { - if err := d.Set("arn", getContactOutput.ContactArn); err != nil { - return err - } - if err := d.Set("alias", getContactOutput.Alias); err != nil { - return err - } - if err := d.Set("type", getContactOutput.Type); err != nil { - return err - } - if err := d.Set("display_name", getContactOutput.DisplayName); err != nil { - return err - } + d.Set("arn", getContactOutput.ContactArn) + d.Set("alias", getContactOutput.Alias) + d.Set("type", getContactOutput.Type) + d.Set("display_name", getContactOutput.DisplayName) + return nil } @@ -25,20 +20,20 @@ func setContactChannelResourceData(d *schema.ResourceData, out *ssmcontacts.GetC d.Set("activation_status", out.ActivationStatus) d.Set("arn", out.ContactChannelArn) d.Set("contact_id", out.ContactArn) - d.Set("name", out.Name) - d.Set("type", out.Type) if err := d.Set("delivery_address", flattenContactChannelAddress(out.DeliveryAddress)); err != nil { - return err + return fmt.Errorf("setting delivery_address: %w", err) } + d.Set("name", out.Name) + d.Set("type", out.Type) + return nil } func setPlanResourceData(d *schema.ResourceData, getContactOutput *ssmcontacts.GetContactOutput) error { - if err := d.Set("contact_id", getContactOutput.ContactArn); err != nil { - return err - } + d.Set("contact_id", getContactOutput.ContactArn) if err := d.Set("stage", flattenStages(getContactOutput.Plan.Stages)); err != nil { - return err + return fmt.Errorf("setting stage: %w", err) } + return nil } From 4de6f8a1eda174acba42464fb8e079d0319bafc0 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 14:59:38 -0400 Subject: [PATCH 13/20] Fix semgrep 'ci.email-address'. --- .../contact_channel_data_source_test.go | 4 +-- .../ssmcontacts/contact_channel_test.go | 25 +++++++++++-------- .../ssmcontacts/plan_data_source_test.go | 4 +-- internal/service/ssmcontacts/plan_test.go | 8 +++--- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/internal/service/ssmcontacts/contact_channel_data_source_test.go b/internal/service/ssmcontacts/contact_channel_data_source_test.go index 9b8a058bb2f..87e3c22d348 100644 --- a/internal/service/ssmcontacts/contact_channel_data_source_test.go +++ b/internal/service/ssmcontacts/contact_channel_data_source_test.go @@ -67,7 +67,7 @@ resource "aws_ssmcontacts_contact_channel" "test" { contact_id = aws_ssmcontacts_contact.test.arn delivery_address { - simple_address = "default@example.com" + simple_address = %[3]q } name = %[2]q @@ -77,5 +77,5 @@ resource "aws_ssmcontacts_contact_channel" "test" { data "aws_ssmcontacts_contact_channel" "test" { arn = aws_ssmcontacts_contact_channel.test.arn } -`, acctest.Region(), rName) +`, acctest.Region(), rName, acctest.DefaultEmailAddress) } diff --git a/internal/service/ssmcontacts/contact_channel_test.go b/internal/service/ssmcontacts/contact_channel_test.go index 04018ea7e1f..40f453b706f 100644 --- a/internal/service/ssmcontacts/contact_channel_test.go +++ b/internal/service/ssmcontacts/contact_channel_test.go @@ -46,7 +46,7 @@ func testContactChannel_basic(t *testing.T) { testAccCheckContactExists(contactResourceName), testAccCheckContactChannelExists(channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), - resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "default@example.com"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", acctest.DefaultEmailAddress), resource.TestCheckResourceAttr(channelResourceName, "name", rName), resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), resource.TestCheckResourceAttrPair(channelResourceName, "contact_id", contactResourceName, "arn"), @@ -161,6 +161,9 @@ func testContactChannel_deliveryAddress(t *testing.T) { ctx := context.Background() rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + domain := acctest.RandomDomainName() + address1 := acctest.RandomEmailAddress(domain) + address2 := acctest.RandomEmailAddress(domain) contactResourceName := "aws_ssmcontacts_contact.test" channelResourceName := "aws_ssmcontacts_contact_channel.test" @@ -174,12 +177,12 @@ func testContactChannel_deliveryAddress(t *testing.T) { CheckDestroy: testAccCheckContactChannelDestroy, Steps: []resource.TestStep{ { - Config: testAccContactChannelConfig(rName, rName, "EMAIL", "first@example.com"), + Config: testAccContactChannelConfig(rName, rName, "EMAIL", address1), Check: resource.ComposeTestCheckFunc( testAccCheckContactExists(contactResourceName), testAccCheckContactChannelExists(channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), - resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "first@example.com"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", address1), resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), ), }, @@ -189,12 +192,12 @@ func testContactChannel_deliveryAddress(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccContactChannelConfig(rName, rName, "EMAIL", "second@example.com"), + Config: testAccContactChannelConfig(rName, rName, "EMAIL", address2), Check: resource.ComposeTestCheckFunc( testAccCheckContactExists(contactResourceName), testAccCheckContactChannelExists(channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), - resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "second@example.com"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", address2), resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), ), }, @@ -229,7 +232,7 @@ func testContactChannel_name(t *testing.T) { CheckDestroy: testAccCheckContactChannelDestroy, Steps: []resource.TestStep{ { - Config: testAccContactChannelConfig(rName1, "update-name-test", "EMAIL", "test@example.com"), + Config: testAccContactChannelConfig(rName1, "update-name-test", "EMAIL", acctest.DefaultEmailAddress), Check: resource.ComposeTestCheckFunc( testAccCheckContactExists(contactResourceName), testAccCheckContactChannelExists(channelResourceName), @@ -242,7 +245,7 @@ func testContactChannel_name(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccContactChannelConfig(rName2, "update-name-test", "EMAIL", "test@example.com"), + Config: testAccContactChannelConfig(rName2, "update-name-test", "EMAIL", acctest.DefaultEmailAddress), Check: resource.ComposeTestCheckFunc( testAccCheckContactExists(contactResourceName), testAccCheckContactChannelExists(channelResourceName), @@ -284,7 +287,7 @@ func testContactChannel_type(t *testing.T) { testAccCheckContactExists(contactResourceName), testAccCheckContactChannelExists(channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), - resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "default@example.com"), + resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", acctest.DefaultEmailAddress), resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), ), }, @@ -397,7 +400,7 @@ func testAccContactChannelConfig_none() string { } func testAccContactChannelConfig_defaultEmail(rName string) string { - return testAccContactChannelConfig(rName, rName, "EMAIL", "default@example.com") + return testAccContactChannelConfig(rName, rName, "EMAIL", acctest.DefaultEmailAddress) } func testAccContactChannelConfig_defaultSms(rName string) string { @@ -454,13 +457,13 @@ resource "aws_ssmcontacts_contact_channel" "test" { contact_id = %[2]s delivery_address { - simple_address = "test@example.com" + simple_address = %[3]q } name = %[1]q type = "EMAIL" } -`, rName, contactArn)) +`, rName, contactArn, acctest.DefaultEmailAddress)) } func testAccContactChannelConfig_base() string { diff --git a/internal/service/ssmcontacts/plan_data_source_test.go b/internal/service/ssmcontacts/plan_data_source_test.go index 11b9d814f2e..34b36f660e1 100644 --- a/internal/service/ssmcontacts/plan_data_source_test.go +++ b/internal/service/ssmcontacts/plan_data_source_test.go @@ -185,7 +185,7 @@ resource "aws_ssmcontacts_contact_channel" "test" { contact_id = aws_ssmcontacts_contact.test_contact_one.arn delivery_address { - simple_address = "email@example.com" + simple_address = %[2]q } name = "Test Contact Channel for %[1]s" @@ -212,7 +212,7 @@ data "aws_ssmcontacts_plan" "test" { depends_on = [aws_ssmcontacts_plan.test] } -`, rName)) +`, rName, acctest.DefaultEmailAddress)) } func testAccPlanDataSourceConfig_base(alias string) string { diff --git a/internal/service/ssmcontacts/plan_test.go b/internal/service/ssmcontacts/plan_test.go index 82a98ef3f95..be5a968c23a 100644 --- a/internal/service/ssmcontacts/plan_test.go +++ b/internal/service/ssmcontacts/plan_test.go @@ -753,6 +753,8 @@ resource "aws_ssmcontacts_plan" "test" { } func testAccPlanConfig_channelTargetInfo(rName, contactChannelResourceName string, retryIntervalInMinutes int) string { + domain := acctest.RandomDomainName() + return acctest.ConfigCompose( testAccPlanConfig_base(rName), fmt.Sprintf(` @@ -760,7 +762,7 @@ resource "aws_ssmcontacts_contact_channel" "test_channel_one" { contact_id = aws_ssmcontacts_contact.test_contact_one.arn delivery_address { - simple_address = "email1@example.com" + simple_address = %[3]q } name = "Test Contact Channel 1" @@ -771,7 +773,7 @@ resource "aws_ssmcontacts_contact_channel" "test_channel_two" { contact_id = aws_ssmcontacts_contact.test_contact_one.arn delivery_address { - simple_address = "email2@example.com" + simple_address = %[4]q } name = "Test Contact Channel 2" @@ -792,7 +794,7 @@ resource "aws_ssmcontacts_plan" "test" { } } } -`, contactChannelResourceName, retryIntervalInMinutes)) +`, contactChannelResourceName, retryIntervalInMinutes, acctest.RandomEmailAddress(domain), acctest.RandomEmailAddress(domain))) } func testAccPlanConfig_base(alias string) string { From 3877aaebcaa2dff842c85f518d7679aa130b92c5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 15:00:32 -0400 Subject: [PATCH 14/20] Fix semgrep 'ci.caps4-in-func-name'. --- internal/service/ssmcontacts/contact_channel_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/ssmcontacts/contact_channel_test.go b/internal/service/ssmcontacts/contact_channel_test.go index 40f453b706f..82986423f13 100644 --- a/internal/service/ssmcontacts/contact_channel_test.go +++ b/internal/service/ssmcontacts/contact_channel_test.go @@ -297,7 +297,7 @@ func testContactChannel_type(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccContactChannelConfig_defaultSms(rName), + Config: testAccContactChannelConfig_defaultSMS(rName), Check: resource.ComposeTestCheckFunc( testAccCheckContactExists(contactResourceName), testAccCheckContactChannelExists(channelResourceName), @@ -403,7 +403,7 @@ func testAccContactChannelConfig_defaultEmail(rName string) string { return testAccContactChannelConfig(rName, rName, "EMAIL", acctest.DefaultEmailAddress) } -func testAccContactChannelConfig_defaultSms(rName string) string { +func testAccContactChannelConfig_defaultSMS(rName string) string { return testAccContactChannelConfig(rName, rName, "SMS", "+12065550100") } From d27e15b66265aebe7247565736587af1f4521f7d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 15:01:54 -0400 Subject: [PATCH 15/20] Fix golangci-lint 'staticcheck/SA1019'. --- internal/service/ssmcontacts/find.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/service/ssmcontacts/find.go b/internal/service/ssmcontacts/find.go index bc72700d52a..c69edfcfad3 100644 --- a/internal/service/ssmcontacts/find.go +++ b/internal/service/ssmcontacts/find.go @@ -7,7 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) @@ -19,7 +19,7 @@ func findContactByID(ctx context.Context, conn *ssmcontacts.Client, id string) ( if err != nil { var nfe *types.ResourceNotFoundException if errors.As(err, &nfe) { - return nil, &resource.NotFoundError{ + return nil, &retry.NotFoundError{ LastError: err, LastRequest: in, } @@ -43,7 +43,7 @@ func findContactChannelByID(ctx context.Context, conn *ssmcontacts.Client, id st if err != nil { var nfe *types.ResourceNotFoundException if errors.As(err, &nfe) { - return nil, &resource.NotFoundError{ + return nil, &retry.NotFoundError{ LastError: err, LastRequest: in, } From b4282749fb65b2253219cd90b9d0292051d25196 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 15:03:29 -0400 Subject: [PATCH 16/20] Fix golangci-lint 'gosimple'. --- internal/service/ssmcontacts/plan_data_source_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/ssmcontacts/plan_data_source_test.go b/internal/service/ssmcontacts/plan_data_source_test.go index 34b36f660e1..c8fc4bfc867 100644 --- a/internal/service/ssmcontacts/plan_data_source_test.go +++ b/internal/service/ssmcontacts/plan_data_source_test.go @@ -128,7 +128,7 @@ func testPlanDataSource_channelTargetInfo(t *testing.T) { func testAccPlanDataSourceConfig_basic(rName string) string { return acctest.ConfigCompose( testAccPlanDataSourceConfig_base(rName), - fmt.Sprintf(` + ` resource "aws_ssmcontacts_plan" "test" { contact_id = aws_ssmcontacts_contact.test_escalation_plan_one.arn @@ -174,7 +174,7 @@ data "aws_ssmcontacts_plan" "test" { depends_on = [aws_ssmcontacts_plan.test] } -`)) +`) } func testAccPlanDataSourceConfig_channelTargetInfo(rName string) string { From 6ee982d1d9de4feaa1dfbeb87402d6412943113d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 15:28:32 -0400 Subject: [PATCH 17/20] Fix golangci-lint 'contextcheck'. --- .../contact_channel_data_source_test.go | 8 +- .../ssmcontacts/contact_channel_test.go | 145 +++++++------- .../ssmcontacts/contact_data_source_test.go | 9 +- internal/service/ssmcontacts/contact_test.go | 127 ++++++------- .../ssmcontacts/plan_data_source_test.go | 16 +- internal/service/ssmcontacts/plan_test.go | 179 +++++++++--------- .../service/ssmcontacts/ssmcontacts_test.go | 2 +- 7 files changed, 224 insertions(+), 262 deletions(-) diff --git a/internal/service/ssmcontacts/contact_channel_data_source_test.go b/internal/service/ssmcontacts/contact_channel_data_source_test.go index 87e3c22d348..b450ccfa20e 100644 --- a/internal/service/ssmcontacts/contact_channel_data_source_test.go +++ b/internal/service/ssmcontacts/contact_channel_data_source_test.go @@ -1,7 +1,6 @@ package ssmcontacts_test import ( - "context" "fmt" "testing" @@ -16,8 +15,7 @@ func testContactChannelDataSource_basic(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) contactChannelResourceName := "aws_ssmcontacts_contact_channel.test" dataSourceName := "data.aws_ssmcontacts_contact_channel.test" @@ -25,16 +23,14 @@ func testContactChannelDataSource_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactChannelDestroy, Steps: []resource.TestStep{ { Config: testAccContactChannelDataSourceConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactChannelExists(dataSourceName), resource.TestCheckResourceAttrPair(dataSourceName, "activation_status", contactChannelResourceName, "activation_status"), resource.TestCheckResourceAttrPair(dataSourceName, "delivery_address.#", contactChannelResourceName, "delivery_address.#"), resource.TestCheckResourceAttrPair(dataSourceName, "delivery_address.0.simple_address", contactChannelResourceName, "delivery_address.0.simple_address"), diff --git a/internal/service/ssmcontacts/contact_channel_test.go b/internal/service/ssmcontacts/contact_channel_test.go index 82986423f13..0480f9768cf 100644 --- a/internal/service/ssmcontacts/contact_channel_test.go +++ b/internal/service/ssmcontacts/contact_channel_test.go @@ -25,8 +25,7 @@ func testContactChannel_basic(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) contactResourceName := "aws_ssmcontacts_contact.test" channelResourceName := "aws_ssmcontacts_contact_channel.test" @@ -34,17 +33,17 @@ func testContactChannel_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactChannelDestroy, + CheckDestroy: testAccCheckContactChannelDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactChannelConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", acctest.DefaultEmailAddress), resource.TestCheckResourceAttr(channelResourceName, "name", rName), @@ -63,7 +62,7 @@ func testContactChannel_basic(t *testing.T) { // because CheckDestroy will run after the replication set has been destroyed and destroying // the replication set will destroy all other resources. Config: testAccContactChannelConfig_none(), - Check: testAccCheckContactChannelDestroy, + Check: testAccCheckContactChannelDestroy(ctx), }, }, }) @@ -74,24 +73,23 @@ func testContactChannel_disappears(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) channelResourceName := "aws_ssmcontacts_contact_channel.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactChannelDestroy, + CheckDestroy: testAccCheckContactChannelDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactChannelConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssmcontacts.ResourceContactChannel(), channelResourceName), ), ExpectNonEmptyPlan: true, @@ -100,13 +98,12 @@ func testContactChannel_disappears(t *testing.T) { }) } -func testContactChannel_contactId(t *testing.T) { +func testContactChannel_contactID(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) testContactOneResourceName := "aws_ssmcontacts_contact.test_contact_one" testContactTwoResourceName := "aws_ssmcontacts_contact.test_contact_two" @@ -115,18 +112,18 @@ func testContactChannel_contactId(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactChannelDestroy, + CheckDestroy: testAccCheckContactChannelDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactChannelConfig_withTwoContacts(rName, testContactOneResourceName+".arn"), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(testContactOneResourceName), - testAccCheckContactExists(testContactTwoResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, testContactOneResourceName), + testAccCheckContactExists(ctx, testContactTwoResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttrPair(channelResourceName, "contact_id", testContactOneResourceName, "arn"), ), }, @@ -138,9 +135,9 @@ func testContactChannel_contactId(t *testing.T) { { Config: testAccContactChannelConfig_withTwoContacts(rName, testContactTwoResourceName+".arn"), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(testContactOneResourceName), - testAccCheckContactExists(testContactTwoResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, testContactOneResourceName), + testAccCheckContactExists(ctx, testContactTwoResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttrPair(channelResourceName, "contact_id", testContactTwoResourceName, "arn"), ), }, @@ -158,8 +155,7 @@ func testContactChannel_deliveryAddress(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) domain := acctest.RandomDomainName() address1 := acctest.RandomEmailAddress(domain) @@ -170,17 +166,17 @@ func testContactChannel_deliveryAddress(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactChannelDestroy, + CheckDestroy: testAccCheckContactChannelDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactChannelConfig(rName, rName, "EMAIL", address1), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", address1), resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), @@ -194,8 +190,8 @@ func testContactChannel_deliveryAddress(t *testing.T) { { Config: testAccContactChannelConfig(rName, rName, "EMAIL", address2), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", address2), resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), @@ -215,8 +211,7 @@ func testContactChannel_name(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix + "1") rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix + "2") contactResourceName := "aws_ssmcontacts_contact.test" @@ -225,17 +220,17 @@ func testContactChannel_name(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactChannelDestroy, + CheckDestroy: testAccCheckContactChannelDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactChannelConfig(rName1, "update-name-test", "EMAIL", acctest.DefaultEmailAddress), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "name", rName1), ), }, @@ -247,8 +242,8 @@ func testContactChannel_name(t *testing.T) { { Config: testAccContactChannelConfig(rName2, "update-name-test", "EMAIL", acctest.DefaultEmailAddress), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "name", rName2), ), }, @@ -266,8 +261,7 @@ func testContactChannel_type(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) contactResourceName := "aws_ssmcontacts_contact.test" channelResourceName := "aws_ssmcontacts_contact_channel.test" @@ -275,17 +269,17 @@ func testContactChannel_type(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactChannelDestroy, + CheckDestroy: testAccCheckContactChannelDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactChannelConfig_defaultEmail(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", acctest.DefaultEmailAddress), resource.TestCheckResourceAttr(channelResourceName, "type", "EMAIL"), @@ -299,8 +293,8 @@ func testContactChannel_type(t *testing.T) { { Config: testAccContactChannelConfig_defaultSMS(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "+12065550100"), resource.TestCheckResourceAttr(channelResourceName, "type", "SMS"), @@ -314,8 +308,8 @@ func testContactChannel_type(t *testing.T) { { Config: testAccContactChannelConfig_defaultVoice(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckContactChannelExists(channelResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckContactChannelExists(ctx, channelResourceName), resource.TestCheckResourceAttr(channelResourceName, "activation_status", "NOT_ACTIVATED"), resource.TestCheckResourceAttr(channelResourceName, "delivery_address.0.simple_address", "+12065550199"), resource.TestCheckResourceAttr(channelResourceName, "type", "VOICE"), @@ -330,45 +324,44 @@ func testContactChannel_type(t *testing.T) { }) } -func testAccCheckContactChannelDestroy(s *terraform.State) error { - ctx := context.Background() - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_ssmcontacts_contact_channel" { - continue - } - - input := &ssmcontacts.GetContactChannelInput{ - ContactChannelId: aws.String(rs.Primary.ID), - } - _, err := conn.GetContactChannel(ctx, input) +func testAccCheckContactChannelDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() - if err != nil { - // Getting resources may return validation exception when the replication set has been destroyed - var ve *types.ValidationException - if errors.As(err, &ve) { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssmcontacts_contact_channel" { continue } - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - continue + input := &ssmcontacts.GetContactChannelInput{ + ContactChannelId: aws.String(rs.Primary.ID), } + _, err := conn.GetContactChannel(ctx, input) + + if err != nil { + // Getting resources may return validation exception when the replication set has been destroyed + var ve *types.ValidationException + if errors.As(err, &ve) { + continue + } + + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + continue + } - return err + return err + } + + return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNameContactChannel, rs.Primary.ID, errors.New("not destroyed")) } - return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNameContactChannel, rs.Primary.ID, errors.New("not destroyed")) + return nil } - - return nil } -func testAccCheckContactChannelExists(name string) resource.TestCheckFunc { +func testAccCheckContactChannelExists(ctx context.Context, name string) resource.TestCheckFunc { return func(s *terraform.State) error { - ctx := context.Background() - rs, ok := s.RootModule().Resources[name] if !ok { return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameContactChannel, name, errors.New("not found")) diff --git a/internal/service/ssmcontacts/contact_data_source_test.go b/internal/service/ssmcontacts/contact_data_source_test.go index 903fb8f9eff..13718aa1418 100644 --- a/internal/service/ssmcontacts/contact_data_source_test.go +++ b/internal/service/ssmcontacts/contact_data_source_test.go @@ -1,7 +1,6 @@ package ssmcontacts_test import ( - "context" "fmt" "testing" @@ -16,8 +15,7 @@ func testContactDataSource_basic(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssmcontacts_contact.contact_one" dataSourceName := "data.aws_ssmcontacts_contact.contact_one" @@ -25,17 +23,14 @@ func testContactDataSource_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactDestroy, Steps: []resource.TestStep{ { Config: testAccContactDataSourceConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(dataSourceName), - testAccCheckContactExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceName, "arn"), resource.TestCheckResourceAttrPair(resourceName, "alias", dataSourceName, "alias"), resource.TestCheckResourceAttrPair(resourceName, "type", dataSourceName, "type"), diff --git a/internal/service/ssmcontacts/contact_test.go b/internal/service/ssmcontacts/contact_test.go index 119cd67571d..870cba4c7d1 100644 --- a/internal/service/ssmcontacts/contact_test.go +++ b/internal/service/ssmcontacts/contact_test.go @@ -25,24 +25,23 @@ func testContact_basic(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssmcontacts_contact.contact_one" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactDestroy, + CheckDestroy: testAccCheckContactDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "alias", rName), resource.TestCheckResourceAttr(resourceName, "type", "PERSONAL"), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ssm-contacts", regexp.MustCompile(`contact/+.`)), @@ -58,7 +57,7 @@ func testContact_basic(t *testing.T) { // because CheckDestroy will run after the replication set has been destroyed and destroying // the replication set will destroy all other resources. Config: testAccContactConfig_none(), - Check: testAccCheckContactDestroy, + Check: testAccCheckContactDestroy(ctx), }, }, }) @@ -69,8 +68,7 @@ func testContact_updateAlias(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) oldAlias := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) newAlias := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -79,16 +77,16 @@ func testContact_updateAlias(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactDestroy, + CheckDestroy: testAccCheckContactDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactConfig_alias(oldAlias), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "alias", oldAlias), ), }, @@ -100,7 +98,7 @@ func testContact_updateAlias(t *testing.T) { { Config: testAccContactConfig_alias(newAlias), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "alias", newAlias), ), }, @@ -118,8 +116,7 @@ func testContact_updateType(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) personalType := "PERSONAL" escalationType := "ESCALATION" @@ -129,16 +126,16 @@ func testContact_updateType(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactDestroy, + CheckDestroy: testAccCheckContactDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactConfig_type(name, personalType), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "type", personalType), ), }, @@ -150,7 +147,7 @@ func testContact_updateType(t *testing.T) { { Config: testAccContactConfig_type(name, escalationType), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "type", escalationType), ), }, @@ -168,24 +165,23 @@ func testContact_disappears(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssmcontacts_contact.contact_one" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactDestroy, + CheckDestroy: testAccCheckContactDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssmcontacts.ResourceContact(), resourceName), ), ExpectNonEmptyPlan: true, @@ -199,8 +195,7 @@ func testContact_updateDisplayName(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) oldDisplayName := sdkacctest.RandString(26) newDisplayName := sdkacctest.RandString(26) @@ -209,16 +204,16 @@ func testContact_updateDisplayName(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactDestroy, + CheckDestroy: testAccCheckContactDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactConfig_displayName(rName, oldDisplayName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "display_name", oldDisplayName), ), }, @@ -230,7 +225,7 @@ func testContact_updateDisplayName(t *testing.T) { { Config: testAccContactConfig_displayName(rName, newDisplayName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "display_name", newDisplayName), ), }, @@ -248,8 +243,7 @@ func testContact_updateTags(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssmcontacts_contact.contact_one" @@ -263,16 +257,16 @@ func testContact_updateTags(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckContactDestroy, + CheckDestroy: testAccCheckContactDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccContactConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -284,7 +278,7 @@ func testContact_updateTags(t *testing.T) { { Config: testAccContactConfig_oneTag(rName, rKey1, rVal1), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags."+rKey1, rVal1), ), @@ -297,7 +291,7 @@ func testContact_updateTags(t *testing.T) { { Config: testAccContactConfig_twoTags(rName, rKey1, rVal1, rKey2, rVal2), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags."+rKey1, rVal1), resource.TestCheckResourceAttr(resourceName, "tags."+rKey2, rVal2), @@ -311,7 +305,7 @@ func testContact_updateTags(t *testing.T) { { Config: testAccContactConfig_twoTags(rName, rKey1, rVal1Updated, rKey2, rVal2Updated), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags."+rKey1, rVal1Updated), resource.TestCheckResourceAttr(resourceName, "tags."+rKey2, rVal2Updated), @@ -325,7 +319,7 @@ func testContact_updateTags(t *testing.T) { { Config: testAccContactConfig_oneTag(rName, rKey1, rVal1Updated), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags."+rKey1, rVal1Updated), ), @@ -338,7 +332,7 @@ func testContact_updateTags(t *testing.T) { { Config: testAccContactConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(resourceName), + testAccCheckContactExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -351,42 +345,43 @@ func testContact_updateTags(t *testing.T) { }) } -func testAccCheckContactDestroy(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() - ctx := context.Background() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_ssmcontacts_contact" { - continue - } - - input := &ssmcontacts.GetContactInput{ - ContactId: aws.String(rs.Primary.ID), - } - _, err := conn.GetContact(ctx, input) +func testAccCheckContactDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() - if err != nil { - // Getting resources may return validation exception when the replication set has been destroyed - var ve *types.ValidationException - if errors.As(err, &ve) { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssmcontacts_contact" { continue } - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - continue + input := &ssmcontacts.GetContactInput{ + ContactId: aws.String(rs.Primary.ID), } + _, err := conn.GetContact(ctx, input) + + if err != nil { + // Getting resources may return validation exception when the replication set has been destroyed + var ve *types.ValidationException + if errors.As(err, &ve) { + continue + } - return err + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + continue + } + + return err + } + + return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNameContact, rs.Primary.ID, errors.New("not destroyed")) } - return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNameContact, rs.Primary.ID, errors.New("not destroyed")) + return nil } - - return nil } -func testAccCheckContactExists(name string) resource.TestCheckFunc { +func testAccCheckContactExists(ctx context.Context, name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { @@ -398,7 +393,6 @@ func testAccCheckContactExists(name string) resource.TestCheckFunc { } conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() - ctx := context.Background() _, err := conn.GetContact(ctx, &ssmcontacts.GetContactInput{ ContactId: aws.String(rs.Primary.ID), @@ -412,9 +406,8 @@ func testAccCheckContactExists(name string) resource.TestCheckFunc { } } -func testAccContactPreCheck(t *testing.T) { +func testAccContactPreCheck(ctx context.Context, t *testing.T) { conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() - ctx := context.Background() input := &ssmcontacts.ListContactsInput{} _, err := conn.ListContacts(ctx, input) diff --git a/internal/service/ssmcontacts/plan_data_source_test.go b/internal/service/ssmcontacts/plan_data_source_test.go index c8fc4bfc867..9155fcdf8d1 100644 --- a/internal/service/ssmcontacts/plan_data_source_test.go +++ b/internal/service/ssmcontacts/plan_data_source_test.go @@ -1,7 +1,6 @@ package ssmcontacts_test import ( - "context" "fmt" "testing" @@ -16,8 +15,7 @@ func testPlanDataSource_basic(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) planDataSourceName := "data.aws_ssmcontacts_plan.test" planResourceName := "aws_ssmcontacts_plan.test" @@ -25,17 +23,14 @@ func testPlanDataSource_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, Steps: []resource.TestStep{ { Config: testAccPlanDataSourceConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckPlanExists(planDataSourceName), - testAccCheckPlanExists(planResourceName), resource.TestCheckResourceAttrPair( planDataSourceName, "contact_id", planResourceName, "contact_id", @@ -87,8 +82,7 @@ func testPlanDataSource_channelTargetInfo(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) planDataSourceName := "data.aws_ssmcontacts_plan.test" planResourceName := "aws_ssmcontacts_plan.test" @@ -96,16 +90,14 @@ func testPlanDataSource_channelTargetInfo(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, Steps: []resource.TestStep{ { Config: testAccPlanDataSourceConfig_channelTargetInfo(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckPlanExists(planDataSourceName), resource.TestCheckResourceAttrPair(planDataSourceName, "stage.0.target.#", planResourceName, "stage.0.target.#"), resource.TestCheckResourceAttrPair( planDataSourceName, diff --git a/internal/service/ssmcontacts/plan_test.go b/internal/service/ssmcontacts/plan_test.go index be5a968c23a..d25d348b0b6 100644 --- a/internal/service/ssmcontacts/plan_test.go +++ b/internal/service/ssmcontacts/plan_test.go @@ -25,8 +25,7 @@ func testPlan_basic(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) contactResourceName := "aws_ssmcontacts_contact.test_contact_one" planResourceName := "aws_ssmcontacts_plan.test" @@ -34,17 +33,17 @@ func testPlan_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, + CheckDestroy: testAccCheckPlanDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccPlanConfig_oneStage(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.#", "1"), resource.TestCheckResourceAttr(planResourceName, "stage.0.duration_in_minutes", "1"), acctest.CheckResourceAttrRegionalARN( @@ -65,7 +64,7 @@ func testPlan_basic(t *testing.T) { // because CheckDestroy will run after the replication set has been destroyed and destroying // the replication set will destroy all other resources. Config: testAccPlanConfig_none(rName), - Check: testAccCheckPlanDestroy, + Check: testAccCheckPlanDestroy(ctx), }, }, }) @@ -76,8 +75,7 @@ func testPlan_disappears(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) contactResourceName := "aws_ssmcontacts_contact.test_contact_one" planResourceName := "aws_ssmcontacts_plan.test" @@ -85,17 +83,17 @@ func testPlan_disappears(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, + CheckDestroy: testAccCheckPlanDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccPlanConfig_oneStage(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckPlanExists(ctx, planResourceName), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssmcontacts.ResourcePlan(), planResourceName), ), ExpectNonEmptyPlan: true, @@ -109,8 +107,7 @@ func testPlan_updateContactId(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) contactOneResourceName := "aws_ssmcontacts_contact.test_contact_one" contactTwoResourceName := "aws_ssmcontacts_contact.test_contact_two" @@ -120,17 +117,17 @@ func testPlan_updateContactId(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, + CheckDestroy: testAccCheckPlanDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccPlanConfig_contactID(rName, contactOneResourceName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactOneResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, contactOneResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckTypeSetElemAttrPair(planResourceName, "contact_id", contactOneResourceName, "arn"), ), }, @@ -142,8 +139,8 @@ func testPlan_updateContactId(t *testing.T) { { Config: testAccPlanConfig_contactID(rName, contactTwoResourceName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactTwoResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, contactTwoResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckTypeSetElemAttrPair(planResourceName, "contact_id", contactTwoResourceName, "arn"), ), }, @@ -161,8 +158,7 @@ func testPlan_updateStages(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) contactResourceName := "aws_ssmcontacts_contact.test_contact_one" planResourceName := "aws_ssmcontacts_plan.test" @@ -170,17 +166,17 @@ func testPlan_updateStages(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, + CheckDestroy: testAccCheckPlanDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccPlanConfig_oneStage(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.#", "1"), resource.TestCheckResourceAttr(planResourceName, "stage.0.duration_in_minutes", "1"), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "0"), @@ -194,8 +190,8 @@ func testPlan_updateStages(t *testing.T) { { Config: testAccPlanConfig_twoStages(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.#", "2"), resource.TestCheckResourceAttr(planResourceName, "stage.0.duration_in_minutes", "1"), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "0"), @@ -211,8 +207,8 @@ func testPlan_updateStages(t *testing.T) { { Config: testAccPlanConfig_oneStage(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.#", "1"), resource.TestCheckResourceAttr(planResourceName, "stage.0.duration_in_minutes", "1"), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "0"), @@ -232,8 +228,7 @@ func testPlan_updateDurationInMinutes(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) contactResourceName := "aws_ssmcontacts_contact.test_contact_one" planResourceName := "aws_ssmcontacts_plan.test" @@ -243,17 +238,17 @@ func testPlan_updateDurationInMinutes(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, + CheckDestroy: testAccCheckPlanDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccPlanConfig_durationInMinutes(rName, oldDurationInMinutes), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr( planResourceName, "stage.0.duration_in_minutes", @@ -269,8 +264,8 @@ func testPlan_updateDurationInMinutes(t *testing.T) { { Config: testAccPlanConfig_durationInMinutes(rName, newDurationInMinutes), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(contactResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr( planResourceName, "stage.0.duration_in_minutes", @@ -292,8 +287,7 @@ func testPlan_updateTargets(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) escalationPlanResourceName := "aws_ssmcontacts_contact.test_escalation_plan_one" planResourceName := "aws_ssmcontacts_plan.test" @@ -303,17 +297,17 @@ func testPlan_updateTargets(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, + CheckDestroy: testAccCheckPlanDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccPlanConfig_oneTarget(rName, testContactOneResourceArn), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(escalationPlanResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, escalationPlanResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), resource.TestCheckResourceAttr( planResourceName, @@ -336,8 +330,8 @@ func testPlan_updateTargets(t *testing.T) { { Config: testAccPlanConfig_twoTargets(rName, testContactOneResourceArn, testContactTwoResourceArn), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(escalationPlanResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, escalationPlanResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "2"), resource.TestCheckResourceAttr( planResourceName, @@ -371,8 +365,8 @@ func testPlan_updateTargets(t *testing.T) { { Config: testAccPlanConfig_oneTarget(rName, testContactOneResourceArn), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(escalationPlanResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, escalationPlanResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), resource.TestCheckResourceAttr( planResourceName, @@ -401,8 +395,7 @@ func testPlan_updateContactTargetInfo(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) escalationPlanResourceName := "aws_ssmcontacts_contact.test_escalation_plan_one" planResourceName := "aws_ssmcontacts_plan.test" @@ -412,17 +405,17 @@ func testPlan_updateContactTargetInfo(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, + CheckDestroy: testAccCheckPlanDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccPlanConfig_contactTargetInfo(rName, false, testContactOneResourceArn), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(escalationPlanResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, escalationPlanResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), resource.TestCheckResourceAttr( planResourceName, @@ -445,8 +438,8 @@ func testPlan_updateContactTargetInfo(t *testing.T) { { Config: testAccPlanConfig_contactTargetInfo(rName, true, testContactTwoResourceArn), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(escalationPlanResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, escalationPlanResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), resource.TestCheckResourceAttr( planResourceName, @@ -475,8 +468,7 @@ func testPlan_updateChannelTargetInfo(t *testing.T) { t.Skip("skipping long-running test in short mode") } - ctx := context.Background() - + ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) escalationPlanResourceName := "aws_ssmcontacts_contact.test_escalation_plan_one" planResourceName := "aws_ssmcontacts_plan.test" @@ -489,11 +481,11 @@ func testPlan_updateChannelTargetInfo(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - testAccContactPreCheck(t) + testAccContactPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy, + CheckDestroy: testAccCheckPlanDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccPlanConfig_channelTargetInfo( @@ -502,8 +494,8 @@ func testPlan_updateChannelTargetInfo(t *testing.T) { oldRetryIntervalInMinutes, ), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(escalationPlanResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, escalationPlanResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), resource.TestCheckResourceAttrPair( planResourceName, @@ -530,8 +522,8 @@ func testPlan_updateChannelTargetInfo(t *testing.T) { newRetryIntervalInMinutes, ), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(escalationPlanResourceName), - testAccCheckPlanExists(planResourceName), + testAccCheckContactExists(ctx, escalationPlanResourceName), + testAccCheckPlanExists(ctx, planResourceName), resource.TestCheckResourceAttr(planResourceName, "stage.0.target.#", "1"), resource.TestCheckResourceAttrPair( planResourceName, @@ -555,7 +547,7 @@ func testPlan_updateChannelTargetInfo(t *testing.T) { }) } -func testAccCheckPlanExists(name string) resource.TestCheckFunc { +func testAccCheckPlanExists(ctx context.Context, name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { @@ -567,7 +559,7 @@ func testAccCheckPlanExists(name string) resource.TestCheckFunc { } conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() - ctx := context.Background() + output, err := conn.GetContact(ctx, &ssmcontacts.GetContactInput{ ContactId: aws.String(rs.Primary.ID), }) @@ -580,42 +572,43 @@ func testAccCheckPlanExists(name string) resource.TestCheckFunc { } } -func testAccCheckPlanDestroy(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() - ctx := context.Background() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_ssmcontacts_plan" { - continue - } +func testAccCheckPlanDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient() - input := &ssmcontacts.GetContactInput{ - ContactId: aws.String(rs.Primary.ID), - } - output, err := conn.GetContact(ctx, input) - if err != nil { - // Getting resources may return validation exception when the replication set has been destroyed - var ve *types.ValidationException - if errors.As(err, &ve) { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssmcontacts_plan" { continue } - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - continue + input := &ssmcontacts.GetContactInput{ + ContactId: aws.String(rs.Primary.ID), + } + output, err := conn.GetContact(ctx, input) + if err != nil { + // Getting resources may return validation exception when the replication set has been destroyed + var ve *types.ValidationException + if errors.As(err, &ve) { + continue + } + + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + continue + } + + return err } - return err - } + if len(output.Plan.Stages) == 0 { + return nil + } - if len(output.Plan.Stages) == 0 { - return nil + return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNamePlan, rs.Primary.ID, errors.New("not destroyed")) } - return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNamePlan, rs.Primary.ID, errors.New("not destroyed")) + return nil } - - return nil } func testAccPlanConfig_contactID(rName, contactName string) string { diff --git a/internal/service/ssmcontacts/ssmcontacts_test.go b/internal/service/ssmcontacts/ssmcontacts_test.go index d96b6e5236f..48c650a66af 100644 --- a/internal/service/ssmcontacts/ssmcontacts_test.go +++ b/internal/service/ssmcontacts/ssmcontacts_test.go @@ -25,7 +25,7 @@ func TestAccSSMContacts_serial(t *testing.T) { }, "Contact Channel Resource Tests": { "basic": testContactChannel_basic, - "contactId": testContactChannel_contactId, + "contactId": testContactChannel_contactID, "deliveryAddress": testContactChannel_deliveryAddress, "disappears": testContactChannel_disappears, "name": testContactChannel_name, From 6753084778c751fe088c24370934604b1fbc56f7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 16:19:33 -0400 Subject: [PATCH 18/20] Fix glonagci-lint 'whitespace'. --- internal/service/ssmcontacts/flex.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/ssmcontacts/flex.go b/internal/service/ssmcontacts/flex.go index 109407b29de..6155b716dd9 100644 --- a/internal/service/ssmcontacts/flex.go +++ b/internal/service/ssmcontacts/flex.go @@ -77,7 +77,6 @@ func expandTargets(targets []interface{}) []types.Target { targetList := make([]types.Target, 0) for _, target := range targets { - if target == nil { continue } From e37137c69a957b5f56ec7c8fd5339cf2e5b42aea Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 16:20:58 -0400 Subject: [PATCH 19/20] Fix glonagci-lint 'unparam'. --- internal/service/ssmcontacts/helper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ssmcontacts/helper.go b/internal/service/ssmcontacts/helper.go index 67b117d2004..c8316140ca9 100644 --- a/internal/service/ssmcontacts/helper.go +++ b/internal/service/ssmcontacts/helper.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func setContactResourceData(d *schema.ResourceData, getContactOutput *ssmcontacts.GetContactOutput) error { +func setContactResourceData(d *schema.ResourceData, getContactOutput *ssmcontacts.GetContactOutput) error { //nolint:unparam d.Set("arn", getContactOutput.ContactArn) d.Set("alias", getContactOutput.Alias) d.Set("type", getContactOutput.Type) From 151854bba79ce53a6e8d58ace60de51cb470e65b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 21 Apr 2023 16:42:23 -0400 Subject: [PATCH 20/20] Fix acceptance test terrafmt errors. --- internal/service/ssmcontacts/contact_channel_test.go | 2 +- internal/service/ssmcontacts/plan_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/ssmcontacts/contact_channel_test.go b/internal/service/ssmcontacts/contact_channel_test.go index 0480f9768cf..573ce0b6c6c 100644 --- a/internal/service/ssmcontacts/contact_channel_test.go +++ b/internal/service/ssmcontacts/contact_channel_test.go @@ -441,7 +441,7 @@ resource "aws_ssmcontacts_contact" "test_contact_one" { resource "aws_ssmcontacts_contact" "test_contact_two" { alias = "test-contact-two-for-%[1]s" - type = "PERSONAL" + type = "PERSONAL" depends_on = [aws_ssmincidents_replication_set.test] } diff --git a/internal/service/ssmcontacts/plan_test.go b/internal/service/ssmcontacts/plan_test.go index d25d348b0b6..ab1368b1805 100644 --- a/internal/service/ssmcontacts/plan_test.go +++ b/internal/service/ssmcontacts/plan_test.go @@ -688,7 +688,7 @@ resource "aws_ssmcontacts_plan" "test" { target { contact_target_info { is_essential = false - contact_id = %[1]s + contact_id = %[1]s } } }