diff --git a/client/destinations.go b/client/destinations.go index b3823dbe..beeda5c6 100644 --- a/client/destinations.go +++ b/client/destinations.go @@ -39,6 +39,13 @@ type ServiceNowAttributes struct { Auth Auth `json:"auth"` } +type OpsgenieAttributes struct { + Name string `json:"name"` + DestinationType string `json:"destination_type"` + URL string `json:"url"` + Auth Auth `json:"auth"` +} + type Auth struct { Username string `json:"username"` // Password is only set for requests. Will be empty for API responses diff --git a/docs/data-sources/stream.md b/docs/data-sources/stream.md index 6650a55a..421c0535 100644 --- a/docs/data-sources/stream.md +++ b/docs/data-sources/stream.md @@ -25,3 +25,5 @@ Use this data source to retrieve information about an existing stream for use in - `id` (String) The ID of this resource. - `stream_name` (String) - `stream_query` (String) Stream query + + diff --git a/docs/resources/opsgenie_destination.md b/docs/resources/opsgenie_destination.md new file mode 100644 index 00000000..6ffaa965 --- /dev/null +++ b/docs/resources/opsgenie_destination.md @@ -0,0 +1,37 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "lightstep_opsgenie_destination Resource - terraform-provider-lightstep" +subcategory: "" +description: |- + +--- + +# lightstep_opsgenie_destination (Resource) + + + + + + +## Schema + +### Required + +- `auth` (Block List, Min: 1, Max: 1) Basic auth used to authenticate with the Opsgenie instance (see [below for nested schema](#nestedblock--auth)) +- `destination_name` (String) Name of the Opsgenie destination +- `project_name` (String) +- `url` (String) Opsgenie instance URL + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `auth` + +Required: + +- `password` (String, Sensitive) +- `username` (String) + + diff --git a/docs/resources/user_role_binding.md b/docs/resources/user_role_binding.md index 5184cba1..8fadf038 100644 --- a/docs/resources/user_role_binding.md +++ b/docs/resources/user_role_binding.md @@ -94,3 +94,5 @@ resource "lightstep_user_role_binding" "proj_viewer" { ### Read-Only - `id` (String) The ID of this resource. + + diff --git a/lightstep/provider.go b/lightstep/provider.go index cdd3b835..5013db8e 100644 --- a/lightstep/provider.go +++ b/lightstep/provider.go @@ -58,6 +58,7 @@ func Provider() *schema.Provider { "lightstep_pagerduty_destination": resourcePagerdutyDestination(), "lightstep_slack_destination": resourceSlackDestination(), "lightstep_servicenow_destination": resourceServiceNowDestination(), + "lightstep_opsgenie_destination": resourceOpsgenieDestination(), "lightstep_alerting_rule": resourceAlertingRule(), "lightstep_dashboard": resourceUnifiedDashboard(UnifiedChartSchema), "lightstep_alert": resourceUnifiedCondition(UnifiedConditionSchema), diff --git a/lightstep/provider_test.go b/lightstep/provider_test.go index 180f760b..ab9a6769 100644 --- a/lightstep/provider_test.go +++ b/lightstep/provider_test.go @@ -24,7 +24,7 @@ func init() { testProject = os.Getenv("LIGHTSTEP_PROJECT") if testProject == "" { - testProject = "terraform-provider-tests" + testProject = "terraform-provider-test" } } diff --git a/lightstep/resource_opsgenie_destination.go b/lightstep/resource_opsgenie_destination.go new file mode 100644 index 00000000..4d94a364 --- /dev/null +++ b/lightstep/resource_opsgenie_destination.go @@ -0,0 +1,128 @@ +package lightstep + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/lightstep/terraform-provider-lightstep/client" +) + +func resourceOpsgenieDestination() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceOpsgenieDestinationCreate, + ReadContext: resourceDestinationRead, + DeleteContext: resourceDestinationDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceOpsgenieDestinationImport, + }, + Schema: map[string]*schema.Schema{ + "project_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "destination_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the Opsgenie destination", + }, + "url": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Opsgenie instance URL", + ValidateFunc: validation.IsURLWithScheme([]string{"https"}), + }, + "auth": { + Type: schema.TypeList, + MinItems: 1, + MaxItems: 1, + Required: true, + ForceNew: true, + Description: "Basic auth used to authenticate with the Opsgenie instance", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "username": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "password": { + Type: schema.TypeString, + Sensitive: true, + Required: true, + ForceNew: true, + }, + }, + }, + }, + }, + } +} + +func resourceOpsgenieDestinationCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*client.Client) + attrs := client.OpsgenieAttributes{ + Name: d.Get("destination_name").(string), + DestinationType: "opsgenie", + URL: d.Get("url").(string), + } + auth := d.Get("auth").([]interface{})[0].(map[string]interface{}) + attrs.Auth = client.Auth{ + Username: auth["username"].(string), + Password: auth["password"].(string), + } + dest := client.Destination{ + Type: "destination", + Attributes: attrs, + } + + destination, err := c.CreateDestination(ctx, d.Get("project_name").(string), dest) + if err != nil { + return diag.FromErr(fmt.Errorf("failed to create Opsgenie destination %v: %v", attrs.Name, err)) + } + + d.SetId(destination.ID) + return resourceDestinationRead(ctx, d, m) +} + +func resourceOpsgenieDestinationImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + c := m.(*client.Client) + + ids := strings.Split(d.Id(), ".") + if len(ids) != 2 { + return []*schema.ResourceData{}, fmt.Errorf("error importing lightstep_opsgenie_destination. Expecting an ID formed as '.'") + } + + project, id := ids[0], ids[1] + dest, err := c.GetDestination(ctx, project, id) + if err != nil { + return []*schema.ResourceData{}, fmt.Errorf("failed to get Opsgenie destination: %v", err) + } + + d.SetId(dest.ID) + if err := d.Set("project_name", project); err != nil { + return []*schema.ResourceData{}, fmt.Errorf("unable to set project_name resource field: %v", err) + } + + attributes := dest.Attributes.(map[string]interface{}) + if err := d.Set("destination_name", attributes["name"]); err != nil { + return []*schema.ResourceData{}, fmt.Errorf("unable to set destination_name resource field: %v", err) + } + + if err := d.Set("url", attributes["url"]); err != nil { + return []*schema.ResourceData{}, fmt.Errorf("unable to set url resource field: %v", err) + } + + if err := d.Set("auth", []interface{}{attributes["auth"]}); err != nil { + return []*schema.ResourceData{}, fmt.Errorf("unable to set auth resource field: %v", err) + } + + return []*schema.ResourceData{d}, nil +} diff --git a/lightstep/resource_opsgenie_destination_test.go b/lightstep/resource_opsgenie_destination_test.go new file mode 100644 index 00000000..093dce40 --- /dev/null +++ b/lightstep/resource_opsgenie_destination_test.go @@ -0,0 +1,150 @@ +package lightstep + +import ( + "context" + "fmt" + "regexp" + "testing" + + "github.com/lightstep/terraform-provider-lightstep/client" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccOpsgenieDestination(t *testing.T) { + var destination client.Destination + + missingUrlConfig := ` +resource "lightstep_opsgenie_destination" "missing_url" { + project_name = ` + fmt.Sprintf("\"%s\"", testProject) + ` + destination_name = "my-destination" + auth { + username = "" + password = "pass123" + } +} +` + missingAuthConfig := ` +resource "lightstep_opsgenie_destination" "missing_auth" { + project_name = ` + fmt.Sprintf("\"%s\"", testProject) + ` + destination_name = "my-destination" + url = "https://example.com" +} +` + + destinationConfig := ` +resource "lightstep_opsgenie_destination" "opsgenie" { + project_name = ` + fmt.Sprintf("\"%s\"", testProject) + ` + destination_name = "my-destination" + url = "https://example.com" + auth { + username = "" + password = "pass123" + } +} +` + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccOpsgenieDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: missingUrlConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckOpsgenieDestinationExists("lightstep_opsgenie_destination.missing_url", &destination), + ), + ExpectError: regexp.MustCompile("The argument \"url\" is required, but no definition was found."), + }, + { + Config: missingAuthConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckOpsgenieDestinationExists("lightstep_opsgenie_destination.missing_auth", &destination), + ), + ExpectError: regexp.MustCompile("Insufficient auth blocks"), + }, + { + Config: destinationConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckWebhookDestinationExists("lightstep_opsgenie_destination.opsgenie", &destination), + resource.TestCheckResourceAttr("lightstep_opsgenie_destination.opsgenie", "destination_name", "my-destination"), + resource.TestCheckResourceAttr("lightstep_opsgenie_destination.opsgenie", "url", "https://example.com"), + resource.TestCheckResourceAttr("lightstep_opsgenie_destination.opsgenie", "auth.0.username", ""), + resource.TestCheckResourceAttr("lightstep_opsgenie_destination.opsgenie", "auth.0.password", "pass123"), + ), + }, + }, + }) + +} + +func TestAccOpsgenieDestinationImport(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: ` +resource "lightstep_opsgenie_destination" "opsgenie" { + project_name = ` + fmt.Sprintf("\"%s\"", testProject) + ` + destination_name = "do-not-delete-opsgenie" + url = "https://example.com" + auth { + username = "" + password = "pass123" + } +} +`, + }, + { + ResourceName: "lightstep_opsgenie_destination.opsgenie", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"auth.0.password"}, + ImportStateIdPrefix: fmt.Sprintf("%s.", testProject), + }, + }, + }) +} + +func testAccCheckOpsgenieDestinationExists(resourceName string, destination *client.Destination) resource.TestCheckFunc { + return func(s *terraform.State) error { + // get destination from TF state + tfDestination, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + + if tfDestination.Primary.ID == "" { + return fmt.Errorf("id is not set") + } + + // get destination from LS + client := testAccProvider.Meta().(*client.Client) + d, err := client.GetDestination(context.Background(), testProject, tfDestination.Primary.ID) + if err != nil { + return err + } + + destination = d + return nil + } +} + +func testAccOpsgenieDestinationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*client.Client) + for _, resource := range s.RootModule().Resources { + if resource.Type != "lightstep_opsgenie_destination" { + continue + } + + s, err := conn.GetDestination(context.Background(), testProject, resource.Primary.ID) + if err == nil { + if s.ID == resource.Primary.ID { + return fmt.Errorf("destination with ID (%v) still exists.", resource.Primary.ID) + } + } + + } + return nil +}