From 2c789677051e5dc7992af28de3c9f1907eda02b2 Mon Sep 17 00:00:00 2001 From: Brandon Waskiewicz Date: Wed, 1 Nov 2023 13:44:54 -0400 Subject: [PATCH 1/3] add support for incident_workflow inline_steps_input --- .../resource_pagerduty_incident_workflow.go | 489 ++++++++++++++---- ...source_pagerduty_incident_workflow_test.go | 360 ++++++++++++- .../docs/r/incident_workflow.html.markdown | 12 +- 3 files changed, 740 insertions(+), 121 deletions(-) diff --git a/pagerduty/resource_pagerduty_incident_workflow.go b/pagerduty/resource_pagerduty_incident_workflow.go index 49575a708..51fa9b2ec 100644 --- a/pagerduty/resource_pagerduty_incident_workflow.go +++ b/pagerduty/resource_pagerduty_incident_workflow.go @@ -6,6 +6,7 @@ import ( "net/http" "regexp" "strconv" + "strings" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -14,6 +15,34 @@ import ( "github.com/heimweh/go-pagerduty/pagerduty" ) +// This integer controls the level of inline_steps_inputs recursion allowed in the Incident Workflow schema. +// A value of 1 indicates that the top level workflow steps may specify inline_steps_inputs of an action whose +// configuration requires it, but a step that exists within an inline_steps_input cannot itself specify an +// inline_steps_input. For example: +// +// resource "pagerduty_incident_workflow" "test_workflow" { +// name = "Test Terraform Incident Workflow" +// step { +// name = "Step 1" +// action = "pagerduty.com:incident-workflows:action:1" +// inline_steps_input { # Allowed iff inlineStepSchemaLevel>0 +// name = "Actions" +// step { +// name = "Inline Step 1" +// action = "pagerduty.com:incident-workflows:action:1" +// inline_steps_input { # Allowed iff inlineStepSchemaLevel>1 +// ... +// inline_steps_input { # Allowed iff inlineStepSchemaLevel>2 +// ... +// } +// } +// } +// } +// } +// +// Each time the value is incremented by 1, it will allow another level of "inline_steps_input". +const inlineStepSchemaLevel = 1 + func resourcePagerDutyIncidentWorkflow() *schema.Resource { return &schema.Resource{ ReadContext: resourcePagerDutyIncidentWorkflowRead, @@ -77,6 +106,114 @@ func resourcePagerDutyIncidentWorkflow() *schema.Resource { }, }, }, + "inline_steps_input": inlineStepSchema(inlineStepSchemaLevel), + }, + }, + }, + }, + } +} + +func inlineStepSchema(level int) *schema.Schema { + if level > 1 { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "step": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "action": { + Type: schema.TypeString, + Required: true, + }, + "input": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + "generated": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + "inline_steps_input": inlineStepSchema(level - 1), + }, + }, + }, + }, + }, + } + } + + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "step": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "action": { + Type: schema.TypeString, + Required: true, + }, + "input": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + "generated": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + }, }, }, }, @@ -89,35 +226,97 @@ func resourcePagerDutyIncidentWorkflow() *schema.Resource { // These inputs get persisted in the state with `generated` set to `true` // but then should not be removed by a `terraform apply` func customizeIncidentWorkflowDiff() schema.CustomizeDiffFunc { - inputCountRegex := regexp.MustCompile(`^(step\.(\d+)\.input)\.#$`) - - nameDoesNotExistAlready := func(inputs []interface{}, name string) bool { - for _, v := range inputs { - m := v.(map[string]interface{}) - if n, ok := m["name"]; ok && n == name { - return false + // Regex targets all changed keys that have affected either an input list's length, a specific input's name, or a + // specific input's value. The diff function is built to handle all input changes. + inputCountRegex := regexp.MustCompile(`^(step\.\d+\.?.*\.input)\.(#|\d+\.name|\d+.value)$`) + + excludeInput := func(inputs []interface{}, inputName string) []interface{} { + result := make([]interface{}, 0) + for _, input := range inputs { + if !(input.(map[string]interface{})["name"] == inputName) { + result = append(result, input) } } - return true + return result } - addMissingGeneratedInputs := func(withGenerated []interface{}, maybeWithoutGenerated []interface{}) (interface{}, []string) { - result := maybeWithoutGenerated - addedNames := make([]string, 0) + determineNewInputs := func(oldInputs []interface{}, newInputs []interface{}) interface{} { + result := make([]interface{}, 0) - for _, v := range withGenerated { - m := v.(map[string]interface{}) - if b, ok := m["generated"]; ok && b.(bool) && nameDoesNotExistAlready(maybeWithoutGenerated, m["name"].(string)) { - result = append(result, m) - addedNames = append(addedNames, m["name"].(string)) + // Statically set generated to false for all newInputs because we know the input was found in the config so it + // is by definition not generated. Terraform may think it is generated if the ordering of the inputs changes + // around, so it helpfully assigns generated since it was not *precisely* the input specified in the config + // since the ordering is different. This static assignment overrides that so ordering issues alone do not + // generate a plan diff. + for _, newInputValue := range newInputs { + newInputValue.(map[string]interface{})["generated"] = false + } + + // 1. Iterate through old versions from tfstate. + for _, oldInputValue := range oldInputs { + oldInput := oldInputValue.(map[string]interface{}) + + // 2. For each old input, search for the same input within all new inputs. + var input map[string]interface{} + for _, newInputValue := range newInputs { + newInput := newInputValue.(map[string]interface{}) + if oldInput["name"].(string) == newInput["name"].(string) { + // 3. If there is a new version of the same input, use that and then remove it from newInputs. + // This keeps the ordering of inputs the same to prevent the diff from thinking a change occurred + // when it is just a different ordering of inputs (ordering does not matter). + // Excluding the new input lets us track which new inputs have been handled or not. + input = newInput + newInputs = excludeInput(newInputs, input["name"].(string)) + break + } + } + + // 4. If there was no matching new input to this old input and the old input is generated, use the old + // input. This lets the diff maintain the cached tfstate generated inputs as they will always exist unless + // overridden with a non-generated input from the config. + if input == nil && oldInput["generated"].(bool) { + input = oldInput + } + + // 5. If an input was found, append it to the result. + if input != nil { + result = append(result, input) } } - return result, addedNames + // 6. Any remaining new inputs that were not already matched to old inputs should now be added to the result. + // This lets new inputs that do not have default values (which are already cached as generated=true) maintain + // their state in the diff, while not disrupting ordering of unchanged inputs. + for _, newInput := range newInputs { + result = append(result, newInput) + } + + return result } - updateStepInput := func(step interface{}, index int64, input interface{}) { - step.([]interface{})[index].(map[string]interface{})["input"] = input + var updateStepInput func(ctx interface{}, path string, input interface{}) + updateStepInput = func(ctx interface{}, path string, input interface{}) { + if path == "input" { + ctx.(map[string]interface{})["input"] = input + return + } + parts := strings.Split(path, ".") + if len(parts) <= 1 { + log.Printf("[WARN] Unexpected input key not terminated by `.input`") + return + } + + top := parts[0] + newPath := strings.Join(parts[1:], ".") + + idx, err := strconv.ParseInt(top, 10, 32) + if err == nil { + updateStepInput(ctx.([]interface{})[idx], newPath, input) + } else if top == "step" || top == "inline_steps_input" { + updateStepInput(ctx.(map[string]interface{})[top], newPath, input) + } else { + log.Printf("[WARN] Unexpected input key part: %s - expected integer,\"step\",\"inline_steps_input\"", top) + } } return func(ctx context.Context, d *schema.ResourceDiff, _ interface{}) error { @@ -132,12 +331,12 @@ func customizeIncidentWorkflowDiff() schema.CustomizeDiffFunc { indexMatch := inputCountRegex.FindStringSubmatch(key) if len(indexMatch) == 3 { inputKey := indexMatch[1] - inputsFromState, inputsAfterDiff := d.GetChange(inputKey) + oldInputs, newInputs := d.GetChange(inputKey) + replacementInputs := determineNewInputs(oldInputs.([]interface{}), newInputs.([]interface{})) - replacementInput, addedNames := addMissingGeneratedInputs(inputsFromState.([]interface{}), inputsAfterDiff.([]interface{})) - stepIndex, _ := strconv.ParseInt(indexMatch[2], 10, 32) - updateStepInput(newStep, stepIndex, replacementInput) - log.Printf("[INFO] Updating diff for step %d to include generated inputs %v.", stepIndex, addedNames) + log.Printf("[INFO] Updating diff for input key %s to include generated inputs.", inputKey) + // Initiate the path after `step.` as we already know we are in a step context from the regex + updateStepInput(newStep, inputKey[5:], replacementInputs) needToSetNew = true } } @@ -160,7 +359,7 @@ func resourcePagerDutyIncidentWorkflowCreate(ctx context.Context, d *schema.Reso return diag.FromErr(err) } - iw, err := buildIncidentWorkflowStruct(d) + iw, specifiedSteps, err := buildIncidentWorkflowStruct(d) if err != nil { return diag.FromErr(err) } @@ -172,13 +371,7 @@ func resourcePagerDutyIncidentWorkflowCreate(ctx context.Context, d *schema.Reso return diag.FromErr(err) } - stepIdMapping := map[int]string{} - for i, s := range createdWorkflow.Steps { - stepIdMapping[i] = s.ID - } - - nonGeneratedInputNames := createNonGeneratedInputNamesFromWorkflowStepsWithoutIDs(iw, stepIdMapping) - err = flattenIncidentWorkflow(d, createdWorkflow, true, nonGeneratedInputNames) + err = flattenIncidentWorkflow(d, createdWorkflow, true, specifiedSteps) if err != nil { return diag.FromErr(err) } @@ -200,9 +393,7 @@ func resourcePagerDutyIncidentWorkflowUpdate(ctx context.Context, d *schema.Reso return diag.FromErr(err) } - nonGeneratedInputNames := createNonGeneratedInputNamesFromWorkflowStepsInResourceData(d) - - iw, err := buildIncidentWorkflowStruct(d) + iw, specifiedSteps, err := buildIncidentWorkflowStruct(d) if err != nil { return diag.FromErr(err) } @@ -214,7 +405,7 @@ func resourcePagerDutyIncidentWorkflowUpdate(ctx context.Context, d *schema.Reso return diag.FromErr(err) } - err = flattenIncidentWorkflow(d, updatedWorkflow, true, nonGeneratedInputNames) + err = flattenIncidentWorkflow(d, updatedWorkflow, true, specifiedSteps) if err != nil { return diag.FromErr(err) } @@ -240,7 +431,10 @@ func fetchIncidentWorkflow(ctx context.Context, d *schema.ResourceData, meta int return err } - nonGeneratedInputNames := createNonGeneratedInputNamesFromWorkflowStepsInResourceData(d) + _, specifiedSteps, err := buildIncidentWorkflowStruct(d) + if err != nil { + return err + } return resource.RetryContext(ctx, 2*time.Minute, func() *resource.RetryError { iw, _, err := client.IncidentWorkflows.GetContext(ctx, d.Id()) @@ -259,7 +453,7 @@ func fetchIncidentWorkflow(ctx context.Context, d *schema.ResourceData, meta int return nil } - if err := flattenIncidentWorkflow(d, iw, true, nonGeneratedInputNames); err != nil { + if err := flattenIncidentWorkflow(d, iw, true, specifiedSteps); err != nil { return resource.NonRetryableError(err) } return nil @@ -267,51 +461,12 @@ func fetchIncidentWorkflow(ctx context.Context, d *schema.ResourceData, meta int }) } -func createNonGeneratedInputNamesFromWorkflowStepsInResourceData(d *schema.ResourceData) map[string][]string { - nonGeneratedInputNames := map[string][]string{} - - if _, ok := d.GetOk("name"); ok { - if s, ok := d.GetOk("step"); ok { - steps := s.([]interface{}) - - for _, v := range steps { - stepData := v.(map[string]interface{}) - if id, idOk := stepData["id"]; idOk { - nonGeneratedInputNamesForStep := make([]string, 0) - if sdis, inputOk := stepData["input"]; inputOk { - for _, sdi := range sdis.([]interface{}) { - inputData := sdi.(map[string]interface{}) - if b, ok := inputData["generated"]; !ok || !b.(bool) { - nonGeneratedInputNamesForStep = append(nonGeneratedInputNamesForStep, inputData["name"].(string)) - } - } - } - nonGeneratedInputNames[id.(string)] = nonGeneratedInputNamesForStep - } - } - } - } - return nonGeneratedInputNames -} - -func createNonGeneratedInputNamesFromWorkflowStepsWithoutIDs(iw *pagerduty.IncidentWorkflow, stepIdMapping map[int]string) map[string][]string { - nonGeneratedInputNames := map[string][]string{} - - if iw != nil { - for i, step := range iw.Steps { - nonGeneratedInputNamesForStep := make([]string, 0) - if step.Configuration != nil { - for _, input := range step.Configuration.Inputs { - nonGeneratedInputNamesForStep = append(nonGeneratedInputNamesForStep, input.Name) - } - } - nonGeneratedInputNames[stepIdMapping[i]] = nonGeneratedInputNamesForStep - } - } - return nonGeneratedInputNames -} - -func flattenIncidentWorkflow(d *schema.ResourceData, iw *pagerduty.IncidentWorkflow, includeSteps bool, nonGeneratedInputNames map[string][]string) error { +func flattenIncidentWorkflow( + d *schema.ResourceData, + iw *pagerduty.IncidentWorkflow, + includeSteps bool, + specifiedSteps []*SpecifiedStep, +) error { d.SetId(iw.ID) d.Set("name", iw.Name) if iw.Description != nil { @@ -322,33 +477,33 @@ func flattenIncidentWorkflow(d *schema.ResourceData, iw *pagerduty.IncidentWorkf } if includeSteps { - steps := flattenIncidentWorkflowSteps(iw, nonGeneratedInputNames) + steps := flattenIncidentWorkflowSteps(iw, specifiedSteps) d.Set("step", steps) } return nil } -func flattenIncidentWorkflowSteps(iw *pagerduty.IncidentWorkflow, nonGeneratedInputNames map[string][]string) []map[string]interface{} { +func flattenIncidentWorkflowSteps(iw *pagerduty.IncidentWorkflow, specifiedSteps []*SpecifiedStep) []map[string]interface{} { newSteps := make([]map[string]interface{}, len(iw.Steps)) for i, s := range iw.Steps { - nonGeneratedInputNamesForStep, ok := nonGeneratedInputNames[s.ID] - if !ok { - nonGeneratedInputNamesForStep = make([]string, 0) - } - m := make(map[string]interface{}) + specifiedStep := *specifiedSteps[i] m["id"] = s.ID m["name"] = s.Name m["action"] = s.Configuration.ActionID - m["input"] = flattenIncidentWorkflowStepInput(s.Configuration.Inputs, nonGeneratedInputNamesForStep) + m["input"] = flattenIncidentWorkflowStepInput(s.Configuration.Inputs, specifiedStep.SpecifiedInputNames) + m["inline_steps_input"] = flattenIncidentWorkflowStepInlineStepsInput( + s.Configuration.InlineStepsInputs, + specifiedStep.SpecifiedInlineInputs, + ) newSteps[i] = m } return newSteps } -func flattenIncidentWorkflowStepInput(inputs []*pagerduty.IncidentWorkflowActionInput, nonGeneratedInputNames []string) *[]interface{} { +func flattenIncidentWorkflowStepInput(inputs []*pagerduty.IncidentWorkflowActionInput, specifiedInputNames []string) *[]interface{} { newInputs := make([]interface{}, len(inputs)) for i, v := range inputs { @@ -356,7 +511,7 @@ func flattenIncidentWorkflowStepInput(inputs []*pagerduty.IncidentWorkflowAction m["name"] = v.Name m["value"] = v.Value - if !isInputInNonGeneratedInputNames(v, nonGeneratedInputNames) { + if !isInputInNonGeneratedInputNames(v, specifiedInputNames) { m["generated"] = true } @@ -365,6 +520,49 @@ func flattenIncidentWorkflowStepInput(inputs []*pagerduty.IncidentWorkflowAction return &newInputs } +func flattenIncidentWorkflowStepInlineStepsInput( + inlineStepsInputs []*pagerduty.IncidentWorkflowActionInlineStepsInput, + specifiedInlineInputs map[string][]*SpecifiedStep, +) *[]interface{} { + newInlineStepsInputs := make([]interface{}, len(inlineStepsInputs)) + + for i, v := range inlineStepsInputs { + m := make(map[string]interface{}) + m["name"] = v.Name + m["step"] = flattenIncidentWorkflowStepInlineStepsInputSteps(v.Value.Steps, specifiedInlineInputs[v.Name]) + + newInlineStepsInputs[i] = m + } + return &newInlineStepsInputs +} + +func flattenIncidentWorkflowStepInlineStepsInputSteps( + inlineSteps []*pagerduty.IncidentWorkflowActionInlineStep, + specifiedSteps []*SpecifiedStep, +) *[]interface{} { + newInlineSteps := make([]interface{}, len(inlineSteps)) + + for i, v := range inlineSteps { + m := make(map[string]interface{}) + specifiedStep := *specifiedSteps[i] + m["name"] = v.Name + m["action"] = v.Configuration.ActionID + m["input"] = flattenIncidentWorkflowStepInput(v.Configuration.Inputs, specifiedStep.SpecifiedInputNames) + if v.Configuration.InlineStepsInputs != nil && len(v.Configuration.InlineStepsInputs) > 0 { + // We should prefer to not set inline_steps_input if the array is empty. This doubles as a schema edge guard + // and prevents an invalid set if we try to set inline_steps_input to an empty array where the schema + // disallows setting any value whatsoever. + m["inline_steps_input"] = flattenIncidentWorkflowStepInlineStepsInput( + v.Configuration.InlineStepsInputs, + specifiedStep.SpecifiedInlineInputs, + ) + } + + newInlineSteps[i] = m + } + return &newInlineSteps +} + func isInputInNonGeneratedInputNames(i *pagerduty.IncidentWorkflowActionInput, names []string) bool { for _, in := range names { if i.Name == in { @@ -374,7 +572,17 @@ func isInputInNonGeneratedInputNames(i *pagerduty.IncidentWorkflowActionInput, n return false } -func buildIncidentWorkflowStruct(d *schema.ResourceData) (*pagerduty.IncidentWorkflow, error) { +// Tracks specified inputs recursively to identify which are generated or not +type SpecifiedStep struct { + SpecifiedInputNames []string + SpecifiedInlineInputs map[string][]*SpecifiedStep +} + +func buildIncidentWorkflowStruct(d *schema.ResourceData) ( + *pagerduty.IncidentWorkflow, + []*SpecifiedStep, + error, +) { iw := pagerduty.IncidentWorkflow{ Name: d.Get("name").(string), } @@ -388,17 +596,21 @@ func buildIncidentWorkflowStruct(d *schema.ResourceData) (*pagerduty.IncidentWor } } + specifiedSteps := make([]*SpecifiedStep, 0) if steps, ok := d.GetOk("step"); ok { - iw.Steps = buildIncidentWorkflowStepsStruct(steps) + iw.Steps, specifiedSteps = buildIncidentWorkflowStepsStruct(steps) } - return &iw, nil - + return &iw, specifiedSteps, nil } -func buildIncidentWorkflowStepsStruct(s interface{}) []*pagerduty.IncidentWorkflowStep { +func buildIncidentWorkflowStepsStruct(s interface{}) ( + []*pagerduty.IncidentWorkflowStep, + []*SpecifiedStep, +) { steps := s.([]interface{}) newSteps := make([]*pagerduty.IncidentWorkflowStep, len(steps)) + specifiedSteps := make([]*SpecifiedStep, len(steps)) for i, v := range steps { stepData := v.(map[string]interface{}) @@ -412,16 +624,28 @@ func buildIncidentWorkflowStepsStruct(s interface{}) []*pagerduty.IncidentWorkfl step.ID = id.(string) } - step.Configuration.Inputs = buildIncidentWorkflowInputsStruct(stepData["input"]) + specifiedStep := SpecifiedStep{ + SpecifiedInputNames: make([]string, 0), + SpecifiedInlineInputs: map[string][]*SpecifiedStep{}, + } + step.Configuration.Inputs, + specifiedStep.SpecifiedInputNames = buildIncidentWorkflowInputsStruct(stepData["input"]) + step.Configuration.InlineStepsInputs, + specifiedStep.SpecifiedInlineInputs = buildIncidentWorkflowInlineStepsInputsStruct(stepData["inline_steps_input"]) newSteps[i] = &step + specifiedSteps[i] = &specifiedStep } - return newSteps + return newSteps, specifiedSteps } -func buildIncidentWorkflowInputsStruct(in interface{}) []*pagerduty.IncidentWorkflowActionInput { +func buildIncidentWorkflowInputsStruct(in interface{}) ( + []*pagerduty.IncidentWorkflowActionInput, + []string, +) { inputs := in.([]interface{}) newInputs := make([]*pagerduty.IncidentWorkflowActionInput, len(inputs)) + specifiedInputNames := make([]string, 0) for i, v := range inputs { inputData := v.(map[string]interface{}) @@ -430,7 +654,68 @@ func buildIncidentWorkflowInputsStruct(in interface{}) []*pagerduty.IncidentWork Value: inputData["value"].(string), } + generated := inputData["generated"].(bool) + if !generated { + specifiedInputNames = append(specifiedInputNames, input.Name) + } newInputs[i] = &input } - return newInputs + return newInputs, specifiedInputNames +} + +func buildIncidentWorkflowInlineStepsInputsStruct(in interface{}) ( + []*pagerduty.IncidentWorkflowActionInlineStepsInput, + map[string][]*SpecifiedStep, +) { + specifiedInlineInputs := map[string][]*SpecifiedStep{} + if in == nil { + // We need to catch the case where the schema stops allowing inline_steps_input where this will be nil so that + // we can return an empty list. + return make([]*pagerduty.IncidentWorkflowActionInlineStepsInput, 0), specifiedInlineInputs + } + inputs := in.([]interface{}) + newInputs := make([]*pagerduty.IncidentWorkflowActionInlineStepsInput, len(inputs)) + + for i, v := range inputs { + inputData := v.(map[string]interface{}) + input := pagerduty.IncidentWorkflowActionInlineStepsInput{ + Name: inputData["name"].(string), + } + inputValue := pagerduty.IncidentWorkflowActionInlineStepsInputValue{} + steps, specifiedInlineSteps := buildIncidentWorkflowActionInlineStepsInputSteps(inputData["step"]) + inputValue.Steps = steps + input.Value = &inputValue + specifiedInlineInputs[input.Name] = specifiedInlineSteps + newInputs[i] = &input + } + return newInputs, specifiedInlineInputs +} + +func buildIncidentWorkflowActionInlineStepsInputSteps(in interface{}) ( + []*pagerduty.IncidentWorkflowActionInlineStep, + []*SpecifiedStep, +) { + inlineSteps := in.([]interface{}) + newInlineSteps := make([]*pagerduty.IncidentWorkflowActionInlineStep, len(inlineSteps)) + specifiedInlineSteps := make([]*SpecifiedStep, len(inlineSteps)) + + for i, v := range inlineSteps { + inlineStepData := v.(map[string]interface{}) + inlineStep := pagerduty.IncidentWorkflowActionInlineStep{ + Name: inlineStepData["name"].(string), + Configuration: &pagerduty.IncidentWorkflowActionConfiguration{ + ActionID: inlineStepData["action"].(string), + }, + } + + specifiedInlineStep := SpecifiedStep{} + inlineStep.Configuration.Inputs, + specifiedInlineStep.SpecifiedInputNames = buildIncidentWorkflowInputsStruct(inlineStepData["input"]) + inlineStep.Configuration.InlineStepsInputs, + specifiedInlineStep.SpecifiedInlineInputs = buildIncidentWorkflowInlineStepsInputsStruct(inlineStepData["inline_steps_input"]) + + newInlineSteps[i] = &inlineStep + specifiedInlineSteps[i] = &specifiedInlineStep + } + return newInlineSteps, specifiedInlineSteps } diff --git a/pagerduty/resource_pagerduty_incident_workflow_test.go b/pagerduty/resource_pagerduty_incident_workflow_test.go index 97ee5600e..301c5342a 100644 --- a/pagerduty/resource_pagerduty_incident_workflow_test.go +++ b/pagerduty/resource_pagerduty_incident_workflow_test.go @@ -117,6 +117,125 @@ func TestAccPagerDutyIncidentWorkflow_Team(t *testing.T) { }) } +func TestAccPagerDutyIncidentWorkflow_InlineInputs(t *testing.T) { + workflowName := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckIncidentWorkflows(t) + }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckPagerDutyIncidentWorkflowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyIncidentWorkflowInlineInputsConfig(workflowName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyIncidentWorkflowExists("pagerduty_incident_workflow.test"), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow.test", "name", workflowName), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "description", "Managed by Terraform"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.#", "3"), + + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.name", "Step 1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.action", "pagerduty.com:incident-workflows:send-status-update:1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.#", "1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.0.name", "Message"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.0.value", "first update"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.0.generated", "false"), + + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.name", "Step 2"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.action", "pagerduty.com:logic:incident-workflows-loop-until:2"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.#", "3"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.0.name", "Condition"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.0.value", "incident.status matches 'resolved'"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.0.generated", "false"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.1.name", "Delay between loops"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.1.value", "10"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.1.generated", "false"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.2.name", "Maximum loops"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.2.value", "20"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.2.generated", "true"), + + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.inline_steps_input.#", "1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.inline_steps_input.0.name", "Actions"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.inline_steps_input.0.step.#", "1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.inline_steps_input.0.step.0.name", "Step 2a"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.inline_steps_input.0.step.0.action", "pagerduty.com:incident-workflows:send-status-update:1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.inline_steps_input.0.step.0.input.#", "1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.inline_steps_input.0.step.0.input.0.name", "Message"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.inline_steps_input.0.step.0.input.0.value", "Loop update"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.inline_steps_input.0.step.0.input.0.generated", "false"), + + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.name", "Step 3"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.action", "pagerduty.com:incident-workflows:add-conference-bridge:5"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.#", "3"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.0.name", "Conference Number"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.0.value", "+1 415-555-1212,,,,1234#,"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.0.generated", "false"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.1.name", "Conference URL"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.1.value", "https://www.testconferenceurl.com/"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.1.generated", "false"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.2.name", "Overwrite existing conference bridge"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.2.value", "No"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.2.input.2.generated", "true"), + ), + }, + { + Config: testAccCheckPagerDutyIncidentWorkflowInlineInputsConfigUpdate(workflowName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyIncidentWorkflowExists("pagerduty_incident_workflow.test"), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow.test", "name", workflowName), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow.test", "description", "some description"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.#", "2"), + + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.name", "Step 1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.action", "pagerduty.com:logic:incident-workflows-loop-until:2"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.#", "3"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.0.name", "Condition"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.0.value", "incident.status matches 'resolved'"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.0.generated", "false"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.1.name", "Delay between loops"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.1.value", "10"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.1.generated", "false"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.2.name", "Maximum loops"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.2.value", "20"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.input.2.generated", "true"), + + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.#", "1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.name", "Actions"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.#", "2"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.0.name", "Step 1a"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.0.action", "pagerduty.com:incident-workflows:send-status-update:1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.0.input.#", "1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.0.input.0.name", "Message"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.0.input.0.value", "Loop update 1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.0.input.0.generated", "false"), + + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.1.name", "Step 1b"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.1.action", "pagerduty.com:incident-workflows:send-status-update:1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.1.input.#", "1"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.1.input.0.name", "Message"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.1.input.0.value", "Loop update 2"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.0.inline_steps_input.0.step.1.input.0.generated", "false"), + + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.name", "Step 2"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.action", "pagerduty.com:incident-workflows:add-conference-bridge:5"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.#", "2"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.0.name", "Conference Number"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.0.value", "+1 415-555-1212,,,,1234#,"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.0.generated", "false"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.1.name", "Overwrite existing conference bridge"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.1.value", "Yes"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow.test", "step.1.input.1.generated", "false"), + ), + }, + }, + }) +} + func testAccCheckPagerDutyIncidentWorkflowConfigWithTeam(name, team string) string { return fmt.Sprintf(` %s @@ -152,6 +271,57 @@ resource "pagerduty_incident_workflow" "test" { `, name) } +func testAccCheckPagerDutyIncidentWorkflowInlineInputsConfig(name string) string { + return fmt.Sprintf(` +resource "pagerduty_incident_workflow" "test" { + name = "%s" + step { + name = "Step 1" + action = "pagerduty.com:incident-workflows:send-status-update:1" + input { + name = "Message" + value = "first update" + } + } + step { + name = "Step 2" + action = "pagerduty.com:logic:incident-workflows-loop-until:2" + input { + name = "Condition" + value = "incident.status matches 'resolved'" + } + input { + name = "Delay between loops" + value = "10" + } + inline_steps_input { + name = "Actions" + step { + name = "Step 2a" + action = "pagerduty.com:incident-workflows:send-status-update:1" + input { + name = "Message" + value = "Loop update" + } + } + } + } + step { + name = "Step 3" + action = "pagerduty.com:incident-workflows:add-conference-bridge:5" + input { + name = "Conference URL" + value = "https://www.testconferenceurl.com/" + } + input { + name = "Conference Number" + value = "+1 415-555-1212,,,,1234#," + } + } +} +`, name) +} + func testAccCheckPagerDutyIncidentWorkflowConfigUpdate(name string) string { return fmt.Sprintf(` resource "pagerduty_incident_workflow" "test" { @@ -177,6 +347,58 @@ resource "pagerduty_incident_workflow" "test" { `, name) } +func testAccCheckPagerDutyIncidentWorkflowInlineInputsConfigUpdate(name string) string { + return fmt.Sprintf(` +resource "pagerduty_incident_workflow" "test" { + name = "%s" + description = "some description" + step { + name = "Step 1" + action = "pagerduty.com:logic:incident-workflows-loop-until:2" + input { + name = "Condition" + value = "incident.status matches 'resolved'" + } + input { + name = "Delay between loops" + value = "10" + } + inline_steps_input { + name = "Actions" + step { + name = "Step 1a" + action = "pagerduty.com:incident-workflows:send-status-update:1" + input { + name = "Message" + value = "Loop update 1" + } + } + step { + name = "Step 1b" + action = "pagerduty.com:incident-workflows:send-status-update:1" + input { + name = "Message" + value = "Loop update 2" + } + } + } + } + step { + name = "Step 2" + action = "pagerduty.com:incident-workflows:add-conference-bridge:5" + input { + name = "Conference Number" + value = "+1 415-555-1212,,,,1234#," + } + input { + name = "Overwrite existing conference bridge" + value = "Yes" + } + } +} +`, name) +} + func testAccCheckPagerDutyIncidentWorkflowDestroy(s *terraform.State) error { client, _ := testAccProvider.Meta().(*Config).Client() for _, r := range s.RootModule().Resources { @@ -219,46 +441,152 @@ func testAccCheckPagerDutyIncidentWorkflowExists(n string) resource.TestCheckFun func TestFlattenIncidentWorkflowStepsOneGenerated(t *testing.T) { - n := &pagerduty.IncidentWorkflow{ + iw := &pagerduty.IncidentWorkflow{ Steps: []*pagerduty.IncidentWorkflowStep{ { ID: "abc-123", - Name: "something", + Name: "step1", Configuration: &pagerduty.IncidentWorkflowActionConfiguration{ + ActionID: "step1-action-id", Inputs: []*pagerduty.IncidentWorkflowActionInput{ { - Name: "test1-value", + Name: "step1-input1", Value: "test1-value", }, { - Name: "test2-value", + Name: "step1-input2", Value: "test2-value", }, { - Name: "test3-value", + Name: "step1-input3", Value: "test3-value", }, }, + InlineStepsInputs: []*pagerduty.IncidentWorkflowActionInlineStepsInput{ + { + Name: "step1-inlineinput1", + Value: &pagerduty.IncidentWorkflowActionInlineStepsInputValue{ + Steps: []*pagerduty.IncidentWorkflowActionInlineStep{ + { + Name: "step1a", + Configuration: &pagerduty.IncidentWorkflowActionConfiguration{ + ActionID: "step1a-action-id", + Inputs: []*pagerduty.IncidentWorkflowActionInput{ + { + Name: "step1a-input1", + Value: "inlineval1", + }, + { + Name: "step1a-input2", + Value: "inlineval2", + }, + }, + }, + }, + }, + }, + }, + { + Name: "step1-inlineinput2", + Value: &pagerduty.IncidentWorkflowActionInlineStepsInputValue{ + Steps: []*pagerduty.IncidentWorkflowActionInlineStep{ + { + Name: "step1b", + Configuration: &pagerduty.IncidentWorkflowActionConfiguration{ + ActionID: "step1b-action-id", + Inputs: []*pagerduty.IncidentWorkflowActionInput{ + { + Name: "step1b-input1", + Value: "inlineval3", + }, + { + Name: "step1b-input2", + Value: "inlineval4", + }, + }, + }, + }, + }, + }, + }, + }, }, }, }, } - o := map[string][]string{ - "abc-123": {"test1-value", "test2-value"}, + specifiedSteps := []*SpecifiedStep{ + { + // "step1-input1" is generated + SpecifiedInputNames: []string{"step1-input2", "step1-input3"}, + SpecifiedInlineInputs: map[string][]*SpecifiedStep{ + "step1-inlineinput1": { + { + // "step1a-input1" is generated + SpecifiedInputNames: []string{"step1a-input2"}, + SpecifiedInlineInputs: map[string][]*SpecifiedStep{}, + }, + }, + "step1-inlineinput2": { + { + // "step1b-input2" is generated + SpecifiedInputNames: []string{"step1b-input1"}, + SpecifiedInlineInputs: map[string][]*SpecifiedStep{}, + }, + }, + }, + }, + } + flattenedSteps := flattenIncidentWorkflowSteps(iw, specifiedSteps) + step1Inputs := flattenedSteps[0]["input"].(*[]interface{}) + if len(*step1Inputs) != 3 { + t.Errorf("flattened step1 had wrong number of inputs. want 3 got %v", len(*step1Inputs)) } - r := flattenIncidentWorkflowSteps(n, o) - l := r[0]["input"].(*[]interface{}) - if len(*l) != 3 { - t.Errorf("flattened step had wrong number of inputs. want 2 got %v", len(*l)) + for i, v := range *step1Inputs { + if i == 0 { + if generated, hadGen := v.(map[string]interface{})["generated"]; !hadGen || !generated.(bool) { + t.Errorf("was not expecting step1 input %v to have generated=false", i) + } + } else { + if _, hadGen := v.(map[string]interface{})["generated"]; hadGen { + t.Errorf("was not expecting step1 input %v to have generated key set", i) + } + } + } + + step1aInlineStepInputs := flattenedSteps[0]["inline_steps_input"].(*[]interface{}) + step1aInlineStepInputs1 := (*step1aInlineStepInputs)[0].(map[string]interface{}) + step1aInlineStepInputs1Steps := step1aInlineStepInputs1["step"].(*[]interface{}) + step1aInputs := (*step1aInlineStepInputs1Steps)[0].(map[string]interface{})["input"].(*[]interface{}) + if len(*step1aInputs) != 2 { + t.Errorf("flattened step1a had wrong number of inputs. want 2 got %v", len(*step1aInputs)) + } + for i, v := range *step1aInputs { + if i == 0 { + if generated, hadGen := v.(map[string]interface{})["generated"]; !hadGen || !generated.(bool) { + t.Errorf("was not expecting step1a input %v to have generated=false", i) + } + } else { + if _, hadGen := v.(map[string]interface{})["generated"]; hadGen { + t.Errorf("was not expecting step1a input %v to have generated key set", i) + } + } + } + + step1bInlineStepInputs := flattenedSteps[0]["inline_steps_input"].(*[]interface{}) + step1bInlineStepInputs2 := (*step1bInlineStepInputs)[1].(map[string]interface{}) + step1bInlineStepInputs2Steps := step1bInlineStepInputs2["step"].(*[]interface{}) + step1bInputs := (*step1bInlineStepInputs2Steps)[0].(map[string]interface{})["input"].(*[]interface{}) + if len(*step1bInputs) != 2 { + t.Errorf("flattened step1b had wrong number of inputs. want 2 got %v", len(*step1aInputs)) } - for i, v := range *l { - if i < 2 { + for i, v := range *step1bInputs { + if i == 0 { if _, hadGen := v.(map[string]interface{})["generated"]; hadGen { - t.Errorf("was not expecting input %v to be generated", i) + t.Errorf("was not expecting step1b input %v to have generated key set", i) } } else { - if gen, hadGen := v.(map[string]interface{})["generated"]; !hadGen || !(gen.(bool)) { - t.Errorf("was expecting input %v to be generated", i) + if generated, hadGen := v.(map[string]interface{})["generated"]; !hadGen || !generated.(bool) { + t.Errorf("was not expecting step1b input %v to have generated=false", i) } } } diff --git a/website/docs/r/incident_workflow.html.markdown b/website/docs/r/incident_workflow.html.markdown index aebd79956..523e99b60 100644 --- a/website/docs/r/incident_workflow.html.markdown +++ b/website/docs/r/incident_workflow.html.markdown @@ -39,14 +39,20 @@ The following arguments are supported: Each incident workflow step (`step`) supports the following: * `name` - (Required) The name of the workflow step. -* `action` - (Required) The action id for the workflow step, including the version. A list of actions available can be retrieved using the [PagerDuty API](https://developer.pagerduty.com/api-reference/aa192a25fac39-list-actions). -* `input` - (Optional) The list of inputs for the workflow action. +* `action` - (Required) The action id for the workflow step, including the version. A list of actions available can be retrieved using the [PagerDuty API](https://developer.pagerduty.com/api-reference/aa192a25fac39-list-actions). +* `input` - (Optional) The list of standard inputs for the workflow action. +* `inline_steps_input` - (Optional) The list of inputs that contain a series of inline steps for the workflow action. -Each incident workflow step input (`input`) supports the following: +Each incident workflow step standard input (`input`) supports the following: * `name` - (Required) The name of the input. * `value` - (Required) The value of the input. +Each incident workflow step inline steps input (`inline_steps_input`) points to an input whose metadata describes the `format` as `inlineSteps` and supports the following: + +* `name` - (Required) The name of the input. +* `step` - (Required) The inline steps of the input. An inline step adheres to the step schema described above. + ## Attributes Reference The following attributes are exported: From 05bd2dbf680350bbf60708e0ed36c17bc2d5e2b2 Mon Sep 17 00:00:00 2001 From: Brandon Waskiewicz Date: Fri, 10 Nov 2023 15:33:36 -0500 Subject: [PATCH 2/3] trigger resource replacement when incident_workflow_trigger type changes --- ...rce_pagerduty_incident_workflow_trigger.go | 1 + ...agerduty_incident_workflow_trigger_test.go | 84 +++++++++++++++++++ .../r/incident_workflow_trigger.html.markdown | 2 +- 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/pagerduty/resource_pagerduty_incident_workflow_trigger.go b/pagerduty/resource_pagerduty_incident_workflow_trigger.go index 0eaa50e98..e1d48e41e 100644 --- a/pagerduty/resource_pagerduty_incident_workflow_trigger.go +++ b/pagerduty/resource_pagerduty_incident_workflow_trigger.go @@ -27,6 +27,7 @@ func resourcePagerDutyIncidentWorkflowTrigger() *schema.Resource { "type": { Type: schema.TypeString, Required: true, + ForceNew: true, ValidateDiagFunc: validateValueDiagFunc([]string{ "manual", "conditional", diff --git a/pagerduty/resource_pagerduty_incident_workflow_trigger_test.go b/pagerduty/resource_pagerduty_incident_workflow_trigger_test.go index 3c2c444e8..e621cf828 100644 --- a/pagerduty/resource_pagerduty_incident_workflow_trigger_test.go +++ b/pagerduty/resource_pagerduty_incident_workflow_trigger_test.go @@ -228,6 +228,41 @@ func TestAccPagerDutyIncidentWorkflowTrigger_BasicConditionalAllServices(t *test }) } +func TestAccPagerDutyIncidentWorkflowTrigger_ChangeTypeCausesReplace(t *testing.T) { + workflow := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckIncidentWorkflows(t) + }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckPagerDutyIncidentWorkflowTriggerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyIncidentWorkflowTriggerConfigConditionalAllServices(workflow, "incident.priority matches 'P1'"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyIncidentWorkflowTriggerExists("pagerduty_incident_workflow_trigger.test"), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow_trigger.test", "type", "conditional"), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow_trigger.test", "condition", "incident.priority matches 'P1'"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow_trigger.test", "subscribed_to_all_services", "true"), + ), + }, + { + Config: testAccCheckPagerDutyIncidentWorkflowTriggerConfigManualAllServices(workflow), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyIncidentWorkflowTriggerExists("pagerduty_incident_workflow_trigger.test"), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow_trigger.test", "type", "manual"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow_trigger.test", "subscribed_to_all_services", "true"), + ), + }, + }, + }) +} + func testAccCheckPagerDutyIncidentWorkflowTriggerConfigConditionalAllServices(workflow, condition string) string { return fmt.Sprintf(` %s @@ -242,6 +277,55 @@ resource "pagerduty_incident_workflow_trigger" "test" { `, testAccCheckPagerDutyIncidentWorkflowConfig(workflow), condition) } +func testAccCheckPagerDutyIncidentWorkflowTriggerConfigManualAllServices(workflow string) string { + return fmt.Sprintf(` +%s + +resource "pagerduty_incident_workflow_trigger" "test" { + type = "manual" + workflow = pagerduty_incident_workflow.test.id + services = [] + subscribed_to_all_services = true +} +`, testAccCheckPagerDutyIncidentWorkflowConfig(workflow)) +} + +func TestAccPagerDutyIncidentWorkflowTrigger_CannotChangeType(t *testing.T) { + workflow := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckIncidentWorkflows(t) + }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckPagerDutyIncidentWorkflowTriggerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyIncidentWorkflowTriggerConfigConditionalAllServices(workflow, "incident.priority matches 'P1'"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyIncidentWorkflowTriggerExists("pagerduty_incident_workflow_trigger.test"), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow_trigger.test", "type", "conditional"), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow_trigger.test", "condition", "incident.priority matches 'P1'"), + resource.TestCheckResourceAttr("pagerduty_incident_workflow_trigger.test", "subscribed_to_all_services", "true"), + ), + }, + { + Config: testAccCheckPagerDutyIncidentWorkflowTriggerConfigConditionalAllServices(workflow, "incident.priority matches 'P2'"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyIncidentWorkflowTriggerExists("pagerduty_incident_workflow_trigger.test"), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow_trigger.test", "type", "conditional"), + resource.TestCheckResourceAttr( + "pagerduty_incident_workflow_trigger.test", "condition", "incident.priority matches 'P2'"), + ), + }, + }, + }) +} + func testAccCheckPagerDutyIncidentWorkflowTriggerDestroy(s *terraform.State) error { client, _ := testAccProvider.Meta().(*Config).Client() for _, r := range s.RootModule().Resources { diff --git a/website/docs/r/incident_workflow_trigger.html.markdown b/website/docs/r/incident_workflow_trigger.html.markdown index 4be138fc5..854e13fcf 100644 --- a/website/docs/r/incident_workflow_trigger.html.markdown +++ b/website/docs/r/incident_workflow_trigger.html.markdown @@ -54,7 +54,7 @@ resource "pagerduty_incident_workflow_trigger" "manual_trigger" { The following arguments are supported: -* `type` - (Required) May be either `manual` or `conditional`. +* `type` - (Required) [Updating causes resource replacement] May be either `manual` or `conditional`. * `workflow` - (Required) The workflow ID for the workflow to trigger. * `services` - (Optional) A list of service IDs. Incidents in any of the listed services are eligible to fire this trigger. * `subscribed_to_all_services` - (Required) Set to `true` if the trigger should be eligible for firing on all services. Only allowed to be `true` if the services list is not defined or empty. From f2f3363b0e4e499fe5660efcca21698be45390a8 Mon Sep 17 00:00:00 2001 From: Brandon Waskiewicz Date: Wed, 29 Nov 2023 12:24:36 -0500 Subject: [PATCH 3/3] update heimweh/go-pagerduty to a11b2f7 --- go.mod | 2 +- go.sum | 4 +-- .../pagerduty/incident_workflow.go | 25 ++++++++++++++++--- vendor/modules.txt | 2 +- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index a7bc1bc7a..e90cd21c4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/terraform-exec v0.16.0 github.com/hashicorp/terraform-json v0.13.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.11.0 - github.com/heimweh/go-pagerduty v0.0.0-20231125004736-fa2590da432f + github.com/heimweh/go-pagerduty v0.0.0-20231201163338-a11b2f79dfcf ) require ( diff --git a/go.sum b/go.sum index d2682ef5c..48b359598 100644 --- a/go.sum +++ b/go.sum @@ -250,8 +250,8 @@ github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/heimweh/go-pagerduty v0.0.0-20231125004736-fa2590da432f h1:vxbb7MQHNvhgBJ7RlN6ZmvDbP87ez+U2ZMWRwSjX9zE= -github.com/heimweh/go-pagerduty v0.0.0-20231125004736-fa2590da432f/go.mod h1:r59w5iyN01Qvi734yA5hZldbSeJJmsJzee/1kQ/MK7s= +github.com/heimweh/go-pagerduty v0.0.0-20231201163338-a11b2f79dfcf h1:LyQb5eb/+R5VSA7aP5bBjDW4nHndclJQ2bG5N4lGmik= +github.com/heimweh/go-pagerduty v0.0.0-20231201163338-a11b2f79dfcf/go.mod h1:r59w5iyN01Qvi734yA5hZldbSeJJmsJzee/1kQ/MK7s= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/incident_workflow.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/incident_workflow.go index f19ee21d9..09b131c22 100644 --- a/vendor/github.com/heimweh/go-pagerduty/pagerduty/incident_workflow.go +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/incident_workflow.go @@ -31,16 +31,35 @@ type IncidentWorkflowStep struct { // IncidentWorkflowActionConfiguration represents the configuration for an incident workflow action type IncidentWorkflowActionConfiguration struct { - ActionID string `json:"action_id,omitempty"` - Description *string `json:"description,omitempty"` - Inputs []*IncidentWorkflowActionInput `json:"inputs,omitempty"` + ActionID string `json:"action_id,omitempty"` + Description *string `json:"description,omitempty"` + Inputs []*IncidentWorkflowActionInput `json:"inputs,omitempty"` + InlineStepsInputs []*IncidentWorkflowActionInlineStepsInput `json:"inline_steps_inputs,omitempty"` } +// IncidentWorkflowActionInput represents the configuration for an incident workflow action input with a serialized string as the value type IncidentWorkflowActionInput struct { Name string `json:"name,omitempty"` Value string `json:"value,omitempty"` } +// IncidentWorkflowActionInlineStepsInput represents the configuration for an incident workflow action input with a series of inlined steps as the value +type IncidentWorkflowActionInlineStepsInput struct { + Name string `json:"name,omitempty"` + Value *IncidentWorkflowActionInlineStepsInputValue `json:"value,omitempty"` +} + +// IncidentWorkflowActionInlineStepsInputValue represents the value for an inline_steps_input input +type IncidentWorkflowActionInlineStepsInputValue struct { + Steps []*IncidentWorkflowActionInlineStep `json:"steps,omitempty"` +} + +// IncidentWorkflowActionInlineStep represents a single step within an inline_steps_input input's value +type IncidentWorkflowActionInlineStep struct { + Name string `json:"name,omitempty"` + Configuration *IncidentWorkflowActionConfiguration `json:"action_configuration,omitempty"` +} + // ListIncidentWorkflowResponse represents a list response of incident workflows. type ListIncidentWorkflowResponse struct { Total int `json:"total,omitempty"` diff --git a/vendor/modules.txt b/vendor/modules.txt index 273e4cc4b..076a1d2f0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -182,7 +182,7 @@ github.com/hashicorp/terraform-svchost # github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d ## explicit github.com/hashicorp/yamux -# github.com/heimweh/go-pagerduty v0.0.0-20231125004736-fa2590da432f +# github.com/heimweh/go-pagerduty v0.0.0-20231201163338-a11b2f79dfcf ## explicit; go 1.17 github.com/heimweh/go-pagerduty/pagerduty github.com/heimweh/go-pagerduty/persistentconfig