From 13f875243ee00e962bcbe7fda8628b7ceff92ca7 Mon Sep 17 00:00:00 2001 From: Kevin Chun Date: Fri, 17 Mar 2023 15:34:26 +1100 Subject: [PATCH 1/5] implement response plan --- internal/service/ssmincidents/find.go | 25 + internal/service/ssmincidents/flex.go | 364 +++++ internal/service/ssmincidents/helper.go | 35 +- .../service/ssmincidents/response_plan.go | 353 +++++ .../ssmincidents/response_plan_data_source.go | 200 +++ .../response_plan_data_source_test.go | 325 ++++ .../ssmincidents/response_plan_test.go | 1349 +++++++++++++++++ .../ssmincidents/service_package_gen.go | 8 + ...o => ssmincidents_replication_set_test.go} | 2 +- .../ssmincidents_response_plan_test.go | 39 + .../ssmincidents_response_plan.html.markdown | 80 + .../ssmincidents_response_plan.html.markdown | 170 +++ 12 files changed, 2947 insertions(+), 3 deletions(-) create mode 100644 internal/service/ssmincidents/response_plan.go create mode 100644 internal/service/ssmincidents/response_plan_data_source.go create mode 100644 internal/service/ssmincidents/response_plan_data_source_test.go create mode 100644 internal/service/ssmincidents/response_plan_test.go rename internal/service/ssmincidents/{ssmincidents_test.go => ssmincidents_replication_set_test.go} (89%) create mode 100644 internal/service/ssmincidents/ssmincidents_response_plan_test.go create mode 100644 website/docs/d/ssmincidents_response_plan.html.markdown create mode 100644 website/docs/r/ssmincidents_response_plan.html.markdown diff --git a/internal/service/ssmincidents/find.go b/internal/service/ssmincidents/find.go index 3545cbd0d59..ccfa7979a5f 100644 --- a/internal/service/ssmincidents/find.go +++ b/internal/service/ssmincidents/find.go @@ -11,6 +11,31 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) +func FindResponsePlanByID(context context.Context, client *ssmincidents.Client, arn string) (*ssmincidents.GetResponsePlanOutput, error) { + input := &ssmincidents.GetResponsePlanInput{ + Arn: aws.String(arn), + } + output, err := client.GetResponsePlan(context, input) + + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + func FindReplicationSetByID(context context.Context, client *ssmincidents.Client, arn string) (*types.ReplicationSet, error) { input := &ssmincidents.GetReplicationSetInput{ Arn: aws.String(arn), diff --git a/internal/service/ssmincidents/flex.go b/internal/service/ssmincidents/flex.go index c6eb751c4e9..5f26cd5257a 100644 --- a/internal/service/ssmincidents/flex.go +++ b/internal/service/ssmincidents/flex.go @@ -3,6 +3,8 @@ package ssmincidents import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssmincidents/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/flex" ) func expandRegions(regions []interface{}) map[string]types.RegionMapInputValue { @@ -48,3 +50,365 @@ func flattenRegions(regions map[string]types.RegionInfo) []map[string]interface{ return tfRegionData } + +func expandIncidentTemplate(config []interface{}) *types.IncidentTemplate { + // we require exactly one item so we grab first in list + templateConfig := config[0].(map[string]interface{}) + + template := &types.IncidentTemplate{} + + if v, ok := templateConfig["title"].(string); ok && v != "" { + template.Title = aws.String(v) + } + + if v, ok := templateConfig["impact"].(int); ok && v != 0 { + template.Impact = aws.Int32(int32(v)) + } + + // dedupe string can be updated to have no value (denoted as "") + if v, ok := templateConfig["dedupe_string"].(string); ok { + template.DedupeString = aws.String(v) + } + + if v, ok := templateConfig["incident_tags"].(map[string]interface{}); ok && len(v) > 0 { + template.IncidentTags = flex.ExpandStringValueMap(v) + } + + // summary can be updated to have no value (denoted as "") + if v, ok := templateConfig["summary"].(string); ok { + template.Summary = aws.String(v) + } + + if v, ok := templateConfig["notification_target"].(*schema.Set); ok && v.Len() > 0 { + template.NotificationTargets = expandNotificationTargets(v.List()) + } + + return template +} + +func flattenIncidentTemplate(template *types.IncidentTemplate) []map[string]interface{} { + result := make([]map[string]interface{}, 0) + tfTemplate := make(map[string]interface{}) + + tfTemplate["impact"] = aws.ToInt32(template.Impact) + tfTemplate["title"] = aws.ToString(template.Title) + + if v := template.DedupeString; v != nil { + tfTemplate["dedupe_string"] = aws.ToString(v) + } + + if v := template.IncidentTags; v != nil { + tfTemplate["incident_tags"] = template.IncidentTags + } + + if v := template.Summary; v != nil { + tfTemplate["summary"] = aws.ToString(template.Summary) + } + + if v := template.NotificationTargets; v != nil { + tfTemplate["notification_target"] = flattenNotificationTargets(template.NotificationTargets) + } + + result = append(result, tfTemplate) + return result +} + +func expandNotificationTargets(targets []interface{}) []types.NotificationTargetItem { + if len(targets) == 0 { + return nil + } + + notificationTargets := make([]types.NotificationTargetItem, len(targets)) + + for i, target := range targets { + targetData := target.(map[string]interface{}) + + targetItem := &types.NotificationTargetItemMemberSnsTopicArn{ + Value: targetData["sns_topic_arn"].(string), + } + + notificationTargets[i] = targetItem + } + + return notificationTargets +} + +func flattenNotificationTargets(targets []types.NotificationTargetItem) []map[string]interface{} { + if len(targets) == 0 { + return nil + } + + notificationTargets := make([]map[string]interface{}, len(targets)) + + for i, target := range targets { + targetItem := make(map[string]interface{}) + + targetItem["sns_topic_arn"] = target.(*types.NotificationTargetItemMemberSnsTopicArn).Value + + notificationTargets[i] = targetItem + } + + return notificationTargets +} + +func expandChatChannel(chatChannels *schema.Set) types.ChatChannel { + chatChannelList := flex.ExpandStringValueList(chatChannels.List()) + + if len(chatChannelList) == 0 { + return &types.ChatChannelMemberEmpty{ + Value: types.EmptyChatChannel{}, + } + } + + return &types.ChatChannelMemberChatbotSns{ + Value: chatChannelList, + } +} + +func flattenChatChannel(chatChannel types.ChatChannel) *schema.Set { + if _, ok := chatChannel.(*types.ChatChannelMemberEmpty); ok { + return &schema.Set{} + } + + if chatBotSns, ok := chatChannel.(*types.ChatChannelMemberChatbotSns); ok { + return flex.FlattenStringValueSet(chatBotSns.Value) + } + return nil +} + +func expandAction(actions []interface{}) []types.Action { + if len(actions) == 0 { + return nil + } + + result := make([]types.Action, 0) + + actionConfig := actions[0].(map[string]interface{}) + if v, ok := actionConfig["ssm_automation"].([]interface{}); ok { + result = append(result, expandSSMAutomations(v)...) + } + + return result +} + +func flattenAction(actions []types.Action) []interface{} { + if len(actions) == 0 { + return nil + } + + result := make([]interface{}, 0) + + action := make(map[string]interface{}) + action["ssm_automation"] = flattenSSMAutomations(actions) + result = append(result, action) + + return result +} + +func expandSSMAutomations(automations []interface{}) []types.Action { + var result []types.Action + for _, automation := range automations { + ssmAutomation := types.SsmAutomation{} + automationData := automation.(map[string]interface{}) + + if v, ok := automationData["document_name"].(string); ok { + ssmAutomation.DocumentName = aws.String(v) + } + + if v, ok := automationData["role_arn"].(string); ok { + ssmAutomation.RoleArn = aws.String(v) + } + + if v, ok := automationData["document_version"].(string); ok { + ssmAutomation.DocumentVersion = aws.String(v) + } + + if v, ok := automationData["target_account"].(string); ok { + ssmAutomation.TargetAccount = types.SsmTargetAccount(v) + } + + if v, ok := automationData["parameter"].(*schema.Set); ok { + ssmAutomation.Parameters = expandParameters(v) + } + + if v, ok := automationData["dynamic_parameters"].(map[string]interface{}); ok { + ssmAutomation.DynamicParameters = expandDynamicParameters(v) + } + + result = append( + result, + &types.ActionMemberSsmAutomation{Value: ssmAutomation}, + ) + } + return result +} + +func flattenSSMAutomations(actions []types.Action) []interface{} { + var result []interface{} + + for _, action := range actions { + if ssmAutomationAction, ok := action.(*types.ActionMemberSsmAutomation); ok { + ssmAutomation := ssmAutomationAction.Value + + a := map[string]interface{}{} + + if v := ssmAutomation.DocumentName; v != nil { + a["document_name"] = aws.ToString(v) + } + + if v := ssmAutomation.RoleArn; v != nil { + a["role_arn"] = aws.ToString(v) + } + + if v := ssmAutomation.DocumentVersion; v != nil { + a["document_version"] = aws.ToString(v) + } + + if v := ssmAutomation.TargetAccount; v != "" { + a["target_account"] = ssmAutomation.TargetAccount + } + + if v := ssmAutomation.Parameters; v != nil { + a["parameter"] = flattenParameters(v) + } + + if v := ssmAutomation.DynamicParameters; v != nil { + a["dynamic_parameters"] = flattenDynamicParameters(v) + } + + result = append(result, a) + } + } + return result +} + +func expandParameters(parameters *schema.Set) map[string][]string { + parameterMap := make(map[string][]string) + for _, parameter := range parameters.List() { + parameterData := parameter.(map[string]interface{}) + name := parameterData["name"].(string) + values := flex.ExpandStringValueList(parameterData["values"].(*schema.Set).List()) + parameterMap[name] = values + } + return parameterMap +} + +func flattenParameters(parameterMap map[string][]string) []map[string]interface{} { + result := make([]map[string]interface{}, 0) + for name, values := range parameterMap { + data := make(map[string]interface{}) + data["name"] = name + data["values"] = flex.FlattenStringValueList(values) + result = append(result, data) + } + return result +} + +func expandDynamicParameters(parameterMap map[string]interface{}) map[string]types.DynamicSsmParameterValue { + result := make(map[string]types.DynamicSsmParameterValue) + for key, value := range parameterMap { + parameterValue := &types.DynamicSsmParameterValueMemberVariable{ + Value: types.VariableType(value.(string)), + } + result[key] = parameterValue + } + return result +} + +func flattenDynamicParameters(parameterMap map[string]types.DynamicSsmParameterValue) map[string]interface{} { + result := make(map[string]interface{}) + for key, value := range parameterMap { + parameterValue := value.(*types.DynamicSsmParameterValueMemberVariable) + result[key] = parameterValue.Value + } + + return result +} + +func expandIntegration(integrations []interface{}) []types.Integration { + if len(integrations) == 0 { + return nil + } + + // we require exactly one integration item + integrationData := integrations[0].(map[string]interface{}) + result := make([]types.Integration, 0) + + if v, ok := integrationData["pagerduty"].([]interface{}); ok { + result = append(result, expandPagerDutyIntegration(v)...) + } + + return result +} + +func flattenIntegration(integrations []types.Integration) []interface{} { + if len(integrations) == 0 { + return nil + } + + result := make([]interface{}, 0) + + integration := make(map[string]interface{}) + integration["pagerduty"] = flattenPagerDutyIntegration(integrations) + result = append(result, integration) + + return result +} + +func expandPagerDutyIntegration(integrations []interface{}) []types.Integration { + result := make([]types.Integration, 0) + + for _, integration := range integrations { + if integration == nil { + continue + } + integrationData := integration.(map[string]interface{}) + + pagerDutyIntegration := types.PagerDutyConfiguration{} + + if v, ok := integrationData["name"].(string); ok && v != "" { + pagerDutyIntegration.Name = aws.String(v) + } + + if v, ok := integrationData["service_id"].(string); ok && v != "" { + pagerDutyIntegration.PagerDutyIncidentConfiguration = + &types.PagerDutyIncidentConfiguration{ + ServiceId: aws.String(v), + } + } + + if v, ok := integrationData["secret_id"].(string); ok && v != "" { + pagerDutyIntegration.SecretId = aws.String(v) + } + + result = append(result, &types.IntegrationMemberPagerDutyConfiguration{Value: pagerDutyIntegration}) + } + + return result +} + +func flattenPagerDutyIntegration(integrations []types.Integration) []interface{} { + result := make([]interface{}, 0) + + for _, integration := range integrations { + if v, ok := integration.(*types.IntegrationMemberPagerDutyConfiguration); ok { + pagerDutyConfiguration := v.Value + pagerDutyData := map[string]interface{}{} + + if v := pagerDutyConfiguration.Name; v != nil { + pagerDutyData["name"] = v + } + + if v := pagerDutyConfiguration.PagerDutyIncidentConfiguration.ServiceId; v != nil { + pagerDutyData["service_id"] = v + } + + if v := pagerDutyConfiguration.SecretId; v != nil { + pagerDutyData["secret_id"] = v + } + + result = append(result, pagerDutyData) + } + } + return result +} diff --git a/internal/service/ssmincidents/helper.go b/internal/service/ssmincidents/helper.go index 138641d9a18..641dd336a32 100644 --- a/internal/service/ssmincidents/helper.go +++ b/internal/service/ssmincidents/helper.go @@ -1,12 +1,12 @@ package ssmincidents -// contains misc functions used by multiple files - import ( "context" "fmt" "github.com/aws/aws-sdk-go-v2/service/ssmincidents" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/flex" ) func getReplicationSetARN(context context.Context, client *ssmincidents.Client) (string, error) { @@ -23,3 +23,34 @@ func getReplicationSetARN(context context.Context, client *ssmincidents.Client) // currently only one replication set is supported return replicationSets.ReplicationSetArns[0], nil } + +func setResponsePlanResourceData( + d *schema.ResourceData, + getResponsePlanOutput *ssmincidents.GetResponsePlanOutput, +) error { + if err := d.Set("action", flattenAction(getResponsePlanOutput.Actions)); err != nil { + return err + } + if err := d.Set("arn", getResponsePlanOutput.Arn); err != nil { + return err + } + if err := d.Set("chat_channel", flattenChatChannel(getResponsePlanOutput.ChatChannel)); err != nil { + return err + } + if err := d.Set("display_name", getResponsePlanOutput.DisplayName); err != nil { + return err + } + if err := d.Set("engagements", flex.FlattenStringValueSet(getResponsePlanOutput.Engagements)); err != nil { + return err + } + if err := d.Set("incident_template", flattenIncidentTemplate(getResponsePlanOutput.IncidentTemplate)); err != nil { + return err + } + if err := d.Set("integration", flattenIntegration(getResponsePlanOutput.Integrations)); err != nil { + return err + } + if err := d.Set("name", getResponsePlanOutput.Name); err != nil { + return err + } + return nil +} diff --git a/internal/service/ssmincidents/response_plan.go b/internal/service/ssmincidents/response_plan.go new file mode 100644 index 00000000000..d853147dc87 --- /dev/null +++ b/internal/service/ssmincidents/response_plan.go @@ -0,0 +1,353 @@ +package ssmincidents + +import ( + "context" + "errors" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmincidents" + "github.com/aws/aws-sdk-go-v2/service/ssmincidents/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/flex" + 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" +) + +const ( + ResNameResponsePlan = "Response Plan" +) + +// @SDKResource("aws_ssmincidents_response_plan") +func ResourceResponsePlan() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceResponsePlanCreate, + ReadWithoutTimeout: resourceResponsePlanRead, + UpdateWithoutTimeout: resourceResponsePlanUpdate, + DeleteWithoutTimeout: resourceResponsePlanDelete, + + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ssm_automation": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "document_name": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + }, + "document_version": { + Type: schema.TypeString, + Optional: true, + }, + "target_account": { + Type: schema.TypeString, + Optional: true, + }, + "parameter": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "values": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "dynamic_parameters": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "chat_channel": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + }, + "engagements": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "incident_template": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "title": { + Type: schema.TypeString, + Required: true, + }, + "impact": { + Type: schema.TypeInt, + Required: true, + }, + "dedupe_string": { + Type: schema.TypeString, + Optional: true, + }, + "incident_tags": tftags.TagsSchema(), + "notification_target": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sns_topic_arn": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "summary": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "integration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pagerduty": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "service_id": { + Type: schema.TypeString, + Required: true, + }, + "secret_id": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + CustomizeDiff: verify.SetTagsDiff, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func resourceResponsePlanCreate(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*conns.AWSClient).SSMIncidentsClient() + + input := &ssmincidents.CreateResponsePlanInput{ + Actions: expandAction(d.Get("action").([]interface{})), + ChatChannel: expandChatChannel(d.Get("chat_channel").(*schema.Set)), + DisplayName: aws.String(d.Get("display_name").(string)), + Engagements: flex.ExpandStringValueList(d.Get("engagements").(*schema.Set).List()), + IncidentTemplate: expandIncidentTemplate(d.Get("incident_template").([]interface{})), + Integrations: expandIntegration(d.Get("integration").([]interface{})), + Name: aws.String(d.Get("name").(string)), + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(context, d.Get("tags").(map[string]interface{}))) + + if len(tags) > 0 { + input.Tags = tags.IgnoreAWS().Map() + } + + output, err := client.CreateResponsePlan(context, input) + + if err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionCreating, ResNameResponsePlan, d.Get("name").(string), err) + } + + if output == nil { + return create.DiagError(names.SSMIncidents, create.ErrActionCreating, ResNameResponsePlan, d.Get("name").(string), errors.New("empty output")) + } + + d.SetId(aws.ToString(output.Arn)) + + return resourceResponsePlanRead(context, d, meta) +} + +func resourceResponsePlanRead(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*conns.AWSClient).SSMIncidentsClient() + + responsePlan, err := FindResponsePlanByID(context, client, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSMIncidents ResponsePlan (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionReading, ResNameResponsePlan, d.Id(), err) + } + + if err := setResponsePlanResourceData(d, responsePlan); err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionSetting, ResNameResponsePlan, d.Id(), err) + } + + if diagErr := setResourceDataTags(context, d, meta, client, ResNameResponsePlan); diagErr != nil { + return diagErr + } + + return nil +} + +func resourceResponsePlanUpdate(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*conns.AWSClient).SSMIncidentsClient() + + hasNonTagUpdate := false + + input := &ssmincidents.UpdateResponsePlanInput{ + Arn: aws.String(d.Id()), + } + + if d.HasChanges("action") { + input.Actions = expandAction(d.Get("action").([]interface{})) + + hasNonTagUpdate = true + } + + if d.HasChanges("chat_channel") { + input.ChatChannel = expandChatChannel(d.Get("chat_channel").(*schema.Set)) + + hasNonTagUpdate = true + } + + if d.HasChanges("display_name") { + input.DisplayName = aws.String(d.Get("display_name").(string)) + + hasNonTagUpdate = true + } + + if d.HasChanges("engagements") { + input.Engagements = flex.ExpandStringValueList(d.Get("engagements").(*schema.Set).List()) + + hasNonTagUpdate = true + } + + if d.HasChanges("incident_template") { + incidentTemplate := d.Get("incident_template") + template := expandIncidentTemplate(incidentTemplate.([]interface{})) + updateResponsePlanInputWithIncidentTemplate(input, template) + + hasNonTagUpdate = true + } + + if d.HasChanges("integration") { + input.Integrations = expandIntegration(d.Get("integration").([]interface{})) + + hasNonTagUpdate = true + } + + // tags can have a change without tags_all having a change when value of tag is "" + if d.HasChanges("tags_all", "tags") { + log.Printf("[DEBUG] Updating SSMIncidents ResponsePlan tags") + + if err := updateResourceTags(context, client, d); err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionUpdating, ResNameResponsePlan, d.Id(), err) + } + } + + if hasNonTagUpdate { + log.Printf("[DEBUG] Updating SSMIncidents ResponsePlan (%s): %#v", d.Id(), input) + _, err := client.UpdateResponsePlan(context, input) + + if err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionUpdating, ResNameResponsePlan, d.Id(), err) + } + } + + return resourceResponsePlanRead(context, d, meta) +} + +func resourceResponsePlanDelete(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*conns.AWSClient).SSMIncidentsClient() + + log.Printf("[INFO] Deleting SSMIncidents ResponsePlan %s", d.Id()) + + input := &ssmincidents.DeleteResponsePlanInput{ + Arn: aws.String(d.Id()), + } + + _, err := client.DeleteResponsePlan(context, input) + + if err != nil { + var notFoundError *types.ResourceNotFoundException + + if errors.As(err, ¬FoundError) { + return nil + } + + return create.DiagError(names.SSMIncidents, create.ErrActionDeleting, ResNameResponsePlan, d.Id(), err) + } + + return nil +} + +// input validation already done in flattenIncidentTemplate function +func updateResponsePlanInputWithIncidentTemplate(input *ssmincidents.UpdateResponsePlanInput, template *types.IncidentTemplate) { + input.IncidentTemplateImpact = template.Impact + input.IncidentTemplateTitle = template.Title + input.IncidentTemplateTags = template.IncidentTags + input.IncidentTemplateNotificationTargets = template.NotificationTargets + input.IncidentTemplateDedupeString = template.DedupeString + input.IncidentTemplateSummary = template.Summary +} diff --git a/internal/service/ssmincidents/response_plan_data_source.go b/internal/service/ssmincidents/response_plan_data_source.go new file mode 100644 index 00000000000..033b7ad6c82 --- /dev/null +++ b/internal/service/ssmincidents/response_plan_data_source.go @@ -0,0 +1,200 @@ +package ssmincidents + +import ( + "context" + + "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_ssmincidents_response_plan") +func DataSourceResponsePlan() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceResponsePlanRead, + + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ssm_automation": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "document_name": { + Type: schema.TypeString, + Computed: true, + }, + "role_arn": { + Type: schema.TypeString, + Computed: true, + }, + "document_version": { + Type: schema.TypeString, + Computed: true, + }, + "target_account": { + Type: schema.TypeString, + Computed: true, + }, + "parameter": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "values": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "dynamic_parameters": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, + "arn": { + Type: schema.TypeString, + Required: true, + }, + "chat_channel": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + }, + "engagements": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "incident_template": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "title": { + Type: schema.TypeString, + Computed: true, + }, + "impact": { + Type: schema.TypeInt, + Computed: true, + }, + "dedupe_string": { + Type: schema.TypeString, + Computed: true, + }, + "incident_tags": tftags.TagsSchemaComputed(), + "notification_target": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sns_topic_arn": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "summary": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "integration": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pagerduty": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "service_id": { + Type: schema.TypeString, + Computed: true, + }, + "secret_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchemaComputed(), + }, + } +} + +const ( + DSNameResponsePlan = "Response Plan Data Source" +) + +func dataSourceResponsePlanRead(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*conns.AWSClient).SSMIncidentsClient() + + d.SetId(d.Get("arn").(string)) + + responsePlan, err := FindResponsePlanByID(context, client, d.Id()) + + if err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameResponsePlan, d.Id(), err) + } + + if err := setResponsePlanResourceData(d, responsePlan); err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameResponsePlan, d.Id(), err) + } + + tags, err := listResourceTags(context, client, d.Id()) + if err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameResponsePlan, 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.SSMIncidents, create.ErrActionSetting, DSNameResponsePlan, d.Id(), err) + } + + return nil +} diff --git a/internal/service/ssmincidents/response_plan_data_source_test.go b/internal/service/ssmincidents/response_plan_data_source_test.go new file mode 100644 index 00000000000..0722660d5b3 --- /dev/null +++ b/internal/service/ssmincidents/response_plan_data_source_test.go @@ -0,0 +1,325 @@ +package ssmincidents_test + +import ( + "context" + "fmt" + "regexp" + "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 testResponsePlanDataSource_basic(t *testing.T) { + ctx := context.Background() + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rTitle := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_ssmincidents_response_plan.test" + resourceName := "aws_ssmincidents_response_plan.test" + + snsTopic1 := "aws_sns_topic.topic1" + snsTopic2 := "aws_sns_topic.topic2" + + displayName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + chatChannelTopic := "aws_sns_topic.channel_topic" + engagementContactArn := "arn:aws:ssm-contacts:us-east-2:111122223333:contact/test1" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.SSMIncidentsEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMIncidentsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckResponsePlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccResponsePlanDataSourceConfig_basic( + rName, + rTitle, + snsTopic1, + snsTopic2, + displayName, + chatChannelTopic, + engagementContactArn, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckResponsePlanExists(dataSourceName), + + resource.TestCheckResourceAttrPair(resourceName, "name", dataSourceName, "name"), + resource.TestCheckResourceAttrPair(resourceName, "incident_template.0.title", dataSourceName, "incident_template.0.title"), + resource.TestCheckResourceAttrPair(resourceName, "incident_template.0.impact", dataSourceName, "incident_template.0.impact"), + resource.TestCheckResourceAttrPair(resourceName, "incident_template.0.dedupe_string", dataSourceName, "incident_template.0.dedupe_string"), + resource.TestCheckResourceAttrPair(resourceName, "incident_template.0.summary", dataSourceName, "incident_template.0.summary"), + resource.TestCheckResourceAttrPair(resourceName, "incident_template.0.incident_tags.%", dataSourceName, "incident_template.0.incident_tags.%"), + resource.TestCheckResourceAttrPair(resourceName, "incident_template.0.incident_tags.a", dataSourceName, "incident_template.0.incident_tags.a"), + resource.TestCheckResourceAttrPair(resourceName, "incident_template.0.incident_tags.b", dataSourceName, "incident_template.0.incident_tags.b"), + resource.TestCheckTypeSetElemAttrPair(dataSourceName, "incident_template.0.notification_target.*.sns_topic_arn", snsTopic1, "arn"), + resource.TestCheckTypeSetElemAttrPair(dataSourceName, "incident_template.0.notification_target.*.sns_topic_arn", snsTopic2, "arn"), + resource.TestCheckResourceAttrPair(resourceName, "display_name", dataSourceName, "display_name"), + resource.TestCheckTypeSetElemAttrPair(dataSourceName, "chat_channel.0", chatChannelTopic, "arn"), + resource.TestCheckResourceAttr(dataSourceName, "engagements.0", engagementContactArn), + resource.TestCheckTypeSetElemAttrPair( + dataSourceName, + "action.0.ssm_automation.0.document_name", + "aws_ssm_document.document", + "name", + ), + resource.TestCheckTypeSetElemAttrPair( + dataSourceName, + "action.0.ssm_automation.0.role_arn", + "aws_iam_role.role", + "arn", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.#", + dataSourceName, + "action.#", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.#", + dataSourceName, + "action.0.ssm_automation.#", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.0.document_version", + dataSourceName, + "action.0.ssm_automation.0.document_version", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.0.target_account", + dataSourceName, + "action.0.ssm_automation.0.target_account", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.0.parameter.#", + dataSourceName, + "action.0.ssm_automation.0.parameter.#", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.0.parameter.0.name", + dataSourceName, + "action.0.ssm_automation.0.parameter.0.name", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.0.parameter.0.values.#", + dataSourceName, + "action.0.ssm_automation.0.parameter.0.values.#", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.0.parameter.0.values.0", + dataSourceName, + "action.0.ssm_automation.0.parameter.0.values.0", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.0.parameter.0.values.1", + dataSourceName, + "action.0.ssm_automation.0.parameter.0.values.1", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.0.dynamic_parameters.#", + dataSourceName, + "action.0.ssm_automation.0.dynamic_parameters.#", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "action.0.ssm_automation.0.dynamic_parameters.anotherKey", + dataSourceName, + "action.0.ssm_automation.0.dynamic_parameters.anotherKey", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "integration.#", + dataSourceName, + "integration.#", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "integration.0.pagerduty.#", + dataSourceName, + "integration.0.pagerduty.#", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "integration.0.pagerduty.0.name", + dataSourceName, + "integration.0.pagerduty.0.name", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "integration.0.pagerduty.0.service_id", + dataSourceName, + "integration.0.pagerduty.0.service_id", + ), + resource.TestCheckResourceAttrPair( + resourceName, + "integration.0.pagerduty.0.secret_id", + dataSourceName, + "integration.0.pagerduty.0.secret_id", + ), + resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), + resource.TestCheckResourceAttrPair(resourceName, "tags.a", dataSourceName, "tags.a"), + resource.TestCheckResourceAttrPair(resourceName, "tags.b", dataSourceName, "tags.b"), + + acctest.MatchResourceAttrGlobalARN(dataSourceName, "arn", "ssm-incidents", regexp.MustCompile(`response-plan/+.`)), + ), + }, + }, + }) +} + +func testAccResponsePlanDataSourceConfig_basic( + name, + title, + topic1, + topic2, + displayName, + chatChannelTopic, + engagementContactArn string) string { + return fmt.Sprintf(` +resource "aws_sns_topic" "topic1" {} +resource "aws_sns_topic" "topic2" {} +resource "aws_sns_topic" "channel_topic" {} + +resource "aws_ssmincidents_replication_set" "test_replication_set" { + region { + name = %[1]q + } +} + +resource "aws_iam_role" "role" { + assume_role_policy = < NOTE: A response plan 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 ResponsePlan Resource. + +The following arguments are required: + +* `name` - (Required) The name of the response plan. + +The `incident_template` configuration block is required and supports the following arguments: + +* `title` - (Required) The title of a generated incident. +* `impact` - (Required) The impact value of a generated incident. The following values are supported: + * `1` - Severe Impact + * `2` - High Impact + * `3` - Medium Impact + * `4` - Low Impact + * `5` - No Impact +* `dedupe_string` - (Optional) A string used to stop Incident Manager from creating multiple incident records for the same incident. +* `incident_tags` - (Optional) The tags assigned to an incident template. When an incident starts, Incident Manager + assigns the tags specified in the template to the incident. +* `summary` - (Optional) The summary of an incident. +* `notification_target` - (Optional) The Amazon Simple Notification Service (Amazon SNS) targets that this incident + notifies when it is updated. The `notification_target` configuration block supports the following argument: + * `sns_topic_arn` - (Required) The ARN of the Amazon SNS topic. + +The following arguments are optional: + +* `tags` - (Optional) The tags applied to the response plan. +* `display_name` - (Optional) The long format of the response plan name. This field can contain spaces. +* `chat_channel` - (Optional) The Chatbot chat channel used for collaboration during an incident. +* `engagements` - (Optional) The Amazon Resource Name (ARN) for the contacts and escalation plans that the response plan engages during an incident. +* `action` - (Optional) The actions that the response plan starts at the beginning of an incident. + * `ssm_automation` - (Optional) The Systems Manager automation document to start as the runbook at the beginning of the incident. The following values are supported: + * `document_name` - (Required) The automation document's name. + * `role_arn` - (Required) The Amazon Resource Name (ARN) of the role that the automation document assumes when + it runs commands. + * `document_version` - (Optional) The version of the automation document to use at runtime. + * `target_account` - (Optional) The account that the automation document runs in. This can be in either the + management account or an application account. + * `parameter` - (Optional) The key-value pair parameters to use when the automation document runs. The following + values are supported: + * `name` - The name of parameter. + * `values` - The values for the associated parameter name. + * `dynamic_parameters` - (Optional) The key-value pair to resolve dynamic parameter values when processing a Systems Manager Automation runbook. +* `integration` - (Optional) Information about third-party services integrated into the response plan. The following values are supported: + * `pagerduty` - (Optional) Details about the PagerDuty configuration for a response plan. The following values are supported: + * `name` - (Required) The name of the PagerDuty configuration. + * `service_id` - (Required) The ID of the PagerDuty service that the response plan associated with the incident at launch. + * `secret_id` - (Required) The ID of the AWS Secrets Manager secret that stores your PagerDuty key — + either a General Access REST API Key or User Token REST API Key — and other user credentials. + +For more information about the constraints for each field, see [CreateResponsePlan](https://docs.aws.amazon.com/incident-manager/latest/APIReference/API_CreateResponsePlan.html) in the *AWS Systems Manager Incident Manager API Reference*. + +## Attributes Reference + +In addition to all preceding arguments, the following attributes are exported: + +* `arn` - The ARN of the response plan. +* `tags_all` - A 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 + +To import an Incident Manager response plan, specify the response plan ARN. You can find the response plan ARN in the +AWS Management Console. Use the following command to run the import operation: + +``` +$ terraform import aws_ssmincidents_response_plan.responsePlanName ARNValue +``` From 9b5a27b5933e14d216a4435030547b9905120759 Mon Sep 17 00:00:00 2001 From: Kevin Chun Date: Wed, 12 Apr 2023 14:48:19 +1000 Subject: [PATCH 2/5] fix formatting --- internal/service/ssmincidents/find.go | 2 +- internal/service/ssmincidents/flex.go | 4 +- internal/service/ssmincidents/helper.go | 20 +- .../service/ssmincidents/response_plan.go | 47 ++-- .../ssmincidents/response_plan_data_source.go | 8 +- .../response_plan_data_source_test.go | 49 ++-- .../ssmincidents/response_plan_test.go | 234 +++++++++--------- .../ssmincidents/service_package_gen.go | 4 + .../ssmincidents_response_plan_test.go | 7 - .../ssmincidents_response_plan.html.markdown | 37 ++- .../ssmincidents_response_plan.html.markdown | 61 ++--- 11 files changed, 231 insertions(+), 242 deletions(-) diff --git a/internal/service/ssmincidents/find.go b/internal/service/ssmincidents/find.go index ccfa7979a5f..7f00715a16d 100644 --- a/internal/service/ssmincidents/find.go +++ b/internal/service/ssmincidents/find.go @@ -20,7 +20,7 @@ func FindResponsePlanByID(context context.Context, client *ssmincidents.Client, if err != nil { var nfe *types.ResourceNotFoundException if errors.As(err, &nfe) { - return nil, &resource.NotFoundError{ + return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, } diff --git a/internal/service/ssmincidents/flex.go b/internal/service/ssmincidents/flex.go index 5f26cd5257a..904d12033f9 100644 --- a/internal/service/ssmincidents/flex.go +++ b/internal/service/ssmincidents/flex.go @@ -152,7 +152,7 @@ func flattenNotificationTargets(targets []types.NotificationTargetItem) []map[st } func expandChatChannel(chatChannels *schema.Set) types.ChatChannel { - chatChannelList := flex.ExpandStringValueList(chatChannels.List()) + chatChannelList := flex.ExpandStringValueSet(chatChannels) if len(chatChannelList) == 0 { return &types.ChatChannelMemberEmpty{ @@ -287,7 +287,7 @@ func expandParameters(parameters *schema.Set) map[string][]string { for _, parameter := range parameters.List() { parameterData := parameter.(map[string]interface{}) name := parameterData["name"].(string) - values := flex.ExpandStringValueList(parameterData["values"].(*schema.Set).List()) + values := flex.ExpandStringValueSet(parameterData["values"].(*schema.Set)) parameterMap[name] = values } return parameterMap diff --git a/internal/service/ssmincidents/helper.go b/internal/service/ssmincidents/helper.go index 641dd336a32..d94ebb1f676 100644 --- a/internal/service/ssmincidents/helper.go +++ b/internal/service/ssmincidents/helper.go @@ -27,30 +27,30 @@ func getReplicationSetARN(context context.Context, client *ssmincidents.Client) func setResponsePlanResourceData( d *schema.ResourceData, getResponsePlanOutput *ssmincidents.GetResponsePlanOutput, -) error { +) (*schema.ResourceData, error) { if err := d.Set("action", flattenAction(getResponsePlanOutput.Actions)); err != nil { - return err + return d, err } if err := d.Set("arn", getResponsePlanOutput.Arn); err != nil { - return err + return d, err } if err := d.Set("chat_channel", flattenChatChannel(getResponsePlanOutput.ChatChannel)); err != nil { - return err + return d, err } if err := d.Set("display_name", getResponsePlanOutput.DisplayName); err != nil { - return err + return d, err } if err := d.Set("engagements", flex.FlattenStringValueSet(getResponsePlanOutput.Engagements)); err != nil { - return err + return d, err } if err := d.Set("incident_template", flattenIncidentTemplate(getResponsePlanOutput.IncidentTemplate)); err != nil { - return err + return d, err } if err := d.Set("integration", flattenIntegration(getResponsePlanOutput.Integrations)); err != nil { - return err + return d, err } if err := d.Set("name", getResponsePlanOutput.Name); err != nil { - return err + return d, err } - return nil + return d, nil } diff --git a/internal/service/ssmincidents/response_plan.go b/internal/service/ssmincidents/response_plan.go index d853147dc87..5d2402566c3 100644 --- a/internal/service/ssmincidents/response_plan.go +++ b/internal/service/ssmincidents/response_plan.go @@ -23,7 +23,8 @@ const ( ResNameResponsePlan = "Response Plan" ) -// @SDKResource("aws_ssmincidents_response_plan") +// @SDKResource("aws_ssmincidents_response_plan", name="Response Plan") +// @Tags(identifierAttribute="id") func ResourceResponsePlan() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceResponsePlanCreate, @@ -179,8 +180,8 @@ func ResourceResponsePlan() *schema.Resource { Required: true, ForceNew: true, }, - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, Importer: &schema.ResourceImporter{ @@ -189,27 +190,21 @@ func ResourceResponsePlan() *schema.Resource { } } -func resourceResponsePlanCreate(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceResponsePlanCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*conns.AWSClient).SSMIncidentsClient() input := &ssmincidents.CreateResponsePlanInput{ Actions: expandAction(d.Get("action").([]interface{})), ChatChannel: expandChatChannel(d.Get("chat_channel").(*schema.Set)), DisplayName: aws.String(d.Get("display_name").(string)), - Engagements: flex.ExpandStringValueList(d.Get("engagements").(*schema.Set).List()), + Engagements: flex.ExpandStringValueSet(d.Get("engagements").(*schema.Set)), IncidentTemplate: expandIncidentTemplate(d.Get("incident_template").([]interface{})), Integrations: expandIntegration(d.Get("integration").([]interface{})), Name: aws.String(d.Get("name").(string)), + Tags: GetTagsIn(ctx), } - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - tags := defaultTagsConfig.MergeTags(tftags.New(context, d.Get("tags").(map[string]interface{}))) - - if len(tags) > 0 { - input.Tags = tags.IgnoreAWS().Map() - } - - output, err := client.CreateResponsePlan(context, input) + output, err := client.CreateResponsePlan(ctx, input) if err != nil { return create.DiagError(names.SSMIncidents, create.ErrActionCreating, ResNameResponsePlan, d.Get("name").(string), err) @@ -221,13 +216,13 @@ func resourceResponsePlanCreate(context context.Context, d *schema.ResourceData, d.SetId(aws.ToString(output.Arn)) - return resourceResponsePlanRead(context, d, meta) + return resourceResponsePlanRead(ctx, d, meta) } -func resourceResponsePlanRead(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceResponsePlanRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*conns.AWSClient).SSMIncidentsClient() - responsePlan, err := FindResponsePlanByID(context, client, d.Id()) + responsePlan, err := FindResponsePlanByID(ctx, client, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] SSMIncidents ResponsePlan (%s) not found, removing from state", d.Id()) @@ -239,18 +234,14 @@ func resourceResponsePlanRead(context context.Context, d *schema.ResourceData, m return create.DiagError(names.SSMIncidents, create.ErrActionReading, ResNameResponsePlan, d.Id(), err) } - if err := setResponsePlanResourceData(d, responsePlan); err != nil { + if d, err := setResponsePlanResourceData(d, responsePlan); err != nil { return create.DiagError(names.SSMIncidents, create.ErrActionSetting, ResNameResponsePlan, d.Id(), err) } - if diagErr := setResourceDataTags(context, d, meta, client, ResNameResponsePlan); diagErr != nil { - return diagErr - } - return nil } -func resourceResponsePlanUpdate(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceResponsePlanUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*conns.AWSClient).SSMIncidentsClient() hasNonTagUpdate := false @@ -278,7 +269,7 @@ func resourceResponsePlanUpdate(context context.Context, d *schema.ResourceData, } if d.HasChanges("engagements") { - input.Engagements = flex.ExpandStringValueList(d.Get("engagements").(*schema.Set).List()) + input.Engagements = flex.ExpandStringValueSet(d.Get("engagements").(*schema.Set)) hasNonTagUpdate = true } @@ -301,24 +292,24 @@ func resourceResponsePlanUpdate(context context.Context, d *schema.ResourceData, if d.HasChanges("tags_all", "tags") { log.Printf("[DEBUG] Updating SSMIncidents ResponsePlan tags") - if err := updateResourceTags(context, client, d); err != nil { + if err := updateResourceTags(ctx, client, d); err != nil { return create.DiagError(names.SSMIncidents, create.ErrActionUpdating, ResNameResponsePlan, d.Id(), err) } } if hasNonTagUpdate { log.Printf("[DEBUG] Updating SSMIncidents ResponsePlan (%s): %#v", d.Id(), input) - _, err := client.UpdateResponsePlan(context, input) + _, err := client.UpdateResponsePlan(ctx, input) if err != nil { return create.DiagError(names.SSMIncidents, create.ErrActionUpdating, ResNameResponsePlan, d.Id(), err) } } - return resourceResponsePlanRead(context, d, meta) + return resourceResponsePlanRead(ctx, d, meta) } -func resourceResponsePlanDelete(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceResponsePlanDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*conns.AWSClient).SSMIncidentsClient() log.Printf("[INFO] Deleting SSMIncidents ResponsePlan %s", d.Id()) @@ -327,7 +318,7 @@ func resourceResponsePlanDelete(context context.Context, d *schema.ResourceData, Arn: aws.String(d.Id()), } - _, err := client.DeleteResponsePlan(context, input) + _, err := client.DeleteResponsePlan(ctx, input) if err != nil { var notFoundError *types.ResourceNotFoundException diff --git a/internal/service/ssmincidents/response_plan_data_source.go b/internal/service/ssmincidents/response_plan_data_source.go index 033b7ad6c82..2b02a529993 100644 --- a/internal/service/ssmincidents/response_plan_data_source.go +++ b/internal/service/ssmincidents/response_plan_data_source.go @@ -169,22 +169,22 @@ const ( DSNameResponsePlan = "Response Plan Data Source" ) -func dataSourceResponsePlanRead(context context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func dataSourceResponsePlanRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*conns.AWSClient).SSMIncidentsClient() d.SetId(d.Get("arn").(string)) - responsePlan, err := FindResponsePlanByID(context, client, d.Id()) + responsePlan, err := FindResponsePlanByID(ctx, client, d.Id()) if err != nil { return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameResponsePlan, d.Id(), err) } - if err := setResponsePlanResourceData(d, responsePlan); err != nil { + if d, err := setResponsePlanResourceData(d, responsePlan); err != nil { return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameResponsePlan, d.Id(), err) } - tags, err := listResourceTags(context, client, d.Id()) + tags, err := ListTags(ctx, client, d.Id()) if err != nil { return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameResponsePlan, d.Id(), err) } diff --git a/internal/service/ssmincidents/response_plan_data_source_test.go b/internal/service/ssmincidents/response_plan_data_source_test.go index 0722660d5b3..54267a9bf39 100644 --- a/internal/service/ssmincidents/response_plan_data_source_test.go +++ b/internal/service/ssmincidents/response_plan_data_source_test.go @@ -25,7 +25,6 @@ func testResponsePlanDataSource_basic(t *testing.T) { displayName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) chatChannelTopic := "aws_sns_topic.channel_topic" - engagementContactArn := "arn:aws:ssm-contacts:us-east-2:111122223333:contact/test1" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { @@ -44,7 +43,6 @@ func testResponsePlanDataSource_basic(t *testing.T) { snsTopic2, displayName, chatChannelTopic, - engagementContactArn, ), Check: resource.ComposeTestCheckFunc( testAccCheckResponsePlanExists(dataSourceName), @@ -61,7 +59,12 @@ func testResponsePlanDataSource_basic(t *testing.T) { resource.TestCheckTypeSetElemAttrPair(dataSourceName, "incident_template.0.notification_target.*.sns_topic_arn", snsTopic2, "arn"), resource.TestCheckResourceAttrPair(resourceName, "display_name", dataSourceName, "display_name"), resource.TestCheckTypeSetElemAttrPair(dataSourceName, "chat_channel.0", chatChannelTopic, "arn"), - resource.TestCheckResourceAttr(dataSourceName, "engagements.0", engagementContactArn), + resource.TestCheckTypeSetElemAttrPair( + resourceName, + "engagements.0", + dataSourceName, + "engagements.0", + ), resource.TestCheckTypeSetElemAttrPair( dataSourceName, "action.0.ssm_automation.0.document_name", @@ -187,8 +190,9 @@ func testAccResponsePlanDataSourceConfig_basic( topic1, topic2, displayName, - chatChannelTopic, - engagementContactArn string) string { + chatChannelTopic string) string { + //lintignore:AWSAT003 + //lintignore:AWSAT005 return fmt.Sprintf(` resource "aws_sns_topic" "topic1" {} resource "aws_sns_topic" "topic2" {} @@ -249,7 +253,7 @@ DOC } resource "aws_ssmincidents_response_plan" "test" { - name = %[2]q + name = %[2]q incident_template { title = %[3]q @@ -273,7 +277,8 @@ resource "aws_ssmincidents_response_plan" "test" { display_name = %[6]q chat_channel = [%[7]s] - engagements = [%[8]q] + + engagements = ["arn:aws:ssm-contacts:us-east-2:111122223333:contact/test1"] action { ssm_automation { @@ -281,26 +286,26 @@ resource "aws_ssmincidents_response_plan" "test" { role_arn = aws_iam_role.role.arn document_version = "version1" target_account = "RESPONSE_PLAN_OWNER_ACCOUNT" - parameter { + parameter { name = "key" - values = ["value1","value2"] - } + values = ["value1", "value2"] + } dynamic_parameters = { anotherKey = "INVOLVED_RESOURCES" - } + } } } - // Comment out integration section as the configured PagerDuty secretId is invalid and the test will fail, - // as we do not want to expose credentials to public repository. - // Tested locally and PagerDuty integration work with response plan. - //integration { - // pagerduty { - // name = "pagerduty-test-terraform" - // service_id = "PNDIQ3N" - // secret_id = "PagerdutyPoshchuSecret" - // } - //} + # Comment out integration section as the configured PagerDuty secretId is invalid and the test will fail, + # as we do not want to expose credentials to public repository. + # Tested locally and PagerDuty integration work with response plan. + # integration { + # pagerduty { + # name = "pagerduty-test-terraform" + # service_id = "PNDIQ3N" + # secret_id = "PagerdutyPoshchuSecret" + # } + # } tags = { a = "tag1" @@ -321,5 +326,5 @@ data "aws_ssmincidents_response_plan" "test" { topic2+".arn", displayName, chatChannelTopic+".arn", - engagementContactArn) + ) } diff --git a/internal/service/ssmincidents/response_plan_test.go b/internal/service/ssmincidents/response_plan_test.go index 7030d6a6d2a..f88fd7b7910 100644 --- a/internal/service/ssmincidents/response_plan_test.go +++ b/internal/service/ssmincidents/response_plan_test.go @@ -568,7 +568,11 @@ func testResponsePlan_engagement(t *testing.T) { ctx := context.Background() rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + //lintignore:AWSAT003 + //lintignore:AWSAT005 contactArn1 := "arn:aws:ssm-contacts:us-east-2:111122223333:contact/test1" + //lintignore:AWSAT003 + //lintignore:AWSAT005 contactArn2 := "arn:aws:ssm-contacts:us-east-2:111122223333:contact/test2" resourceName := "aws_ssmincidents_response_plan.test" @@ -783,66 +787,72 @@ func testResponsePlan_action(t *testing.T) { }) } -func testResponsePlan_integration(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_ssmincidents_response_plan.test" - pagerdutyName := "pagerduty-test-terraform" - pagerdutyServiceId := "example" - pagerdutySecretId := "example" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.SSMIncidentsEndpointID) - }, - ErrorCheck: acctest.ErrorCheck(t, names.SSMIncidentsEndpointID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckResponsePlanDestroy, - Steps: []resource.TestStep{ - { - Config: testAccResponsePlanConfig_pagerdutyIntegration( - rName, - pagerdutyName, - pagerdutyServiceId, - pagerdutySecretId, - ), - Check: resource.ComposeTestCheckFunc( - testAccCheckResponsePlanExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "integration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "integration.0.pagerduty.#", "1"), - resource.TestCheckResourceAttr( - resourceName, - "integration.0.pagerduty.0.name", - pagerdutyName, - ), - resource.TestCheckResourceAttr( - resourceName, - "integration.0.pagerduty.0.service_id", - pagerdutyServiceId, - ), - resource.TestCheckResourceAttr( - resourceName, - "integration.0.pagerduty.0.secret_id", - pagerdutySecretId, - ), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"replication_set_arn"}, - }, - }, - }) -} +// +// Comment out integration test as the configured PagerDuty secretId is invalid and the test will fail, +// as we do not want to expose credentials to public repository. +// +// Tested locally and PagerDuty integration work with response plan. +// +//func testResponsePlan_integration(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_ssmincidents_response_plan.test" +// pagerdutyName := "pagerduty-test-terraform" +// pagerdutyServiceId := "example" +// pagerdutySecretId := "example" +// +// resource.Test(t, resource.TestCase{ +// PreCheck: func() { +// acctest.PreCheck(ctx, t) +// acctest.PreCheckPartitionHasService(t, names.SSMIncidentsEndpointID) +// }, +// ErrorCheck: acctest.ErrorCheck(t, names.SSMIncidentsEndpointID), +// ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, +// CheckDestroy: testAccCheckResponsePlanDestroy, +// Steps: []resource.TestStep{ +// { +// Config: testAccResponsePlanConfig_pagerdutyIntegration( +// rName, +// pagerdutyName, +// pagerdutyServiceId, +// pagerdutySecretId, +// ), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckResponsePlanExists(resourceName), +// resource.TestCheckResourceAttr(resourceName, "integration.#", "1"), +// resource.TestCheckResourceAttr(resourceName, "integration.0.pagerduty.#", "1"), +// resource.TestCheckResourceAttr( +// resourceName, +// "integration.0.pagerduty.0.name", +// pagerdutyName, +// ), +// resource.TestCheckResourceAttr( +// resourceName, +// "integration.0.pagerduty.0.service_id", +// pagerdutyServiceId, +// ), +// resource.TestCheckResourceAttr( +// resourceName, +// "integration.0.pagerduty.0.secret_id", +// pagerdutySecretId, +// ), +// ), +// }, +// { +// ResourceName: resourceName, +// ImportState: true, +// ImportStateVerify: true, +// ImportStateVerifyIgnore: []string{"replication_set_arn"}, +// }, +// }, +// }) +//} func testAccCheckResponsePlanDestroy(s *terraform.State) error { client := acctest.Provider.Meta().(*conns.AWSClient).SSMIncidentsClient() @@ -917,7 +927,7 @@ func testAccResponsePlanConfig_basic(name, title, impact string) string { testAccResponsePlanConfigBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[2]q @@ -940,7 +950,7 @@ func testAccResponsePlanConfig_oneTag(name, title, tagKey, tagVal string) string testAccResponsePlanConfigBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[2]q @@ -961,7 +971,7 @@ func testAccResponsePlanConfig_twoTags(name, title, tag1Key, tag1Val, tag2Key, t testAccResponsePlanConfigBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[2]q @@ -984,7 +994,7 @@ func testAccResponsePlanConfig_incidentTemplateOptionalFields(name, title, dedup testAccResponsePlanConfigSNSTopicBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[2]q @@ -1015,7 +1025,7 @@ func testAccResponsePlanConfig_displayName(name, displayName string) string { testAccResponsePlanConfigBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[1]q @@ -1035,7 +1045,7 @@ func testAccResponsePlanConfig_chatChannel(name, chatChannelTopic string) string testAccResponsePlanConfigSNSTopicBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[1]q @@ -1055,7 +1065,7 @@ func testAccResponsePlanConfig_twoChatChannels(name, chatChannelOneTopic, chatCh testAccResponsePlanConfigSNSTopicBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[1]q @@ -1074,7 +1084,7 @@ func testAccResponsePlanConfig_emptyChatChannel(name string) string { testAccResponsePlanConfigBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[1]q @@ -1093,7 +1103,7 @@ func testAccResponsePlanConfig_engagement(name, contactArn string) string { testAccResponsePlanConfigBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[1]q @@ -1112,7 +1122,7 @@ func testAccResponsePlanConfig_twoEngagements(name, contactArn1, contactArn2 str testAccResponsePlanConfigBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[1]q @@ -1131,7 +1141,7 @@ func testAccResponsePlanConfig_emptyEngagements(name string) string { testAccResponsePlanConfigBase(), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[1]q @@ -1148,11 +1158,11 @@ resource "aws_ssmincidents_response_plan" "test" { func testAccResponsePlanConfig_action1(name string) string { return acctest.ConfigCompose( testAccResponsePlanConfigBase(), - testAccResponsePlanConfigIamRoleBase(name), + testAccResponsePlanConfigIAMRoleBase(name), testAccResponsePlanConfigDocumentBase(name), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[1]q @@ -1165,13 +1175,13 @@ resource "aws_ssmincidents_response_plan" "test" { role_arn = aws_iam_role.role1.arn document_version = "version1" target_account = "RESPONSE_PLAN_OWNER_ACCOUNT" - parameter { + parameter { name = "key" - values = ["value1","value2"] - } + values = ["value1", "value2"] + } dynamic_parameters = { anotherKey = "INVOLVED_RESOURCES" - } + } } } @@ -1183,11 +1193,11 @@ resource "aws_ssmincidents_response_plan" "test" { func testAccResponsePlanConfig_action2(name string) string { return acctest.ConfigCompose( testAccResponsePlanConfigBase(), - testAccResponsePlanConfigIamRoleBase(name), + testAccResponsePlanConfigIAMRoleBase(name), testAccResponsePlanConfigDocumentBase(name), fmt.Sprintf(` resource "aws_ssmincidents_response_plan" "test" { - name = %[1]q + name = %[1]q incident_template { title = %[1]q @@ -1200,13 +1210,13 @@ resource "aws_ssmincidents_response_plan" "test" { role_arn = aws_iam_role.role2.arn document_version = "version2" target_account = "IMPACTED_ACCOUNT" - parameter { + parameter { name = "foo" - values = ["bar"] - } + values = ["bar"] + } dynamic_parameters = { someKey = "INCIDENT_RECORD_ARN" - } + } } } @@ -1215,7 +1225,7 @@ resource "aws_ssmincidents_response_plan" "test" { `, name)) } -func testAccResponsePlanConfigIamRoleBase(name string) string { +func testAccResponsePlanConfigIAMRoleBase(name string) string { return fmt.Sprintf(` resource "aws_iam_role" "role1" { assume_role_policy = < Date: Thu, 13 Apr 2023 10:09:27 +1000 Subject: [PATCH 3/5] add changelog --- .changelog/30665.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/30665.txt diff --git a/.changelog/30665.txt b/.changelog/30665.txt new file mode 100644 index 00000000000..439cec44031 --- /dev/null +++ b/.changelog/30665.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_ssmincidents_response_plan +``` + +```release-note:new-data-source +aws_ssmincidents_response_plan +``` \ No newline at end of file From 1a5f188b640addba3b5ffa2aaa43baff68371e17 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 24 Apr 2023 08:00:06 -0400 Subject: [PATCH 4/5] Simplify 'resourceResponsePlanUpdate'. --- .../service/ssmincidents/response_plan.go | 73 +++++++------------ 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/internal/service/ssmincidents/response_plan.go b/internal/service/ssmincidents/response_plan.go index 5d2402566c3..cf7bf01414d 100644 --- a/internal/service/ssmincidents/response_plan.go +++ b/internal/service/ssmincidents/response_plan.go @@ -244,68 +244,51 @@ func resourceResponsePlanRead(ctx context.Context, d *schema.ResourceData, meta func resourceResponsePlanUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*conns.AWSClient).SSMIncidentsClient() - hasNonTagUpdate := false - - input := &ssmincidents.UpdateResponsePlanInput{ - Arn: aws.String(d.Id()), - } - - if d.HasChanges("action") { - input.Actions = expandAction(d.Get("action").([]interface{})) - - hasNonTagUpdate = true - } - - if d.HasChanges("chat_channel") { - input.ChatChannel = expandChatChannel(d.Get("chat_channel").(*schema.Set)) - - hasNonTagUpdate = true - } + if d.HasChangesExcept("tags", "tags_all") { + input := &ssmincidents.UpdateResponsePlanInput{ + Arn: aws.String(d.Id()), + } - if d.HasChanges("display_name") { - input.DisplayName = aws.String(d.Get("display_name").(string)) + if d.HasChanges("action") { + input.Actions = expandAction(d.Get("action").([]interface{})) + } - hasNonTagUpdate = true - } + if d.HasChanges("chat_channel") { + input.ChatChannel = expandChatChannel(d.Get("chat_channel").(*schema.Set)) + } - if d.HasChanges("engagements") { - input.Engagements = flex.ExpandStringValueSet(d.Get("engagements").(*schema.Set)) + if d.HasChanges("display_name") { + input.DisplayName = aws.String(d.Get("display_name").(string)) + } - hasNonTagUpdate = true - } + if d.HasChanges("engagements") { + input.Engagements = flex.ExpandStringValueSet(d.Get("engagements").(*schema.Set)) + } - if d.HasChanges("incident_template") { - incidentTemplate := d.Get("incident_template") - template := expandIncidentTemplate(incidentTemplate.([]interface{})) - updateResponsePlanInputWithIncidentTemplate(input, template) + if d.HasChanges("incident_template") { + incidentTemplate := d.Get("incident_template") + template := expandIncidentTemplate(incidentTemplate.([]interface{})) + updateResponsePlanInputWithIncidentTemplate(input, template) + } - hasNonTagUpdate = true - } + if d.HasChanges("integration") { + input.Integrations = expandIntegration(d.Get("integration").([]interface{})) + } - if d.HasChanges("integration") { - input.Integrations = expandIntegration(d.Get("integration").([]interface{})) + _, err := client.UpdateResponsePlan(ctx, input) - hasNonTagUpdate = true + if err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionUpdating, ResNameResponsePlan, d.Id(), err) + } } // tags can have a change without tags_all having a change when value of tag is "" if d.HasChanges("tags_all", "tags") { - log.Printf("[DEBUG] Updating SSMIncidents ResponsePlan tags") - if err := updateResourceTags(ctx, client, d); err != nil { return create.DiagError(names.SSMIncidents, create.ErrActionUpdating, ResNameResponsePlan, d.Id(), err) } } - if hasNonTagUpdate { - log.Printf("[DEBUG] Updating SSMIncidents ResponsePlan (%s): %#v", d.Id(), input) - _, err := client.UpdateResponsePlan(ctx, input) - - if err != nil { - return create.DiagError(names.SSMIncidents, create.ErrActionUpdating, ResNameResponsePlan, d.Id(), err) - } - } - return resourceResponsePlanRead(ctx, d, meta) } From 1102ebc42caf79b06d1c35af4759c1077e2678ec Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 24 Apr 2023 08:02:35 -0400 Subject: [PATCH 5/5] 'resource.ParallelTest' -> 'resource.Test' in 'testResponsePlanDataSource_basic'. --- internal/service/ssmincidents/response_plan_data_source_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ssmincidents/response_plan_data_source_test.go b/internal/service/ssmincidents/response_plan_data_source_test.go index 54267a9bf39..81d16e38b80 100644 --- a/internal/service/ssmincidents/response_plan_data_source_test.go +++ b/internal/service/ssmincidents/response_plan_data_source_test.go @@ -26,7 +26,7 @@ func testResponsePlanDataSource_basic(t *testing.T) { displayName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) chatChannelTopic := "aws_sns_topic.channel_topic" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SSMIncidentsEndpointID)