diff --git a/pagerduty/import_pagerduty_extension_servicenow_test.go b/pagerduty/import_pagerduty_extension_servicenow_test.go new file mode 100644 index 000000000..d1adea375 --- /dev/null +++ b/pagerduty/import_pagerduty_extension_servicenow_test.go @@ -0,0 +1,32 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccPagerDutyExtensionServicenow_import(t *testing.T) { + extension_name := fmt.Sprintf("tf-%s", acctest.RandString(5)) + name := fmt.Sprintf("tf-%s", acctest.RandString(5)) + url := "https://example.com/receive_a_pagerduty_webhook" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyExtensionServicenowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyExtensionServicenowConfig(name, extension_name, url, "false", "any"), + }, + + { + ResourceName: "pagerduty_extension_servicenow.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/pagerduty/provider.go b/pagerduty/provider.go index 983a18935..96b8f34cb 100644 --- a/pagerduty/provider.go +++ b/pagerduty/provider.go @@ -54,6 +54,7 @@ func Provider() terraform.ResourceProvider { "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(), diff --git a/pagerduty/resource_pagerduty_extension_servicenow.go b/pagerduty/resource_pagerduty_extension_servicenow.go new file mode 100644 index 000000000..06cd3ad7d --- /dev/null +++ b/pagerduty/resource_pagerduty_extension_servicenow.go @@ -0,0 +1,248 @@ +package pagerduty + +import ( + "encoding/json" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +type PagerDutyExtensionServicenowConfig struct { + User string `json:"snow_user"` + Password string `json:"snow_password"` + SyncOptions string `json:"sync_options"` + Target string `json:"target"` + TaskType string `json:"task_type"` + Referer string `json:"referer"` +} + +func resourcePagerDutyExtensionServicenow() *schema.Resource { + return &schema.Resource{ + Create: resourcePagerDutyExtensionServicenowCreate, + Read: resourcePagerDutyExtensionServicenowRead, + Update: resourcePagerDutyExtensionServicenowUpdate, + Delete: resourcePagerDutyExtensionServicenowDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutyExtensionServicenowImport, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "html_url": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "endpoint_url": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "extension_objects": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "extension_schema": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "snow_user": { + Type: schema.TypeString, + Required: true, + }, + "snow_password": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "sync_options": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"manual_sync", "sync_all"}, false), + }, + "target": { + Type: schema.TypeString, + Required: true, + }, + "task_type": { + Type: schema.TypeString, + Required: true, + }, + "referer": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func buildExtensionServicenowStruct(d *schema.ResourceData) *pagerduty.Extension { + Extension := &pagerduty.Extension{ + Name: d.Get("name").(string), + Type: "extension", + EndpointURL: d.Get("endpoint_url").(string), + ExtensionSchema: &pagerduty.ExtensionSchemaReference{ + Type: "extension_schema_reference", + ID: d.Get("extension_schema").(string), + }, + ExtensionObjects: expandServiceNowServiceObjects(d.Get("extension_objects")), + } + + var config = &PagerDutyExtensionServicenowConfig{ + User: d.Get("snow_user").(string), + Password: d.Get("snow_password").(string), + SyncOptions: d.Get("sync_options").(string), + Target: d.Get("target").(string), + TaskType: d.Get("task_type").(string), + Referer: d.Get("referer").(string), + } + Extension.Config = config + + return Extension +} + +func resourcePagerDutyExtensionServicenowCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + extension := buildExtensionServicenowStruct(d) + + log.Printf("[INFO] Creating PagerDuty extension %s", extension.Name) + + extension, _, err := client.Extensions.Create(extension) + if err != nil { + return err + } + + d.SetId(extension.ID) + + return resourcePagerDutyExtensionServicenowRead(d, meta) +} + +func resourcePagerDutyExtensionServicenowRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Reading PagerDuty extension %s", d.Id()) + + return resource.Retry(2*time.Minute, func() *resource.RetryError { + extension, _, err := client.Extensions.Get(d.Id()) + if err != nil { + errResp := handleNotFoundError(err, d) + if errResp != nil { + time.Sleep(2 * time.Second) + return resource.RetryableError(errResp) + } + + return nil + } + + d.Set("summary", extension.Summary) + d.Set("name", extension.Name) + d.Set("endpoint_url", extension.EndpointURL) + d.Set("html_url", extension.HTMLURL) + if err := d.Set("extension_objects", flattenExtensionServicenowObjects(extension.ExtensionObjects)); err != nil { + log.Printf("[WARN] error setting extension_objects: %s", err) + } + d.Set("extension_schema", extension.ExtensionSchema) + + b, _ := json.Marshal(extension.Config) + var config = new(PagerDutyExtensionServicenowConfig) + json.Unmarshal(b, config) + d.Set("snow_user", config.User) + d.Set("snow_password", config.Password) + d.Set("sync_options", config.SyncOptions) + d.Set("target", config.Target) + d.Set("task_type", config.TaskType) + d.Set("referer", config.Referer) + + return nil + }) +} + +func resourcePagerDutyExtensionServicenowUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + extension := buildExtensionServicenowStruct(d) + + log.Printf("[INFO] Updating PagerDuty extension %s", d.Id()) + + if _, _, err := client.Extensions.Update(d.Id(), extension); err != nil { + return err + } + + return resourcePagerDutyExtensionServicenowRead(d, meta) +} + +func resourcePagerDutyExtensionServicenowDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*pagerduty.Client) + + log.Printf("[INFO] Deleting PagerDuty extension %s", d.Id()) + + if _, err := client.Extensions.Delete(d.Id()); err != nil { + if perr, ok := err.(*pagerduty.Error); ok && perr.Code == 5001 { + log.Printf("[WARN] Extension (%s) not found, removing from state", d.Id()) + return nil + } + return err + } + + d.SetId("") + + return nil +} + +func resourcePagerDutyExtensionServicenowImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client := meta.(*pagerduty.Client) + + extension, _, err := client.Extensions.Get(d.Id()) + + if err != nil { + return []*schema.ResourceData{}, fmt.Errorf("error importing pagerduty_extension. Expecting an importation ID for extension") + } + + d.Set("endpoint_url", extension.EndpointURL) + d.Set("extension_objects", []string{extension.ExtensionObjects[0].ID}) + d.Set("extension_schema", extension.ExtensionSchema.ID) + + return []*schema.ResourceData{d}, err +} + +func expandServiceNowServiceObjects(v interface{}) []*pagerduty.ServiceReference { + var services []*pagerduty.ServiceReference + + for _, srv := range v.(*schema.Set).List() { + service := &pagerduty.ServiceReference{ + Type: "service_reference", + ID: srv.(string), + } + services = append(services, service) + } + + return services +} + +func flattenExtensionServicenowObjects(serviceList []*pagerduty.ServiceReference) interface{} { + var services []interface{} + for _, s := range serviceList { + // only flatten service_reference types, because that's all we send at this + // time + if s.Type == "service_reference" { + services = append(services, s.ID) + } + } + return services +} diff --git a/pagerduty/resource_pagerduty_extension_servicenow_test.go b/pagerduty/resource_pagerduty_extension_servicenow_test.go new file mode 100644 index 000000000..e7ba6e9b6 --- /dev/null +++ b/pagerduty/resource_pagerduty_extension_servicenow_test.go @@ -0,0 +1,193 @@ +package pagerduty + +import ( + "fmt" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +func init() { + resource.AddTestSweepers("pagerduty_extension_servicenow", &resource.Sweeper{ + Name: "pagerduty_extension_servicenow", + F: testSweepExtension, + }) +} + +func testSweepExtensionServicenow(region string) error { + config, err := sharedConfigForRegion(region) + if err != nil { + return err + } + + client, err := config.Client() + if err != nil { + return err + } + + resp, _, err := client.Extensions.List(&pagerduty.ListExtensionsOptions{}) + if err != nil { + return err + } + + for _, extension := range resp.Extensions { + if strings.HasPrefix(extension.Name, "test") || strings.HasPrefix(extension.Name, "tf-") { + log.Printf("Destroying extension %s (%s)", extension.Name, extension.ID) + if _, err := client.Extensions.Delete(extension.ID); err != nil { + return err + } + } + } + + return nil +} + +func TestAccPagerDutyExtensionServicenow_Basic(t *testing.T) { + extension_name := resource.PrefixedUniqueId("tf-") + extension_name_updated := resource.PrefixedUniqueId("tf-") + name := resource.PrefixedUniqueId("tf-") + url := "https://example.com/recieve_a_pagerduty_webhook" + url_updated := "https://example.com/webhook_foo" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutyExtensionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyExtensionServicenowConfig(name, extension_name, url, "false", "any"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyExtensionServicenowExists("pagerduty_extension_servicenow.foo"), + resource.TestCheckResourceAttr( + "pagerduty_extension_servicenow.foo", "name", extension_name), + resource.TestCheckResourceAttr( + "pagerduty_extension_servicenow.foo", "extension_schema", "PJFWPEP"), + resource.TestCheckResourceAttr( + "pagerduty_extension_servicenow.foo", "endpoint_url", url), + resource.TestCheckResourceAttr( + "pagerduty_extension_servicenow.foo", "config", "{\"notify_types\":{\"acknowledge\":false,\"assignments\":false,\"resolve\":false},\"restrict\":\"any\"}"), + resource.TestCheckResourceAttr( + "pagerduty_extension_servicenow.foo", "html_url", ""), + ), + }, + { + Config: testAccCheckPagerDutyExtensionServicenowConfig(name, extension_name_updated, url_updated, "true", "pd-users"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyExtensionServicenowExists("pagerduty_extension_servicenow.foo"), + resource.TestCheckResourceAttr( + "pagerduty_extension_servicenow.foo", "name", extension_name_updated), + resource.TestCheckResourceAttr( + "pagerduty_extension_servicenow.foo", "extension_schema", "PJFWPEP"), + resource.TestCheckResourceAttr( + "pagerduty_extension_servicenow.foo", "endpoint_url", url_updated), + resource.TestCheckResourceAttr( + "pagerduty_extension_servicenow.foo", "config", "{\"notify_types\":{\"acknowledge\":true,\"assignments\":true,\"resolve\":true},\"restrict\":\"pd-users\"}"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyExtensionServicenowDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*pagerduty.Client) + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_extension_servicenow" { + continue + } + + if _, _, err := client.Extensions.Get(r.Primary.ID); err == nil { + return fmt.Errorf("Extension still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyExtensionServicenowExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No extension ID is set") + } + + client := testAccProvider.Meta().(*pagerduty.Client) + + found, _, err := client.Extensions.Get(rs.Primary.ID) + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Extension not found: %v - %v", rs.Primary.ID, found) + } + + return nil + } +} + +func testAccCheckPagerDutyExtensionServicenowConfig(name string, extension_name string, url string, notify_types string, restrict string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%[1]v" + email = "%[1]v@foo.com" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%[1]v" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%[1]v" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + + incident_urgency_rule { + type = "constant" + urgency = "high" + } +} + +data "pagerduty_extension_schema" "foo" { + name = "Generic V2 Webhook" +} + +resource "pagerduty_extension_servicenow" "foo"{ + name = "%s" + endpoint_url = "%s" + extension_schema = data.pagerduty_extension_servicenow_schema.foo.id + extension_objects = [pagerduty_service.foo.id] + snow_user = "meeps" + snow_password = "zorz" + sync_options = "manual_sync" + target = foo.servicenow.com/webhook_foo + task_type = "incident" + referer = "None" +} + +`, name, extension_name, url, restrict, notify_types) +} diff --git a/website/docs/r/extension_servicenow.html.markdown b/website/docs/r/extension_servicenow.html.markdown new file mode 100644 index 000000000..f3dbaf9f5 --- /dev/null +++ b/website/docs/r/extension_servicenow.html.markdown @@ -0,0 +1,87 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_extension_servicenow" +sidebar_current: "docs-pagerduty-resource-extension-servicenow" +description: |- + Creates and manages a ServiceNow service extension in PagerDuty. +--- + +# pagerduty\_extension\_servicenow + +A special case for [extension](https://v2.developer.pagerduty.com/v2/page/api-reference#!/Extensions/post_extensions) for ServiceNow. + +## Example Usage + +```hcl +data "pagerduty_extension_schema" "webhook" { + name = "Generic V2 Webhook" +} + +resource "pagerduty_user" "example" { + name = "Howard James" + email = "howard.james@example.domain" + teams = [pagerduty_team.example.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 +} + + +resource "pagerduty_extension_servicenow" "snow"{ + name = "My Web App Extension" + extension_schema = data.pagerduty_extension_schema.webhook.id + extension_objects = [pagerduty_service.example.id] + snow_user = "meeps" + snow_password = "zorz" + sync_options = "manual_sync" + target = "https://foo.servicenow.com/webhook_foo" + task_type = "incident" + referer = "None" +} +``` + +## Argument Reference + +The following arguments are supported: + + * `name` - (Optional) The name of the service extension. + * `extension_schema` - (Required) This is the schema for this extension. + * `extension_objects` - (Required) This is the objects for which the extension applies (An array of service ids). + * `snow_user` - (Required) The ServiceNow username. + * `snow_password` - (Required) The ServiceNow password. + * `sync_options` - (Required) The ServiceNow sync option. + * `target` - (Required) Target Webhook URL + * `task_type` - (Required) The ServiceNow task type, typically `incident`. + * `referer` - (Required) The ServiceNow referer. +## Attributes Reference + +The following attributes are exported: + + * `id` - The ID of the extension. + * `html_url` - URL at which the entity is uniquely displayed in the Web app + +## Import + +Extensions can be imported using the id.e.g. + +``` +$ terraform import pagerduty_extension_servicenow.main PLBP09X +``` diff --git a/website/pagerduty.erb b/website/pagerduty.erb index 7da4ac235..5a478472e 100644 --- a/website/pagerduty.erb +++ b/website/pagerduty.erb @@ -67,6 +67,9 @@ > pagerduty_extension + > + pagerduty_extension_servicenow + > pagerduty_maintenance_window