From 28795b02b5edecbc86d45a29e4759d61d4f0aa77 Mon Sep 17 00:00:00 2001 From: Scott McAllister Date: Tue, 31 Aug 2021 16:41:49 -0700 Subject: [PATCH 1/3] added notification_type and validation --- README.md | 2 + go.mod | 2 +- go.sum | 10 + .../import_pagerduty_slack_connection_test.go | 64 +++ pagerduty/provider.go | 1 + .../resource_pagerduty_slack_connection.go | 309 ++++++++++++++ ...esource_pagerduty_slack_connection_test.go | 390 ++++++++++++++++++ .../pagerduty/business_service.go | 8 +- .../go-pagerduty/pagerduty/pagerduty.go | 2 + .../pagerduty/slack_connection.go | 126 ++++++ vendor/modules.txt | 2 +- website/docs/r/ruleset_rule.html.markdown | 2 +- website/docs/r/slack_connection.html.markdown | 93 +++++ website/pagerduty.erb | 21 +- 14 files changed, 1016 insertions(+), 16 deletions(-) create mode 100644 pagerduty/import_pagerduty_slack_connection_test.go create mode 100644 pagerduty/resource_pagerduty_slack_connection.go create mode 100644 pagerduty/resource_pagerduty_slack_connection_test.go create mode 100644 vendor/github.com/heimweh/go-pagerduty/pagerduty/slack_connection.go create mode 100644 website/docs/r/slack_connection.html.markdown diff --git a/README.md b/README.md index 34f5c3347..030750a96 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,5 @@ In order to run the full suite of Acceptance tests, run `make testacc`. ```sh $ make testacc ``` + +*Additional Note:* In order for the tests on the Slack Connection resources to pass you will need valid Slack workspace and channel IDs from a [Slack workspace connected to your PagerDuty account](https://support.pagerduty.com/docs/slack-integration-guide#integration-walkthrough). \ No newline at end of file diff --git a/go.mod b/go.mod index 637a2f117..e7aba00a7 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go v0.71.0 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/hashicorp/terraform-plugin-sdk v1.7.0 - github.com/heimweh/go-pagerduty v0.0.0-20210811005434-dc24e464325a + github.com/heimweh/go-pagerduty v0.0.0-20210831220234-54710c5e87d1 go.mongodb.org/mongo-driver v1.7.0 // indirect golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd // indirect google.golang.org/api v0.35.0 // indirect diff --git a/go.sum b/go.sum index 6e06a9372..c8285ea7b 100644 --- a/go.sum +++ b/go.sum @@ -213,6 +213,16 @@ github.com/heimweh/go-pagerduty v0.0.0-20210810155842-7de939d717c0 h1:F3c5KAaStF github.com/heimweh/go-pagerduty v0.0.0-20210810155842-7de939d717c0/go.mod h1:6+bccpjQ/PM8uQY9m8avM4MJea+3vo3ta9r8kGQ4XFY= github.com/heimweh/go-pagerduty v0.0.0-20210811005434-dc24e464325a h1:mPx+k0gk1FNkQgWutkSFO/IYcHFv8ueUdA/1hIDkO9E= github.com/heimweh/go-pagerduty v0.0.0-20210811005434-dc24e464325a/go.mod h1:6+bccpjQ/PM8uQY9m8avM4MJea+3vo3ta9r8kGQ4XFY= +github.com/heimweh/go-pagerduty v0.0.0-20210817215245-9604e31ec66b h1:e4lTBCIrfI2Fq6MmQpEv7ejyearCTVw0m8WaW/G6a+Y= +github.com/heimweh/go-pagerduty v0.0.0-20210817215245-9604e31ec66b/go.mod h1:6+bccpjQ/PM8uQY9m8avM4MJea+3vo3ta9r8kGQ4XFY= +github.com/heimweh/go-pagerduty v0.0.0-20210825175603-80b7f8a9ddd7 h1:glR2g8bLfJzRJaSX13tK2aBd7QiZttr7xpfhxwXq1jY= +github.com/heimweh/go-pagerduty v0.0.0-20210825175603-80b7f8a9ddd7/go.mod h1:6+bccpjQ/PM8uQY9m8avM4MJea+3vo3ta9r8kGQ4XFY= +github.com/heimweh/go-pagerduty v0.0.0-20210825225232-caa0f6e992e6 h1:N39eXQVRlxiy0Of5NBJVhv0x3Jjsu3piCZY5v9Gy+vM= +github.com/heimweh/go-pagerduty v0.0.0-20210825225232-caa0f6e992e6/go.mod h1:6+bccpjQ/PM8uQY9m8avM4MJea+3vo3ta9r8kGQ4XFY= +github.com/heimweh/go-pagerduty v0.0.0-20210826184549-45940c9493fc h1:qK78KCqtm+9NDIWPffdGy9udPf7TX9iX+BRpocjhYpw= +github.com/heimweh/go-pagerduty v0.0.0-20210826184549-45940c9493fc/go.mod h1:6+bccpjQ/PM8uQY9m8avM4MJea+3vo3ta9r8kGQ4XFY= +github.com/heimweh/go-pagerduty v0.0.0-20210831220234-54710c5e87d1 h1:yBAqdyzvIpGktzh8EnWqaXAtwhdgWqrP2Zk53MDH89Y= +github.com/heimweh/go-pagerduty v0.0.0-20210831220234-54710c5e87d1/go.mod h1:6+bccpjQ/PM8uQY9m8avM4MJea+3vo3ta9r8kGQ4XFY= 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= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= diff --git a/pagerduty/import_pagerduty_slack_connection_test.go b/pagerduty/import_pagerduty_slack_connection_test.go new file mode 100644 index 000000000..613edd226 --- /dev/null +++ b/pagerduty/import_pagerduty_slack_connection_test.go @@ -0,0 +1,64 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccPagerDutySlackConnection_import(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.com", username) + 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: testAccCheckPagerDutySlackConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutySlackConnectionConfig(username, email, escalationPolicy, service, workspaceID, channelID), + }, + { + Config: testAccCheckPagerDutySlackConnectionConfigUpdated(username, email, escalationPolicy, service, workspaceID, channelID), + }, + { + ResourceName: "pagerduty_slack_connection.foo", + ImportStateIdFunc: testAccCheckPagerDutySlackConnectionID, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccPagerDutySlackConnectionTeam_import(t *testing.T) { + team := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutySlackConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutySlackConnectionConfigTeam(team, workspaceID, channelID), + }, + { + ResourceName: "pagerduty_slack_connection.foo", + ImportStateIdFunc: testAccCheckPagerDutySlackConnectionID, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckPagerDutySlackConnectionID(s *terraform.State) (string, error) { + scatts := s.RootModule().Resources["pagerduty_slack_connection.foo"].Primary.Attributes + + return fmt.Sprintf("%v.%v", scatts["workspace_id"], s.RootModule().Resources["pagerduty_slack_connection.foo"].Primary.ID), nil +} diff --git a/pagerduty/provider.go b/pagerduty/provider.go index 80de9aae4..96d5a508f 100644 --- a/pagerduty/provider.go +++ b/pagerduty/provider.go @@ -63,6 +63,7 @@ func Provider() terraform.ResourceProvider { "pagerduty_service_dependency": resourcePagerDutyServiceDependency(), "pagerduty_response_play": resourcePagerDutyResponsePlay(), "pagerduty_service_event_rule": resourcePagerDutyServiceEventRule(), + "pagerduty_slack_connection": resourcePagerDutySlackConnection(), }, } diff --git a/pagerduty/resource_pagerduty_slack_connection.go b/pagerduty/resource_pagerduty_slack_connection.go new file mode 100644 index 000000000..fd32037c3 --- /dev/null +++ b/pagerduty/resource_pagerduty_slack_connection.go @@ -0,0 +1,309 @@ +package pagerduty + +import ( + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +func resourcePagerDutySlackConnection() *schema.Resource { + return &schema.Resource{ + Create: resourcePagerDutySlackConnectionCreate, + Read: resourcePagerDutySlackConnectionRead, + Update: resourcePagerDutySlackConnectionUpdate, + Delete: resourcePagerDutySlackConnectionDelete, + Importer: &schema.ResourceImporter{ + State: resourcePagerDutySlackConnectionImport, + }, + Schema: map[string]*schema.Schema{ + "source_id": { + Type: schema.TypeString, + Required: true, + }, + "source_name": { + Type: schema.TypeString, + Computed: true, + }, + "source_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateValueFunc([]string{ + "service_reference", + "team_reference", + }), + }, + "channel_id": { + Type: schema.TypeString, + Required: true, + }, + "channel_name": { + Type: schema.TypeString, + Computed: true, + }, + "workspace_id": { + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("SLACK_CONNECTION_WORKSPACE_ID", nil), + }, + "notification_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateValueFunc([]string{ + "responder", + "stakeholder", + }), + }, + "config": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "events": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "priorities": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "urgency": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateValueFunc([]string{ + "high", + "low", + }), + }, + }, + }, + }, + }, + } +} + +func buildSlackConnectionStruct(d *schema.ResourceData) (*pagerduty.SlackConnection, error) { + slackConn := pagerduty.SlackConnection{ + SourceID: d.Get("source_id").(string), + SourceName: d.Get("source_name").(string), + SourceType: d.Get("source_type").(string), + ChannelID: d.Get("channel_id").(string), + ChannelName: d.Get("channel_name").(string), + WorkspaceID: d.Get("workspace_id").(string), + NotificationType: d.Get("notification_type").(string), + Config: expandConnectionConfig(d.Get("config").(interface{})), + } + return &slackConn, nil +} + +func resourcePagerDutySlackConnectionCreate(d *schema.ResourceData, meta interface{}) error { + config := &pagerduty.Config{ + Token: os.Getenv("PAGERDUTY_USER_TOKEN"), + BaseURL: "https://app.pagerduty.com", + } + client, err := pagerduty.NewClient(config) + if err != nil { + return err + } + + retryErr := resource.Retry(2*time.Minute, func() *resource.RetryError { + + slackConn, err := buildSlackConnectionStruct(d) + if err != nil { + return resource.NonRetryableError(err) + } + log.Printf("[INFO] Creating PagerDuty slack connection for source %s and slack channel %s", slackConn.SourceID, slackConn.ChannelID) + + if slackConn, _, err = client.SlackConnections.Create(slackConn.WorkspaceID, slackConn); err != nil { + return resource.RetryableError(err) + } else if slackConn != nil { + d.SetId(slackConn.ID) + d.Set("workspace_id", slackConn.WorkspaceID) + } + return nil + }) + if retryErr != nil { + time.Sleep(2 * time.Second) + return retryErr + } + return resourcePagerDutySlackConnectionRead(d, meta) +} + +func resourcePagerDutySlackConnectionRead(d *schema.ResourceData, meta interface{}) error { + config := &pagerduty.Config{ + Token: os.Getenv("PAGERDUTY_USER_TOKEN"), + BaseURL: "https://app.pagerduty.com", + } + client, err := pagerduty.NewClient(config) + if err != nil { + return err + } + + log.Printf("[INFO] Reading PagerDuty slack connection %s", d.Id()) + + workspaceID := d.Get("workspace_id").(string) + log.Printf("[DEBUG] Read Slack Connection: workspace_id %s", workspaceID) + + retryErr := resource.Retry(2*time.Minute, func() *resource.RetryError { + if slackConn, _, err := client.SlackConnections.Get(workspaceID, d.Id()); err != nil { + return resource.RetryableError(err) + } else if slackConn != nil { + d.Set("source_id", slackConn.SourceID) + d.Set("source_name", slackConn.SourceName) + d.Set("source_type", slackConn.SourceType) + d.Set("channel_id", slackConn.ChannelID) + d.Set("channel_name", slackConn.ChannelName) + d.Set("notification_type", slackConn.NotificationType) + d.Set("config", flattenConnectionConfig(slackConn.Config)) + } + return nil + }) + + if retryErr != nil { + time.Sleep(2 * time.Second) + return retryErr + } + + return nil +} + +func resourcePagerDutySlackConnectionUpdate(d *schema.ResourceData, meta interface{}) error { + config := &pagerduty.Config{ + Token: os.Getenv("PAGERDUTY_USER_TOKEN"), + BaseURL: "https://app.pagerduty.com", + } + client, err := pagerduty.NewClient(config) + if err != nil { + return err + } + + slackConn, err := buildSlackConnectionStruct(d) + if err != nil { + return err + } + log.Printf("[INFO] Updating PagerDuty slack connection %s", d.Id()) + + if _, _, err := client.SlackConnections.Update(slackConn.WorkspaceID, d.Id(), slackConn); err != nil { + return err + } + + return nil +} + +func resourcePagerDutySlackConnectionDelete(d *schema.ResourceData, meta interface{}) error { + config := &pagerduty.Config{ + Token: os.Getenv("PAGERDUTY_USER_TOKEN"), + BaseURL: "https://app.pagerduty.com", + } + client, err := pagerduty.NewClient(config) + if err != nil { + return err + } + + log.Printf("[INFO] Deleting PagerDuty slack connection %s", d.Id()) + workspaceID := d.Get("workspace_id").(string) + + if _, err := client.SlackConnections.Delete(workspaceID, d.Id()); err != nil { + return err + } + + d.SetId("") + + return nil +} + +func expandConnectionConfig(v interface{}) pagerduty.ConnectionConfig { + c := v.([]interface{})[0].(map[string]interface{}) + + var config pagerduty.ConnectionConfig + + config = pagerduty.ConnectionConfig{ + Events: expandConfigList(c["events"].([]interface{})), + Priorities: expandConfigList(c["priorities"].([]interface{})), + Urgency: nil, + } + if val, ok := c["urgency"]; ok { + urgency := val.(string) + if len(urgency) > 0 { + config.Urgency = &urgency + } + } + return config +} + +func expandConfigList(v interface{}) []string { + var items []string + for _, i := range v.([]interface{}) { + items = append(items, i.(string)) + } + return items +} + +func flattenConnectionConfig(config pagerduty.ConnectionConfig) []map[string]interface{} { + var configs []map[string]interface{} + configMap := map[string]interface{}{ + "events": flattenConfigList(config.Events), + "priorities": flattenConfigList(config.Priorities), + } + if config.Urgency != nil { + configMap["urgency"] = *config.Urgency + } + configs = append(configs, configMap) + return configs +} + +func flattenConfigList(list []string) interface{} { + var items []interface{} + + for _, i := range list { + items = append(items, i) + } + + return items +} + +func setClientConfig(c *pagerduty.Client) { + c.Config = &pagerduty.Config{ + Token: os.Getenv("PAGERDUTY_USER_TOKEN"), + BaseURL: "https://app.pagerduty.com", + } +} + +func resourcePagerDutySlackConnectionImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := &pagerduty.Config{ + Token: os.Getenv("PAGERDUTY_USER_TOKEN"), + BaseURL: "https://app.pagerduty.com", + } + client, err := pagerduty.NewClient(config) + if err != nil { + return nil, err + } + + ids := strings.Split(d.Id(), ".") + + if len(ids) != 2 { + return []*schema.ResourceData{}, fmt.Errorf("Error importing pagerduty_slack_connection. Expecting an importation ID formed as '.'") + } + workspaceID, connectionID := ids[0], ids[1] + + _, _, err = client.SlackConnections.Get(workspaceID, connectionID) + if err != nil { + return []*schema.ResourceData{}, err + } + + d.SetId(connectionID) + d.Set("workspace_id", workspaceID) + + return []*schema.ResourceData{d}, nil +} diff --git a/pagerduty/resource_pagerduty_slack_connection_test.go b/pagerduty/resource_pagerduty_slack_connection_test.go new file mode 100644 index 000000000..1ddb04070 --- /dev/null +++ b/pagerduty/resource_pagerduty_slack_connection_test.go @@ -0,0 +1,390 @@ +package pagerduty + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/heimweh/go-pagerduty/pagerduty" +) + +var ( + /* workspaceID and channelID must be valid IDs from a Slack workspace connected + to the PagerDuty account running these tests. For these tests value for workspaceID + is taken from the SLACK_CONNECTION_WORKSPACE_ID environment variable */ + channelID string = "C02CLUSDAC9" + workspaceID string = "T02ADG9LV1A" +) + +func TestAccPagerDutySlackConnection_Basic(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.com", username) + 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: testAccCheckPagerDutySlackConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutySlackConnectionConfig(username, email, escalationPolicy, service, workspaceID, channelID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutySlackConnectionExists("pagerduty_slack_connection.foo"), + resource.TestCheckResourceAttr( + "pagerduty_slack_connection.foo", "source_name", service), + resource.TestCheckResourceAttr( + "pagerduty_slack_connection.foo", "config.0.events.#", "13"), + ), + }, + { + Config: testAccCheckPagerDutySlackConnectionConfigUpdated(username, email, escalationPolicy, service, workspaceID, channelID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutySlackConnectionExists("pagerduty_slack_connection.foo"), + resource.TestCheckResourceAttr( + "pagerduty_slack_connection.foo", "config.0.urgency", ""), + ), + }, + }, + }) +} + +func TestAccPagerDutySlackConnection_Team(t *testing.T) { + team := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutySlackConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutySlackConnectionConfigTeam(team, workspaceID, channelID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutySlackConnectionExists("pagerduty_slack_connection.foo"), + resource.TestCheckResourceAttr( + "pagerduty_slack_connection.foo", "source_name", team), + resource.TestCheckResourceAttr( + "pagerduty_slack_connection.foo", "config.0.events.#", "13"), + ), + }, + { + Config: testAccCheckPagerDutySlackConnectionConfigTeamUpdated(team, workspaceID, channelID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutySlackConnectionExists("pagerduty_slack_connection.foo"), + resource.TestCheckResourceAttr( + "pagerduty_slack_connection.foo", "config.0.urgency", "low"), + ), + }, + }, + }) +} + +func TestAccPagerDutySlackConnection_Envar(t *testing.T) { + team := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPagerDutySlackConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutySlackConnectionConfigEnvar(team, channelID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutySlackConnectionExists("pagerduty_slack_connection.foo"), + resource.TestCheckResourceAttr( + "pagerduty_slack_connection.foo", "workspace_id", os.Getenv("SLACK_CONNECTION_WORKSPACE_ID")), + ), + }, + }, + }) +} + +func testAccCheckPagerDutySlackConnectionDestroy(s *terraform.State) error { + config := &pagerduty.Config{ + Token: os.Getenv("PAGERDUTY_USER_TOKEN"), + BaseURL: "https://app.pagerduty.com", + } + client, err := pagerduty.NewClient(config) + if err != nil { + return err + } + + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_slack_connection" { + continue + } + + scatts := r.Primary.Attributes + if _, _, err := client.SlackConnections.Get(scatts["workspace_id"], r.Primary.ID); err == nil { + return fmt.Errorf("response play still exists") + } + + } + return nil +} + +func testAccCheckPagerDutySlackConnectionExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + sc, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if sc.Primary.ID == "" { + return fmt.Errorf("No slack connection ID is set") + } + log.Printf("[DEBUG] Slack Connection EXISTS: %v", sc.Primary.ID) + + config := &pagerduty.Config{ + Token: os.Getenv("PAGERDUTY_USER_TOKEN"), + BaseURL: "https://app.pagerduty.com", + } + client, err := pagerduty.NewClient(config) + if err != nil { + return err + } + + scatts := sc.Primary.Attributes + found, _, err := client.SlackConnections.Get(scatts["workspace_id"], sc.Primary.ID) + if err != nil { + return err + } + + if found.ID != sc.Primary.ID { + return fmt.Errorf("slack connection not found: %v - %v", sc.Primary.ID, found) + } + + return nil + } +} + +func testAccCheckPagerDutySlackConnectionConfig(username, useremail, escalationPolicy, service, workspaceID, channelID string) string { + return fmt.Sprintf(` + resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + } + + resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "foo" + num_loops = 1 + + rule { + escalation_delay_in_minutes = 10 + + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } + } + + resource "pagerduty_service" "foo" { + name = "%s" + 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_priority" "p1" { + name = "P1" + } + resource "pagerduty_slack_connection" "foo" { + source_id = pagerduty_service.foo.id + source_type = "service_reference" + workspace_id = "%s" + channel_id = "%s" + notification_type = "responder" + config { + events = [ + "incident.triggered", + "incident.acknowledged", + "incident.escalated", + "incident.resolved", + "incident.reassigned", + "incident.annotated", + "incident.unacknowledged", + "incident.delegated", + "incident.priority_updated", + "incident.responder.added", + "incident.responder.replied", + "incident.status_update_published", + "incident.reopened" + ] + priorities = [data.pagerduty_priority.p1.id] + urgency = "high" + } + } + `, username, useremail, escalationPolicy, service, workspaceID, channelID) +} + +func testAccCheckPagerDutySlackConnectionConfigUpdated(username, email, escalationPolicy, service, workspaceID, channelID string) string { + return fmt.Sprintf(` + resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + } + + resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "foo" + num_loops = 1 + + rule { + escalation_delay_in_minutes = 10 + + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } + } + + resource "pagerduty_service" "foo" { + name = "%s" + 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_priority" "p1" { + name = "P1" + } + resource "pagerduty_slack_connection" "foo" { + source_id = pagerduty_service.foo.id + source_type = "service_reference" + workspace_id = "%s" + channel_id = "%s" + notification_type = "stakeholder" + config { + events = [ + "incident.triggered", + "incident.acknowledged", + "incident.escalated", + "incident.resolved", + "incident.reassigned", + "incident.annotated", + "incident.unacknowledged", + "incident.delegated", + "incident.priority_updated", + "incident.responder.added", + "incident.responder.replied", + "incident.status_update_published", + "incident.reopened" + ] + priorities = [data.pagerduty_priority.p1.id] + } + } + `, username, email, escalationPolicy, service, workspaceID, channelID) +} + +func testAccCheckPagerDutySlackConnectionConfigTeam(team, workspaceID, channelID string) string { + return fmt.Sprintf(` + resource "pagerduty_team" "foo" { + name = "%s" + } + resource "pagerduty_slack_connection" "foo" { + source_id = pagerduty_team.foo.id + source_type = "team_reference" + workspace_id = "%s" + channel_id = "%s" + notification_type = "responder" + config { + events = [ + "incident.triggered", + "incident.acknowledged", + "incident.escalated", + "incident.resolved", + "incident.reassigned", + "incident.annotated", + "incident.unacknowledged", + "incident.delegated", + "incident.priority_updated", + "incident.responder.added", + "incident.responder.replied", + "incident.status_update_published", + "incident.reopened" + ] + } + } + `, team, workspaceID, channelID) +} +func testAccCheckPagerDutySlackConnectionConfigTeamUpdated(team, workspaceID, channelID string) string { + return fmt.Sprintf(` + resource "pagerduty_team" "foo" { + name = "%s" + } + resource "pagerduty_slack_connection" "foo" { + source_id = pagerduty_team.foo.id + source_type = "team_reference" + workspace_id = "%s" + channel_id = "%s" + notification_type = "responder" + config { + events = [ + "incident.triggered", + "incident.acknowledged", + "incident.escalated", + "incident.resolved", + "incident.reassigned", + "incident.annotated", + "incident.unacknowledged", + "incident.delegated", + "incident.priority_updated", + "incident.responder.added", + "incident.responder.replied", + "incident.status_update_published", + "incident.reopened" + ] + urgency = "low" + } + } + `, team, workspaceID, channelID) +} + +func testAccCheckPagerDutySlackConnectionConfigEnvar(team, channelID string) string { + return fmt.Sprintf(` + resource "pagerduty_team" "foo" { + name = "%s" + } + resource "pagerduty_slack_connection" "foo" { + source_id = pagerduty_team.foo.id + source_type = "team_reference" + channel_id = "%s" + notification_type = "responder" + config { + events = [ + "incident.triggered", + "incident.acknowledged", + "incident.escalated", + "incident.resolved", + "incident.reassigned", + "incident.annotated", + "incident.unacknowledged", + "incident.delegated", + "incident.priority_updated", + "incident.responder.added", + "incident.responder.replied", + "incident.status_update_published", + "incident.reopened" + ] + urgency = "low" + } + } + `, team, channelID) +} diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/business_service.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/business_service.go index fb9ac94dd..21705e794 100644 --- a/vendor/github.com/heimweh/go-pagerduty/pagerduty/business_service.go +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/business_service.go @@ -76,10 +76,10 @@ func (s *BusinessServiceService) List() (*ListBusinessServicesResponse, *Respons } // Create creates a new business service. -func (s *BusinessServiceService) Create(ruleset *BusinessService) (*BusinessService, *Response, error) { +func (s *BusinessServiceService) Create(bservice *BusinessService) (*BusinessService, *Response, error) { u := "/business_services" v := new(BusinessServicePayload) - p := &BusinessServicePayload{BusinessService: ruleset} + p := &BusinessServicePayload{BusinessService: bservice} resp, err := s.client.newRequestDo("POST", u, nil, p, v) if err != nil { @@ -110,10 +110,10 @@ func (s *BusinessServiceService) Delete(ID string) (*Response, error) { } // Update updates a business service. -func (s *BusinessServiceService) Update(ID string, ruleset *BusinessService) (*BusinessService, *Response, error) { +func (s *BusinessServiceService) Update(ID string, bserv *BusinessService) (*BusinessService, *Response, error) { u := fmt.Sprintf("/business_services/%s", ID) v := new(BusinessServicePayload) - p := BusinessServicePayload{BusinessService: ruleset} + p := BusinessServicePayload{BusinessService: bserv} resp, err := s.client.newRequestDo("PUT", u, nil, p, v) if err != nil { diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go index 4c9ab420f..6d9bf4f08 100644 --- a/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go @@ -52,6 +52,7 @@ type Client struct { ServiceDependencies *ServiceDependencyService Priorities *PriorityService ResponsePlays *ResponsePlayService + SlackConnections *SlackConnectionService } // Response is a wrapper around http.Response @@ -107,6 +108,7 @@ func NewClient(config *Config) (*Client, error) { c.ServiceDependencies = &ServiceDependencyService{c} c.Priorities = &PriorityService{c} c.ResponsePlays = &ResponsePlayService{c} + c.SlackConnections = &SlackConnectionService{c} InitCache(c) PopulateCache() diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/slack_connection.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/slack_connection.go new file mode 100644 index 000000000..0ca44da0d --- /dev/null +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/slack_connection.go @@ -0,0 +1,126 @@ +package pagerduty + +import "fmt" + +// SlackConnectionService handles the communication with the integration slack +// related methods of the PagerDuty API. +type SlackConnectionService service + +// SlackConnection represents a slack connection. +type SlackConnection struct { + ID string `json:"id,omitempty"` + SourceID string `json:"source_id,omitempty"` + SourceName string `json:"source_name,omitempty"` + SourceType string `json:"source_type,omitempty"` + ChannelID string `json:"channel_id,omitempty"` + ChannelName string `json:"channel_name,omitempty"` + WorkspaceID string `json:"workspace_id,omitempty"` + Config ConnectionConfig `json:"config,omitempty"` + NotificationType string `json:"notification_type,omitempty"` +} + +// ConnectionConfig represents a config object in a slack connection +type ConnectionConfig struct { + Events []string `json:"events,omitempty"` + Priorities []string `json:"priorities"` + Urgency *string `json:"urgency"` +} + +// SlackConnectionPayload represents payload with a slack connect object +type SlackConnectionPayload struct { + SlackConnection *SlackConnection `json:"slack_connection,omitempty"` +} + +// ListSlackConnectionsResponse represents a list response of slack connections. +type ListSlackConnectionsResponse struct { + Total int `json:"total,omitempty"` + SlackConnections []*SlackConnection `json:"slack_connections,omitempty"` + Offset int `json:"offset,omitempty"` + More bool `json:"more,omitempty"` + Limit int `json:"limit,omitempty"` +} + +// List lists existing slack connections. +func (s *SlackConnectionService) List(workspaceID string) (*ListSlackConnectionsResponse, *Response, error) { + u := fmt.Sprintf("/integration-slack/workspaces/%s/connections", workspaceID) + v := new(ListSlackConnectionsResponse) + + slackConnections := make([]*SlackConnection, 0) + + // Create a handler closure capable of parsing data from the integration-slack connections endpoint + // and appending resultant response plays to the return slice. + responseHandler := func(response *Response) (ListResp, *Response, error) { + var result ListSlackConnectionsResponse + + if err := s.client.DecodeJSON(response, &result); err != nil { + return ListResp{}, response, err + } + + slackConnections = append(slackConnections, result.SlackConnections...) + + // 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(u, responseHandler) + if err != nil { + return nil, nil, err + } + v.SlackConnections = slackConnections + + return v, nil, nil +} + +// Create creates a new slack connection. +func (s *SlackConnectionService) Create(workspaceID string, sconn *SlackConnection) (*SlackConnection, *Response, error) { + u := fmt.Sprintf("/integration-slack/workspaces/%s/connections", workspaceID) + v := new(SlackConnectionPayload) + p := &SlackConnectionPayload{SlackConnection: sconn} + + resp, err := s.client.newRequestDo("POST", u, nil, p, v) + if err != nil { + return nil, nil, err + } + // Slack Connection in Terraform Provider needs workspaceID set to the object + v.SlackConnection.WorkspaceID = workspaceID + + return v.SlackConnection, resp, nil +} + +// Get gets a slack connection. +func (s *SlackConnectionService) Get(workspaceID, ID string) (*SlackConnection, *Response, error) { + u := fmt.Sprintf("/integration-slack/workspaces/%s/connections/%s", workspaceID, ID) + v := new(SlackConnectionPayload) + p := &SlackConnectionPayload{} + + resp, err := s.client.newRequestDo("GET", u, nil, p, v) + if err != nil { + return nil, nil, err + } + + return v.SlackConnection, resp, nil +} + +// Delete deletes a slack connection. +func (s *SlackConnectionService) Delete(workspaceID, ID string) (*Response, error) { + u := fmt.Sprintf("/integration-slack/workspaces/%s/connections/%s", workspaceID, ID) + return s.client.newRequestDo("DELETE", u, nil, nil, nil) +} + +// Update updates a slack connection. +func (s *SlackConnectionService) Update(workspaceID, ID string, sconn *SlackConnection) (*SlackConnection, *Response, error) { + u := fmt.Sprintf("/integration-slack/workspaces/%s/connections/%s", workspaceID, ID) + v := new(SlackConnectionPayload) + p := SlackConnectionPayload{SlackConnection: sconn} + + resp, err := s.client.newRequestDo("PUT", u, nil, p, v) + if err != nil { + return nil, nil, err + } + + return v.SlackConnection, resp, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c13f3e796..332b6ae5b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -192,7 +192,7 @@ github.com/hashicorp/terraform-svchost/auth github.com/hashicorp/terraform-svchost/disco # github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d github.com/hashicorp/yamux -# github.com/heimweh/go-pagerduty v0.0.0-20210811005434-dc24e464325a +# github.com/heimweh/go-pagerduty v0.0.0-20210831220234-54710c5e87d1 ## explicit github.com/heimweh/go-pagerduty/pagerduty # github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af diff --git a/website/docs/r/ruleset_rule.html.markdown b/website/docs/r/ruleset_rule.html.markdown index 973df9d38..31502fe2a 100644 --- a/website/docs/r/ruleset_rule.html.markdown +++ b/website/docs/r/ruleset_rule.html.markdown @@ -161,7 +161,7 @@ The following attributes are exported: ## Import -Ruleset rules can be imported using using the related `ruleset` id and the `ruleset_rule` id separated by a dot, e.g. +Ruleset rules can be imported using using the related `ruleset` ID and the `ruleset_rule` ID separated by a dot, e.g. ``` $ terraform import pagerduty_ruleset_rule.main a19cdca1-3d5e-4b52-bfea-8c8de04da243.19acac92-027a-4ea0-b06c-bbf516519601 diff --git a/website/docs/r/slack_connection.html.markdown b/website/docs/r/slack_connection.html.markdown new file mode 100644 index 000000000..5425693f7 --- /dev/null +++ b/website/docs/r/slack_connection.html.markdown @@ -0,0 +1,93 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_slack_connection" +sidebar_current: "docs-pagerduty-resource-slack-connection" +description: |- + Creates and manages a slack connection in PagerDuty. +--- + +# pagerduty\_slack\_connection + +A [slack connection](https://developer.pagerduty.com/api-reference/reference/integration-slack-service/openapiv3.json) allows you to connect a workspace in Slack to a PagerDuty service or team which allows you to acknowledge and resolve PagerDuty incidents from the Slack user interface. + +**NOTE: Using this resource requires a PagerDuty [user-level API key](https://support.pagerduty.com/docs/generating-api-keys#section-generating-a-personal-rest-api-key) set as the `PAGERDUTY_USER_TOKEN` environment variable.* +## Example Usage + +```hcl +resource "pagerduty_team" "foo" { + name = "Team Foo" +} + +data "pagerduty_priority" "p1" { + name = "P1" +} + +resource "pagerduty_slack_connection" "foo" { + source_id = pagerduty_team.foo.id + source_type = "team_reference" + workspace_id = "T02A123LV1A" + channel_id = "C02CABCDAC9" + config { + events = [ + "incident.triggered", + "incident.acknowledged", + "incident.escalated", + "incident.resolved", + "incident.reassigned", + "incident.annotated", + "incident.unacknowledged", + "incident.delegated", + "incident.priority_updated", + "incident.responder.added", + "incident.responder.replied", + "incident.status_update_published", + "incident.reopened" + ] + priorities = [data.pagerduty_priority.p1.id] + + } +} +``` + +## Argument Reference + +The following arguments are supported: + + * `source_id` - (Required) The ID of the source in PagerDuty. Valid sources are services or teams. + * `source_type` - (Required) The type of the source. Either `team_reference` or `service_reference`. + * `workspace_id` - (Required) The ID of the connected Slack workspace. Can also be defined by the `SLACK_CONNECTION_WORKSPACE_ID` environment variable. + * `channel_id` - (Required) The ID of a Slack channel in the workspace. + * `config` - (Required) Configuration options for the Slack connection that provide options to filter events. + * `notification_type` - (Required) Type of notification. Either `responder` or `stakeholder`. + +### Connection Config (`config`) Supports the following: + * `events` - (Required) A list of strings to filter events by PagerDuty event type. `"incident.triggered"` is required. The follow event types are also possible: + - `incident.acknowledged` + - `incident.escalated` + - `incident.resolved` + - `incident.reassigned` + - `incident.annotated` + - `incident.unacknowledged` + - `incident.delegated` + - `incident.priority_updated` + - `incident.responder.added` + - `incident.responder.replied` + - `incident.status_update_published` + - `incident.reopened` + * `priorities` - (Optional) Allows you to filter events by priority. Needs to be an array of PagerDuty priority IDs. Available through [pagerduty_priority](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/data-sources/priority) data source. + * `urgency` - (Optional) Allows you to filter events by urgency. Either `high` or `low`. + +## Attributes Reference + +The following attributes are exported: + + * `id` - The ID of the slack connection. + * `source_name`- Name of the source (team or service) in Slack connection. + * `channel_name`- Name of the Slack channel in Slack connection + +## Import + +Slack connections can be imported using using the related `workspace` ID and the `slack_connection` ID separated by a dot, e.g. +``` +$ terraform import pagerduty_slack_connection.main T02A123LV1A.PUABCDL +``` diff --git a/website/pagerduty.erb b/website/pagerduty.erb index 32594c590..36b84a140 100644 --- a/website/pagerduty.erb +++ b/website/pagerduty.erb @@ -37,18 +37,18 @@ > pagerduty_service_integration - > - pagerduty_user - - > - pagerduty_user_contact_method - > pagerduty_team > pagerduty_vendor + > + pagerduty_user + + > + pagerduty_user_contact_method + @@ -91,14 +91,17 @@ > pagerduty_service - > - pagerduty_service_event_rule - > pagerduty_service_dependency + > + pagerduty_service_event_rule + > pagerduty_service_integration + + > + pagerduty_slack_connection > pagerduty_team From 79a064c03ee644616b48fd662eddbab93915ef0b Mon Sep 17 00:00:00 2001 From: Scott McAllister Date: Tue, 31 Aug 2021 16:59:10 -0700 Subject: [PATCH 2/3] updated docs --- website/docs/r/slack_connection.html.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/r/slack_connection.html.markdown b/website/docs/r/slack_connection.html.markdown index 5425693f7..8bc36f7dd 100644 --- a/website/docs/r/slack_connection.html.markdown +++ b/website/docs/r/slack_connection.html.markdown @@ -10,7 +10,9 @@ description: |- A [slack connection](https://developer.pagerduty.com/api-reference/reference/integration-slack-service/openapiv3.json) allows you to connect a workspace in Slack to a PagerDuty service or team which allows you to acknowledge and resolve PagerDuty incidents from the Slack user interface. -**NOTE: Using this resource requires a PagerDuty [user-level API key](https://support.pagerduty.com/docs/generating-api-keys#section-generating-a-personal-rest-api-key) set as the `PAGERDUTY_USER_TOKEN` environment variable.* +**NOTES for using this resource:** +* To first use this resource you will need to [map your PagerDuty account to a valid Slack Workspace](https://support.pagerduty.com/docs/slack-integration-guide#integration-walkthrough). *This can only be done through the PagerDuty UI.* +* This resource requires a PagerDuty [user-level API key](https://support.pagerduty.com/docs/generating-api-keys#section-generating-a-personal-rest-api-key) set as the `PAGERDUTY_USER_TOKEN` environment variable. ## Example Usage ```hcl From b295072490f4a1f8fa0e3fe8fe560cda104d5bd6 Mon Sep 17 00:00:00 2001 From: Scott McAllister Date: Wed, 8 Sep 2021 12:53:09 -0700 Subject: [PATCH 3/3] addressed pr feedback --- .../resource_pagerduty_slack_connection.go | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/pagerduty/resource_pagerduty_slack_connection.go b/pagerduty/resource_pagerduty_slack_connection.go index fd32037c3..94dc03d0f 100644 --- a/pagerduty/resource_pagerduty_slack_connection.go +++ b/pagerduty/resource_pagerduty_slack_connection.go @@ -12,6 +12,8 @@ import ( "github.com/heimweh/go-pagerduty/pagerduty" ) +const AppBaseUrl = "https://app.pagerduty.com" + func resourcePagerDutySlackConnection() *schema.Resource { return &schema.Resource{ Create: resourcePagerDutySlackConnectionCreate, @@ -108,10 +110,7 @@ func buildSlackConnectionStruct(d *schema.ResourceData) (*pagerduty.SlackConnect } func resourcePagerDutySlackConnectionCreate(d *schema.ResourceData, meta interface{}) error { - config := &pagerduty.Config{ - Token: os.Getenv("PAGERDUTY_USER_TOKEN"), - BaseURL: "https://app.pagerduty.com", - } + config := setClientConfig() client, err := pagerduty.NewClient(config) if err != nil { return err @@ -141,10 +140,7 @@ func resourcePagerDutySlackConnectionCreate(d *schema.ResourceData, meta interfa } func resourcePagerDutySlackConnectionRead(d *schema.ResourceData, meta interface{}) error { - config := &pagerduty.Config{ - Token: os.Getenv("PAGERDUTY_USER_TOKEN"), - BaseURL: "https://app.pagerduty.com", - } + config := setClientConfig() client, err := pagerduty.NewClient(config) if err != nil { return err @@ -181,7 +177,7 @@ func resourcePagerDutySlackConnectionRead(d *schema.ResourceData, meta interface func resourcePagerDutySlackConnectionUpdate(d *schema.ResourceData, meta interface{}) error { config := &pagerduty.Config{ Token: os.Getenv("PAGERDUTY_USER_TOKEN"), - BaseURL: "https://app.pagerduty.com", + BaseURL: AppBaseUrl, } client, err := pagerduty.NewClient(config) if err != nil { @@ -202,10 +198,7 @@ func resourcePagerDutySlackConnectionUpdate(d *schema.ResourceData, meta interfa } func resourcePagerDutySlackConnectionDelete(d *schema.ResourceData, meta interface{}) error { - config := &pagerduty.Config{ - Token: os.Getenv("PAGERDUTY_USER_TOKEN"), - BaseURL: "https://app.pagerduty.com", - } + config := setClientConfig() client, err := pagerduty.NewClient(config) if err != nil { return err @@ -273,18 +266,15 @@ func flattenConfigList(list []string) interface{} { return items } -func setClientConfig(c *pagerduty.Client) { - c.Config = &pagerduty.Config{ +func setClientConfig() *pagerduty.Config { + return &pagerduty.Config{ Token: os.Getenv("PAGERDUTY_USER_TOKEN"), - BaseURL: "https://app.pagerduty.com", + BaseURL: AppBaseUrl, } } func resourcePagerDutySlackConnectionImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - config := &pagerduty.Config{ - Token: os.Getenv("PAGERDUTY_USER_TOKEN"), - BaseURL: "https://app.pagerduty.com", - } + config := setClientConfig() client, err := pagerduty.NewClient(config) if err != nil { return nil, err