diff --git a/go.mod b/go.mod index 98a5fd37d..7724ef2aa 100644 --- a/go.mod +++ b/go.mod @@ -76,3 +76,5 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) + +replace github.com/heimweh/go-pagerduty => github.com/alexzakabluk/go-pagerduty v0.0.0-20240607142119-ac9a64bba6da diff --git a/go.sum b/go.sum index c7794d9a7..746f28393 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/alexzakabluk/go-pagerduty v0.0.0-20240607142119-ac9a64bba6da h1:O2mVKSglj/gEz+Z7GX+L4Y1zGRIiDxPHdfp6Ri7klww= +github.com/alexzakabluk/go-pagerduty v0.0.0-20240607142119-ac9a64bba6da/go.mod h1:r59w5iyN01Qvi734yA5hZldbSeJJmsJzee/1kQ/MK7s= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= @@ -95,8 +97,6 @@ github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/heimweh/go-pagerduty v0.0.0-20240503143637-3459408ac715 h1:DbdS2LIPkhsqgRcQzOAux0RpTJSH8VYOrN4rZZgznak= -github.com/heimweh/go-pagerduty v0.0.0-20240503143637-3459408ac715/go.mod h1:r59w5iyN01Qvi734yA5hZldbSeJJmsJzee/1kQ/MK7s= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= diff --git a/pagerduty/event_orchestration_path_util.go b/pagerduty/event_orchestration_path_util.go index 92f7b41de..f69d12656 100644 --- a/pagerduty/event_orchestration_path_util.go +++ b/pagerduty/event_orchestration_path_util.go @@ -427,3 +427,7 @@ func emptyOrchestrationPathStructBuilder(pathType string) *pagerduty.EventOrches return commonEmptyOrchestrationPath() } + +func isNonEmptyList(arg interface{}) bool { + return !isNilFunc(arg) && len(arg.([]interface{})) > 0 +} diff --git a/pagerduty/resource_pagerduty_event_orchestration_path_router.go b/pagerduty/resource_pagerduty_event_orchestration_path_router.go index 5a1a0e8b0..9be29d6f0 100644 --- a/pagerduty/resource_pagerduty_event_orchestration_path_router.go +++ b/pagerduty/resource_pagerduty_event_orchestration_path_router.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net/http" + "strings" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -22,6 +23,7 @@ func resourcePagerDutyEventOrchestrationPathRouter() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: resourcePagerDutyEventOrchestrationPathRouterImport, }, + CustomizeDiff: checkDynamicRoutingRule, Schema: map[string]*schema.Schema{ "event_orchestration": { Type: schema.TypeString, @@ -63,9 +65,29 @@ func resourcePagerDutyEventOrchestrationPathRouter() *schema.Resource { MaxItems: 1, // there can only be one action for router Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "dynamic_route_to": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "lookup_by": { + Type: schema.TypeString, + Required: true, + }, + "regex": { + Type: schema.TypeString, + Required: true, + }, + "source": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, "route_to": { Type: schema.TypeString, - Required: true, + Optional: true, ValidateFunc: func(v interface{}, key string) (warns []string, errs []error) { value := v.(string) if value == "unrouted" { @@ -113,6 +135,54 @@ func resourcePagerDutyEventOrchestrationPathRouter() *schema.Resource { } } +func checkDynamicRoutingRule(context context.Context, diff *schema.ResourceDiff, i interface{}) error { + rNum := diff.Get("set.0.rule.#").(int) + draIdxs := []int{} + errorMsgs := []string{} + + for ri := 0; ri < rNum; ri++ { + dra := diff.Get(fmt.Sprintf("set.0.rule.%d.actions.0.dynamic_route_to", ri)) + hasDra := isNonEmptyList(dra) + if !hasDra { + continue + } + draIdxs = append(draIdxs, ri) + } + // 1. Only the first rule of the first ("start") set can have the Dynamic Routing action: + if len(draIdxs) > 1 { + idxs := []string{} + for _, idx := range draIdxs { + idxs = append(idxs, fmt.Sprintf("%d", idx)) + } + errorMsgs = append(errorMsgs, fmt.Sprintf("A Router can have at most one Dynamic Routing rule; Rules with the dynamic_route_to action found at indexes: %s", strings.Join(idxs, ", "))) + } + // 2. The Dynamic Routing action can only be used in the first rule of the first set: + if len(draIdxs) > 0 && draIdxs[0] != 0 { + errorMsgs = append(errorMsgs, fmt.Sprintf("The Dynamic Routing rule must be the first rule in a Router")) + } + // 3. If the Dynamic Routing rule is the first rule of the first set, + // validate its configuration. It cannot have any conditions or the `route_to` action: + if len(draIdxs) == 1 && draIdxs[0] == 0 { + condNum := diff.Get("set.0.rule.0.condition.#").(int) + // diff.NewValueKnown(str) will return false if the value is based on interpolation that was unavailable at diff time, + // which may be the case for the `route_to` action when it references a pagerduty_service resource. + // Source: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/schema#ResourceDiff.NewValueKnown + routeToValueKnown := diff.NewValueKnown("set.0.rule.0.actions.0.route_to") + routeTo := diff.Get("set.0.rule.0.actions.0.route_to").(string) + if condNum > 0 { + errorMsgs = append(errorMsgs, fmt.Sprintf("Dynamic Routing rules cannot have conditions")) + } + if !routeToValueKnown || routeToValueKnown && routeTo != "" { + errorMsgs = append(errorMsgs, fmt.Sprintf("Dynamic Routing rules cannot have the `route_to` action")) + } + } + + if len(errorMsgs) > 0 { + return fmt.Errorf("Invalid Dynamic Routing rule configuration:\n- %s", strings.Join(errorMsgs, "\n- ")) + } + return nil +} + func resourcePagerDutyEventOrchestrationPathRouterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics @@ -294,7 +364,12 @@ func expandRouterActions(v interface{}) *pagerduty.EventOrchestrationPathRuleAct actions := new(pagerduty.EventOrchestrationPathRuleActions) for _, ai := range v.([]interface{}) { am := ai.(map[string]interface{}) - actions.RouteTo = am["route_to"].(string) + dra := am["dynamic_route_to"] + if isNonEmptyList(dra) { + actions.DynamicRouteTo = expandRouterDynamicRouteToAction(dra) + } else { + actions.RouteTo = am["route_to"].(string) + } } return actions @@ -311,6 +386,17 @@ func expandCatchAll(v interface{}) *pagerduty.EventOrchestrationPathCatchAll { return catchAll } +func expandRouterDynamicRouteToAction(v interface{}) *pagerduty.EventOrchestrationPathDynamicRouteTo { + dr := new(pagerduty.EventOrchestrationPathDynamicRouteTo) + for _, i := range v.([]interface{}) { + dra := i.(map[string]interface{}) + dr.LookupBy = dra["lookup_by"].(string) + dr.Regex = dra["regex"].(string) + dr.Source = dra["source"].(string) + } + return dr +} + func flattenSets(orchPathSets []*pagerduty.EventOrchestrationPathSet) []interface{} { var flattenedSets []interface{} for _, set := range orchPathSets { @@ -344,7 +430,11 @@ func flattenRouterActions(actions *pagerduty.EventOrchestrationPathRuleActions) var actionsMap []map[string]interface{} am := make(map[string]interface{}) - am["route_to"] = actions.RouteTo + if actions.DynamicRouteTo != nil { + am["dynamic_route_to"] = flattenRouterDynamicRouteToAction(actions.DynamicRouteTo) + } else { + am["route_to"] = actions.RouteTo + } actionsMap = append(actionsMap, am) return actionsMap } @@ -360,6 +450,18 @@ func flattenCatchAll(catchAll *pagerduty.EventOrchestrationPathCatchAll) []map[s return caMap } +func flattenRouterDynamicRouteToAction(dra *pagerduty.EventOrchestrationPathDynamicRouteTo) []map[string]interface{} { + var dr []map[string]interface{} + + dr = append(dr, map[string]interface{}{ + "lookup_by": dra.LookupBy, + "regex": dra.Regex, + "source": dra.Source, + }) + + return dr +} + func resourcePagerDutyEventOrchestrationPathRouterImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { client, err := meta.(*Config).Client() if err != nil { diff --git a/pagerduty/resource_pagerduty_event_orchestration_path_router_test.go b/pagerduty/resource_pagerduty_event_orchestration_path_router_test.go index 246c349d5..fcf81f2c1 100644 --- a/pagerduty/resource_pagerduty_event_orchestration_path_router_test.go +++ b/pagerduty/resource_pagerduty_event_orchestration_path_router_test.go @@ -3,11 +3,13 @@ package pagerduty import ( "context" "fmt" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/heimweh/go-pagerduty/pagerduty" ) func init() { @@ -23,11 +25,42 @@ func TestAccPagerDutyEventOrchestrationPathRouter_Basic(t *testing.T) { service := fmt.Sprintf("tf-%s", acctest.RandString(5)) orchestration := fmt.Sprintf("tf-orchestration-%s", acctest.RandString(5)) + dynamicRouteToByNameInput := &pagerduty.EventOrchestrationPathDynamicRouteTo{ + LookupBy: "service_name", + Regex: ".*", + Source: "event.custom_details.pd_service_name", + } + dynamicRouteToByIDInput := &pagerduty.EventOrchestrationPathDynamicRouteTo{ + LookupBy: "service_id", + Regex: "ID:(.*)", + Source: "event.custom_details.pd_service_id", + } + invalidDynamicRouteToPlacementMessage := "Invalid Dynamic Routing rule configuration:\n- A Router can have at most one Dynamic Routing rule; Rules with the dynamic_route_to action found at indexes: 1, 2\n- The Dynamic Routing rule must be the first rule in a Router" + invalidDynamicRouteToConfigMessage := "Invalid Dynamic Routing rule configuration:\n- Dynamic Routing rules cannot have conditions\n- Dynamic Routing rules cannot have the `route_to` action" + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckPagerDutyEventOrchestrationRouterDestroy, Steps: []resource.TestStep{ + // Invalid Dynamic Routing rule config for a new resource: multiple Dynamic Routing rules, Dynamic Routing rule not the first rule in the Router: + { + Config: testAccCheckPagerDutyEventOrchestrationRouterInvalidDynamicRoutingRulePlacement(team, escalationPolicy, service, orchestration), + PlanOnly: true, + ExpectError: regexp.MustCompile(invalidDynamicRouteToPlacementMessage), + }, + // Invalid Dynamic Routing rule config for a new resource: Dynamic Routing rule with conditions and the interpolated route_to action: + { + Config: testAccCheckPagerDutyEventOrchestrationRouterInvalidDynamicRoutingRuleConfig(team, escalationPolicy, service, orchestration, "pagerduty_service.bar.id"), + PlanOnly: true, + ExpectError: regexp.MustCompile(invalidDynamicRouteToConfigMessage), + }, + // Invalid Dynamic Routing rule config for a new resource: Dynamic Routing rule with conditions and the hard-coded route_to action: + { + Config: testAccCheckPagerDutyEventOrchestrationRouterInvalidDynamicRoutingRuleConfig(team, escalationPolicy, service, orchestration, "\"PARASOL\""), + PlanOnly: true, + ExpectError: regexp.MustCompile(invalidDynamicRouteToConfigMessage), + }, { Config: testAccCheckPagerDutyEventOrchestrationRouterConfigNoRules(team, escalationPolicy, service, orchestration), Check: resource.ComposeTestCheckFunc( @@ -48,6 +81,45 @@ func TestAccPagerDutyEventOrchestrationPathRouter_Basic(t *testing.T) { "pagerduty_event_orchestration_router.router", "unrouted", true), //test for catch_all route_to prop, by default it should be unrouted ), }, + // Configure a Dynamic Routing rule: + { + Config: testAccCheckPagerDutyEventOrchestrationRouterDynamicRouteToConfig(team, escalationPolicy, service, orchestration, dynamicRouteToByNameInput), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterExists("pagerduty_event_orchestration_router.router"), + testAccCheckPagerDutyEventOrchestrationRouterPathDynamicRouteToMatch("pagerduty_event_orchestration_router.router", dynamicRouteToByNameInput), + testAccCheckPagerDutyEventOrchestrationRouterPathRouteToMatch( + "pagerduty_event_orchestration_router.router", "unrouted", true), + ), + }, + // Invalid Dynamic Routing rule config for an existing resource: multiple Dynamic Routing rules, Dynamic Routing rule not the first rule in the Router: + { + Config: testAccCheckPagerDutyEventOrchestrationRouterInvalidDynamicRoutingRulePlacement(team, escalationPolicy, service, orchestration), + PlanOnly: true, + ExpectError: regexp.MustCompile(invalidDynamicRouteToPlacementMessage), + }, + // Invalid Dynamic Routing rule config for an existing resource: Dynamic Routing rule with conditions and the interpolated route_to action: + { + Config: testAccCheckPagerDutyEventOrchestrationRouterInvalidDynamicRoutingRuleConfig(team, escalationPolicy, service, orchestration, "pagerduty_service.bar.id"), + PlanOnly: true, + ExpectError: regexp.MustCompile(invalidDynamicRouteToConfigMessage), + }, + // Invalid Dynamic Routing rule config for an existing resource: Dynamic Routing rule with conditions and the hard-coded route_to action: + { + Config: testAccCheckPagerDutyEventOrchestrationRouterInvalidDynamicRoutingRuleConfig(team, escalationPolicy, service, orchestration, "\"PARASOL\""), + PlanOnly: true, + ExpectError: regexp.MustCompile(invalidDynamicRouteToConfigMessage), + }, + // Update the Dynamic Routing rule: + { + Config: testAccCheckPagerDutyEventOrchestrationRouterDynamicRouteToConfig(team, escalationPolicy, service, orchestration, dynamicRouteToByIDInput), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterExists("pagerduty_event_orchestration_router.router"), + testAccCheckPagerDutyEventOrchestrationRouterPathDynamicRouteToMatch("pagerduty_event_orchestration_router.router", dynamicRouteToByIDInput), + testAccCheckPagerDutyEventOrchestrationRouterPathRouteToMatch( + "pagerduty_event_orchestration_router.router", "unrouted", true), + ), + }, + // Delete the Dynamic Routing rule: { Config: testAccCheckPagerDutyEventOrchestrationRouterConfigWithConditions(team, escalationPolicy, service, orchestration), Check: resource.ComposeTestCheckFunc( @@ -238,6 +310,96 @@ func createBaseConfig(t, ep, s, o string) string { `, t, ep, s, o) } +func testAccCheckPagerDutyEventOrchestrationRouterInvalidDynamicRoutingRulePlacement(t, ep, s, o string) string { + return fmt.Sprintf("%s%s", createBaseConfig(t, ep, s, o), + `resource "pagerduty_event_orchestration_router" "router" { + event_orchestration = pagerduty_event_orchestration.orch.id + + catch_all { + actions { + route_to = "unrouted" + } + } + set { + id = "start" + rule { + label = "static routing rule 1" + actions { + route_to = pagerduty_service.bar.id + } + } + rule { + disabled = false + label = "dynamic routing rule 1" + actions { + dynamic_route_to { + lookup_by = "service_id" + regex = ".*" + source = "event.custom_details.pd_service_id" + } + } + } + rule { + label = "dynamic routing rule 2" + actions { + dynamic_route_to { + lookup_by = "service_name" + regex = ".*" + source = "event.custom_details.pd_service_name" + } + } + } + rule { + label = "static routing rule 2" + actions { + route_to = "P1B2C23" + } + } + + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationRouterInvalidDynamicRoutingRuleConfig(t, ep, s, o, routeTo string) string { + routerConfig := fmt.Sprintf( + `resource "pagerduty_event_orchestration_router" "router" { + event_orchestration = pagerduty_event_orchestration.orch.id + + catch_all { + actions { + route_to = "unrouted" + } + } + set { + id = "start" + rule { + label = "dynamic routing rule 1" + condition { + expression = "event.summary matches part 'production'" + } + actions { + dynamic_route_to { + lookup_by = "service_id" + regex = ".*" + source = "event.custom_details.pd_service_id" + } + route_to = %s + } + } + rule { + label = "static routing rule 1" + actions { + route_to = "P1B2C23" + } + } + + } + } + `, routeTo) + return fmt.Sprintf("%s%s", createBaseConfig(t, ep, s, o), routerConfig) +} + func testAccCheckPagerDutyEventOrchestrationRouterConfigNoRules(t, ep, s, o string) string { return fmt.Sprintf("%s%s", createBaseConfig(t, ep, s, o), `resource "pagerduty_event_orchestration_router" "router" { @@ -279,6 +441,42 @@ func testAccCheckPagerDutyEventOrchestrationRouterConfig(t, ep, s, o string) str `) } +func testAccCheckPagerDutyEventOrchestrationRouterDynamicRouteToConfig(t, ep, s, o string, dynamicRouteToByNameInput *pagerduty.EventOrchestrationPathDynamicRouteTo) string { + routerConfig := fmt.Sprintf( + `resource "pagerduty_event_orchestration_router" "router" { + event_orchestration = pagerduty_event_orchestration.orch.id + + catch_all { + actions { + route_to = "unrouted" + } + } + set { + id = "start" + rule { + disabled = false + label = "dynamic routing rule" + actions { + dynamic_route_to { + lookup_by = "%s" + regex = "%s" + source = "%s" + } + } + } + rule { + label = "static routing rule" + actions { + route_to = pagerduty_service.bar.id + } + } + } + } + `, dynamicRouteToByNameInput.LookupBy, dynamicRouteToByNameInput.Regex, dynamicRouteToByNameInput.Source) + + return fmt.Sprintf("%s%s", createBaseConfig(t, ep, s, o), routerConfig) +} + func testAccCheckPagerDutyEventOrchestrationRouterConfigWithConditions(t, ep, s, o string) string { return fmt.Sprintf("%s%s", createBaseConfig(t, ep, s, o), `resource "pagerduty_event_orchestration_router" "router" { @@ -510,3 +708,28 @@ func testAccCheckPagerDutyEventOrchestrationRouterPathRouteToMatch(router, servi return nil } } + +func testAccCheckPagerDutyEventOrchestrationRouterPathDynamicRouteToMatch(router string, expectedDynamicRouteTo *pagerduty.EventOrchestrationPathDynamicRouteTo) resource.TestCheckFunc { + return func(s *terraform.State) error { + r, rOk := s.RootModule().Resources[router] + if !rOk { + return fmt.Errorf("Not found: %s", router) + } + + rLookupBy := r.Primary.Attributes["set.0.rule.0.actions.0.dynamic_route_to.0.lookup_by"] + rRegex := r.Primary.Attributes["set.0.rule.0.actions.0.dynamic_route_to.0.regex"] + rSource := r.Primary.Attributes["set.0.rule.0.actions.0.dynamic_route_to.0.source"] + + if rLookupBy != expectedDynamicRouteTo.LookupBy { + return fmt.Errorf("Event Orchestration Router `dynamic_route_to.lookup_by` (%v) does not match expected value: %v", rLookupBy, expectedDynamicRouteTo.LookupBy) + } + if rRegex != expectedDynamicRouteTo.Regex { + return fmt.Errorf("Event Orchestration Router `dynamic_route_to.regex` (%v) does not match expected value: %v", rRegex, expectedDynamicRouteTo.Regex) + } + if rSource != expectedDynamicRouteTo.Source { + return fmt.Errorf("Event Orchestration Router `dynamic_route_to.source` (%v) does not match expected value: %v", rSource, expectedDynamicRouteTo.Source) + } + + return nil + } +} diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/event_orchestration_path.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/event_orchestration_path.go index 72b60e0f4..9760a6702 100644 --- a/vendor/github.com/heimweh/go-pagerduty/pagerduty/event_orchestration_path.go +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/event_orchestration_path.go @@ -53,6 +53,7 @@ type EventOrchestrationPathRuleCondition struct { type EventOrchestrationPathRuleActions struct { DropEvent bool `json:"drop_event"` RouteTo string `json:"route_to"` + DynamicRouteTo *EventOrchestrationPathDynamicRouteTo `json:"dynamic_route_to"` Suppress bool `json:"suppress"` Suspend *int `json:"suspend"` Priority string `json:"priority"` @@ -66,6 +67,12 @@ type EventOrchestrationPathRuleActions struct { Extractions []*EventOrchestrationPathActionExtractions `json:"extractions"` } +type EventOrchestrationPathDynamicRouteTo struct { + Source string `json:"source,omitempty"` + Regex string `json:"regex,omitempty"` + LookupBy string `json:"lookup_by,omitempty"` +} + type EventOrchestrationPathIncidentCustomFieldUpdate struct { ID string `json:"id,omitempty"` Value string `json:"value,omitempty"` diff --git a/vendor/modules.txt b/vendor/modules.txt index 26f324e00..2f1765710 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -258,7 +258,7 @@ github.com/hashicorp/terraform-svchost # github.com/hashicorp/yamux v0.1.1 ## explicit; go 1.15 github.com/hashicorp/yamux -# github.com/heimweh/go-pagerduty v0.0.0-20240503143637-3459408ac715 +# github.com/heimweh/go-pagerduty v0.0.0-20240503143637-3459408ac715 => github.com/alexzakabluk/go-pagerduty v0.0.0-20240607142119-ac9a64bba6da ## explicit; go 1.17 github.com/heimweh/go-pagerduty/pagerduty github.com/heimweh/go-pagerduty/persistentconfig @@ -559,3 +559,4 @@ google.golang.org/protobuf/types/known/timestamppb # gopkg.in/ini.v1 v1.67.0 ## explicit gopkg.in/ini.v1 +# github.com/heimweh/go-pagerduty => github.com/alexzakabluk/go-pagerduty v0.0.0-20240607142119-ac9a64bba6da diff --git a/website/docs/r/event_orchestration_global.html.markdown b/website/docs/r/event_orchestration_global.html.markdown index d59a62809..c57c6b39d 100644 --- a/website/docs/r/event_orchestration_global.html.markdown +++ b/website/docs/r/event_orchestration_global.html.markdown @@ -14,7 +14,7 @@ A [Global Orchestration](https://support.pagerduty.com/docs/event-orchestration# This example shows creating `Team`, and `Event Orchestration` resources followed by creating a Global Orchestration to handle Events sent to that Event Orchestration. -This example also shows using `priority` [data source](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/priority) to configure `priority` action for a rule. If the Event matches the third rule in set "step-two" the resulting incident will have the Priority `P1`. +This example also shows using the [pagerduty_priority](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/priority) and [pagerduty_escalation_policy](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/escalation_policy) data sources to configure `priority` and `escalation_policy` actions for a rule. This example shows a Global Orchestration that has nested sets: a rule in the "start" set has a `route_to` action pointing at the "step-two" set. @@ -35,6 +35,10 @@ data "pagerduty_priority" "p1" { name = "P1" } +data "pagerduty_escalation_policy" "sre_esc_policy" { + name = "SRE Escalation Policy" +} + resource "pagerduty_event_orchestration_global" "global" { event_orchestration = pagerduty_event_orchestration.event_orchestration.id set { @@ -59,6 +63,15 @@ resource "pagerduty_event_orchestration_global" "global" { drop_event = true } } + rule { + label = "If the DB host is running out of space, then page the SRE team" + condition { + expression = "event.summary matches part 'running out of space'" + } + actions { + escalation_policy = data.pagerduty_escalation_policy.sre_esc_policy.id + } + } rule { label = "If there's something wrong on the replica, then mark the alert as a warning" condition { @@ -112,6 +125,7 @@ The following arguments are supported: * `suppress` - (Optional) Set whether the resulting alert is suppressed. Suppressed alerts will not trigger an incident. * `suspend` - (Optional) The number of seconds to suspend the resulting alert before triggering. This effectively pauses incident notifications. If a `resolve` event arrives before the alert triggers then PagerDuty won't create an incident for this alert. * `priority` - (Optional) The ID of the priority you want to set on resulting incident. Consider using the [`pagerduty_priority`](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/priority) data source. +* `escalation_policy` - (Optional) The ID of the Escalation Policy you want to assign incidents to. Event rules with this action will override the Escalation Policy already set on a Service's settings, with what is configured by this action. * `annotate` - (Optional) Add this text as a note on the resulting incident. * `incident_custom_field_update` - (Optional) Assign a custom field to the resulting incident. * `id` - (Required) The custom field id diff --git a/website/docs/r/event_orchestration_router.html.markdown b/website/docs/r/event_orchestration_router.html.markdown index 821289d4a..c232ea8b4 100644 --- a/website/docs/r/event_orchestration_router.html.markdown +++ b/website/docs/r/event_orchestration_router.html.markdown @@ -12,15 +12,31 @@ An Orchestration Router allows users to create a set of Event Rules. The Router ## Example of configuring Router rules for an Orchestration -In this example the user has defined the Router with two rules, each routing to a different service. - -This example assumes services used in the `route_to` configuration already exists. So it does not show creation of service resource. +In this example the user has defined the Router with three rules. The first rule configures a dynamic route: any event containing a value in its `pd_service_id` custom detail will be routed to the Service with the ID specified by that value. The other rules route events matching a condition to specific services. ```hcl +data "pagerduty_service" "database" { + name = "Primary Data Store" +} + +data "pagerduty_service" "www" { + name = "Web Server App" +} + resource "pagerduty_event_orchestration_router" "router" { event_orchestration = pagerduty_event_orchestration.my_monitor.id set { id = "start" + rule { + label = "Dynamically route events related to specific PagerDuty services" + actions { + dynamic_route_to = { + lookup_by = "service_id" + source = "event.custom_details.pd_service_id" + regexp = "(.*)" + } + } + } rule { label = "Events relating to our relational database" condition { @@ -30,7 +46,7 @@ resource "pagerduty_event_orchestration_router" "router" { expression = "event.source matches regex 'db[0-9]+-server'" } actions { - route_to = pagerduty_service.database.id + route_to = data.pagerduty_service.database.id } } rule { @@ -38,7 +54,7 @@ resource "pagerduty_event_orchestration_router" "router" { expression = "event.summary matches part 'www'" } actions { - route_to = pagerduty_service.www.id + route_to = data.pagerduty_service.www.id } } } @@ -72,6 +88,22 @@ The following arguments are supported: * `expression`- (Required) A [PCL condition](https://developer.pagerduty.com/docs/ZG9jOjM1NTE0MDc0-pcl-overview) string. ### Actions (`actions`) supports the following: + +#### Dynamic Routing + +Use the contents of an event payload to dynamically route an event to the target service. Note these setting can only be used once in the Router, and only in the first rule. The dynamic routing rule cannot have `conditions` nor a `route_to` action defined. + +* `dynamic_route_to` - (Required) supports the following: + * `source` - (Required) The path to a field in an event. + * `regex` - (Required) The regular expression, used to extract a value from the source field. Must use valid [RE2 regular expression](https://github.com/google/re2/wiki/Syntax) syntax. + * `lookup_by` - (Required) Indicates whether the extracted value from the source is a service's name or ID. Allowed values are: `service_name`, `service_id` + +If an event has a value at the specified `source`, and if the `regex` successfully matches the value, and if the matching portion is valid Service ID or Name, then the event will be routed to that service. Otherwise the event will be checked against any subsequent router rules. + +#### Service Route + +If an event matches this rule's conditions, then route it to the specified Service. + * `route_to` - (Required) The ID of the target Service for the resulting alert. ### Catch All (`catch_all`) supports the following: diff --git a/website/docs/r/event_orchestration_service.html.markdown b/website/docs/r/event_orchestration_service.html.markdown index 17f7d8fb3..74b25f7e3 100644 --- a/website/docs/r/event_orchestration_service.html.markdown +++ b/website/docs/r/event_orchestration_service.html.markdown @@ -16,7 +16,7 @@ A [Service Orchestration](https://support.pagerduty.com/docs/event-orchestration This example shows creating `Team`, `User`, `Escalation Policy`, and `Service` resources followed by creating a Service Orchestration to handle Events sent to that Service. -This example also shows using `priority` [data source](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/priority) to configure `priority` action for a rule. If the Event matches the first rule in set "step-two" the resulting incident will have the Priority `P1`. +This example also shows using the [pagerduty_priority](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/priority) and [pagerduty_escalation_policy](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/escalation_policy) data sources to configure `priority` and `escalation_policy` actions for a rule. This example shows a Service Orchestration that has nested sets: a rule in the "start" set has a `route_to` action pointing at the "step-two" set. @@ -70,6 +70,10 @@ data "pagerduty_priority" "p1" { name = "P1" } +data "pagerduty_escalation_policy" "sre_esc_policy" { + name = "SRE Escalation Policy" +} + resource "pagerduty_event_orchestration_service" "www" { service = pagerduty_service.example.id enable_event_orchestration_for_service = true @@ -116,6 +120,15 @@ resource "pagerduty_event_orchestration_service" "www" { } } } + rule { + label = "If any of the API apps are unavailable, page the SRE team" + condition { + expression = "event.custom_details.service_name matches part '-api' and event.custom_details.status_code matches '502'" + } + actions { + escalation_policy = data.pagerduty_escalation_policy.sre_esc_policy.id + } + } rule { label = "If there's something wrong on the canary let the team know about it in our deployments Slack channel" condition { @@ -184,6 +197,7 @@ The following arguments are supported: * `suppress` - (Optional) Set whether the resulting alert is suppressed. Suppressed alerts will not trigger an incident. * `suspend` - (Optional) The number of seconds to suspend the resulting alert before triggering. This effectively pauses incident notifications. If a `resolve` event arrives before the alert triggers then PagerDuty won't create an incident for this alert. * `priority` - (Optional) The ID of the priority you want to set on resulting incident. Consider using the [`pagerduty_priority`](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/priority) data source. +* `escalation_policy` - (Optional) The ID of the Escalation Policy you want to assign incidents to. Event rules with this action will override the Escalation Policy already set on a Service's settings, with what is configured by this action. * `annotate` - (Optional) Add this text as a note on the resulting incident. * `incident_custom_field_update` - (Optional) Assign a custom field to the resulting incident. * `id` - (Required) The custom field id