diff --git a/CHANGELOG.md b/CHANGELOG.md index ab8225a49..b5dfe244f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 2.5.0 (June 1, 2022) + +FEATURES: +* Support for Event Orchestration via several new resources. ([#512](https://github.com/PagerDuty/terraform-provider-pagerduty/pull/512)) + * `resource/pagerduty_event_orchestration` + * `resource/pagerduty_event_orchestration_router` + * `resource/pagerduty_event_orchestration_unrouted` + * `resource/pagerduty_event_orchestration_service` + ## 2.4.2 (May 20, 2022) IMPROVEMENTS: diff --git a/go.mod b/go.mod index b3380923d..c5f0bc0d2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( cloud.google.com/go v0.71.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1 - github.com/heimweh/go-pagerduty v0.0.0-20220422231448-43095fe5ba3f + github.com/heimweh/go-pagerduty v0.0.0-20220527195341-4e587aa9b58e golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd // indirect google.golang.org/api v0.35.0 // indirect google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb // indirect diff --git a/go.sum b/go.sum index 9c893f216..dd09ff741 100644 --- a/go.sum +++ b/go.sum @@ -286,6 +286,10 @@ github.com/heimweh/go-pagerduty v0.0.0-20220208023456-83fe435832fb h1:p3faOVCU8L github.com/heimweh/go-pagerduty v0.0.0-20220208023456-83fe435832fb/go.mod h1:JtJGtgN0y9KOCaqFMZFaBCWskpO/KK3Ro9TwjP9ss6w= github.com/heimweh/go-pagerduty v0.0.0-20220422231448-43095fe5ba3f h1:NLk7iDq85F2lz0q1gY32vZR506aYiNcgvV+Us1rX1q4= github.com/heimweh/go-pagerduty v0.0.0-20220422231448-43095fe5ba3f/go.mod h1:JtJGtgN0y9KOCaqFMZFaBCWskpO/KK3Ro9TwjP9ss6w= +github.com/heimweh/go-pagerduty v0.0.0-20220428180718-5a69bb821163 h1:ETKxW+KSjOPaRzZU9f+QrjCkrL7hQqtMPKDv8DnLDO4= +github.com/heimweh/go-pagerduty v0.0.0-20220428180718-5a69bb821163/go.mod h1:JtJGtgN0y9KOCaqFMZFaBCWskpO/KK3Ro9TwjP9ss6w= +github.com/heimweh/go-pagerduty v0.0.0-20220527195341-4e587aa9b58e h1:xit0rQWTVlM9ohz4IzrddDKglC7jey+m3GSI/bz3TIw= +github.com/heimweh/go-pagerduty v0.0.0-20220527195341-4e587aa9b58e/go.mod h1:JtJGtgN0y9KOCaqFMZFaBCWskpO/KK3Ro9TwjP9ss6w= 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= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/pagerduty/data_source_pagerduty_event_orchestration.go b/pagerduty/data_source_pagerduty_event_orchestration.go new file mode 100644 index 000000000..0e42d5a80 --- /dev/null +++ b/pagerduty/data_source_pagerduty_event_orchestration.go @@ -0,0 +1,102 @@ +package pagerduty + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +func dataSourcePagerDutyEventOrchestration() *schema.Resource { + return &schema.Resource{ + Read: dataSourcePagerDutyEventOrchestrationRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "integration": { + Type: schema.TypeList, + Computed: true, + Optional: true, // Tests keep failing if "Optional: true" is not provided + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "parameters": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "routing_key": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func dataSourcePagerDutyEventOrchestrationRead(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + log.Printf("[INFO] Reading PagerDuty Event Orchestration") + + searchName := d.Get("name").(string) + + return resource.Retry(5*time.Minute, func() *resource.RetryError { + resp, _, err := client.EventOrchestrations.List() + if err != nil { + return resource.RetryableError(err) + } + + var found *pagerduty.EventOrchestration + + for _, orchestration := range resp.Orchestrations { + if orchestration.Name == searchName { + found = orchestration + break + } + } + + if found == nil { + return resource.NonRetryableError( + fmt.Errorf("Unable to locate any Event Orchestration with the name: %s", searchName), + ) + } + + // Get the found orchestration by ID so we can set the integrations property + // since the list ndpoint does not return it + orch, _, err := client.EventOrchestrations.Get(found.ID) + if err != nil { + return resource.RetryableError(err) + } + + d.SetId(orch.ID) + d.Set("name", orch.Name) + + if len(orch.Integrations) > 0 { + d.Set("integration", flattenEventOrchestrationIntegrations(orch.Integrations)) + } + + return nil + }) +} diff --git a/pagerduty/data_source_pagerduty_event_orchestration_test.go b/pagerduty/data_source_pagerduty_event_orchestration_test.go new file mode 100644 index 000000000..554c19037 --- /dev/null +++ b/pagerduty/data_source_pagerduty_event_orchestration_test.go @@ -0,0 +1,64 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccDataSourcePagerDutyEventOrchestration_Basic(t *testing.T) { + name := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourcePagerDutyEventOrchestrationConfig(name), + Check: resource.ComposeTestCheckFunc( + testAccDataSourcePagerDutyEventOrchestration("pagerduty_event_orchestration.test", "data.pagerduty_event_orchestration.by_name"), + ), + }, + }, + }) +} + +func testAccDataSourcePagerDutyEventOrchestration(src, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + srcR := s.RootModule().Resources[src] + srcA := srcR.Primary.Attributes + + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + if a["id"] == "" { + return fmt.Errorf("Expected to get an Event Orchestration ID from PagerDuty") + } + + testAtts := []string{"id", "name", "integration"} + + for _, att := range testAtts { + if a[att] != srcA[att] { + return fmt.Errorf("Expected the Event Orchestration %s to be: %s, but got: %s", att, srcA[att], a[att]) + } + } + + return nil + } +} + +func testAccDataSourcePagerDutyEventOrchestrationConfig(name string) string { + return fmt.Sprintf(` +resource "pagerduty_event_orchestration" "test" { + name = "%s" +} + +data "pagerduty_event_orchestration" "by_name" { + name = pagerduty_event_orchestration.test.name +} +`, name) +} diff --git a/pagerduty/event_orchestration_path_util.go b/pagerduty/event_orchestration_path_util.go new file mode 100644 index 000000000..7f5bc6f65 --- /dev/null +++ b/pagerduty/event_orchestration_path_util.go @@ -0,0 +1,225 @@ +package pagerduty + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +var eventOrchestrationPathConditionsSchema = map[string]*schema.Schema{ + "expression": { + Type: schema.TypeString, + Required: true, + }, +} + +var eventOrchestrationPathVariablesSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "path": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, +} + +var eventOrchestrationPathExtractionsSchema = map[string]*schema.Schema{ + "regex": { + Type: schema.TypeString, + Optional: true, + }, + "source": { + Type: schema.TypeString, + Optional: true, + }, + "target": { + Type: schema.TypeString, + Required: true, + }, + "template": { + Type: schema.TypeString, + Optional: true, + }, +} + +func invalidExtractionRegexTemplateNilConfig() string { + return ` + extraction { + target = "event.summary" + }` +} + +func invalidExtractionRegexTemplateValConfig() string { + return ` + extraction { + regex = ".*" + template = "hi" + target = "event.summary" + }` +} + +func invalidExtractionRegexNilSourceConfig() string { + return ` + extraction { + regex = ".*" + target = "event.summary" + }` +} + +func validateEventOrchestrationPathSeverity() schema.SchemaValidateFunc { + return validateValueFunc([]string{ + "info", + "error", + "warning", + "critical", + }) +} + +func validateEventOrchestrationPathEventAction() schema.SchemaValidateFunc { + return validateValueFunc([]string{ + "trigger", + "resolve", + }) +} + +func checkExtractions(context context.Context, diff *schema.ResourceDiff, i interface{}) error { + sn := diff.Get("set.#").(int) + + for si := 0; si < sn; si++ { + rn := diff.Get(fmt.Sprintf("set.%d.rule.#", si)).(int) + for ri := 0; ri < rn; ri++ { + res := checkExtractionAttributes(diff, fmt.Sprintf("set.%d.rule.%d.actions.0.extraction", si, ri)) + if res != nil { + return res + } + } + } + return checkExtractionAttributes(diff, "catch_all.0.actions.0.extraction") +} + +func checkExtractionAttributes(diff *schema.ResourceDiff, loc string) error { + num := diff.Get(fmt.Sprintf("%s.#", loc)).(int) + for i := 0; i < num; i++ { + prefix := fmt.Sprintf("%s.%d", loc, i) + r := diff.Get(fmt.Sprintf("%s.regex", prefix)).(string) + t := diff.Get(fmt.Sprintf("%s.template", prefix)).(string) + + if r == "" && t == "" { + return fmt.Errorf("Invalid configuration in %s: regex and template cannot both be null", prefix) + } + if r != "" && t != "" { + return fmt.Errorf("Invalid configuration in %s: regex and template cannot both have values", prefix) + } + + s := diff.Get(fmt.Sprintf("%s.source", prefix)).(string) + if r != "" && s == "" { + return fmt.Errorf("Invalid configuration in %s: source can't be blank", prefix) + } + } + return nil +} + +func expandEventOrchestrationPathConditions(v interface{}) []*pagerduty.EventOrchestrationPathRuleCondition { + conditions := []*pagerduty.EventOrchestrationPathRuleCondition{} + + for _, cond := range v.([]interface{}) { + c := cond.(map[string]interface{}) + + cx := &pagerduty.EventOrchestrationPathRuleCondition{ + Expression: c["expression"].(string), + } + + conditions = append(conditions, cx) + } + + return conditions +} + +func flattenEventOrchestrationPathConditions(conditions []*pagerduty.EventOrchestrationPathRuleCondition) []interface{} { + var flattendConditions []interface{} + + for _, condition := range conditions { + flattendCondition := map[string]interface{}{ + "expression": condition.Expression, + } + flattendConditions = append(flattendConditions, flattendCondition) + } + + return flattendConditions +} + +func expandEventOrchestrationPathVariables(v interface{}) []*pagerduty.EventOrchestrationPathActionVariables { + res := []*pagerduty.EventOrchestrationPathActionVariables{} + + for _, er := range v.([]interface{}) { + rer := er.(map[string]interface{}) + + av := &pagerduty.EventOrchestrationPathActionVariables{ + Name: rer["name"].(string), + Path: rer["path"].(string), + Type: rer["type"].(string), + Value: rer["value"].(string), + } + + res = append(res, av) + } + + return res +} + +func flattenEventOrchestrationPathVariables(v []*pagerduty.EventOrchestrationPathActionVariables) []interface{} { + var res []interface{} + + for _, s := range v { + fv := map[string]interface{}{ + "name": s.Name, + "path": s.Path, + "type": s.Type, + "value": s.Value, + } + res = append(res, fv) + } + return res +} + +func expandEventOrchestrationPathExtractions(v interface{}) []*pagerduty.EventOrchestrationPathActionExtractions { + res := []*pagerduty.EventOrchestrationPathActionExtractions{} + + for _, eai := range v.([]interface{}) { + ea := eai.(map[string]interface{}) + ext := &pagerduty.EventOrchestrationPathActionExtractions{ + Target: ea["target"].(string), + Regex: ea["regex"].(string), + Template: ea["template"].(string), + Source: ea["source"].(string), + } + res = append(res, ext) + } + return res +} + +func flattenEventOrchestrationPathExtractions(e []*pagerduty.EventOrchestrationPathActionExtractions) []interface{} { + var res []interface{} + + for _, s := range e { + e := map[string]interface{}{ + "target": s.Target, + "regex": s.Regex, + "template": s.Template, + "source": s.Source, + } + res = append(res, e) + } + return res +} diff --git a/pagerduty/import_pagerduty_event_orchestration_path_router_test.go b/pagerduty/import_pagerduty_event_orchestration_path_router_test.go new file mode 100644 index 000000000..976e80057 --- /dev/null +++ b/pagerduty/import_pagerduty_event_orchestration_path_router_test.go @@ -0,0 +1,38 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccPagerDutyEventOrchestrationPathRouter_import(t *testing.T) { + team := fmt.Sprintf("tf-name-%s", acctest.RandString(5)) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + orchestration := fmt.Sprintf("tf-orchestration-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEventOrchestrationRouterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyEventOrchestrationRouterConfigWithMultipleRules(team, escalationPolicy, service, orchestration), + }, + { + ResourceName: "pagerduty_event_orchestration_router.router", + ImportStateIdFunc: testAccCheckPagerDutyEventOrchestrationPathRouterID, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckPagerDutyEventOrchestrationPathRouterID(s *terraform.State) (string, error) { + return s.RootModule().Resources["pagerduty_event_orchestration.orch"].Primary.ID, nil +} diff --git a/pagerduty/import_pagerduty_event_orchestration_path_service_test.go b/pagerduty/import_pagerduty_event_orchestration_path_service_test.go new file mode 100644 index 000000000..579d2c957 --- /dev/null +++ b/pagerduty/import_pagerduty_event_orchestration_path_service_test.go @@ -0,0 +1,36 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccPagerDutyEventOrchestrationPathService_import(t *testing.T) { + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEventOrchestrationServicePathDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceAllActionsConfig(escalationPolicy, service), + }, + { + ResourceName: "pagerduty_event_orchestration_service.serviceA", + ImportStateIdFunc: testAccCheckPagerDutyEventOrchestrationPathServiceID, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceID(s *terraform.State) (string, error) { + return s.RootModule().Resources["pagerduty_service.bar"].Primary.ID, nil +} diff --git a/pagerduty/import_pagerduty_event_orchestration_path_unrouted_test.go b/pagerduty/import_pagerduty_event_orchestration_path_unrouted_test.go new file mode 100644 index 000000000..9ab539ba4 --- /dev/null +++ b/pagerduty/import_pagerduty_event_orchestration_path_unrouted_test.go @@ -0,0 +1,38 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccPagerDutyEventOrchestrationPathUnrouted_import(t *testing.T) { + team := fmt.Sprintf("tf-name-%s", acctest.RandString(5)) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + orchestration := fmt.Sprintf("tf-orchestration-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEventOrchestrationPathUnroutedDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedWithAllConfig(team, escalationPolicy, service, orchestration), + }, + { + ResourceName: "pagerduty_event_orchestration_unrouted.unrouted", + ImportStateIdFunc: testAccCheckPagerDutyEventOrchestrationPathUnroutedID, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedID(s *terraform.State) (string, error) { + return s.RootModule().Resources["pagerduty_event_orchestration.orch"].Primary.ID, nil +} diff --git a/pagerduty/import_pagerduty_event_orchestration_test.go b/pagerduty/import_pagerduty_event_orchestration_test.go new file mode 100644 index 000000000..d6b7c8d14 --- /dev/null +++ b/pagerduty/import_pagerduty_event_orchestration_test.go @@ -0,0 +1,53 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccPagerDutyEventOrchestration_import(t *testing.T) { + name := fmt.Sprintf("tf-name-%s", acctest.RandString(5)) + description := fmt.Sprintf("tf-description-%s", acctest.RandString(5)) + team1 := fmt.Sprintf("tf-team1-%s", acctest.RandString(5)) + team2 := fmt.Sprintf("tf-team2-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEventOrchestrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyEventOrchestrationConfig(name, description, team1, team2), + }, + { + ResourceName: "pagerduty_event_orchestration.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccPagerDutyEventOrchestrationNameOnly_import(t *testing.T) { + name := fmt.Sprintf("tf-name-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEventOrchestrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyEventOrchestrationConfigNameOnly(name), + }, + + { + ResourceName: "pagerduty_event_orchestration.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/pagerduty/provider.go b/pagerduty/provider.go index 4af8626dc..92e478397 100644 --- a/pagerduty/provider.go +++ b/pagerduty/provider.go @@ -59,34 +59,39 @@ func Provider() *schema.Provider { "pagerduty_priority": dataSourcePagerDutyPriority(), "pagerduty_ruleset": dataSourcePagerDutyRuleset(), "pagerduty_tag": dataSourcePagerDutyTag(), + "pagerduty_event_orchestration": dataSourcePagerDutyEventOrchestration(), }, ResourcesMap: map[string]*schema.Resource{ - "pagerduty_addon": resourcePagerDutyAddon(), - "pagerduty_escalation_policy": resourcePagerDutyEscalationPolicy(), - "pagerduty_maintenance_window": resourcePagerDutyMaintenanceWindow(), - "pagerduty_schedule": resourcePagerDutySchedule(), - "pagerduty_service": resourcePagerDutyService(), - "pagerduty_service_integration": resourcePagerDutyServiceIntegration(), - "pagerduty_team": resourcePagerDutyTeam(), - "pagerduty_team_membership": resourcePagerDutyTeamMembership(), - "pagerduty_user": resourcePagerDutyUser(), - "pagerduty_user_contact_method": resourcePagerDutyUserContactMethod(), - "pagerduty_user_notification_rule": resourcePagerDutyUserNotificationRule(), - "pagerduty_extension": resourcePagerDutyExtension(), - "pagerduty_extension_servicenow": resourcePagerDutyExtensionServiceNow(), - "pagerduty_event_rule": resourcePagerDutyEventRule(), - "pagerduty_ruleset": resourcePagerDutyRuleset(), - "pagerduty_ruleset_rule": resourcePagerDutyRulesetRule(), - "pagerduty_business_service": resourcePagerDutyBusinessService(), - "pagerduty_service_dependency": resourcePagerDutyServiceDependency(), - "pagerduty_response_play": resourcePagerDutyResponsePlay(), - "pagerduty_tag": resourcePagerDutyTag(), - "pagerduty_tag_assignment": resourcePagerDutyTagAssignment(), - "pagerduty_service_event_rule": resourcePagerDutyServiceEventRule(), - "pagerduty_slack_connection": resourcePagerDutySlackConnection(), - "pagerduty_business_service_subscriber": resourcePagerDutyBusinessServiceSubscriber(), - "pagerduty_webhook_subscription": resourcePagerDutyWebhookSubscription(), + "pagerduty_addon": resourcePagerDutyAddon(), + "pagerduty_escalation_policy": resourcePagerDutyEscalationPolicy(), + "pagerduty_maintenance_window": resourcePagerDutyMaintenanceWindow(), + "pagerduty_schedule": resourcePagerDutySchedule(), + "pagerduty_service": resourcePagerDutyService(), + "pagerduty_service_integration": resourcePagerDutyServiceIntegration(), + "pagerduty_team": resourcePagerDutyTeam(), + "pagerduty_team_membership": resourcePagerDutyTeamMembership(), + "pagerduty_user": resourcePagerDutyUser(), + "pagerduty_user_contact_method": resourcePagerDutyUserContactMethod(), + "pagerduty_user_notification_rule": resourcePagerDutyUserNotificationRule(), + "pagerduty_extension": resourcePagerDutyExtension(), + "pagerduty_extension_servicenow": resourcePagerDutyExtensionServiceNow(), + "pagerduty_event_rule": resourcePagerDutyEventRule(), + "pagerduty_ruleset": resourcePagerDutyRuleset(), + "pagerduty_ruleset_rule": resourcePagerDutyRulesetRule(), + "pagerduty_business_service": resourcePagerDutyBusinessService(), + "pagerduty_service_dependency": resourcePagerDutyServiceDependency(), + "pagerduty_response_play": resourcePagerDutyResponsePlay(), + "pagerduty_tag": resourcePagerDutyTag(), + "pagerduty_tag_assignment": resourcePagerDutyTagAssignment(), + "pagerduty_service_event_rule": resourcePagerDutyServiceEventRule(), + "pagerduty_slack_connection": resourcePagerDutySlackConnection(), + "pagerduty_business_service_subscriber": resourcePagerDutyBusinessServiceSubscriber(), + "pagerduty_webhook_subscription": resourcePagerDutyWebhookSubscription(), + "pagerduty_event_orchestration": resourcePagerDutyEventOrchestration(), + "pagerduty_event_orchestration_router": resourcePagerDutyEventOrchestrationPathRouter(), + "pagerduty_event_orchestration_unrouted": resourcePagerDutyEventOrchestrationPathUnrouted(), + "pagerduty_event_orchestration_service": resourcePagerDutyEventOrchestrationPathService(), }, } diff --git a/pagerduty/resource_pagerduty_event_orchestration.go b/pagerduty/resource_pagerduty_event_orchestration.go new file mode 100644 index 000000000..8e876c1cb --- /dev/null +++ b/pagerduty/resource_pagerduty_event_orchestration.go @@ -0,0 +1,240 @@ +package pagerduty + +import ( + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +func resourcePagerDutyEventOrchestration() *schema.Resource { + return &schema.Resource{ + Create: resourcePagerDutyEventOrchestrationCreate, + Read: resourcePagerDutyEventOrchestrationRead, + Update: resourcePagerDutyEventOrchestrationUpdate, + Delete: resourcePagerDutyEventOrchestrationDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "team": { + Type: schema.TypeString, + Optional: true, + }, + "routes": { + Type: schema.TypeInt, + Computed: true, + }, + "integration": { + Type: schema.TypeList, + Computed: true, + Optional: true, // Tests keep failing if "Optional: true" is not provided + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "parameters": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "routing_key": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func buildEventOrchestrationStruct(d *schema.ResourceData) *pagerduty.EventOrchestration { + orchestration := &pagerduty.EventOrchestration{ + Name: d.Get("name").(string), + } + + if attr, ok := d.GetOk("description"); ok { + orchestration.Description = attr.(string) + } + + if attr, ok := d.GetOk("team"); ok { + orchestration.Team = &pagerduty.EventOrchestrationObject{ + ID: stringTypeToStringPtr(attr.(string)), + } + } else { + var tId *string + orchestration.Team = &pagerduty.EventOrchestrationObject{ + ID: tId, + } + } + + return orchestration +} + +func resourcePagerDutyEventOrchestrationCreate(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + payload := buildEventOrchestrationStruct(d) + var orchestration *pagerduty.EventOrchestration + + log.Printf("[INFO] Creating PagerDuty Event Orchestration: %s", payload.Name) + + retryErr := resource.Retry(10*time.Second, func() *resource.RetryError { + if orch, _, err := client.EventOrchestrations.Create(payload); err != nil { + if isErrCode(err, 400) || isErrCode(err, 429) { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } else if orch != nil { + d.SetId(orch.ID) + orchestration = orch + } + return nil + }) + + if retryErr != nil { + return retryErr + } + + setEventOrchestrationProps(d, orchestration) + + return nil +} + +func resourcePagerDutyEventOrchestrationRead(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + return resource.Retry(2*time.Minute, func() *resource.RetryError { + orch, _, err := client.EventOrchestrations.Get(d.Id()) + if err != nil { + errResp := handleNotFoundError(err, d) + if errResp != nil { + time.Sleep(2 * time.Second) + return resource.RetryableError(errResp) + } + + return nil + } + + setEventOrchestrationProps(d, orch) + + return nil + }) +} + +func resourcePagerDutyEventOrchestrationUpdate(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + orchestration := buildEventOrchestrationStruct(d) + + log.Printf("[INFO] Updating PagerDuty Event Orchestration: %s", d.Id()) + + retryErr := resource.Retry(10*time.Second, func() *resource.RetryError { + if _, _, err := client.EventOrchestrations.Update(d.Id(), orchestration); err != nil { + if isErrCode(err, 400) || isErrCode(err, 429) { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + + return nil + }) + + if retryErr != nil { + return retryErr + } + + return nil +} + +func resourcePagerDutyEventOrchestrationDelete(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + log.Printf("[INFO] Deleting PagerDuty Event Orchestration: %s", d.Id()) + if _, err := client.EventOrchestrations.Delete(d.Id()); err != nil { + return err + } + + d.SetId("") + + return nil +} + +func flattenEventOrchestrationTeam(v *pagerduty.EventOrchestrationObject) []interface{} { + team := map[string]interface{}{ + "id": v.ID, + } + + return []interface{}{team} +} + +func flattenEventOrchestrationIntegrations(eoi []*pagerduty.EventOrchestrationIntegration) []interface{} { + var result []interface{} + + for _, i := range eoi { + integration := map[string]interface{}{ + "id": i.ID, + "parameters": flattenEventOrchestrationIntegrationParameters(i.Parameters), + } + result = append(result, integration) + } + return result +} + +func flattenEventOrchestrationIntegrationParameters(p *pagerduty.EventOrchestrationIntegrationParameters) []interface{} { + result := map[string]interface{}{ + "routing_key": p.RoutingKey, + "type": p.Type, + } + + return []interface{}{result} +} + +func setEventOrchestrationProps(d *schema.ResourceData, o *pagerduty.EventOrchestration) error { + d.Set("name", o.Name) + d.Set("description", o.Description) + d.Set("routes", o.Routes) + + if o.Team != nil { + d.Set("team", o.Team.ID) + } + + if len(o.Integrations) > 0 { + d.Set("integration", flattenEventOrchestrationIntegrations(o.Integrations)) + } + + return nil +} diff --git a/pagerduty/resource_pagerduty_event_orchestration_path_router.go b/pagerduty/resource_pagerduty_event_orchestration_path_router.go new file mode 100644 index 000000000..d535e14a2 --- /dev/null +++ b/pagerduty/resource_pagerduty_event_orchestration_path_router.go @@ -0,0 +1,335 @@ +package pagerduty + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +func resourcePagerDutyEventOrchestrationPathRouter() *schema.Resource { + return &schema.Resource{ + Read: resourcePagerDutyEventOrchestrationPathRouterRead, + Create: resourcePagerDutyEventOrchestrationPathRouterCreate, + Update: resourcePagerDutyEventOrchestrationPathRouterUpdate, + Delete: resourcePagerDutyEventOrchestrationPathRouterDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyEventOrchestrationPathRouterImport, + }, + Schema: map[string]*schema.Schema{ + "event_orchestration": { + Type: schema.TypeString, + Required: true, + }, + "set": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, // Router can only have 'start' set + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + "rule": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Optional: true, + }, + "condition": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathConditionsSchema, + }, + }, + "actions": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, //there can only be one action for router + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "route_to": { + Type: schema.TypeString, + Required: true, + ValidateFunc: func(v interface{}, key string) (warns []string, errs []error) { + value := v.(string) + if value == "unrouted" { + errs = append(errs, fmt.Errorf("route_to within a set's rule has to be a Service ID. Got: %q", v)) + } + return + }, + }, + }, + }, + }, + "disabled": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "catch_all": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "actions": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "route_to": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourcePagerDutyEventOrchestrationPathRouterRead(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + return resource.Retry(2*time.Minute, func() *resource.RetryError { + log.Printf("[INFO] Reading PagerDuty Event Orchestration Path of type %s for orchestration: %s", "router", d.Id()) + + if routerPath, _, err := client.EventOrchestrationPaths.Get(d.Id(), "router"); err != nil { + time.Sleep(2 * time.Second) + return resource.RetryableError(err) + } else if routerPath != nil { + d.Set("event_orchestration", routerPath.Parent.ID) + + if routerPath.Sets != nil { + d.Set("set", flattenSets(routerPath.Sets)) + } + + if routerPath.CatchAll != nil { + d.Set("catch_all", flattenCatchAll(routerPath.CatchAll)) + } + } + return nil + }) + +} + +// EventOrchestrationPath cannot be created, use update to add / edit / remove rules and sets +func resourcePagerDutyEventOrchestrationPathRouterCreate(d *schema.ResourceData, meta interface{}) error { + return resourcePagerDutyEventOrchestrationPathRouterUpdate(d, meta) +} + +func resourcePagerDutyEventOrchestrationPathRouterDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourcePagerDutyEventOrchestrationPathRouterUpdate(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + updatePath := buildRouterPathStructForUpdate(d) + + log.Printf("[INFO] Updating PagerDuty Event Orchestration Path of type %s for orchestration: %s", "router", updatePath.Parent.ID) + + return performRouterPathUpdate(d, updatePath, client) +} + +func performRouterPathUpdate(d *schema.ResourceData, routerPath *pagerduty.EventOrchestrationPath, client *pagerduty.Client) error { + retryErr := resource.Retry(30*time.Second, func() *resource.RetryError { + updatedPath, _, err := client.EventOrchestrationPaths.Update(routerPath.Parent.ID, "router", routerPath) + if err != nil { + return resource.RetryableError(err) + } + if updatedPath == nil { + return resource.NonRetryableError(fmt.Errorf("No Event Orchestration Router found.")) + } + d.SetId(routerPath.Parent.ID) + d.Set("event_orchestration", routerPath.Parent.ID) + + if routerPath.Sets != nil { + d.Set("set", flattenSets(routerPath.Sets)) + } + if updatedPath.CatchAll != nil { + d.Set("catch_all", flattenCatchAll(updatedPath.CatchAll)) + } + return nil + }) + if retryErr != nil { + time.Sleep(2 * time.Second) + return retryErr + } + return nil +} + +func buildRouterPathStructForUpdate(d *schema.ResourceData) *pagerduty.EventOrchestrationPath { + + orchPath := &pagerduty.EventOrchestrationPath{ + Parent: &pagerduty.EventOrchestrationPathReference{ + ID: d.Get("event_orchestration").(string), + }, + } + + if attr, ok := d.GetOk("set"); ok { + orchPath.Sets = expandSets(attr) + } + + if attr, ok := d.GetOk("catch_all"); ok { + orchPath.CatchAll = expandCatchAll(attr) + } + + return orchPath +} + +func expandSets(v interface{}) []*pagerduty.EventOrchestrationPathSet { + var sets []*pagerduty.EventOrchestrationPathSet + + for _, set := range v.([]interface{}) { + s := set.(map[string]interface{}) + + orchPathSet := &pagerduty.EventOrchestrationPathSet{ + ID: s["id"].(string), + Rules: expandRules(s["rule"]), + } + + sets = append(sets, orchPathSet) + } + + return sets +} + +func expandRules(v interface{}) []*pagerduty.EventOrchestrationPathRule { + items := v.([]interface{}) + rules := []*pagerduty.EventOrchestrationPathRule{} + + for _, rule := range items { + r := rule.(map[string]interface{}) + + ruleInSet := &pagerduty.EventOrchestrationPathRule{ + ID: r["id"].(string), + Label: r["label"].(string), + Disabled: r["disabled"].(bool), + Conditions: expandEventOrchestrationPathConditions(r["condition"]), + Actions: expandRouterActions(r["actions"]), + } + + rules = append(rules, ruleInSet) + } + return rules +} + +func expandRouterActions(v interface{}) *pagerduty.EventOrchestrationPathRuleActions { + var actions = new(pagerduty.EventOrchestrationPathRuleActions) + for _, ai := range v.([]interface{}) { + am := ai.(map[string]interface{}) + actions.RouteTo = am["route_to"].(string) + } + + return actions +} + +func expandCatchAll(v interface{}) *pagerduty.EventOrchestrationPathCatchAll { + var catchAll = new(pagerduty.EventOrchestrationPathCatchAll) + + for _, ca := range v.([]interface{}) { + am := ca.(map[string]interface{}) + catchAll.Actions = expandRouterActions(am["actions"]) + } + + return catchAll +} + +func flattenSets(orchPathSets []*pagerduty.EventOrchestrationPathSet) []interface{} { + var flattenedSets []interface{} + for _, set := range orchPathSets { + flattenedSet := map[string]interface{}{ + "id": set.ID, + "rule": flattenRules(set.Rules), + } + flattenedSets = append(flattenedSets, flattenedSet) + } + return flattenedSets +} + +func flattenRules(rules []*pagerduty.EventOrchestrationPathRule) []interface{} { + var flattenedRules []interface{} + + for _, rule := range rules { + flattenedRule := map[string]interface{}{ + "id": rule.ID, + "label": rule.Label, + "disabled": rule.Disabled, + "condition": flattenEventOrchestrationPathConditions(rule.Conditions), + "actions": flattenRouterActions(rule.Actions), + } + flattenedRules = append(flattenedRules, flattenedRule) + } + + return flattenedRules +} + +func flattenRouterActions(actions *pagerduty.EventOrchestrationPathRuleActions) []map[string]interface{} { + var actionsMap []map[string]interface{} + + am := make(map[string]interface{}) + am["route_to"] = actions.RouteTo + actionsMap = append(actionsMap, am) + return actionsMap +} + +func flattenCatchAll(catchAll *pagerduty.EventOrchestrationPathCatchAll) []map[string]interface{} { + var caMap []map[string]interface{} + + c := make(map[string]interface{}) + + c["actions"] = flattenRouterActions(catchAll.Actions) + caMap = append(caMap, c) + + return caMap +} + +func resourcePagerDutyEventOrchestrationPathRouterImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client, err := meta.(*Config).Client() + if err != nil { + return []*schema.ResourceData{}, err + } + // given an orchestration ID import the router orchestration path + orchestrationID := d.Id() + pathType := "router" + _, _, err = client.EventOrchestrationPaths.Get(orchestrationID, pathType) + + if err != nil { + return []*schema.ResourceData{}, err + } + + d.SetId(orchestrationID) + d.Set("event_orchestration", orchestrationID) + + return []*schema.ResourceData{d}, nil +} diff --git a/pagerduty/resource_pagerduty_event_orchestration_path_router_test.go b/pagerduty/resource_pagerduty_event_orchestration_path_router_test.go new file mode 100644 index 000000000..3e03a1428 --- /dev/null +++ b/pagerduty/resource_pagerduty_event_orchestration_path_router_test.go @@ -0,0 +1,428 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func init() { + resource.AddTestSweepers("pagerduty_event_orchestration_router", &resource.Sweeper{ + Name: "pagerduty_event_orchestration_router", + F: testSweepEventOrchestration, + }) +} + +func TestAccPagerDutyEventOrchestrationPathRouter_Basic(t *testing.T) { + team := fmt.Sprintf("tf-name-%s", acctest.RandString(5)) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + orchestration := fmt.Sprintf("tf-orchestration-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEventOrchestrationRouterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyEventOrchestrationRouterConfigNoRules(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterExists("pagerduty_event_orchestration_router.router"), + testAccCheckPagerDutyEventOrchestrationRouterPathRouteToMatch( + "pagerduty_event_orchestration_router.router", "unrouted", true), //test for catch_all route_to prop, by default it should be unrouted + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_router.router", "set.0.rule.#", "0"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationRouterConfig(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterExists("pagerduty_event_orchestration_router.router"), + testAccCheckPagerDutyEventOrchestrationRouterPathRouteToMatch( + "pagerduty_event_orchestration_router.router", "pagerduty_service.bar", false), // test for rule action route_to + testAccCheckPagerDutyEventOrchestrationRouterPathRouteToMatch( + "pagerduty_event_orchestration_router.router", "unrouted", true), //test for catch_all route_to prop, by default it should be unrouted + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationRouterConfigWithConditions(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterExists("pagerduty_event_orchestration_router.router"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_router.router", "set.0.rule.0.condition.0.expression", "event.summary matches part 'database'"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationRouterConfigWithMultipleRules(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterExists("pagerduty_event_orchestration_router.router"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_router.router", "set.0.rule.#", "2"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_router.router", "set.0.rule.0.condition.0.expression", "event.summary matches part 'database'"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_router.router", "set.0.rule.1.condition.0.expression", "event.severity matches part 'critical'"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationRouterConfigWithCatchAllToService(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterExists("pagerduty_event_orchestration_router.router"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_router.router", "set.0.rule.#", "1"), + testAccCheckPagerDutyEventOrchestrationRouterPathRouteToMatch( + "pagerduty_event_orchestration_router.router", "pagerduty_service.bar", true), //test for catch_all routing to service if provided + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationRouterConfigNoConditions(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterExists("pagerduty_event_orchestration_router.router"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_router.router", "set.0.rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_router.router", "set.0.rule.0.condition.#", "0"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationRouterConfigDeleteAllRulesInSet(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterExists("pagerduty_event_orchestration_router.router"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_router.router", "set.0.rule.#", "0"), + testAccCheckPagerDutyEventOrchestrationRouterPathRouteToMatch( + "pagerduty_event_orchestration_router.router", "pagerduty_service.bar", true), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationRouterConfigDelete(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationRouterNotExists("pagerduty_event_orchestration_router.router"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyEventOrchestrationRouterDestroy(s *terraform.State) error { + client, _ := testAccProvider.Meta().(*Config).Client() + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_event_orchestration_path_router" { + continue + } + + orch, _ := s.RootModule().Resources["pagerduty_event_orchestration.orch"] + + if _, _, err := client.EventOrchestrationPaths.Get(orch.Primary.ID, "router"); err == nil { + return fmt.Errorf("Event Orchestration Path still exists") + } + } + return nil +} + +func testAccCheckPagerDutyEventOrchestrationRouterExists(rn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("Not found: %s", rn) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Event Orchestration Router is set") + } + + orch, _ := s.RootModule().Resources["pagerduty_event_orchestration.orch"] + client, _ := testAccProvider.Meta().(*Config).Client() + _, _, err := client.EventOrchestrationPaths.Get(orch.Primary.ID, "router") + + if err != nil { + return fmt.Errorf("Orchestration Path type not found: %v for orchestration %v", "router", orch.Primary.ID) + } + + return nil + } +} + +func testAccCheckPagerDutyEventOrchestrationRouterNotExists(rn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[rn] + if ok { + return fmt.Errorf("Event Orchestration Router Path is not deleted: %s", rn) + } + + return nil + } +} + +func createBaseConfig(t, ep, s, o string) string { + return fmt.Sprintf(` + resource "pagerduty_team" "foo" { + name = "%s" + } + + resource "pagerduty_user" "foo" { + name = "tf-user" + email = "user@pagerduty.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" + } + + resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } + } + + resource "pagerduty_service" "bar" { + name = "%s" + escalation_policy = pagerduty_escalation_policy.foo.id + + incident_urgency_rule { + type = "constant" + urgency = "high" + } + } + + resource "pagerduty_event_orchestration" "orch" { + name = "%s" + team = pagerduty_team.foo.id + } + `, t, ep, s, o) +} + +func testAccCheckPagerDutyEventOrchestrationRouterConfigNoRules(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" + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationRouterConfig(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 { + disabled = false + label = "rule1 label" + actions { + route_to = pagerduty_service.bar.id + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationRouterConfigWithConditions(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 { + disabled = false + label = "rule1 label" + actions { + route_to = pagerduty_service.bar.id + } + condition { + expression = "event.summary matches part 'database'" + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationRouterConfigWithMultipleRules(t, ep, s, o string) string { + return fmt.Sprintf( + "%s%s", createBaseConfig(t, ep, s, o), + `resource "pagerduty_service" "bar2" { + name = "tf-barService2" + escalation_policy = pagerduty_escalation_policy.foo.id + + incident_urgency_rule { + type = "constant" + urgency = "high" + } + } + + 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 = "rule1 label" + actions { + route_to = pagerduty_service.bar.id + } + condition { + expression = "event.summary matches part 'database'" + } + condition { + expression = "event.severity matches part 'critical'" + } + } + + rule { + disabled = false + label = "rule2 label" + actions { + route_to = pagerduty_service.bar2.id + } + condition { + expression = "event.severity matches part 'critical'" + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationRouterConfigNoConditions(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 = pagerduty_service.bar.id + } + } + set { + id = "start" + rule { + disabled = false + label = "rule1 label" + actions { + route_to = pagerduty_service.bar.id + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationRouterConfigWithCatchAllToService(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 = pagerduty_service.bar.id + } + } + set { + id = "start" + rule { + disabled = false + label = "rule1 label" + actions { + route_to = pagerduty_service.bar.id + } + condition { + expression = "event.severity matches part 'critical'" + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationRouterConfigDeleteAllRulesInSet(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 = pagerduty_service.bar.id + } + } + set { + id = "start" + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationRouterConfigDelete(t, ep, s, o string) string { + return createBaseConfig(t, ep, s, o) +} + +func testAccCheckPagerDutyEventOrchestrationRouterPathRouteToMatch(router, service string, catchAll bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + r, rOk := s.RootModule().Resources[router] + if !rOk { + return fmt.Errorf("Not found: %s", router) + } + + var rRouteToId = "" + if catchAll == true { + rRouteToId = r.Primary.Attributes["catch_all.0.actions.0.route_to"] + } else { + rRouteToId = r.Primary.Attributes["set.0.rule.0.actions.0.route_to"] + } + + var sId = "" + if service == "unrouted" { + sId = "unrouted" + } else { + svc, sOk := s.RootModule().Resources[service] + if !sOk { + return fmt.Errorf("Not found: %s", service) + } + sId = svc.Primary.Attributes["id"] + } + + if rRouteToId != sId { + return fmt.Errorf("Event Orchestration Router Route to Service ID (%v) not matching provided service ID: %v", rRouteToId, sId) + } + + return nil + } +} diff --git a/pagerduty/resource_pagerduty_event_orchestration_path_service.go b/pagerduty/resource_pagerduty_event_orchestration_path_service.go new file mode 100644 index 000000000..a956bb8f4 --- /dev/null +++ b/pagerduty/resource_pagerduty_event_orchestration_path_service.go @@ -0,0 +1,555 @@ +package pagerduty + +import ( + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +var eventOrchestrationAutomationActionObjectSchema = map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, +} + +var eventOrchestrationPathServiceCatchAllActionsSchema = map[string]*schema.Schema{ + "suppress": { + Type: schema.TypeBool, + Optional: true, + }, + "suspend": { + Type: schema.TypeInt, + Optional: true, + }, + "priority": { + Type: schema.TypeString, + Optional: true, + }, + "annotate": { + Type: schema.TypeString, + Optional: true, + }, + "pagerduty_automation_action": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action_id": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "automation_action": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "url": { + Type: schema.TypeString, + Required: true, + }, + "auto_send": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "header": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationAutomationActionObjectSchema, + }, + }, + "parameter": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationAutomationActionObjectSchema, + }, + }, + }, + }, + }, + "severity": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateEventOrchestrationPathSeverity(), + }, + "event_action": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateEventOrchestrationPathEventAction(), + }, + "variable": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathVariablesSchema, + }, + }, + "extraction": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathExtractionsSchema, + }, + }, +} + +var eventOrchestrationPathServiceRuleActionsSchema = buildEventOrchestrationPathServiceRuleActionsSchema() + +func buildEventOrchestrationPathServiceRuleActionsSchema() map[string]*schema.Schema { + a := eventOrchestrationPathServiceCatchAllActionsSchema + a["route_to"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + } + + return a +} + +func resourcePagerDutyEventOrchestrationPathService() *schema.Resource { + return &schema.Resource{ + Read: resourcePagerDutyEventOrchestrationPathServiceRead, + Create: resourcePagerDutyEventOrchestrationPathServiceCreate, + Update: resourcePagerDutyEventOrchestrationPathServiceUpdate, + Delete: resourcePagerDutyEventOrchestrationPathServiceDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyEventOrchestrationPathServiceImport, + }, + CustomizeDiff: checkExtractions, + Schema: map[string]*schema.Schema{ + "service": { + Type: schema.TypeString, + Required: true, + }, + "set": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + "rule": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Optional: true, + }, + "condition": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathConditionsSchema, + }, + }, + "actions": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathServiceRuleActionsSchema, + }, + }, + "disabled": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "catch_all": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "actions": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathServiceRuleActionsSchema, + }, + }, + }, + }, + }, + }, + } +} + +func resourcePagerDutyEventOrchestrationPathServiceRead(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + return resource.Retry(2*time.Minute, func() *resource.RetryError { + id := d.Id() + t := "service" + log.Printf("[INFO] Reading PagerDuty Event Orchestration Path of type %s for orchestration: %s", t, id) + + if path, _, err := client.EventOrchestrationPaths.Get(d.Id(), t); err != nil { + time.Sleep(2 * time.Second) + return resource.RetryableError(err) + } else if path != nil { + setEventOrchestrationPathServiceProps(d, path) + } + return nil + }) + +} + +func resourcePagerDutyEventOrchestrationPathServiceCreate(d *schema.ResourceData, meta interface{}) error { + return resourcePagerDutyEventOrchestrationPathServiceUpdate(d, meta) +} + +func resourcePagerDutyEventOrchestrationPathServiceUpdate(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + payload := buildServicePathStruct(d) + var servicePath *pagerduty.EventOrchestrationPath + + log.Printf("[INFO] Creating PagerDuty Event Orchestration Service Path: %s", payload.Parent.ID) + + retryErr := resource.Retry(30*time.Second, func() *resource.RetryError { + if path, _, err := client.EventOrchestrationPaths.Update(payload.Parent.ID, "service", payload); err != nil { + return resource.RetryableError(err) + } else if path != nil { + d.SetId(path.Parent.ID) + servicePath = path + } + return nil + }) + + if retryErr != nil { + return retryErr + } + + setEventOrchestrationPathServiceProps(d, servicePath) + + return nil +} + +func resourcePagerDutyEventOrchestrationPathServiceDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourcePagerDutyEventOrchestrationPathServiceImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client, err := meta.(*Config).Client() + if err != nil { + return []*schema.ResourceData{}, err + } + + id := d.Id() + + _, _, pErr := client.EventOrchestrationPaths.Get(id, "service") + if pErr != nil { + return []*schema.ResourceData{}, pErr + } + + d.SetId(id) + d.Set("service", id) + + return []*schema.ResourceData{d}, nil +} + +func buildServicePathStruct(d *schema.ResourceData) *pagerduty.EventOrchestrationPath { + return &pagerduty.EventOrchestrationPath{ + Parent: &pagerduty.EventOrchestrationPathReference{ + ID: d.Get("service").(string), + }, + Sets: expandServicePathSets(d.Get("set")), + CatchAll: expandServicePathCatchAll(d.Get("catch_all")), + } +} + +func expandServicePathSets(v interface{}) []*pagerduty.EventOrchestrationPathSet { + var sets []*pagerduty.EventOrchestrationPathSet + + for _, set := range v.([]interface{}) { + s := set.(map[string]interface{}) + + orchPathSet := &pagerduty.EventOrchestrationPathSet{ + ID: s["id"].(string), + Rules: expandServicePathRules(s["rule"].(interface{})), + } + + sets = append(sets, orchPathSet) + } + + return sets +} + +func expandServicePathRules(v interface{}) []*pagerduty.EventOrchestrationPathRule { + items := v.([]interface{}) + rules := []*pagerduty.EventOrchestrationPathRule{} + + for _, rule := range items { + r := rule.(map[string]interface{}) + + ruleInSet := &pagerduty.EventOrchestrationPathRule{ + ID: r["id"].(string), + Label: r["label"].(string), + Disabled: r["disabled"].(bool), + Conditions: expandEventOrchestrationPathConditions(r["condition"]), + Actions: expandServicePathActions(r["actions"]), + } + + rules = append(rules, ruleInSet) + } + return rules +} + +func expandServicePathCatchAll(v interface{}) *pagerduty.EventOrchestrationPathCatchAll { + var catchAll = new(pagerduty.EventOrchestrationPathCatchAll) + + for _, ca := range v.([]interface{}) { + if ca != nil { + am := ca.(map[string]interface{}) + catchAll.Actions = expandServicePathActions(am["actions"]) + } + } + + return catchAll +} + +func expandServicePathActions(v interface{}) *pagerduty.EventOrchestrationPathRuleActions { + var actions = &pagerduty.EventOrchestrationPathRuleActions{ + AutomationActions: []*pagerduty.EventOrchestrationPathAutomationAction{}, + PagerdutyAutomationActions: []*pagerduty.EventOrchestrationPathPagerdutyAutomationAction{}, + Variables: []*pagerduty.EventOrchestrationPathActionVariables{}, + Extractions: []*pagerduty.EventOrchestrationPathActionExtractions{}, + } + + for _, i := range v.([]interface{}) { + if i == nil { + continue + } + a := i.(map[string]interface{}) + + actions.RouteTo = a["route_to"].(string) + actions.Suppress = a["suppress"].(bool) + actions.Suspend = intTypeToIntPtr(a["suspend"].(int)) + actions.Priority = a["priority"].(string) + actions.Annotate = a["annotate"].(string) + actions.Severity = a["severity"].(string) + actions.EventAction = a["event_action"].(string) + actions.PagerdutyAutomationActions = expandServicePathPagerDutyAutomationActions(a["pagerduty_automation_action"]) + actions.AutomationActions = expandServicePathAutomationActions(a["automation_action"]) + actions.Variables = expandEventOrchestrationPathVariables(a["variable"]) + actions.Extractions = expandEventOrchestrationPathExtractions(a["extraction"]) + } + + return actions +} + +func expandServicePathPagerDutyAutomationActions(v interface{}) []*pagerduty.EventOrchestrationPathPagerdutyAutomationAction { + result := []*pagerduty.EventOrchestrationPathPagerdutyAutomationAction{} + + for _, i := range v.([]interface{}) { + a := i.(map[string]interface{}) + pdaa := &pagerduty.EventOrchestrationPathPagerdutyAutomationAction{ + ActionId: a["action_id"].(string), + } + + result = append(result, pdaa) + } + + return result +} + +func expandServicePathAutomationActions(v interface{}) []*pagerduty.EventOrchestrationPathAutomationAction { + result := []*pagerduty.EventOrchestrationPathAutomationAction{} + + for _, i := range v.([]interface{}) { + a := i.(map[string]interface{}) + aa := &pagerduty.EventOrchestrationPathAutomationAction{ + Name: a["name"].(string), + Url: a["url"].(string), + AutoSend: a["auto_send"].(bool), + Headers: expandEventOrchestrationAutomationActionObjects(a["header"]), + Parameters: expandEventOrchestrationAutomationActionObjects(a["parameter"]), + } + + result = append(result, aa) + } + + return result +} + +func expandEventOrchestrationAutomationActionObjects(v interface{}) []*pagerduty.EventOrchestrationPathAutomationActionObject { + result := []*pagerduty.EventOrchestrationPathAutomationActionObject{} + + for _, i := range v.([]interface{}) { + o := i.(map[string]interface{}) + obj := &pagerduty.EventOrchestrationPathAutomationActionObject{ + Key: o["key"].(string), + Value: o["value"].(string), + } + + result = append(result, obj) + } + + return result +} + +func setEventOrchestrationPathServiceProps(d *schema.ResourceData, p *pagerduty.EventOrchestrationPath) error { + d.SetId(p.Parent.ID) + d.Set("service", p.Parent.ID) + d.Set("set", flattenServicePathSets(p.Sets)) + d.Set("catch_all", flattenServicePathCatchAll(p.CatchAll)) + return nil +} + +func flattenServicePathSets(orchPathSets []*pagerduty.EventOrchestrationPathSet) []interface{} { + var flattenedSets []interface{} + + for _, set := range orchPathSets { + flattenedSet := map[string]interface{}{ + "id": set.ID, + "rule": flattenServicePathRules(set.Rules), + } + flattenedSets = append(flattenedSets, flattenedSet) + } + return flattenedSets +} + +func flattenServicePathCatchAll(catchAll *pagerduty.EventOrchestrationPathCatchAll) []map[string]interface{} { + var caMap []map[string]interface{} + + c := make(map[string]interface{}) + + c["actions"] = flattenServicePathActions(catchAll.Actions) + caMap = append(caMap, c) + + return caMap +} + +func flattenServicePathRules(rules []*pagerduty.EventOrchestrationPathRule) []interface{} { + var flattenedRules []interface{} + + for _, rule := range rules { + flattenedRule := map[string]interface{}{ + "id": rule.ID, + "label": rule.Label, + "disabled": rule.Disabled, + "condition": flattenEventOrchestrationPathConditions(rule.Conditions), + "actions": flattenServicePathActions(rule.Actions), + } + flattenedRules = append(flattenedRules, flattenedRule) + } + + return flattenedRules +} + +func flattenServicePathActions(actions *pagerduty.EventOrchestrationPathRuleActions) []map[string]interface{} { + var actionsMap []map[string]interface{} + + flattenedAction := map[string]interface{}{ + "route_to": actions.RouteTo, + "severity": actions.Severity, + "event_action": actions.EventAction, + "suppress": actions.Suppress, + "suspend": actions.Suspend, + "priority": actions.Priority, + "annotate": actions.Annotate, + } + + if actions.Variables != nil { + flattenedAction["variable"] = flattenEventOrchestrationPathVariables(actions.Variables) + } + if actions.Extractions != nil { + flattenedAction["extraction"] = flattenEventOrchestrationPathExtractions(actions.Extractions) + } + if actions.PagerdutyAutomationActions != nil { + flattenedAction["pagerduty_automation_action"] = flattenServicePathPagerDutyAutomationActions(actions.PagerdutyAutomationActions) + } + if actions.AutomationActions != nil { + flattenedAction["automation_action"] = flattenServicePathAutomationActions(actions.AutomationActions) + } + + actionsMap = append(actionsMap, flattenedAction) + + return actionsMap +} + +func flattenServicePathPagerDutyAutomationActions(v []*pagerduty.EventOrchestrationPathPagerdutyAutomationAction) []interface{} { + var result []interface{} + + for _, i := range v { + pdaa := map[string]string{ + "action_id": i.ActionId, + } + + result = append(result, pdaa) + } + + return result +} + +func flattenServicePathAutomationActions(v []*pagerduty.EventOrchestrationPathAutomationAction) []interface{} { + var result []interface{} + + for _, i := range v { + pdaa := map[string]interface{}{ + "name": i.Name, + "url": i.Url, + "auto_send": i.AutoSend, + "header": flattenServicePathAutomationActionObjects(i.Headers), + "parameter": flattenServicePathAutomationActionObjects(i.Parameters), + } + + result = append(result, pdaa) + } + + return result +} + +func flattenServicePathAutomationActionObjects(v []*pagerduty.EventOrchestrationPathAutomationActionObject) []interface{} { + var result []interface{} + + for _, i := range v { + pdaa := map[string]interface{}{ + "key": i.Key, + "value": i.Value, + } + + result = append(result, pdaa) + } + + return result +} diff --git a/pagerduty/resource_pagerduty_event_orchestration_path_service_test.go b/pagerduty/resource_pagerduty_event_orchestration_path_service_test.go new file mode 100644 index 000000000..700bf32ea --- /dev/null +++ b/pagerduty/resource_pagerduty_event_orchestration_path_service_test.go @@ -0,0 +1,733 @@ +package pagerduty + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func init() { + resource.AddTestSweepers("pagerduty_event_orchestration_service", &resource.Sweeper{ + Name: "pagerduty_event_orchestration_service", + F: testSweepEventOrchestration, + }) +} + +func TestAccPagerDutyEventOrchestrationPathService_Basic(t *testing.T) { + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resourceName := "pagerduty_event_orchestration_service.serviceA" + serviceResourceName := "pagerduty_service.bar" + + // Checks that run on every step except the last one. These checks that verify the existance of the resource + // and computed/default attributes. We're not checking individual resource attributes because + // according to the official docs (https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource#TestCheckResourceAttr) + // "State value checking is only recommended for testing Computed attributes and attribute defaults." + baseChecks := []resource.TestCheckFunc{ + testAccCheckPagerDutyEventOrchestrationPathServiceExists(resourceName), + testAccCheckPagerDutyEventOrchestrationPathServiceServiceID(resourceName, serviceResourceName), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEventOrchestrationServicePathDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceDefaultConfig(escalationPolicy, service), + Check: resource.ComposeTestCheckFunc(baseChecks...), + }, + // Adding/updating/deleting automation_action properties + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceAutomationActionsConfig(escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + append( + baseChecks, + resource.TestCheckResourceAttrSet(resourceName, "set.0.rule.0.id"), + )..., + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceAutomationActionsParamsUpdateConfig(escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + append( + baseChecks, + resource.TestCheckResourceAttr( + resourceName, "set.0.rule.0.actions.0.automation_action.0.auto_send", "false", + ), + )..., + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceAutomationActionsParamsDeleteConfig(escalationPolicy, service), + Check: resource.ComposeTestCheckFunc(baseChecks...), + }, + // Providing invalid extractions attributes for set rules + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceInvalidExtractionsConfig( + escalationPolicy, service, invalidExtractionRegexTemplateNilConfig(), "", + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in set.0.rule.0.actions.0.extraction.0: regex and template cannot both be null"), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceInvalidExtractionsConfig( + escalationPolicy, service, invalidExtractionRegexTemplateValConfig(), "", + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in set.0.rule.0.actions.0.extraction.0: regex and template cannot both have values"), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceInvalidExtractionsConfig( + escalationPolicy, service, invalidExtractionRegexNilSourceConfig(), "", + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in set.0.rule.0.actions.0.extraction.0: source can't be blank"), + }, + // Providing invalid extractions attributes for the catch_all rule + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceInvalidExtractionsConfig( + escalationPolicy, service, "", invalidExtractionRegexTemplateNilConfig(), + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in catch_all.0.actions.0.extraction.0: regex and template cannot both be null"), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceInvalidExtractionsConfig( + escalationPolicy, service, "", invalidExtractionRegexTemplateValConfig(), + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in catch_all.0.actions.0.extraction.0: regex and template cannot both have values"), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceInvalidExtractionsConfig( + escalationPolicy, service, "", invalidExtractionRegexNilSourceConfig(), + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in catch_all.0.actions.0.extraction.0: source can't be blank"), + }, + // Adding/updating/deleting all actions + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceAllActionsConfig(escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + append( + baseChecks, + []resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(resourceName, "set.0.rule.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "set.1.rule.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "set.1.rule.1.id"), + }..., + )..., + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceAllActionsUpdateConfig(escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + append( + baseChecks, + []resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(resourceName, "set.0.rule.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "set.1.rule.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "set.1.rule.1.id"), + }..., + )..., + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceAllActionsDeleteConfig(escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + append( + baseChecks, + []resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(resourceName, "set.0.rule.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "set.1.rule.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "set.1.rule.1.id"), + }..., + )..., + ), + }, + // Deleting sets and the service path resource + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceOneSetNoActionsConfig(escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + append( + baseChecks, + resource.TestCheckResourceAttrSet(resourceName, "set.0.rule.0.id"), + )..., + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathServiceResourceDeleteConfig(escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationServicePathNotExists(resourceName), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyEventOrchestrationServicePathDestroy(s *terraform.State) error { + client, _ := testAccProvider.Meta().(*Config).Client() + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_event_orchestration_path_service" { + continue + } + + srv := s.RootModule().Resources["pagerduty_service.bar"] + + if _, _, err := client.EventOrchestrationPaths.Get(srv.Primary.ID, "service"); err == nil { + return fmt.Errorf("Event Orchestration Service Path still exists") + } + } + return nil +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceExists(rn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + orch, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("Not found: %s", rn) + } + if orch.Primary.ID == "" { + return fmt.Errorf("No Event Orchestration Service ID is set") + } + + client, _ := testAccProvider.Meta().(*Config).Client() + found, _, err := client.EventOrchestrationPaths.Get(orch.Primary.ID, "service") + if err != nil { + return err + } + if found.Parent.ID != orch.Primary.ID { + return fmt.Errorf("Event Orchrestration Service not found: %v - %v", orch.Primary.ID, found) + } + + return nil + } +} + +func testAccCheckPagerDutyEventOrchestrationServicePathNotExists(rn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[rn] + if ok { + return fmt.Errorf("Event Orchestration Service Path is not deleted from the state: %s", rn) + } + + return nil + } +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceServiceID(rn, sn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + p, _ := s.RootModule().Resources[rn] + srv, ok := s.RootModule().Resources[sn] + + if !ok { + return fmt.Errorf("Service not found: %s", sn) + } + + var pId = p.Primary.Attributes["service"] + var sId = srv.Primary.Attributes["id"] + if pId != sId { + return fmt.Errorf("Event Orchestration Service path service ID (%v) not matching provided service ID: %v", pId, sId) + } + + return nil + } +} + +func createBaseServicePathConfig(ep, s string) string { + return fmt.Sprintf(` + resource "pagerduty_user" "foo" { + name = "tf-user" + email = "user@pagerduty.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" + } + + resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } + } + + resource "pagerduty_service" "bar" { + name = "%s" + escalation_policy = pagerduty_escalation_policy.foo.id + + incident_urgency_rule { + type = "constant" + urgency = "high" + } + } + `, ep, s) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceDefaultConfig(ep, s string) string { + return fmt.Sprintf("%s%s", createBaseServicePathConfig(ep, s), + `resource "pagerduty_event_orchestration_service" "serviceA" { + service = pagerduty_service.bar.id + + set { + id = "start" + } + + catch_all { + actions { } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceAutomationActionsConfig(ep, s string) string { + return fmt.Sprintf("%s%s", createBaseServicePathConfig(ep, s), + `resource "pagerduty_event_orchestration_service" "serviceA" { + service = pagerduty_service.bar.id + + set { + id = "start" + rule { + label = "rule 1" + actions { + automation_action { + name = "test" + url = "https://test.com" + auto_send = true + + header { + key = "foo" + value = "bar" + } + header { + key = "baz" + value = "buz" + } + + parameter { + key = "source" + value = "orch" + } + parameter { + key = "region" + value = "us" + } + } + } + } + } + + catch_all { + actions { + automation_action { + name = "catch-all test" + url = "https://catch-all-test.com" + auto_send = true + + header { + key = "foo1" + value = "bar1" + } + header { + key = "baz1" + value = "buz1" + } + + parameter { + key = "source1" + value = "orch1" + } + parameter { + key = "region1" + value = "us1" + } + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceAutomationActionsParamsUpdateConfig(ep, s string) string { + return fmt.Sprintf("%s%s", createBaseServicePathConfig(ep, s), + `resource "pagerduty_event_orchestration_service" "serviceA" { + service = pagerduty_service.bar.id + + set { + id = "start" + rule { + label = "rule 1" + actions { + automation_action { + name = "test1" + url = "https://test1.com" + + header { + key = "foo1" + value = "bar1" + } + parameter { + key = "source_region" + value = "eu" + } + } + } + } + } + + catch_all { + actions { + automation_action { + name = "catch-all test upd" + url = "https://catch-all-test-upd.com" + + header { + key = "baz2" + value = "buz2" + } + + parameter { + key = "source2" + value = "orch2" + } + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceAutomationActionsParamsDeleteConfig(ep, s string) string { + return fmt.Sprintf("%s%s", createBaseServicePathConfig(ep, s), + `resource "pagerduty_event_orchestration_service" "serviceA" { + service = pagerduty_service.bar.id + + set { + id = "start" + rule { + label = "rule 1" + actions { + automation_action { + name = "test" + url = "https://test.com" + } + } + } + } + + catch_all { + actions { + automation_action { + name = "catch-all test upd" + url = "https://catch-all-test-upd.com" + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceInvalidExtractionsConfig(ep, s, re, cae string) string { + return fmt.Sprintf( + "%s%s", + createBaseServicePathConfig(ep, s), + fmt.Sprintf(`resource "pagerduty_event_orchestration_service" "serviceA" { + service = pagerduty_service.bar.id + + set { + id = "start" + rule { + actions { + %s + } + } + } + catch_all { + actions { + %s + } + } + } + `, re, cae), + ) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceAllActionsConfig(ep, s string) string { + return fmt.Sprintf("%s%s", createBaseServicePathConfig(ep, s), + `resource "pagerduty_event_orchestration_service" "serviceA" { + service = pagerduty_service.bar.id + + set { + id = "start" + rule { + label = "rule 1" + condition { + expression = "event.summary matches part 'timeout'" + } + condition { + expression = "event.custom_details.timeout_err exists" + } + actions { + route_to = "set-1" + priority = "P0IN2KQ" + annotate = "Routed through an event orchestration" + pagerduty_automation_action { + action_id = "01CSB5SMOKCKVRI5GN0LJG7SMB" + } + severity = "critical" + event_action = "trigger" + variable { + name = "hostname" + path = "event.source" + type = "regex" + value = "Source host: (.*)" + } + variable { + name = "cpu_val" + path = "event.custom_details.cpu" + type = "regex" + value = "(.*)" + } + extraction { + target = "event.summary" + template = "High CPU usage on {{variables.hostname}}" + } + extraction { + regex = ".*" + source = "event.group" + target = "event.custom_details.message" + } + } + } + } + set { + id = "set-1" + rule { + label = "set-1 rule 1" + actions { + suspend = 300 + } + } + rule { + label = "set-1 rule 2" + condition { + expression = "event.source matches part 'stg-'" + } + actions { + suppress = true + } + } + } + + catch_all { + actions { + suspend = 120 + priority = "P0IN2KW" + annotate = "Routed through an event orchestration - catch-all rule" + pagerduty_automation_action { + action_id = "01CSB5SMOKCKVRI5GN0LJG7SMC" + } + severity = "warning" + event_action = "trigger" + variable { + name = "user_id" + path = "event.custom_details.user_id" + type = "regex" + value = "Source host: (.*)" + } + variable { + name = "updated_at" + path = "event.custom_details.updated_at" + type = "regex" + value = "(.*)" + } + extraction { + target = "event.custom_details.message" + template = "Last modified by {{variables.user_id}} on {{variables.updated_at}}" + } + extraction { + regex = ".*" + source = "event.custom_details.region" + target = "event.group" + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceAllActionsUpdateConfig(ep, s string) string { + return fmt.Sprintf("%s%s", createBaseServicePathConfig(ep, s), + `resource "pagerduty_event_orchestration_service" "serviceA" { + service = pagerduty_service.bar.id + + set { + id = "start" + rule { + label = "rule 1 updated" + condition { + expression = "event.custom_details.timeout_err matches part 'timeout'" + } + actions { + route_to = "set-2" + priority = "P0IN2KR" + annotate = "Routed through a service orchestration!" + pagerduty_automation_action { + action_id = "01CSB5SMOKCKVRI5GN0LJG7SMBUPDATED" + } + severity = "warning" + event_action = "resolve" + variable { + name = "cpu_val_upd" + path = "event.custom_details.cpu_upd" + type = "regex" + value = "CPU:(.*)" + } + extraction { + regex = ".*" + source = "event.custom_details.region_upd" + target = "event.source" + } + extraction { + target = "event.custom_details.message_upd" + template = "[UPD] High CPU usage on {{variables.hostname}}: {{variables.cpu_val}}" + } + } + } + } + set { + id = "set-2" + rule { + label = "set-2 rule 1" + actions { + suspend = 15 + } + } + rule { + label = "set-2 rule 2" + condition { + expression = "event.source matches part 'test-'" + } + actions { + annotate = "Matched set-2 rule 2" + variable { + name = "host_name" + path = "event.custom_details.memory" + type = "regex" + value = "High memory usage on (.*) server" + } + extraction { + target = "event.summary" + template = "High memory usage on {{variables.hostname}} server: {{event.custom_details.max_memory}}" + } + extraction { + regex = ".*" + source = "event.custom_details.region" + target = "event.group" + } + extraction { + regex = ".*" + source = "event.custom_details.hostname" + target = "event.source" + } + } + } + } + + catch_all { + actions { + suspend = 360 + suppress = true + priority = "P0IN2KX" + annotate = "[UPD] Routed through an event orchestration - catch-all rule" + pagerduty_automation_action { + action_id = "01CSB5SMOKCKVRI5GN0LJG7SMD" + } + severity = "info" + event_action = "resolve" + variable { + name = "updated_at_upd" + path = "event.custom_details.updated_at" + type = "regex" + value = "UPD (.*)" + } + extraction { + regex = ".*" + source = "event.custom_details.region_upd" + target = "event.class" + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceAllActionsDeleteConfig(ep, s string) string { + return fmt.Sprintf("%s%s", createBaseServicePathConfig(ep, s), + `resource "pagerduty_event_orchestration_service" "serviceA" { + service = pagerduty_service.bar.id + + set { + id = "start" + rule { + label = "rule 1 updated" + actions { + route_to = "set-2" + } + } + } + set { + id = "set-2" + rule { + label = "set-2 rule 1" + actions { } + } + rule { + label = "set-2 rule 2" + actions { } + } + } + + catch_all { + actions { } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceOneSetNoActionsConfig(ep, s string) string { + return fmt.Sprintf("%s%s", createBaseServicePathConfig(ep, s), + `resource "pagerduty_event_orchestration_service" "serviceA" { + service = pagerduty_service.bar.id + + set { + id = "start" + rule { + label = "rule 1 updated" + actions {} + } + } + + catch_all { + actions { } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathServiceResourceDeleteConfig(ep, s string) string { + return createBaseServicePathConfig(ep, s) +} diff --git a/pagerduty/resource_pagerduty_event_orchestration_path_unrouted.go b/pagerduty/resource_pagerduty_event_orchestration_path_unrouted.go new file mode 100644 index 000000000..2fc4cc787 --- /dev/null +++ b/pagerduty/resource_pagerduty_event_orchestration_path_unrouted.go @@ -0,0 +1,445 @@ +package pagerduty + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +func resourcePagerDutyEventOrchestrationPathUnrouted() *schema.Resource { + return &schema.Resource{ + Read: resourcePagerDutyEventOrchestrationPathUnroutedRead, + Create: resourcePagerDutyEventOrchestrationPathUnroutedCreate, + Update: resourcePagerDutyEventOrchestrationPathUnroutedUpdate, + Delete: resourcePagerDutyEventOrchestrationPathUnroutedDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyEventOrchestrationPathUnroutedImport, + }, + CustomizeDiff: checkExtractions, + Schema: map[string]*schema.Schema{ + "event_orchestration": { + Type: schema.TypeString, + Required: true, + }, + "set": { + Type: schema.TypeList, + Required: true, + MinItems: 1, // An Unrouted Orchestration must contain at least a "start" set + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + "rule": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Optional: true, + }, + "condition": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathConditionsSchema, + }, + }, + "actions": { + Type: schema.TypeList, + Required: true, // even if there are no actions, API returns actions as an empty list + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "route_to": { + Type: schema.TypeString, + Optional: true, // If there is only start set we don't need route_to + }, + "severity": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateEventOrchestrationPathSeverity(), + }, + "event_action": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateEventOrchestrationPathEventAction(), + }, + "variable": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathVariablesSchema, + }, + }, + "extraction": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathExtractionsSchema, + }, + }, + }, + }, + }, + "disabled": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "catch_all": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "actions": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "suppress": { + Type: schema.TypeBool, + Computed: true, + }, + "severity": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateValueFunc([]string{ + "info", + "error", + "warning", + "critical", + }), + }, + "event_action": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateValueFunc([]string{ + "trigger", + "resolve", + }), + }, + "variable": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathVariablesSchema, + }, + }, + "extraction": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: eventOrchestrationPathExtractionsSchema, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourcePagerDutyEventOrchestrationPathUnroutedRead(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + return resource.Retry(2*time.Minute, func() *resource.RetryError { + + log.Printf("[INFO] Reading PagerDuty Event Orchestration Path of type: %s for orchestration: %s", "unrouted", d.Id()) + + if unroutedPath, _, err := client.EventOrchestrationPaths.Get(d.Id(), "unrouted"); err != nil { + time.Sleep(2 * time.Second) + return resource.RetryableError(err) + } else if unroutedPath != nil { + if unroutedPath.Sets != nil { + d.Set("set", flattenUnroutedSets(unroutedPath.Sets)) + } + + if unroutedPath.CatchAll != nil { + d.Set("catch_all", flattenUnroutedCatchAll(unroutedPath.CatchAll)) + } + } + return nil + }) + +} + +// EventOrchestrationPath cannot be created, use update to add / edit / remove rules and sets +func resourcePagerDutyEventOrchestrationPathUnroutedCreate(d *schema.ResourceData, meta interface{}) error { + return resourcePagerDutyEventOrchestrationPathUnroutedUpdate(d, meta) +} + +// EventOrchestrationPath cannot be deleted, use update to add / edit / remove rules and sets +func resourcePagerDutyEventOrchestrationPathUnroutedDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourcePagerDutyEventOrchestrationPathUnroutedUpdate(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + updatePath := buildUnroutedPathStructForUpdate(d) + + log.Printf("[INFO] Updating PagerDuty EventOrchestrationPath of type: %s for orchestration: %s", "unrouted", updatePath.Parent.ID) + + return performUnroutedPathUpdate(d, updatePath, client) +} + +func performUnroutedPathUpdate(d *schema.ResourceData, unroutedPath *pagerduty.EventOrchestrationPath, client *pagerduty.Client) error { + retryErr := resource.Retry(30*time.Second, func() *resource.RetryError { + updatedPath, _, err := client.EventOrchestrationPaths.Update(unroutedPath.Parent.ID, "unrouted", unroutedPath) + if err != nil { + return resource.RetryableError(err) + } + if updatedPath == nil { + return resource.NonRetryableError(fmt.Errorf("no event orchestration unrouted found")) + } + d.SetId(unroutedPath.Parent.ID) + d.Set("event_orchestration", unroutedPath.Parent.ID) + if unroutedPath.Sets != nil { + d.Set("set", flattenUnroutedSets(unroutedPath.Sets)) + } + if updatedPath.CatchAll != nil { + d.Set("catch_all", flattenUnroutedCatchAll(updatedPath.CatchAll)) + } + return nil + }) + if retryErr != nil { + time.Sleep(2 * time.Second) + return retryErr + } + return nil +} + +func buildUnroutedPathStructForUpdate(d *schema.ResourceData) *pagerduty.EventOrchestrationPath { + + orchPath := &pagerduty.EventOrchestrationPath{ + Parent: &pagerduty.EventOrchestrationPathReference{ + ID: d.Get("event_orchestration").(string), + }, + } + + if attr, ok := d.GetOk("set"); ok { + orchPath.Sets = expandUnroutedSets(attr.([]interface{})) + } + + if attr, ok := d.GetOk("catch_all"); ok { + orchPath.CatchAll = expandUnroutedCatchAll(attr.([]interface{})) + } + + return orchPath +} + +func expandUnroutedSets(v interface{}) []*pagerduty.EventOrchestrationPathSet { + var sets []*pagerduty.EventOrchestrationPathSet + + for _, set := range v.([]interface{}) { + s := set.(map[string]interface{}) + + orchPathSet := &pagerduty.EventOrchestrationPathSet{ + ID: s["id"].(string), + Rules: expandUnroutedRules(s["rule"]), + } + + sets = append(sets, orchPathSet) + } + + return sets +} + +func expandUnroutedRules(v interface{}) []*pagerduty.EventOrchestrationPathRule { + items := v.([]interface{}) + rules := []*pagerduty.EventOrchestrationPathRule{} + + for _, rule := range items { + r := rule.(map[string]interface{}) + + ruleInSet := &pagerduty.EventOrchestrationPathRule{ + ID: r["id"].(string), + Label: r["label"].(string), + Disabled: r["disabled"].(bool), + Conditions: expandEventOrchestrationPathConditions(r["condition"]), + Actions: expandUnroutedActions(r["actions"]), + } + + rules = append(rules, ruleInSet) + } + return rules +} + +func expandUnroutedActions(v interface{}) *pagerduty.EventOrchestrationPathRuleActions { + var actions = &pagerduty.EventOrchestrationPathRuleActions{ + Variables: []*pagerduty.EventOrchestrationPathActionVariables{}, + Extractions: []*pagerduty.EventOrchestrationPathActionExtractions{}, + } + + for _, ai := range v.([]interface{}) { + if ai != nil { + am := ai.(map[string]interface{}) + actions.RouteTo = am["route_to"].(string) + actions.Severity = am["severity"].(string) + actions.EventAction = am["event_action"].(string) + actions.Variables = expandEventOrchestrationPathVariables(am["variable"]) + actions.Extractions = expandEventOrchestrationPathExtractions(am["extraction"]) + } + } + + return actions +} + +func expandUnroutedCatchAll(v interface{}) *pagerduty.EventOrchestrationPathCatchAll { + var catchAll = new(pagerduty.EventOrchestrationPathCatchAll) + + for _, ca := range v.([]interface{}) { + if ca != nil { + am := ca.(map[string]interface{}) + catchAll.Actions = expandUnroutedCatchAllActions(am["actions"]) + } + } + + return catchAll +} + +func expandUnroutedCatchAllActions(v interface{}) *pagerduty.EventOrchestrationPathRuleActions { + var actions = new(pagerduty.EventOrchestrationPathRuleActions) + for _, ai := range v.([]interface{}) { + if ai != nil { + am := ai.(map[string]interface{}) + actions.Severity = am["severity"].(string) + actions.EventAction = am["event_action"].(string) + actions.Variables = expandEventOrchestrationPathVariables(am["variable"]) + actions.Extractions = expandEventOrchestrationPathExtractions(am["extraction"]) + } + } + + return actions +} + +func flattenUnroutedSets(orchPathSets []*pagerduty.EventOrchestrationPathSet) []interface{} { + var flattenedSets []interface{} + + for _, set := range orchPathSets { + flattenedSet := map[string]interface{}{ + "id": set.ID, + "rule": flattenUnroutedRules(set.Rules), + } + flattenedSets = append(flattenedSets, flattenedSet) + } + return flattenedSets +} + +func flattenUnroutedRules(rules []*pagerduty.EventOrchestrationPathRule) []interface{} { + var flattenedRules []interface{} + + for _, rule := range rules { + flattenedRule := map[string]interface{}{ + "id": rule.ID, + "label": rule.Label, + "disabled": rule.Disabled, + "condition": flattenEventOrchestrationPathConditions(rule.Conditions), + "actions": flattenUnroutedActions(rule.Actions), + } + flattenedRules = append(flattenedRules, flattenedRule) + } + + return flattenedRules +} + +func flattenUnroutedActions(actions *pagerduty.EventOrchestrationPathRuleActions) []map[string]interface{} { + var actionsMap []map[string]interface{} + + flattenedAction := map[string]interface{}{ + "route_to": actions.RouteTo, + "severity": actions.Severity, + "event_action": actions.EventAction, + } + + if actions.Variables != nil { + flattenedAction["variable"] = flattenEventOrchestrationPathVariables(actions.Variables) + } + if actions.Extractions != nil { + flattenedAction["extraction"] = flattenEventOrchestrationPathExtractions(actions.Extractions) + } + + actionsMap = append(actionsMap, flattenedAction) + + return actionsMap +} + +func flattenUnroutedCatchAll(catchAll *pagerduty.EventOrchestrationPathCatchAll) []map[string]interface{} { + var caMap []map[string]interface{} + + c := make(map[string]interface{}) + + c["actions"] = flattenUnroutedCatchAllActions(catchAll.Actions) + caMap = append(caMap, c) + + return caMap +} + +func flattenUnroutedCatchAllActions(actions *pagerduty.EventOrchestrationPathRuleActions) []map[string]interface{} { + var actionsMap []map[string]interface{} + + flattenedAction := map[string]interface{}{ + "severity": actions.Severity, + "event_action": actions.EventAction, + "suppress": actions.Suppress, // By default suppress is set to "true" by API for unrouted + } + + if actions.Variables != nil { + flattenedAction["variable"] = flattenEventOrchestrationPathVariables(actions.Variables) + } + if actions.Variables != nil { + flattenedAction["extraction"] = flattenEventOrchestrationPathExtractions(actions.Extractions) + } + + actionsMap = append(actionsMap, flattenedAction) + + return actionsMap +} + +func resourcePagerDutyEventOrchestrationPathUnroutedImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client, err := meta.(*Config).Client() + if err != nil { + return []*schema.ResourceData{}, err + } + // given an orchestration ID import the unrouted orchestration path + orchestrationID := d.Id() + pathType := "unrouted" + _, _, err = client.EventOrchestrationPaths.Get(orchestrationID, pathType) + + if err != nil { + return []*schema.ResourceData{}, err + } + + d.SetId(orchestrationID) + d.Set("event_orchestration", orchestrationID) + + return []*schema.ResourceData{d}, nil +} diff --git a/pagerduty/resource_pagerduty_event_orchestration_path_unrouted_test.go b/pagerduty/resource_pagerduty_event_orchestration_path_unrouted_test.go new file mode 100644 index 000000000..4d2180ec0 --- /dev/null +++ b/pagerduty/resource_pagerduty_event_orchestration_path_unrouted_test.go @@ -0,0 +1,549 @@ +package pagerduty + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func init() { + resource.AddTestSweepers("pagerduty_event_orchestration_unrouted", &resource.Sweeper{ + Name: "pagerduty_event_orchestration_unrouted", + F: testSweepEventOrchestration, + }) +} + +func TestAccPagerDutyEventOrchestrationPathUnrouted_Basic(t *testing.T) { + team := fmt.Sprintf("tf-team-%s", acctest.RandString(5)) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + orchestration := fmt.Sprintf("tf-orchestration-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEventOrchestrationPathUnroutedDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedConfigNoRules(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationPathUnroutedExists("pagerduty_event_orchestration_unrouted.unrouted"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.#", "0"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedConfigWithConditions(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationPathUnroutedExists("pagerduty_event_orchestration_unrouted.unrouted"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.condition.0.expression", "event.summary matches part 'rds'"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedConfigWithMultipleRules(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationPathUnroutedExists("pagerduty_event_orchestration_unrouted.unrouted"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.#", "2"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.condition.0.expression", "event.summary matches part 'rds'"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.condition.1.expression", "event.severity matches part 'warning'"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.1.condition.0.expression", "event.severity matches part 'info'"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedWithAllConfig(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationPathUnroutedExists("pagerduty_event_orchestration_unrouted.unrouted"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.#", "2"), + //Set #1 + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.id", "start"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.condition.#", "2"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.actions.0.route_to", "child-1"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.condition.0.expression", "event.severity matches part 'info'"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.condition.1.expression", "event.severity matches part 'warning'"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.actions.0.severity", "info"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.actions.0.event_action", "trigger"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.actions.0.variable.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs( + "pagerduty_event_orchestration_unrouted.unrouted", + "set.0.rule.0.actions.0.variable.*", + map[string]string{ + "name": "server_name_cpu", + "path": "event.summary", + "type": "regex", + "value": "High CPU on (.*) server", + }, + ), + resource.TestCheckTypeSetElemNestedAttrs( + "pagerduty_event_orchestration_unrouted.unrouted", + "set.0.rule.0.actions.0.variable.*", + map[string]string{ + "name": "server_name_memory", + "path": "event.custom_details", + "type": "regex", + "value": "High memory usage on (.*) server", + }, + ), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.0.actions.0.extraction.#", "2"), + + resource.TestCheckTypeSetElemNestedAttrs( + "pagerduty_event_orchestration_unrouted.unrouted", + "set.0.rule.0.actions.0.extraction.*", + map[string]string{ + "target": "event.summary", + "template": "High memory usage on {{variables.hostname}} server", + }, + ), + resource.TestCheckTypeSetElemNestedAttrs( + "pagerduty_event_orchestration_unrouted.unrouted", + "set.0.rule.0.actions.0.extraction.*", + map[string]string{ + "target": "event.custom_details", + "template": "High memory usage on {{variables.hostname}} server", + }, + ), + //Set #2 + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.1.id", "child-1"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.1.rule.0.condition.0.expression", "event.severity matches part 'warning'"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.1.rule.0.actions.0.event_action", "resolve"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.1.rule.1.condition.0.expression", "event.severity matches part 'critical'"), + // Catch All + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "catch_all.0.actions.0.severity", "critical"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "catch_all.0.actions.0.event_action", "trigger"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "catch_all.0.actions.0.variable.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs( + "pagerduty_event_orchestration_unrouted.unrouted", + "catch_all.0.actions.0.variable.*", + map[string]string{ + "name": "server_name_cpu", + "path": "event.summary", + "type": "regex", + "value": "High CPU on (.*) server", + }, + ), + resource.TestCheckTypeSetElemNestedAttrs( + "pagerduty_event_orchestration_unrouted.unrouted", + "catch_all.0.actions.0.variable.*", + map[string]string{ + "name": "server_name_memory", + "path": "event.custom_details", + "type": "regex", + "value": "High memory usage on (.*) server", + }, + ), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "catch_all.0.actions.0.extraction.#", "2"), + + resource.TestCheckTypeSetElemNestedAttrs( + "pagerduty_event_orchestration_unrouted.unrouted", + "catch_all.0.actions.0.extraction.*", + map[string]string{ + "target": "event.summary", + "template": "High memory usage on {{variables.hostname}} server", + }, + ), + resource.TestCheckTypeSetElemNestedAttrs( + "pagerduty_event_orchestration_unrouted.unrouted", + "catch_all.0.actions.0.extraction.*", + map[string]string{ + "target": "event.custom_details", + "template": "High memory usage on {{variables.hostname}} server", + }, + ), + ), + }, + // Providing invalid extractions attributes for set rules + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedInvalidExtractionsConfig( + team, escalationPolicy, service, orchestration, invalidExtractionRegexTemplateValConfig(), "", + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in set.0.rule.0.actions.0.extraction.0: regex and template cannot both have values"), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedInvalidExtractionsConfig( + team, escalationPolicy, service, orchestration, invalidExtractionRegexTemplateValConfig(), "", + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in set.0.rule.0.actions.0.extraction.0: regex and template cannot both have values"), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedInvalidExtractionsConfig( + team, escalationPolicy, service, orchestration, invalidExtractionRegexNilSourceConfig(), "", + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in set.0.rule.0.actions.0.extraction.0: source can't be blank"), + }, + // Providing invalid extractions attributes for the catch_all rule + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedInvalidExtractionsConfig( + team, escalationPolicy, service, orchestration, "", invalidExtractionRegexTemplateNilConfig(), + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in catch_all.0.actions.0.extraction.0: regex and template cannot both be null"), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedInvalidExtractionsConfig( + team, escalationPolicy, service, orchestration, "", invalidExtractionRegexTemplateValConfig(), + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in catch_all.0.actions.0.extraction.0: regex and template cannot both have values"), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedInvalidExtractionsConfig( + team, escalationPolicy, service, orchestration, "", invalidExtractionRegexNilSourceConfig(), + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Invalid configuration in catch_all.0.actions.0.extraction.0: source can't be blank"), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedConfigNoRules(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationPathUnroutedExists("pagerduty_event_orchestration_unrouted.unrouted"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration_unrouted.unrouted", "set.0.rule.#", "0"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationPathUnroutedConfigDelete(team, escalationPolicy, service, orchestration), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationPathUnroutedNotExists("pagerduty_event_orchestration_unrouted.unrouted"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedDestroy(s *terraform.State) error { + client, _ := testAccProvider.Meta().(*Config).Client() + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_event_orchestration_path_unrouted" { + continue + } + + orch := s.RootModule().Resources["pagerduty_event_orchestration.orch"] + + if _, _, err := client.EventOrchestrationPaths.Get(orch.Primary.ID, "unrouted"); err == nil { + return fmt.Errorf("Event Orchestration Path still exists") + } + } + return nil +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedExists(rn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("Not found: %s", rn) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Event Orchestration Unrouted Path is set") + } + + orch := s.RootModule().Resources["pagerduty_event_orchestration.orch"] + client, _ := testAccProvider.Meta().(*Config).Client() + _, _, err := client.EventOrchestrationPaths.Get(orch.Primary.ID, "unrouted") + + if err != nil { + return fmt.Errorf("Event Orchestration Unrouted Path not found: %v for orchestration %v", "unrouted", orch.Primary.ID) + } + + return nil + } +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedNotExists(rn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[rn] + if ok { + return fmt.Errorf("Event Orchestration Unrouted Path is not deleted: %s", rn) + } + + return nil + } +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedConfigDelete(t, ep, s, o string) string { + return createUnroutedBaseConfig(t, ep, s, o) +} + +func createUnroutedBaseConfig(t, ep, s, o string) string { + return fmt.Sprintf(` + resource "pagerduty_team" "foo" { + name = "%s" + } + resource "pagerduty_user" "foo" { + name = "tf-user" + email = "user@pagerduty.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" + } + resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } + } + resource "pagerduty_service" "bar" { + name = "%s" + escalation_policy = pagerduty_escalation_policy.foo.id + incident_urgency_rule { + type = "constant" + urgency = "high" + } + } + resource "pagerduty_event_orchestration" "orch" { + name = "%s" + team = pagerduty_team.foo.id + } + `, t, ep, s, o) +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedConfigNoRules(t, ep, s, o string) string { + return fmt.Sprintf("%s%s", createUnroutedBaseConfig(t, ep, s, o), + `resource "pagerduty_event_orchestration_unrouted" "unrouted" { + event_orchestration = pagerduty_event_orchestration.orch.id + + set { + id = "start" + } + catch_all { + actions { } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedConfigWithConditions(t, ep, s, o string) string { + return fmt.Sprintf("%s%s", createUnroutedBaseConfig(t, ep, s, o), + `resource "pagerduty_event_orchestration_unrouted" "unrouted" { + event_orchestration = pagerduty_event_orchestration.orch.id + + set { + id = "start" + rule { + disabled = false + label = "rule1 label" + actions { } + condition { + expression = "event.summary matches part 'rds'" + } + } + } + catch_all { + actions { } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedConfigWithMultipleRules(t, ep, s, o string) string { + return fmt.Sprintf("%s%s", createUnroutedBaseConfig(t, ep, s, o), + `resource "pagerduty_event_orchestration_unrouted" "unrouted" { + event_orchestration = pagerduty_event_orchestration.orch.id + + set { + id = "start" + rule { + disabled = false + label = "rule1 label" + actions { } + condition { + expression = "event.summary matches part 'rds'" + } + condition { + expression = "event.severity matches part 'warning'" + } + } + + rule { + disabled = false + label = "rule2 label" + actions { } + condition { + expression = "event.severity matches part 'info'" + } + } + } + catch_all { + actions { } + } + } +`) +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedWithAllConfig(t, ep, s, o string) string { + return fmt.Sprintf("%s%s", createUnroutedBaseConfig(t, ep, s, o), + `resource "pagerduty_event_orchestration_unrouted" "unrouted" { + event_orchestration = pagerduty_event_orchestration.orch.id + + set { + id = "start" + rule { + disabled = false + label = "rule1 label" + condition { + expression = "event.severity matches part 'info'" + } + condition { + expression = "event.severity matches part 'warning'" + } + actions { + route_to = "child-1" + severity = "info" + event_action = "trigger" + variable { + name = "server_name_cpu" + path = "event.summary" + type = "regex" + value = "High CPU on (.*) server" + } + variable { + name = "server_name_memory" + path = "event.custom_details" + type = "regex" + value = "High memory usage on (.*) server" + } + extraction { + target = "event.summary" + template = "High memory usage on {{variables.hostname}} server" + } + extraction { + target = "event.custom_details" + template = "High memory usage on {{variables.hostname}} server" + } + } + } + } + set { + id = "child-1" + rule { + disabled = false + label = "rule2 label1" + condition { + expression = "event.severity matches part 'warning'" + } + actions { + severity = "warning" + event_action = "resolve" + variable { + name = "server_name_cpu" + path = "event.summary" + type = "regex" + value = "High CPU on (.*) server" + } + extraction { + target = "event.summary" + template = "High CPU on {{event.custom_details.hostname}} server" + } + } + } + rule { + disabled = false + label = "rule2 label2" + condition { + expression = "event.severity matches part 'critical'" + } + actions { + severity = "warning" + event_action = "trigger" + variable { + name = "server_name_cpu" + path = "event.summary" + type = "regex" + value = "High CPU on (.*) server" + } + extraction { + target = "event.summary" + template = "High CPU on {{event.custom_details.hostname}} server" + } + } + } + } + catch_all { + actions { + severity = "critical" + event_action = "trigger" + variable { + name = "server_name_cpu" + path = "event.summary" + type = "regex" + value = "High CPU on (.*) server" + } + variable { + name = "server_name_memory" + path = "event.custom_details" + type = "regex" + value = "High memory usage on (.*) server" + } + extraction { + target = "event.summary" + template = "High memory usage on {{variables.hostname}} server" + } + extraction { + target = "event.custom_details" + template = "High memory usage on {{variables.hostname}} server" + } + } + } + } + `) +} + +func testAccCheckPagerDutyEventOrchestrationPathUnroutedInvalidExtractionsConfig(t, ep, s, o, re, cae string) string { + return fmt.Sprintf( + "%s%s", + createUnroutedBaseConfig(t, ep, s, o), + fmt.Sprintf(`resource "pagerduty_event_orchestration_unrouted" "unrouted" { + event_orchestration = pagerduty_event_orchestration.orch.id + + set { + id = "start" + rule { + actions { + %s + } + } + } + catch_all { + actions { + %s + } + } + } + `, re, cae), + ) +} diff --git a/pagerduty/resource_pagerduty_event_orchestration_test.go b/pagerduty/resource_pagerduty_event_orchestration_test.go new file mode 100644 index 000000000..6e39fa640 --- /dev/null +++ b/pagerduty/resource_pagerduty_event_orchestration_test.go @@ -0,0 +1,245 @@ +package pagerduty + +import ( + "fmt" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func init() { + resource.AddTestSweepers("pagerduty_event_orchestration", &resource.Sweeper{ + Name: "pagerduty_event_orchestration", + F: testSweepEventOrchestration, + Dependencies: []string{ + "pagerduty_schedule", + "pagerduty_team", + "pagerduty_user", + "pagerduty_escalation_policy", + "pagerduty_service", + }, + }) +} + +func testSweepEventOrchestration(region string) error { + config, err := sharedConfigForRegion(region) + if err != nil { + return err + } + + client, err := config.Client() + if err != nil { + return err + } + + resp, _, err := client.EventOrchestrations.List() + if err != nil { + return err + } + + for _, orchestration := range resp.Orchestrations { + if strings.HasPrefix(orchestration.Name, "tf-orchestration-") { + log.Printf("Destroying Event Orchestration %s (%s)", orchestration.Name, orchestration.ID) + if _, err := client.EventOrchestrations.Delete(orchestration.ID); err != nil { + return err + } + } + } + + return nil +} + +func TestAccPagerDutyEventOrchestration_Basic(t *testing.T) { + name := fmt.Sprintf("tf-orchestration-%s", acctest.RandString(5)) + description := fmt.Sprintf("tf-description-%s", acctest.RandString(5)) + nameUpdated := fmt.Sprintf("tf-name-%s", acctest.RandString(5)) + descriptionUpdated := fmt.Sprintf("tf-description-%s", acctest.RandString(5)) + team1 := fmt.Sprintf("tf-team-%s", acctest.RandString(5)) + team2 := fmt.Sprintf("tf-team-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyEventOrchestrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyEventOrchestrationConfigNameOnly(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationExists("pagerduty_event_orchestration.foo"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "name", name, + ), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "description", "", + ), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "team.#", "0", + ), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationConfig(name, description, team1, team2), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationExists("pagerduty_event_orchestration.foo"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "name", name, + ), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "description", description, + ), + testAccCheckPagerDutyEventOrchestrationTeamMatch("pagerduty_event_orchestration.foo", "pagerduty_team.foo"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationConfigUpdated(nameUpdated, descriptionUpdated, team1, team2), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationExists("pagerduty_event_orchestration.foo"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "name", nameUpdated, + ), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "description", descriptionUpdated, + ), + testAccCheckPagerDutyEventOrchestrationTeamMatch("pagerduty_event_orchestration.foo", "pagerduty_team.bar"), + ), + }, + { + Config: testAccCheckPagerDutyEventOrchestrationConfigDescriptionTeamDeleted(nameUpdated, team1, team2), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyEventOrchestrationExists("pagerduty_event_orchestration.foo"), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "name", nameUpdated, + ), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "description", "", + ), + resource.TestCheckResourceAttr( + "pagerduty_event_orchestration.foo", "team.#", "0", + ), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyEventOrchestrationDestroy(s *terraform.State) error { + client, _ := testAccProvider.Meta().(*Config).Client() + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_event_orchestration" { + continue + } + if _, _, err := client.EventOrchestrations.Get(r.Primary.ID); err == nil { + return fmt.Errorf("Event Orchestration still exists") + } + } + return nil +} + +func testAccCheckPagerDutyEventOrchestrationExists(rn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + orch, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("Not found: %s", rn) + } + if orch.Primary.ID == "" { + return fmt.Errorf("No Event Orchestration ID is set") + } + + client, _ := testAccProvider.Meta().(*Config).Client() + found, _, err := client.EventOrchestrations.Get(orch.Primary.ID) + if err != nil { + return err + } + if found.ID != orch.Primary.ID { + return fmt.Errorf("Event Orchrestration not found: %v - %v", orch.Primary.ID, found) + } + + return nil + } +} + +func testAccCheckPagerDutyEventOrchestrationTeamMatch(orchName, teamName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + o, orchOk := s.RootModule().Resources[orchName] + + if !orchOk { + return fmt.Errorf("Not found: %s", orchName) + } + + t, tOk := s.RootModule().Resources[teamName] + if !tOk { + return fmt.Errorf("Not found: %s", teamName) + } + + var otId = o.Primary.Attributes["team"] + var tId = t.Primary.Attributes["id"] + + if otId != tId { + return fmt.Errorf("Event Orchestration team ID (%v) not matching provided team ID: %v", otId, tId) + } + + return nil + } +} + +func testAccCheckPagerDutyEventOrchestrationConfigNameOnly(n string) string { + return fmt.Sprintf(` + +resource "pagerduty_event_orchestration" "foo" { + name = "%s" +} +`, n) +} + +func testAccCheckPagerDutyEventOrchestrationConfig(name, description, team1, team2 string) string { + return fmt.Sprintf(` + +resource "pagerduty_team" "foo" { + name = "%s" +} +resource "pagerduty_team" "bar" { + name = "%s" +} +resource "pagerduty_event_orchestration" "foo" { + name = "%s" + description = "%s" + team = pagerduty_team.foo.id +} +`, team1, team2, name, description) +} + +func testAccCheckPagerDutyEventOrchestrationConfigUpdated(name, description, team1, team2 string) string { + return fmt.Sprintf(` + +resource "pagerduty_team" "foo" { + name = "%s" +} +resource "pagerduty_team" "bar" { + name = "%s" +} +resource "pagerduty_event_orchestration" "foo" { + name = "%s" + description = "%s" + team = pagerduty_team.bar.id +} +`, team1, team2, name, description) +} + +func testAccCheckPagerDutyEventOrchestrationConfigDescriptionTeamDeleted(name, team1, team2 string) string { + return fmt.Sprintf(` + +resource "pagerduty_team" "foo" { + name = "%s" +} +resource "pagerduty_team" "bar" { + name = "%s" +} +resource "pagerduty_event_orchestration" "foo" { + name = "%s" +} +`, team1, team2, name) +} diff --git a/pagerduty/util.go b/pagerduty/util.go index ea256a931..7689b8c45 100644 --- a/pagerduty/util.go +++ b/pagerduty/util.go @@ -145,3 +145,10 @@ func stringPtrToStringType(v *string) string { } return *v } + +func intTypeToIntPtr(v int) *int { + if v == 0 { + return nil + } + return &v +} diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/event_orchestration.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/event_orchestration.go new file mode 100644 index 000000000..0eeedbd5f --- /dev/null +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/event_orchestration.go @@ -0,0 +1,127 @@ +package pagerduty + +import ( + "fmt" +) + +type EventOrchestrationService service + +type EventOrchestration struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description"` + Team *EventOrchestrationObject `json:"team"` + Routes int `json:"routes,omitempty"` + Integrations []*EventOrchestrationIntegration `json:"integrations,omitempty"` +} + +type EventOrchestrationObject struct { + Type string `json:"type,omitempty"` + ID *string `json:"id"` +} + +type EventOrchestrationIntegrationParameters struct { + RoutingKey string `json:"routing_key,omitempty"` + Type string `json:"type,omitempty"` +} + +type EventOrchestrationIntegration struct { + ID string `json:"id,omitempty"` + Parameters *EventOrchestrationIntegrationParameters `json:"parameters,omitempty"` +} + +type EventOrchestrationPayload struct { + Orchestration *EventOrchestration `json:"orchestration,omitempty"` +} + +type ListEventOrchestrationsResponse struct { + Total int `json:"total,omitempty"` + Offset int `json:"offset,omitempty"` + More bool `json:"more,omitempty"` + Limit int `json:"limit,omitempty"` + Orchestrations []*EventOrchestration `json:"orchestrations,omitempty"` +} + +var eventOrchestrationBaseUrl = "/event_orchestrations" + +func (s *EventOrchestrationService) List() (*ListEventOrchestrationsResponse, *Response, error) { + v := new(ListEventOrchestrationsResponse) + v.Total = 0 + + orchestrations := make([]*EventOrchestration, 0) + + // Create a handler closure capable of parsing data from the event orchestrations endpoint + // and appending resultant orchestrations to the return slice. + responseHandler := func(response *Response) (ListResp, *Response, error) { + var result ListEventOrchestrationsResponse + + if err := s.client.DecodeJSON(response, &result); err != nil { + return ListResp{}, response, err + } + + v.Total += result.Total + v.Offset = result.Offset + v.More = result.More + v.Limit = result.Limit + orchestrations = append(orchestrations, result.Orchestrations...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return ListResp{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, response, nil + } + err := s.client.newRequestPagedGetDo(eventOrchestrationBaseUrl, responseHandler) + if err != nil { + return nil, nil, err + } + v.Orchestrations = orchestrations + + return v, nil, nil +} + +func (s *EventOrchestrationService) Create(orchestration *EventOrchestration) (*EventOrchestration, *Response, error) { + v := new(EventOrchestrationPayload) + p := &EventOrchestrationPayload{Orchestration: orchestration} + + resp, err := s.client.newRequestDo("POST", eventOrchestrationBaseUrl, nil, p, v) + + if err != nil { + return nil, nil, err + } + + return v.Orchestration, resp, nil +} + +func (s *EventOrchestrationService) Get(ID string) (*EventOrchestration, *Response, error) { + u := fmt.Sprintf("%s/%s", eventOrchestrationBaseUrl, ID) + v := new(EventOrchestrationPayload) + p := &EventOrchestrationPayload{} + + resp, err := s.client.newRequestDo("GET", u, nil, p, v) + if err != nil { + return nil, nil, err + } + + return v.Orchestration, resp, nil +} + +func (s *EventOrchestrationService) Update(ID string, orchestration *EventOrchestration) (*EventOrchestration, *Response, error) { + u := fmt.Sprintf("%s/%s", eventOrchestrationBaseUrl, ID) + v := new(EventOrchestrationPayload) + p := &EventOrchestrationPayload{Orchestration: orchestration} + + resp, err := s.client.newRequestDo("PUT", u, nil, p, v) + if err != nil { + return nil, nil, err + } + + return v.Orchestration, resp, nil +} + +func (s *EventOrchestrationService) Delete(ID string) (*Response, error) { + u := fmt.Sprintf("%s/%s", eventOrchestrationBaseUrl, ID) + return s.client.newRequestDo("DELETE", u, nil, nil, 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 new file mode 100644 index 000000000..ddc7e0091 --- /dev/null +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/event_orchestration_path.go @@ -0,0 +1,147 @@ +package pagerduty + +import ( + "fmt" +) + +type EventOrchestrationPathService service + +type EventOrchestrationPath struct { + Type string `json:"type,omitempty"` + Self string `json:"self,omitempty"` + Parent *EventOrchestrationPathReference `json:"parent,omitempty"` + Sets []*EventOrchestrationPathSet `json:"sets,omitempty"` + CatchAll *EventOrchestrationPathCatchAll `json:"catch_all,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + CreatedBy *EventOrchestrationPathReference `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy *EventOrchestrationPathReference `json:"updated_by,omitempty"` + Version string `json:"version,omitempty"` +} + +// A reference to a related object (e.g. an EventOrchestration, User, Team, etc) +type EventOrchestrationPathReference struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Self string `json:"self,omitempty"` +} + +type EventOrchestrationPathSet struct { + ID string `json:"id,omitempty"` + Rules []*EventOrchestrationPathRule `json:"rules"` +} + +type EventOrchestrationPathRule struct { + ID string `json:"id,omitempty"` + Label string `json:"label,omitempty"` + Conditions []*EventOrchestrationPathRuleCondition `json:"conditions"` + Actions *EventOrchestrationPathRuleActions `json:"actions,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +type EventOrchestrationPathRuleCondition struct { + // A PCL string: https://developer.pagerduty.com/docs/ZG9jOjM1NTE0MDc0-pcl-overview + Expression string `json:"expression,omitempty"` +} + +// See the full list of supported actions for path types: +// Router: https://developer.pagerduty.com/api-reference/f0fae270c70b3-get-the-router-for-a-global-event-orchestration +// Service: https://developer.pagerduty.com/api-reference/179537b835e2d-get-the-service-orchestration-for-a-service +// Unrouted: https://developer.pagerduty.com/api-reference/70aa1139e1013-get-the-unrouted-orchestration-for-a-global-event-orchestration +type EventOrchestrationPathRuleActions struct { + RouteTo string `json:"route_to"` + Suppress bool `json:"suppress"` + Suspend *int `json:"suspend"` + Priority string `json:"priority"` + Annotate string `json:"annotate"` + PagerdutyAutomationActions []*EventOrchestrationPathPagerdutyAutomationAction `json:"pagerduty_automation_actions"` + AutomationActions []*EventOrchestrationPathAutomationAction `json:"automation_actions"` + Severity string `json:"severity"` + EventAction string `json:"event_action"` + Variables []*EventOrchestrationPathActionVariables `json:"variables"` + Extractions []*EventOrchestrationPathActionExtractions `json:"extractions"` +} + +type EventOrchestrationPathPagerdutyAutomationAction struct { + ActionId string `json:"action_id,omitempty"` +} + +type EventOrchestrationPathAutomationAction struct { + Name string `json:"name,omitempty"` + Url string `json:"url,omitempty"` + AutoSend bool `json:"auto_send,omitempty"` + Headers []*EventOrchestrationPathAutomationActionObject `json:"headers"` + Parameters []*EventOrchestrationPathAutomationActionObject `json:"parameters"` +} + +type EventOrchestrationPathAutomationActionObject struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` +} + +type EventOrchestrationPathActionVariables struct { + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` + Type string `json:"type,omitempty"` + Value string `json:"value,omitempty"` +} + +type EventOrchestrationPathActionExtractions struct { + Target string `json:"target,omitempty"` + Regex string `json:"regex,omitempty"` + Template string `json:"template,omitempty"` + Source string `json:"source,omitempty"` +} + +type EventOrchestrationPathCatchAll struct { + Actions *EventOrchestrationPathRuleActions `json:"actions,omitempty"` +} + +type EventOrchestrationPathPayload struct { + OrchestrationPath *EventOrchestrationPath `json:"orchestration_path,omitempty"` +} + +const PathTypeRouter string = "router" +const PathTypeService string = "service" +const PathTypeUnrouted string = "unrouted" + +func orchestrationPathUrlBuilder(id string, pathType string) string { + switch { + case pathType == PathTypeService: + return fmt.Sprintf("%s/services/%s", eventOrchestrationBaseUrl, id) + case pathType == PathTypeUnrouted: + return fmt.Sprintf("%s/%s/unrouted", eventOrchestrationBaseUrl, id) + case pathType == PathTypeRouter: + return fmt.Sprintf("%s/%s/router", eventOrchestrationBaseUrl, id) + default: + return "" + } +} + +// Get for EventOrchestrationPath +func (s *EventOrchestrationPathService) Get(id string, pathType string) (*EventOrchestrationPath, *Response, error) { + u := orchestrationPathUrlBuilder(id, pathType) + v := new(EventOrchestrationPathPayload) + + resp, err := s.client.newRequestDo("GET", u, nil, nil, &v) + + if err != nil { + return nil, nil, err + } + + return v.OrchestrationPath, resp, nil +} + +// Update for EventOrchestrationPath +func (s *EventOrchestrationPathService) Update(id string, pathType string, orchestration_path *EventOrchestrationPath) (*EventOrchestrationPath, *Response, error) { + u := orchestrationPathUrlBuilder(id, pathType) + v := new(EventOrchestrationPathPayload) + p := EventOrchestrationPathPayload{OrchestrationPath: orchestration_path} + + resp, err := s.client.newRequestDo("PUT", u, nil, p, &v) + if err != nil { + return nil, nil, err + } + + return v.OrchestrationPath, resp, nil +} diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go index e3dbbaf8f..f25b916d7 100644 --- a/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go @@ -41,6 +41,8 @@ type Client struct { Extensions *ExtensionService MaintenanceWindows *MaintenanceWindowService Rulesets *RulesetService + EventOrchestrations *EventOrchestrationService + EventOrchestrationPaths *EventOrchestrationPathService Schedules *ScheduleService Services *ServicesService Teams *TeamService @@ -99,6 +101,8 @@ func NewClient(config *Config) (*Client, error) { c.EscalationPolicies = &EscalationPolicyService{c} c.MaintenanceWindows = &MaintenanceWindowService{c} c.Rulesets = &RulesetService{c} + c.EventOrchestrations = &EventOrchestrationService{c} + c.EventOrchestrationPaths = &EventOrchestrationPathService{c} c.Schedules = &ScheduleService{c} c.Services = &ServicesService{c} c.Teams = &TeamService{c} diff --git a/vendor/go.mongodb.org/mongo-driver/mongo/options/datakeyoptions.go b/vendor/go.mongodb.org/mongo-driver/mongo/options/datakeyoptions.go index 3da8f652a..c6a17f9e0 100644 --- a/vendor/go.mongodb.org/mongo-driver/mongo/options/datakeyoptions.go +++ b/vendor/go.mongodb.org/mongo-driver/mongo/options/datakeyoptions.go @@ -22,7 +22,7 @@ func DataKey() *DataKeyOptions { // If being used with a local KMS provider, this option is not applicable and should not be specified. // // For the AWS, Azure, and GCP KMS providers, this option is required and must be a document. For each, the value of the -// "endpoint" or "keyVaultEndpoint" must be a host name with an optional port number (e.g. "foo.test" or "foo.test:443"). +// "endpoint" or "keyVaultEndpoint" must be a host name with an optional port number (e.g. "foo.com" or "foo.com:443"). // // When using AWS, the document must have the format: // { diff --git a/vendor/modules.txt b/vendor/modules.txt index 8f52ef197..0fd9953e9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -124,7 +124,7 @@ github.com/hashicorp/terraform-registry-address github.com/hashicorp/terraform-svchost # github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d github.com/hashicorp/yamux -# github.com/heimweh/go-pagerduty v0.0.0-20220422231448-43095fe5ba3f +# github.com/heimweh/go-pagerduty v0.0.0-20220527195341-4e587aa9b58e ## explicit github.com/heimweh/go-pagerduty/pagerduty # github.com/klauspost/compress v1.11.2 diff --git a/website/docs/d/event_orchestration.html.markdown b/website/docs/d/event_orchestration.html.markdown new file mode 100644 index 000000000..e9cb650ca --- /dev/null +++ b/website/docs/d/event_orchestration.html.markdown @@ -0,0 +1,60 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_event_orchestration" +sidebar_current: "docs-pagerduty-datasource-event-orchestration" +description: |- + Get information about a Global Event Orchestration that you have created. +--- + +# pagerduty\_event_orchestration + +Use this data source to get information about a specific Global [Event Orchestration][1] + +## Example Usage +```hcl +resource "pagerduty_event_orchestration" "tf_orch_a" { + name = "Test Event Orchestration" +} + +data "pagerduty_event_orchestration" "tf_my_monitor" { + name = pagerduty_event_orchestration.tf_orch_a.name +} + +resource "pagerduty_event_orchestration_router" "router" { + parent { + id = data.pagerduty_event_orchestration.tf_my_monitor.id + } + catch_all { + actions { + route_to = "unrouted" + } + } + set { + id = "start" + rule { + actions { + route_to = pagerduty_service.db.id + } + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Global Event orchestration to find in the PagerDuty API. + +## Attributes Reference + +* `id` - The ID of the found Event Orchestration. +* `name` - The name of the found Event Orchestration. +* `integration` - An integration for the Event Orchestration. + * `id` - ID of the integration + * `parameters` + * `routing_key` - Routing key that routes to this Orchestration. + * `type` - Type of the routing key. `global` is the default type. + + +[1]: https://developer.pagerduty.com/api-reference/7ba0fe7bdb26a-list-event-orchestrations diff --git a/website/docs/r/event_orchestration.html.markdown b/website/docs/r/event_orchestration.html.markdown new file mode 100644 index 000000000..1fcb07d33 --- /dev/null +++ b/website/docs/r/event_orchestration.html.markdown @@ -0,0 +1,52 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_event_orchestration" +sidebar_current: "docs-pagerduty-resource-event-orchestration" +description: |- + Creates and manages a Global Event Orchestration in PagerDuty. +--- + +# pagerduty_event_orchestration + +[Global Event Orchestrations](https://support.pagerduty.com/docs/event-orchestration#global-orchestrations) allow you define a set of Event Rules, so that when you ingest events using the Orchestration's Routing Key your events will be routed to the correct Service, based on the event's content. + +## Example of configuring a Global Event Orchestration + +```hcl +resource "pagerduty_team" "engineering" { + name = "Engineering" +} + +resource "pagerduty_event_orchestration" "my_monitor" { + name = "My Monitoring Orchestration" + description = "Send events to a pair of services" + team = pagerduty_team.engineering.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Name of the Event Orchestration. +* `description` - (Optional) A human-friendly description of the Event Orchestration. +* `team` - (Optional) ID of the team that owns the Event Orchestration. If none is specified, only admins have access. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Event Orchestration. +* `integration` - An integration for the Event Orchestration. + * `id` - ID of the integration + * `parameters` + * `routing_key` - Routing key that routes to this Orchestration. + * `type` - Type of the routing key. `global` is the default type. + +## Import + +EventOrchestrations can be imported using the `id`, e.g. + +``` +$ terraform import pagerduty_event_orchestration.main 19acac92-027a-4ea0-b06c-bbf516519601 +``` diff --git a/website/docs/r/event_orchestration_router.html.markdown b/website/docs/r/event_orchestration_router.html.markdown new file mode 100644 index 000000000..822588006 --- /dev/null +++ b/website/docs/r/event_orchestration_router.html.markdown @@ -0,0 +1,93 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_event_orchestration_router" +sidebar_current: "docs-pagerduty-resource-event-orchestration-router" +description: |- + Creates and manages a Router for Global Event Orchestration in PagerDuty. +--- + +# pagerduty_event_orchestration_router + +An Orchestration Router allows users to create a set of Event Rules. The Router evaluates events sent to this Orchestration against each of its rules, one at a time, and routes the event to a specific Service based on the first rule that matches. If an event doesn't match any rules, it'll be sent to service specified in the `catch_all` or to the "Unrouted" Orchestration if no service is specified. + +## 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. + +```hcl +resource "pagerduty_event_orchestration_router" "router" { + event_orchestration = pagerduty_event_orchestration.my_monitor.id + set { + rule { + label = "Events relating to our relational database" + condition { + expression = "event.summary matches part 'database'" + } + condition { + expression = "event.source matches regex 'db[0-9]+-server'" + } + actions { + route_to = pageduty_service.database.id + } + } + rule { + condition { + expression = "event.summary matches part 'www'" + } + actions { + route_to = pagerduty_service.www.id + } + } + } + catch_all { + actions { + route_to = "unrouted" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `event_orchestration` - (Required) ID of the Event Orchestration to which the Router belongs. +* `set` - (Required) The Router contains a single set of rules (the "start" set). +* `catch_all` - (Required) When none of the rules match an event, the event will be routed according to the catch_all settings. + +### Set (`set`) supports the following: +* `id` - (Required) ID of the `start` set. Router supports only one set and it's id has to be `start` +* `rule` - (Optional) The Router evaluates Events against these Rules, one at a time, and routes each Event to a specific Service based on the first rule that matches. If no rules are provided as part of Terraform configuration, the API returns empty list of rules. + +### Rule (`rule`) supports the following: +* `label` - (Optional) A description of this rule's purpose. +* `condition` - (Optional) Each of these conditions is evaluated to check if an event matches this rule. The rule is considered a match if any of these conditions match. If none are provided, the event will _always_ match against the rule. +* `actions` - (Required) Actions that will be taken to change the resulting alert and incident, when an event matches this rule. +* `disabled` - (Optional) Indicates whether the rule is disabled and would therefore not be evaluated. + +### Condition (`condition`) supports the following: +* `expression`- (Required) A [PCL condition](https://developer.pagerduty.com/docs/ZG9jOjM1NTE0MDc0-pcl-overview) string. + +### Actions (`actions`) supports the following: +* `route_to` - (Required) The ID of the target Service for the resulting alert. + +### Catch All (`catch_all`) supports the following: +* `actions` - (Required) These are the actions that will be taken to change the resulting alert and incident. + * `route_to` - (Required) Defines where an alert will be sent if doesn't match any rules. Can either be the ID of a Service _or_ the string `"unrouted"` to send events to the Unrouted Orchestration. + +## Attributes Reference + +The following attributes are exported: +* `self` - The URL at which the Router Orchestration is accessible. +* `rule` + * `id` - The ID of the rule within the `start` set. + +## Import + +Router can be imported using the `id` of the Event Orchestration, e.g. + +``` +$ terraform import pagerduty_event_orchestration_router 1b49abe7-26db-4439-a715-c6d883acfb3e +``` diff --git a/website/docs/r/event_orchestration_service.html.markdown b/website/docs/r/event_orchestration_service.html.markdown new file mode 100644 index 000000000..cda7a031d --- /dev/null +++ b/website/docs/r/event_orchestration_service.html.markdown @@ -0,0 +1,215 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_event_orchestration_service" +sidebar_current: "docs-pagerduty-resource-event-orchestration-service" +description: |- + Creates and manages a Service Orchestration for a Service. +--- + +# pagerduty_event_orchestration_service + +A [Service Orchestration](https://support.pagerduty.com/docs/event-orchestration#service-orchestrations) allows you to create a set of Event Rules. The Service Orchestration evaluates Events sent to this Service against each of its rules, beginning with the rules in the "start" set. When a matching rule is found, it can modify and enhance the event and can route the event to another set of rules within this Service Orchestration for further processing. + +**Note:** If you have a Service that uses [Service Event Rules](https://support.pagerduty.com/docs/rulesets#service-event-rules), you can switch to [Service Orchestrations](https://support.pagerduty.com/docs/event-orchestration#service-orchestrations) at any time. Please read the [Switch to Service Orchestrations](https://support.pagerduty.com/docs/event-orchestration#switch-to-service-orchestrations) instructions for more information. + +## Example of configuring a Service 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 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. + +The `catch_all` actions will be applied if an Event reaches the end of any set without matching any rules in that set. In this example the `catch_all` doesn't have any `actions` so it'll leave events as-is. + + +```hcl +resource "pagerduty_team" "engineering" { + name = "Engineering" +} + +resource "pagerduty_user" "example" { + name = "Earline Greenholt" + email = "125.greenholt.earline@graham.name" + teams = [pagerduty_team.engineering.id] +} + +resource "pagerduty_escalation_policy" "foo" { + name = "Engineering Escalation Policy" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user" + id = pagerduty_user.example.id + } + } +} + +resource "pagerduty_service" "example" { + name = "My Web App" + auto_resolve_timeout = 14400 + acknowledgement_timeout = 600 + escalation_policy = pagerduty_escalation_policy.example.id + alert_creation = "create_alerts_and_incidents" +} + +data "pagerduty_priority" "p1" { + name = "P1" +} + +resource "pagerduty_event_orchestration_service" "www" { + service = pagerduty_service.example.id + set { + id = "start" + rule { + label = "Always apply some consistent event transformations to all events" + actions { + variable { + name = "hostname" + path = "event.component" + value = "hostname: (.*)" + type = "regex" + } + extraction { + # Demonstrating a template-style extraction + template = "{{variables.hostname}}" + target = "event.custom_details.hostname" + } + extraction { + # Demonstrating a regex-style extraction + source = "event.source" + regex = "www (.*) service" + target = "event.source" + } + # Id of the next set + route_to = "step-two" + } + } + } + set { + id = "step-two" + rule { + label = "All critical alerts should be treated as P1 incident" + condition { + expression = "event.severity matches 'critical'" + } + actions { + annotate = "Please use our P1 runbook: https://docs.test/p1-runbook" + priority = data.pagerduty_priority.p1.id + } + } + rule { + label = "If there's something wrong on the canary let the team know about it in our deployments Slack channel" + condition { + expression = "event.custom_details.hostname matches part 'canary'" + } + # create webhook action with parameters and headers + actions { + automation_action { + name = "Canary Slack Notification" + url = "https://our-slack-listerner.test/canary-notification" + auto_send = true + parameter { + key = "channel" + value = "#my-team-channel" + } + parameter { + key = "message" + value = "something is wrong with the canary deployment" + } + header { + key = "X-Notification-Source" + value = "PagerDuty Incident Webhook" + } + } + } + } + rule { + label = "Never bother the on-call for info-level events outside of work hours" + condition { + expression = "event.severity matches 'info' and not (now in Mon,Tue,Wed,Thu,Fri 09:00:00 to 17:00:00 America/Los_Angeles)" + } + actions { + suppress = true + } + } + } + catch_all { + actions { } + } +} +``` +## Argument Reference + +The following arguments are supported: + +* `service` - (Required) ID of the Service to which this Service Orchestration belongs to. +* `set` - (Required) A Service Orchestration must contain at least a "start" set, but can contain any number of additional sets that are routed to by other rules to form a directional graph. +* `catch_all` - (Required) the `catch_all` actions will be applied if an Event reaches the end of any set without matching any rules in that set. + +### Set (`set`) supports the following: +* `id` - (Required) The ID of this set of rules. Rules in other sets can route events into this set using the rule's `route_to` property. +* `rule` - (Optional) The service orchestration evaluates Events against these Rules, one at a time, and applies all the actions for first rule it finds where the event matches the rule's conditions. If no rules are provided as part of Terraform configuration, the API returns empty list of rules. + +### Rule (`rule`) supports the following: +* `label` - (Optional) A description of this rule's purpose. +* `condition` - (Optional) Each of these conditions is evaluated to check if an event matches this rule. The rule is considered a match if any of these conditions match. If none are provided, the event will `always` match against the rule. +* `actions` - (Required) Actions that will be taken to change the resulting alert and incident, when an event matches this rule. +* `disabled` - (Optional) Indicates whether the rule is disabled and would therefore not be evaluated. + +### Condition (`condition`) supports the following: +* `expression`- (Required) A [PCL condition](https://developer.pagerduty.com/docs/ZG9jOjM1NTE0MDc0-pcl-overview) string. + +### Actions (`actions`) supports the following: +* `route_to` - (Optional) The ID of a Set from this Service Orchestration whose rules you also want to use with event that match this rule. +* `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 the resulting 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. +* `annotate` - (Optional) Add this text as a note on the resulting incident. +* `pagerduty_automation_action` - (Optional) Configure a [Process Automation](https://support.pagerduty.com/docs/event-orchestration#process-automation) associated with the resulting incident. + * `action_id` - (Required) Id of the Process Automation action to be triggered. +* `automation_action` - (Optional) Create a [Webhook](https://support.pagerduty.com/docs/event-orchestration#webhooks) associated with the resulting incident. + * `name` - (Required) Name of this Webhook. + * `url` - (Required) The API endpoint where PagerDuty's servers will send the webhook request. + * `auto_send` - (Optional) When true, PagerDuty's servers will automatically send this webhook request as soon as the resulting incident is created. When false, your incident responder will be able to manually trigger the Webhook via the PagerDuty website and mobile app. + * `header` - (Optional) Specify custom key/value pairs that'll be sent with the webhook request as request headers. + * `key` - (Required) Name to identify the header + * `value` - (Required) Value of this header + * `parameter` - (Optional) Specify custom key/value pairs that'll be included in the webhook request's JSON payload. + * `key` - (Required) Name to identify the parameter + * `value` - (Required) Value of this parameter +* `severity` - (Optional) sets Severity of the resulting alert. Allowed values are: `info`, `error`, `warning`, `critical` +* `event_action` - (Optional) sets whether the resulting alert status is trigger or resolve. Allowed values are: `trigger`, `resolve` +* `variable` - (Optional) Populate variables from event payloads and use those variables in other event actions. + * `name` - (Required) The name of the variable + * `path` - (Required) Path to a field in an event, in dot-notation. This supports both PagerDuty Common Event Format [PD-CEF](https://support.pagerduty.com/docs/pd-cef) and non-CEF fields. Eg: Use `event.summary` for the `summary` CEF field. Use `raw_event.fieldname` to read from the original event `fieldname` data. You can use any valid [PCL path](https://developer.pagerduty.com/docs/ZG9jOjM1NTE0MDc0-pcl-overview#paths). + * `type` - (Required) Only `regex` is supported + * `value` - (Required) The Regex expression to match against. Must use valid [RE2 regular expression](https://github.com/google/re2/wiki/Syntax) syntax. +* `extraction` - (Optional) Replace any CEF field or Custom Details object field using custom variables. + * `target` - (Required) The PagerDuty Common Event Format [PD-CEF](https://support.pagerduty.com/docs/pd-cef) field that will be set with the value from the `template` or based on `regex` and `source` fields. + * `template` - (Optional) A string that will be used to populate the `target` field. You can reference variables or event data within your template using double curly braces. For example: + * Use variables named `ip` and `subnet` with a template like: `{{variables.ip}}/{{variables.subnet}}` + * Combine the event severity & summary with template like: `{{event.severity}}:{{event.summary}}` + * `regex` - (Optional) A [RE2 regular expression](https://github.com/google/re2/wiki/Syntax) that will be matched against field specified via the `source` argument. If the regex contains one or more capture groups, their values will be extracted and appended together. If it contains no capture groups, the whole match is used. This field can be ignored for `template` based extractions. + * `source` - (Optional) The path to the event field where the `regex` will be applied to extract a value. You can use any valid [PCL path](https://developer.pagerduty.com/docs/ZG9jOjM1NTE0MDc0-pcl-overview#paths) like `event.summary` and you can reference previously-defined variables using a path like `variables.hostname`. This field can be ignored for `template` based extractions. + +### Catch All (`catch_all`) supports the following: +* `actions` - (Required) These are the actions that will be taken to change the resulting alert and incident. `catch_all` supports all actions described above for `rule` _except_ `route_to` action. + + +## Attributes Reference + +The following attributes are exported: +* `self` - The URL at which the Service Orchestration is accessible. +* `rule` + * `id` - The ID of the rule within the set. + +## Import + +Service Orchestration can be imported using the `id` of the Service, e.g. + +``` +$ terraform import pagerduty_event_orchestration_service PFEODA7 +``` diff --git a/website/docs/r/event_orchestration_unrouted.html.markdown b/website/docs/r/event_orchestration_unrouted.html.markdown new file mode 100644 index 000000000..0ae8f084d --- /dev/null +++ b/website/docs/r/event_orchestration_unrouted.html.markdown @@ -0,0 +1,102 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_event_orchestration_unrouted" +sidebar_current: "docs-pagerduty-resource-event-orchestration-unrouted" +description: |- + Creates and manages an Unrouted Orchestration for a Global Event Orchestration in PagerDuty. +--- + +# pagerduty_event_orchestration_unrouted + +An Unrouted Orchestration allows users to create a set of Event Rules that will be evaluated against all events that don't match any rules in the Orchestration's Router. + +The Unrouted Orchestration evaluates events sent to it against each of its rules, beginning with the rules in the "start" set. When a matching rule is found, it can modify and enhance the event and can route the event to another set of rules within this Unrouted Orchestration for further processing. + +## Example of configuring Unrouted Rules for an Orchestration + +In this example of an Unrouted Orchestration, the rule matches only if the condition is matched. +Alerts created for events that do not match the rule will have severity level set to `info` as defined in `catch_all` block. + +```hcl +resource "pagerduty_event_orchestration_unrouted" "unrouted" { + event_orchestration = pagerduty_event_orchestration.my_monitor.id + set { + id = "start" + rule { + label = "Update the summary of un-matched Critical alerts so they're easier to spot" + condition { + expression = "event.severity matches 'critical'" + } + actions { + severity = "critical" + extraction { + target = "event.summary" + template = "[Critical Unrouted] {{event.summary}}" + } + } + } + } + catch_all { + actions { + severity = "info" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `event_orchestration` - (Required) The Event Orchestration to which this Unrouted Orchestration belongs to. +* `set` - (Required) An Unrouted Orchestration must contain at least a "start" set, but can contain any number of additional sets that are routed to by other rules to form a directional graph. +* `catch_all` - (Required) the `catch_all` actions will be applied if an Event reaches the end of any set without matching any rules in that set. + +### Set (`set`) supports the following: +* `id` - (Required) The ID of this set of rules. Rules in other sets can route events into this set using the rule's `route_to` property. +* `rule` - (Optional) The Unrouted Orchestration evaluates Events against these Rules, one at a time, and applies all the actions for first rule it finds where the event matches the rule's conditions. If no rules are provided as part of Terraform configuration, the API returns empty list of rules. + +### Rule (`rule`) supports the following: +* `label` - (Optional) A description of this rule's purpose. +* `condition` - (Optional) Each of these conditions is evaluated to check if an event matches this rule. The rule is considered a match if any of these conditions match. If none are provided, the event will `always` match against the rule. +* `actions` - (Required) Actions that will be taken to change the resulting alert and incident, when an event matches this rule. +* `disabled` - (Optional) Indicates whether the rule is disabled and would therefore not be evaluated. + +### Condition (`condition`) supports the following: +* `expression`- (Required) A [PCL condition](https://developer.pagerduty.com/docs/ZG9jOjM1NTE0MDc0-pcl-overview) string. + +### Actions (`actions`) supports the following: +* `route_to` - (Optional) The ID of a Set from this Unrouted Orchestration whose rules you also want to use with event that match this rule. +* `severity` - (Optional) sets Severity of the resulting alert. Allowed values are: `info`, `error`, `warning`, `critical` +* `event_action` - (Optional) sets whether the resulting alert status is trigger or resolve. Allowed values are: `trigger`, `resolve` +* `variable` - (Optional) Populate variables from event payloads and use those variables in other event actions. + * `name` - (Required) The name of the variable + * `path` - (Required) Path to a field in an event, in dot-notation. This supports both [PD-CEF](https://support.pagerduty.com/docs/pd-cef) and non-CEF fields. Eg: Use `event.summary` for the `summary` CEF field. Use `raw_event.fieldname` to read from the original event `fieldname` data. + * `type` - (Required) Only `regex` is supported + * `value` - (Required) The Regex expression to match against. Must use valid [RE2 regular expression](https://github.com/google/re2/wiki/Syntax) syntax. +* `extraction` - (Optional) Replace any CEF field or Custom Details object field using custom variables. + * `target` - (Required) The PagerDuty Common Event Format [PD-CEF](https://support.pagerduty.com/docs/pd-cef) field that will be set with the value from the `template` or based on `regex` and `source` fields. + * `template` - (Optional) A string that will be used to populate the `target` field. You can reference variables or event data within your template using double curly braces. For example: + * Use variables named `ip` and `subnet` with a template like: `{{variables.ip}}/{{variables.subnet}}` + * Combine the event severity & summary with template like: `{{event.severity}}:{{event.summary}}` + * `target` - (Required) The PagerDuty Common Event Format [PD-CEF](https://support.pagerduty.com/docs/pd-cef) field that will be set with the value from the `template` or based on `regex` and `source` fields. + * `regex` - (Optional) A [RE2 regular expression](https://github.com/google/re2/wiki/Syntax) that will be matched against field specified via the `source` argument. If the regex contains one or more capture groups, their values will be extracted and appended together. If it contains no capture groups, the whole match is used. This field can be ignored for `template` based extractions. + * `source` - (Optional) The path to the event field where the `regex` will be applied to extract a value. You can use any valid [PCL path](https://developer.pagerduty.com/docs/ZG9jOjM1NTE0MDc0-pcl-overview#paths) like `event.summary` and you can reference previously-defined variables using a path like `variables.hostname`. This field can be ignored for `template` based extractions. + +### Catch All (`catch_all`) supports the following: +* `actions` - (Required) These are the actions that will be taken to change the resulting alert and incident. `catch_all` supports all actions described above for `rule` _except_ `route_to` action. + +## Attributes Reference + +The following attributes are exported: +* `self` - The URL at which the Unrouted Event Orchestration is accessible. +* `rule` + * `id` - The ID of the rule within the set. + +## Import + +Unrouted Orchestration can be imported using the `id` of the Event Orchestration, e.g. + +``` +$ terraform import pagerduty_event_orchestration_unrouted 1b49abe7-26db-4439-a715-c6d883acfb3e +```