Skip to content

Commit

Permalink
U-1159 On-call calendar data source (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrHeinz authored May 3, 2024
1 parent 21d23e4 commit 0ea73b7
Showing 5 changed files with 438 additions and 0 deletions.
39 changes: 39 additions & 0 deletions docs/data-sources/betteruptime_on_call_calendar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "betteruptime_on_call_calendar Data Source - terraform-provider-better-uptime"
subcategory: ""
description: |-
On-call calendar lookup.
---

# betteruptime_on_call_calendar (Data Source)

On-call calendar lookup.



<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- **name** (String) Name of the on-call calendar.

### Read-Only

- **default_calendar** (Boolean) Whether the on-call calendar is the default on-call calendar.
- **id** (String) The ID of the on-call calendar.
- **on_call_users** (List of Object) Array of on-call persons. (see [below for nested schema](#nestedatt--on_call_users))

<a id="nestedatt--on_call_users"></a>
### Nested Schema for `on_call_users`

Read-Only:

- **email** (String)
- **first_name** (String)
- **id** (String)
- **last_name** (String)
- **phone_numbers** (List of String)


148 changes: 148 additions & 0 deletions internal/provider/data_on_call_calendar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package provider

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func newOnCallCalendarDataSource() *schema.Resource {
s := make(map[string]*schema.Schema)
for k, v := range onCallCalendarSchema {
cp := *v
switch k {
case "name":
cp.Required = false
cp.Optional = true
cp.Computed = false
default:
cp.Computed = true
cp.Optional = false
cp.Required = false
cp.ValidateDiagFunc = nil
cp.Default = nil
cp.DefaultFunc = nil
cp.DiffSuppressFunc = nil
}
s[k] = &cp
}
return &schema.Resource{
ReadContext: onCallCalendarDefaultOrLookup,
Description: "On-call calendar lookup.",
Schema: s,
}
}

func onCallCalendarDefaultOrLookup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if name, ok := d.Get("name").(string); ok && name != "" {
return onCallCalendarLookup(ctx, d, meta)
}
return onCallDefaultCalendar(ctx, d, meta)
}

type onCallDefaultCalendarHTTPResponse struct {
Data struct {
ID string `json:"id"`
Attributes onCallCalendar `json:"attributes"`
Relationships onCallRelationships `json:"relationships"`
} `json:"data"`
Included []onCallIncluded `json:"included"`
}

func onCallDefaultCalendar(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
fetch := func() (*onCallDefaultCalendarHTTPResponse, error) {
res, err := meta.(*client).Get(ctx, "/api/v2/on-calls/default")
if err != nil {
return nil, err
}
defer func() {
// Keep-Alive.
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
body, err := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("GET %s returned %d: %s", res.Request.URL.String(), res.StatusCode, string(body))
}
if err != nil {
return nil, err
}
var tr onCallDefaultCalendarHTTPResponse
return &tr, json.Unmarshal(body, &tr)
}
res, err := fetch()
if err != nil {
return diag.FromErr(err)
}
d.SetId(res.Data.ID)
if derr := onCallCalendarCopyAttrs(d, &res.Data.Attributes, res.Data.Relationships, res.Included); derr != nil {
return derr
}
return nil
}

type onCallCalendarsPageHTTPResponse struct {
Data []struct {
ID string `json:"id"`
Attributes onCallCalendar `json:"attributes"`
Relationships onCallRelationships `json:"relationships"`
} `json:"data"`
Included []onCallIncluded `json:"included"`
Pagination struct {
First string `json:"first"`
Last string `json:"last"`
Prev string `json:"prev"`
Next string `json:"next"`
} `json:"pagination"`
}

func onCallCalendarLookup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
fetch := func(page int) (*onCallCalendarsPageHTTPResponse, error) {
res, err := meta.(*client).Get(ctx, fmt.Sprintf("/api/v2/on-calls?page=%d", page))
if err != nil {
return nil, err
}
defer func() {
// Keep-Alive.
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
body, err := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("GET %s returned %d: %s", res.Request.URL.String(), res.StatusCode, string(body))
}
if err != nil {
return nil, err
}
var tr onCallCalendarsPageHTTPResponse
return &tr, json.Unmarshal(body, &tr)
}
calendarName := d.Get("name").(string)
page := 1
for {
res, err := fetch(page)
if err != nil {
return diag.FromErr(err)
}
for _, e := range res.Data {
if *e.Attributes.Name == calendarName {
if d.Id() != "" {
return diag.Errorf("There are multiple on-call calendars with the same name: %s", calendarName)
}
d.SetId(e.ID)
if derr := onCallCalendarCopyAttrs(d, &e.Attributes, e.Relationships, res.Included); derr != nil {
return derr
}
}
}
page++
if res.Pagination.Next == "" {
return nil
}
}
}
115 changes: 115 additions & 0 deletions internal/provider/data_on_call_calendar_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package provider

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func TestOnCallCalendarData(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Log("Received " + r.Method + " " + r.RequestURI)

if r.Header.Get("Authorization") != "Bearer foo" {
t.Fatal("Not authorized: " + r.Header.Get("Authorization"))
}

prefix := "/api/v2/on-calls"

switch {
case r.Method == http.MethodGet && r.RequestURI == prefix+"?page=1":
_, _ = w.Write([]byte(`{"data":[{"id":"123","attributes":{"name":"Primary","default_calendar":true},"relationships":{"on_call_users":{"data":[{"id":"123456","type":"user"}]}}}],"included":[{"id":"123456","type":"user","attributes":{"first_name":"John","last_name":"Smith","email":"[email protected]","phone_numbers":[]}}],"pagination":{"next":"..."}}`))
case r.Method == http.MethodGet && r.RequestURI == prefix+"?page=2":
_, _ = w.Write([]byte(`{"data":[{"id":"456","attributes":{"name":"Secondary","default_calendar":false},"relationships":{"on_call_users":{"data":[{"id":"456789","type":"user"}]}}}],"included":[{"id":"456789","type":"user","attributes":{"first_name":"Jane","last_name":"Doe","email":"[email protected]","phone_numbers":["+44 808 157 0192"]}}],"pagination":{"next":null}}`))
default:
t.Fatal("Unexpected " + r.Method + " " + r.RequestURI)
}
}))
defer server.Close()

var calendarName = "Secondary"

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{
{
Config: fmt.Sprintf(`
provider "betteruptime" {
api_token = "foo"
}
data "betteruptime_on_call_calendar" "this" {
name = "%s"
}
`, calendarName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("data.betteruptime_on_call_calendar.this", "id"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "name", calendarName),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "default_calendar", "false"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "on_call_users.0.id", "456789"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "on_call_users.0.first_name", "Jane"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "on_call_users.0.last_name", "Doe"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "on_call_users.0.email", "[email protected]"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "on_call_users.0.phone_numbers.0", "+44 808 157 0192"),
),
},
},
})
}

func TestDefaultOnCallCalendarData(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Log("Received " + r.Method + " " + r.RequestURI)

if r.Header.Get("Authorization") != "Bearer foo" {
t.Fatal("Not authorized: " + r.Header.Get("Authorization"))
}

switch {
case r.Method == http.MethodGet && r.RequestURI == "/api/v2/on-calls/default":
_, _ = w.Write([]byte(`{"data":{"id":"123","attributes":{"name":"Primary","default_calendar":true},"relationships":{"on_call_users":{"data":[{"id":"123456","type":"user"}]}}},"included":[{"id":"123456","type":"user","attributes":{"first_name":"John","last_name":"Smith","email":"[email protected]","phone_numbers":[]}}]}`))
default:
t.Fatal("Unexpected " + r.Method + " " + r.RequestURI)
}
}))
defer server.Close()

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{
{
Config: `
provider "betteruptime" {
api_token = "foo"
}
data "betteruptime_on_call_calendar" "this" {
}
`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("data.betteruptime_on_call_calendar.this", "id"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "name", "Primary"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "default_calendar", "true"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "on_call_users.0.id", "123456"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "on_call_users.0.first_name", "John"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "on_call_users.0.last_name", "Smith"),
resource.TestCheckResourceAttr("data.betteruptime_on_call_calendar.this", "on_call_users.0.email", "[email protected]"),
),
},
},
})
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@ func New(opts ...Option) *schema.Provider {
},
DataSourcesMap: map[string]*schema.Resource{
"betteruptime_monitor": newMonitorDataSource(),
"betteruptime_on_call_calendar": newOnCallCalendarDataSource(),
"betteruptime_policy": newPolicyDataSource(),
"betteruptime_severity": newSeverityDataSource(),
"betteruptime_slack_integration": newSlackIntegrationDataSource(),
Loading

0 comments on commit 0ea73b7

Please sign in to comment.