Skip to content

Commit

Permalink
ORCA-4813 - Add validation for Dynamic Routing rule (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
alenapan authored Jun 20, 2024
1 parent 31bdc49 commit 10b848f
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 3 deletions.
4 changes: 4 additions & 0 deletions pagerduty/event_orchestration_path_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,7 @@ func emptyOrchestrationPathStructBuilder(pathType string) *pagerduty.EventOrches

return commonEmptyOrchestrationPath()
}

func isNonEmptyList(arg interface{}) bool {
return !isNilFunc(arg) && len(arg.([]interface{})) > 0
}
52 changes: 51 additions & 1 deletion pagerduty/resource_pagerduty_event_orchestration_path_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"net/http"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand All @@ -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,
Expand Down Expand Up @@ -133,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

Expand Down Expand Up @@ -315,7 +365,7 @@ func expandRouterActions(v interface{}) *pagerduty.EventOrchestrationPathRuleAct
for _, ai := range v.([]interface{}) {
am := ai.(map[string]interface{})
dra := am["dynamic_route_to"]
if !isNilFunc(dra) && len(dra.([]interface{})) > 0 {
if isNonEmptyList(dra) {
actions.DynamicRouteTo = expandRouterDynamicRouteToAction(dra)
} else {
actions.RouteTo = am["route_to"].(string)
Expand Down
133 changes: 131 additions & 2 deletions pagerduty/resource_pagerduty_event_orchestration_path_router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pagerduty
import (
"context"
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
Expand Down Expand Up @@ -34,12 +35,32 @@ func TestAccPagerDutyEventOrchestrationPathRouter_Basic(t *testing.T) {
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(
Expand Down Expand Up @@ -70,7 +91,25 @@ func TestAccPagerDutyEventOrchestrationPathRouter_Basic(t *testing.T) {
"pagerduty_event_orchestration_router.router", "unrouted", true),
),
},
// Update the Dynamic Routing rule added in the previous test case:
// 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(
Expand All @@ -80,7 +119,7 @@ func TestAccPagerDutyEventOrchestrationPathRouter_Basic(t *testing.T) {
"pagerduty_event_orchestration_router.router", "unrouted", true),
),
},
// Delete the Dynamic Routing rule added in the previous test cases:
// Delete the Dynamic Routing rule:
{
Config: testAccCheckPagerDutyEventOrchestrationRouterConfigWithConditions(team, escalationPolicy, service, orchestration),
Check: resource.ComposeTestCheckFunc(
Expand Down Expand Up @@ -271,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" {
Expand Down

0 comments on commit 10b848f

Please sign in to comment.