From 24b6cf77ea11189dc9492a23d81700f659680096 Mon Sep 17 00:00:00 2001 From: Carwyn Nelson <3310215+CarwynNelson@users.noreply.github.com> Date: Mon, 15 Jul 2024 08:53:30 +0100 Subject: [PATCH] Status page groups (#109) --- .../resources/betteruptime_heartbeat_group.md | 5 +- docs/resources/betteruptime_monitor_group.md | 5 +- docs/resources/betteruptime_status_page.md | 1 + .../betteruptime_status_page_group.md | 33 ++++ examples/basic/main.tf | 6 + internal/provider/provider.go | 1 + internal/provider/resource_heartbeat_group.go | 3 +- internal/provider/resource_monitor_group.go | 3 +- internal/provider/resource_status_page.go | 8 + .../provider/resource_status_page_group.go | 150 ++++++++++++++++++ .../resource_status_page_group_test.go | 84 ++++++++++ 11 files changed, 293 insertions(+), 6 deletions(-) create mode 100644 docs/resources/betteruptime_status_page_group.md create mode 100644 internal/provider/resource_status_page_group.go create mode 100644 internal/provider/resource_status_page_group_test.go diff --git a/docs/resources/betteruptime_heartbeat_group.md b/docs/resources/betteruptime_heartbeat_group.md index 19a5b20..5111fd1 100644 --- a/docs/resources/betteruptime_heartbeat_group.md +++ b/docs/resources/betteruptime_heartbeat_group.md @@ -15,9 +15,12 @@ https://betterstack.com/docs/uptime/api/heartbeat-groups/ ## Schema -### Optional +### Required - **name** (String) A name of the group that you can see in the dashboard. + +### Optional + - **paused** (Boolean) Set to true to pause monitoring for any existing heartbeats in the group - we won't notify you about downtime. Set to false to resume monitoring for any existing heartbeats in the group. - **sort_index** (Number) Set sort_index to specify how to sort your heartbeat groups. - **team_name** (String) Used to specify the team the resource should be created in when using global tokens. diff --git a/docs/resources/betteruptime_monitor_group.md b/docs/resources/betteruptime_monitor_group.md index 494f1a2..ffa489a 100644 --- a/docs/resources/betteruptime_monitor_group.md +++ b/docs/resources/betteruptime_monitor_group.md @@ -15,9 +15,12 @@ https://betterstack.com/docs/uptime/api/monitor-groups/ ## Schema -### Optional +### Required - **name** (String) A name of the group that you can see in the dashboard. + +### Optional + - **paused** (Boolean) Set to true to pause monitoring for any existing monitors in the group - we won't notify you about downtime. Set to false to resume monitoring for any existing monitors in the group. - **sort_index** (Number) Set sort_index to specify how to sort your monitor groups. - **team_name** (String) Used to specify the team the resource should be created in when using global tokens. diff --git a/docs/resources/betteruptime_status_page.md b/docs/resources/betteruptime_status_page.md index f740424..986566a 100644 --- a/docs/resources/betteruptime_status_page.md +++ b/docs/resources/betteruptime_status_page.md @@ -42,6 +42,7 @@ https://betterstack.com/docs/uptime/api/status-pages/ - **min_incident_length** (Number) If you don't want to display short incidents on your status page, this attribute is for you. - **password** (String, Sensitive) Set a password of your status page (we won't store it as plaintext, promise). Required when password_enabled: true. We will set password_enabled: false automatically when you send us an empty password. - **password_enabled** (Boolean) Do you want to enable password protection on your status page? +- **status_page_group_id** (Number) Set this attribute if you want to add this status page to a status page group. - **subscribable** (Boolean) Do you want to allow users to subscribe to your status page changes? - **theme** (String) Choose theme of your status page. Only applicable when design: v2. Possible values: 'light', 'dark'. diff --git a/docs/resources/betteruptime_status_page_group.md b/docs/resources/betteruptime_status_page_group.md new file mode 100644 index 0000000..03dcb33 --- /dev/null +++ b/docs/resources/betteruptime_status_page_group.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "betteruptime_status_page_group Resource - terraform-provider-better-uptime" +subcategory: "" +description: |- + https://betterstack.com/docs/uptime/api/status-page-groups/ +--- + +# betteruptime_status_page_group (Resource) + +https://betterstack.com/docs/uptime/api/status-page-groups/ + + + + +## Schema + +### Required + +- **name** (String) A name of the group that you can see in the dashboard. + +### Optional + +- **sort_index** (Number) Set sort_index to specify how to sort your status page groups. +- **team_name** (String) Used to specify the team the resource should be created in when using global tokens. + +### Read-Only + +- **created_at** (String) The time when this status page group was created. +- **id** (String) The ID of this status page group. +- **updated_at** (String) The time when this status page group was updated. + + diff --git a/examples/basic/main.tf b/examples/basic/main.tf index c42e6a8..4f8db1a 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -7,11 +7,17 @@ resource "random_id" "status_page_subdomain" { prefix = "tf-status-" } +resource "betteruptime_status_page_group" "this" { + name = "Status pages from Terraform" +} + resource "betteruptime_status_page" "this" { company_name = "Example, Inc" company_url = "https://example.com" timezone = "UTC" subdomain = coalesce(var.betteruptime_status_page_subdomain, random_id.status_page_subdomain.hex) + + status_page_group_id = betteruptime_status_page_group.this.id } resource "betteruptime_monitor" "this" { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 4e710a0..cb56805 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -64,6 +64,7 @@ func New(opts ...Option) *schema.Provider { "betteruptime_policy": newPolicyResource(), "betteruptime_severity": newSeverityResource(), "betteruptime_status_page": newStatusPageResource(), + "betteruptime_status_page_group": newStatusPageGroupResource(), "betteruptime_status_page_section": newStatusPageSectionResource(), "betteruptime_status_page_resource": newStatusPageResourceResource(), "betteruptime_pagerduty_integration": newPagerdutyIntegrationResource(), diff --git a/internal/provider/resource_heartbeat_group.go b/internal/provider/resource_heartbeat_group.go index c09cc3f..fc450b2 100644 --- a/internal/provider/resource_heartbeat_group.go +++ b/internal/provider/resource_heartbeat_group.go @@ -35,8 +35,7 @@ var heartbeatGroupSchema = map[string]*schema.Schema{ "name": { Description: "A name of the group that you can see in the dashboard.", Type: schema.TypeString, - Optional: true, - Computed: true, + Required: true, }, "sort_index": { Description: "Set sort_index to specify how to sort your heartbeat groups.", diff --git a/internal/provider/resource_monitor_group.go b/internal/provider/resource_monitor_group.go index 0243018..c72ce10 100644 --- a/internal/provider/resource_monitor_group.go +++ b/internal/provider/resource_monitor_group.go @@ -35,8 +35,7 @@ var monitorGroupSchema = map[string]*schema.Schema{ "name": { Description: "A name of the group that you can see in the dashboard.", Type: schema.TypeString, - Optional: true, - Computed: true, + Required: true, }, "sort_index": { Description: "Set sort_index to specify how to sort your monitor groups.", diff --git a/internal/provider/resource_status_page.go b/internal/provider/resource_status_page.go index 4ee8266..525e2ce 100644 --- a/internal/provider/resource_status_page.go +++ b/internal/provider/resource_status_page.go @@ -184,6 +184,12 @@ var statusPageSchema = map[string]*schema.Schema{ Optional: true, Computed: true, }, + "status_page_group_id": { + Description: "Set this attribute if you want to add this status page to a status page group.", + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, } func newStatusPageResource() *schema.Resource { @@ -228,6 +234,7 @@ type statusPage struct { Theme *string `json:"theme,omitempty"` Layout *string `json:"layout,omitempty"` AutomaticReports *bool `json:"automatic_reports,omitempty"` + StatusPageGroupID *int `json:"status_page_group_id,omitempty"` } type statusPageHTTPResponse struct { @@ -273,6 +280,7 @@ func statusPageRef(in *statusPage) []struct { {k: "theme", v: &in.Theme}, {k: "layout", v: &in.Layout}, {k: "automatic_reports", v: &in.AutomaticReports}, + {k: "status_page_group_id", v: &in.StatusPageGroupID}, } } func statusPageCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { diff --git a/internal/provider/resource_status_page_group.go b/internal/provider/resource_status_page_group.go new file mode 100644 index 0000000..97e9c43 --- /dev/null +++ b/internal/provider/resource_status_page_group.go @@ -0,0 +1,150 @@ +package provider + +import ( + "context" + "fmt" + "net/url" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var statusPageGroupSchema = map[string]*schema.Schema{ + "team_name": { + Description: "Used to specify the team the resource should be created in when using global tokens.", + Type: schema.TypeString, + Optional: true, + Default: nil, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return d.Id() != "" + }, + }, + "id": { + Description: "The ID of this status page group.", + Type: schema.TypeString, + Optional: false, + Computed: true, + }, + "name": { + Description: "A name of the group that you can see in the dashboard.", + Type: schema.TypeString, + Required: true, + }, + "sort_index": { + Description: "Set sort_index to specify how to sort your status page groups.", + Type: schema.TypeInt, + Optional: true, + Computed: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return !d.HasChange(k) + }, + }, + "created_at": { + Description: "The time when this status page group was created.", + Type: schema.TypeString, + Optional: false, + Computed: true, + }, + "updated_at": { + Description: "The time when this status page group was updated.", + Type: schema.TypeString, + Optional: false, + Computed: true, + }, +} + +func newStatusPageGroupResource() *schema.Resource { + return &schema.Resource{ + CreateContext: statusPageGroupCreate, + ReadContext: statusPageGroupRead, + UpdateContext: statusPageGroupUpdate, + DeleteContext: statusPageGroupDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Description: "https://betterstack.com/docs/uptime/api/status-page-groups/", + Schema: statusPageGroupSchema, + } +} + +type statusPageGroup struct { + Name *string `json:"name,omitempty"` + SortIndex *int `json:"sort_index,omitempty"` + CreatedAt *string `json:"created_at,omitempty"` + UpdatedAt *string `json:"updated_at,omitempty"` + TeamName *string `json:"team_name,omitempty"` +} + +type statusPageGroupHTTPResponse struct { + Data struct { + ID string `json:"id"` + Attributes statusPageGroup `json:"attributes"` + } `json:"data"` +} + +func statusPageGroupRef(in *statusPageGroup) []struct { + k string + v interface{} +} { + // TODO: if reflect.TypeOf(in).NumField() != len([]struct) + return []struct { + k string + v interface{} + }{ + {k: "name", v: &in.Name}, + {k: "sort_index", v: &in.SortIndex}, + {k: "created_at", v: &in.CreatedAt}, + {k: "updated_at", v: &in.UpdatedAt}, + } +} + +func statusPageGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var in statusPageGroup + for _, e := range statusPageGroupRef(&in) { + load(d, e.k, e.v) + } + load(d, "team_name", &in.TeamName) + var out statusPageGroupHTTPResponse + if err := resourceCreate(ctx, meta, "/api/v2/status-page-groups", &in, &out); err != nil { + return err + } + d.SetId(out.Data.ID) + return statusPageGroupCopyAttrs(d, &out.Data.Attributes) +} + +func statusPageGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var out statusPageGroupHTTPResponse + if err, ok := resourceRead(ctx, meta, fmt.Sprintf("/api/v2/status-page-groups/%s", url.PathEscape(d.Id())), &out); err != nil { + return err + } else if !ok { + d.SetId("") // Force "create" on 404. + return nil + } + return statusPageGroupCopyAttrs(d, &out.Data.Attributes) +} + +func statusPageGroupCopyAttrs(d *schema.ResourceData, in *statusPageGroup) diag.Diagnostics { + var derr diag.Diagnostics + for _, e := range statusPageGroupRef(in) { + if err := d.Set(e.k, reflect.Indirect(reflect.ValueOf(e.v)).Interface()); err != nil { + derr = append(derr, diag.FromErr(err)[0]) + } + } + return derr +} + +func statusPageGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var in statusPageGroup + var out policyHTTPResponse + for _, e := range statusPageGroupRef(&in) { + if d.HasChange(e.k) { + load(d, e.k, e.v) + } + } + return resourceUpdate(ctx, meta, fmt.Sprintf("/api/v2/status-page-groups/%s", url.PathEscape(d.Id())), &in, &out) +} + +func statusPageGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return resourceDelete(ctx, meta, fmt.Sprintf("/api/v2/status-page-groups/%s", url.PathEscape(d.Id()))) +} diff --git a/internal/provider/resource_status_page_group_test.go b/internal/provider/resource_status_page_group_test.go new file mode 100644 index 0000000..6eb74c8 --- /dev/null +++ b/internal/provider/resource_status_page_group_test.go @@ -0,0 +1,84 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestResourceStatusPageGroup(t *testing.T) { + server := newResourceServer(t, "/api/v2/status-page-groups", "1") + defer server.Close() + + var name = "example" + + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "betteruptime": func() (*schema.Provider, error) { + return New(WithURL(server.URL)), nil + }, + }, + Steps: []resource.TestStep{ + // Step 1 - create. + { + Config: fmt.Sprintf(` + provider "betteruptime" { + api_token = "foo" + } + + resource "betteruptime_status_page_group" "this" { + name = "%s" + sort_index = 1 + } + `, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("betteruptime_status_page_group.this", "id"), + resource.TestCheckResourceAttr("betteruptime_status_page_group.this", "name", name), + resource.TestCheckResourceAttr("betteruptime_status_page_group.this", "sort_index", "1"), + ), + }, + // Step 2 - update. + { + Config: fmt.Sprintf(` + provider "betteruptime" { + api_token = "foo" + } + + resource "betteruptime_status_page_group" "this" { + name = "%s" + sort_index = 0 + } + `, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("betteruptime_status_page_group.this", "id"), + resource.TestCheckResourceAttr("betteruptime_status_page_group.this", "name", name), + resource.TestCheckResourceAttr("betteruptime_status_page_group.this", "sort_index", "0"), + ), + }, + // Step 3 - make no changes, check plan is empty. + { + Config: fmt.Sprintf(` + provider "betteruptime" { + api_token = "foo" + } + + resource "betteruptime_status_page_group" "this" { + name = "%s" + sort_index = 0 + } + `, name), + PlanOnly: true, + }, + // Step 4 - destroy. + { + ResourceName: "betteruptime_status_page_group.this", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +}