Skip to content

Commit f532c68

Browse files
committed
Add StatusCake provider code
1 parent 1c33eb2 commit f532c68

14 files changed

+4276
-2
lines changed

go.mod

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,49 @@
11
module github.com/StatusCakeDev/terraform-provider-statuscake
22

33
go 1.17
4+
5+
require (
6+
github.com/StatusCakeDev/statuscake-go v1.0.0-beta.2
7+
github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1
8+
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
9+
)
10+
11+
require (
12+
github.com/agext/levenshtein v1.2.3 // indirect
13+
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
14+
github.com/fatih/color v1.13.0 // indirect
15+
github.com/golang/protobuf v1.5.2 // indirect
16+
github.com/google/go-cmp v0.5.7 // indirect
17+
github.com/hashicorp/errwrap v1.1.0 // indirect
18+
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
19+
github.com/hashicorp/go-hclog v1.1.0 // indirect
20+
github.com/hashicorp/go-multierror v1.1.1 // indirect
21+
github.com/hashicorp/go-plugin v1.4.3 // indirect
22+
github.com/hashicorp/go-uuid v1.0.2 // indirect
23+
github.com/hashicorp/go-version v1.4.0 // indirect
24+
github.com/hashicorp/hcl/v2 v2.11.1 // indirect
25+
github.com/hashicorp/terraform-plugin-go v0.7.0 // indirect
26+
github.com/hashicorp/terraform-plugin-log v0.2.1 // indirect
27+
github.com/hashicorp/terraform-registry-address v0.0.0-20210816115301-cb2034eba045 // indirect
28+
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
29+
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
30+
github.com/mattn/go-colorable v0.1.12 // indirect
31+
github.com/mattn/go-isatty v0.0.14 // indirect
32+
github.com/mitchellh/copystructure v1.2.0 // indirect
33+
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
34+
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
35+
github.com/mitchellh/mapstructure v1.4.3 // indirect
36+
github.com/mitchellh/reflectwalk v1.0.2 // indirect
37+
github.com/oklog/run v1.1.0 // indirect
38+
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
39+
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
40+
github.com/vmihailenco/tagparser v0.1.2 // indirect
41+
github.com/zclconf/go-cty v1.10.0 // indirect
42+
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba // indirect
43+
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
44+
golang.org/x/text v0.3.7 // indirect
45+
google.golang.org/appengine v1.6.7 // indirect
46+
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect
47+
google.golang.org/grpc v1.43.0 // indirect
48+
google.golang.org/protobuf v1.27.1 // indirect
49+
)

go.sum

Lines changed: 784 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
"time"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
12+
13+
statuscake "github.com/StatusCakeDev/statuscake-go"
14+
)
15+
16+
type monitoringLocationsFunc func(context.Context, *statuscake.Client, string) (statuscake.MonitoringLocations, error)
17+
18+
func dataSourceStatusCakeMonitoringLocations(fn monitoringLocationsFunc) *schema.Resource {
19+
return &schema.Resource{
20+
ReadContext: dataSourceStatusCakeUptimeLocationsRead(fn),
21+
22+
Schema: map[string]*schema.Schema{
23+
"region_code": &schema.Schema{
24+
Type: schema.TypeString,
25+
Optional: true,
26+
Description: "Location region code",
27+
ValidateFunc: validation.StringIsNotEmpty,
28+
},
29+
"locations": &schema.Schema{
30+
Type: schema.TypeList,
31+
Computed: true,
32+
Description: "List of monitoring locations",
33+
Elem: &schema.Resource{
34+
Schema: locationSchema(),
35+
},
36+
},
37+
},
38+
}
39+
}
40+
41+
// locationsSchema returns the schema describing a single monitoring locations.
42+
// Since locations features within multiple resources its structure has been
43+
// encapsulated within a function.
44+
func locationSchema() map[string]*schema.Schema {
45+
return map[string]*schema.Schema{
46+
"description": &schema.Schema{
47+
Type: schema.TypeString,
48+
Computed: true,
49+
Description: "Location description",
50+
},
51+
"ipv4": &schema.Schema{
52+
Type: schema.TypeString,
53+
Computed: true,
54+
Description: "Location IPv4 address",
55+
},
56+
"ipv6": &schema.Schema{
57+
Type: schema.TypeString,
58+
Computed: true,
59+
Description: "Location IPv6 address",
60+
},
61+
"region": &schema.Schema{
62+
Type: schema.TypeString,
63+
Computed: true,
64+
Description: "Location region",
65+
},
66+
"region_code": &schema.Schema{
67+
Type: schema.TypeString,
68+
Computed: true,
69+
Description: "Location region code",
70+
},
71+
"status": &schema.Schema{
72+
Type: schema.TypeString,
73+
Computed: true,
74+
Description: "Location status",
75+
},
76+
}
77+
}
78+
79+
func dataSourceStatusCakeUptimeLocationsRead(fn monitoringLocationsFunc) schema.ReadContextFunc {
80+
return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
81+
client := meta.(*statuscake.Client)
82+
83+
res, err := fn(ctx, client, d.Get("region_code").(string))
84+
if err != nil {
85+
return diag.FromErr(fmt.Errorf("failed to list monitoring locations: %w", err))
86+
}
87+
88+
if err := d.Set("locations", flattenMonitoringLocations(res.Data, d)); err != nil {
89+
return diag.FromErr(fmt.Errorf("error setting monitoring locations: %w", err))
90+
}
91+
92+
d.SetId(strconv.FormatInt(time.Now().Unix(), 10))
93+
94+
return nil
95+
}
96+
}
97+
98+
func listUptimeMonitoringLocations(ctx context.Context, client *statuscake.Client, location string) (statuscake.MonitoringLocations, error) {
99+
req := client.ListUptimeMonitoringLocations(ctx)
100+
101+
if len(location) != 0 {
102+
req = req.Location(location)
103+
}
104+
105+
return req.Execute()
106+
}
107+
108+
func listPagespeedMonitoringLocations(ctx context.Context, client *statuscake.Client, location string) (statuscake.MonitoringLocations, error) {
109+
req := client.ListPagespeedMonitoringLocations(ctx)
110+
111+
if len(location) != 0 {
112+
req = req.Location(location)
113+
}
114+
115+
return req.Execute()
116+
}
117+
118+
func flattenMonitoringLocationsDescription(v interface{}, d *schema.ResourceData) interface{} {
119+
return v
120+
}
121+
122+
func flattenMonitoringLocationsIPv4(v interface{}, d *schema.ResourceData) interface{} {
123+
return v
124+
}
125+
126+
func flattenMonitoringLocationsIPv6(v interface{}, d *schema.ResourceData) interface{} {
127+
return v
128+
}
129+
130+
func flattenMonitoringLocations(v interface{}, d *schema.ResourceData) interface{} {
131+
data := v.([]statuscake.MonitoringLocation)
132+
133+
locations := make([]interface{}, len(data))
134+
for idx, location := range data {
135+
locations[idx] = flattenMonitoringLocation(location, d)
136+
}
137+
138+
return locations
139+
}
140+
141+
func flattenMonitoringLocation(v interface{}, d *schema.ResourceData) interface{} {
142+
data := v.(statuscake.MonitoringLocation)
143+
144+
return map[string]interface{}{
145+
"description": flattenMonitoringLocationsDescription(data.Description, d),
146+
"ipv4": flattenMonitoringLocationsIPv4(data.IPv4, d),
147+
"ipv6": flattenMonitoringLocationsIPv6(data.IPv6, d),
148+
"region": flattenMonitoringLocationsRegion(data.Region, d),
149+
"region_code": flattenMonitoringLocationsRegionCode(data.RegionCode, d),
150+
"status": flattenMonitoringLocationsStatus(data.Status, d),
151+
}
152+
}
153+
154+
func flattenMonitoringLocationsRegion(v interface{}, d *schema.ResourceData) interface{} {
155+
return v
156+
}
157+
158+
func flattenMonitoringLocationsRegionCode(v interface{}, d *schema.ResourceData) interface{} {
159+
return v
160+
}
161+
162+
func flattenMonitoringLocationsStatus(v interface{}, d *schema.ResourceData) interface{} {
163+
return v
164+
}

internal/provider/provider.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"errors"
6+
"net/http"
7+
"regexp"
8+
"runtime"
9+
"time"
10+
11+
"golang.org/x/time/rate"
12+
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
14+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
15+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
16+
17+
statuscake "github.com/StatusCakeDev/statuscake-go"
18+
"github.com/StatusCakeDev/statuscake-go/backoff"
19+
"github.com/StatusCakeDev/statuscake-go/credentials"
20+
"github.com/StatusCakeDev/statuscake-go/throttle"
21+
)
22+
23+
// Provider returns a resource provider for Terraform.
24+
func Provider() *schema.Provider {
25+
return &schema.Provider{
26+
Schema: map[string]*schema.Schema{
27+
"api_token": &schema.Schema{
28+
Type: schema.TypeString,
29+
Required: true,
30+
DefaultFunc: schema.EnvDefaultFunc("STATUSCAKE_API_TOKEN", nil),
31+
Description: "The API token for operations",
32+
ValidateFunc: validation.StringMatch(regexp.MustCompile("[0-9a-zA-Z_]{20,30}"), "API token must only contain characters 0-9, a-zA-Z and underscores"),
33+
},
34+
"rps": &schema.Schema{
35+
Type: schema.TypeInt,
36+
Optional: true,
37+
DefaultFunc: schema.EnvDefaultFunc("STATUSCAKE_RPS", 4),
38+
Description: "RPS limit to apply when making calls to the API",
39+
ValidateFunc: validation.IntAtLeast(1),
40+
},
41+
"retries": &schema.Schema{
42+
Type: schema.TypeInt,
43+
Optional: true,
44+
DefaultFunc: schema.EnvDefaultFunc("STATUSCAKE_RETRIES", 3),
45+
Description: "Maximum number of retries to perform when an API request fails",
46+
ValidateFunc: validation.IntBetween(0, 10),
47+
},
48+
"min_backoff": &schema.Schema{
49+
Type: schema.TypeInt,
50+
Optional: true,
51+
DefaultFunc: schema.EnvDefaultFunc("STATUSCAKE_MIN_BACKOFF", 1),
52+
Description: "Minimum backoff period in seconds after failed API calls",
53+
ValidateFunc: validation.IntAtLeast(0),
54+
},
55+
"max_backoff": &schema.Schema{
56+
Type: schema.TypeInt,
57+
Optional: true,
58+
DefaultFunc: schema.EnvDefaultFunc("STATUSCAKE_MAX_BACKOFF", 30),
59+
Description: "Maximum backoff period in seconds after failed API calls",
60+
ValidateFunc: validation.IntAtLeast(1),
61+
},
62+
"statuscake_custom_endpoint": &schema.Schema{
63+
Type: schema.TypeString,
64+
Optional: true,
65+
DefaultFunc: schema.EnvDefaultFunc("STATUCAKE_CUSTOM_ENDPOINT", nil),
66+
Description: "Custom endpoint to which request will be made",
67+
ValidateFunc: validation.IsURLWithHTTPorHTTPS,
68+
},
69+
},
70+
ResourcesMap: map[string]*schema.Resource{
71+
"statuscake_contact_group": resourceStatusCakeContactGroup(),
72+
"statuscake_maintenance_window": resourceStatusCakeMaintenanceWindow(),
73+
"statuscake_pagespeed_check": resourceStatusCakePagespeedCheck(),
74+
"statuscake_ssl_check": resourceStatusCakeSSLCheck(),
75+
"statuscake_uptime_check": resourceStatusCakeUptimeCheck(),
76+
},
77+
DataSourcesMap: map[string]*schema.Resource{
78+
"statuscake_pagespeed_monitoring_locations": dataSourceStatusCakeMonitoringLocations(listPagespeedMonitoringLocations),
79+
"statuscake_uptime_monitoring_locations": dataSourceStatusCakeMonitoringLocations(listUptimeMonitoringLocations),
80+
},
81+
ConfigureContextFunc: providerConfigure,
82+
}
83+
}
84+
85+
// providerConfigure parses the config into the Terraform provider meta object.
86+
func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
87+
apiToken, ok := d.GetOk("api_token")
88+
if !ok {
89+
return nil, diag.FromErr(errors.New("credentials are not set correctly"))
90+
}
91+
92+
bearer := credentials.NewBearerWithStaticToken(apiToken.(string))
93+
opts := []statuscake.Option{
94+
statuscake.WithBackoff(backoff.Exponential{
95+
Config: backoff.Config{
96+
BaseDelay: time.Duration(d.Get("min_backoff").(int)) * time.Second,
97+
Multiplier: 2.0,
98+
Jitter: 0.2,
99+
MaxDelay: time.Duration(d.Get("max_backoff").(int)) * time.Second,
100+
},
101+
}),
102+
statuscake.WithHTTPClient(&http.Client{
103+
Transport: throttle.NewWithDefaultTransport(
104+
rate.NewLimiter(rate.Limit(d.Get("rps").(int)), 1),
105+
),
106+
}),
107+
statuscake.WithMaxRetries(d.Get("retries").(int)),
108+
statuscake.WithRequestCredentials(bearer),
109+
statuscake.WithUserAgent("terraform-provider-statuscake/" + runtime.Version()),
110+
}
111+
112+
if customEndpoint, ok := d.GetOk("statuscake_custom_endpoint"); ok {
113+
opts = append(opts, statuscake.WithHost(customEndpoint.(string)))
114+
}
115+
116+
return statuscake.NewClient(opts...), nil
117+
}

internal/provider/provider_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package provider_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
7+
8+
"github.com/StatusCakeDev/terraform-provider-statuscake/internal/provider"
9+
)
10+
11+
var testProviders = map[string]*schema.Provider{
12+
"statuscake": provider.Provider(),
13+
}
14+
15+
func TestProvider(t *testing.T) {
16+
if err := testProviders["statuscake"].InternalValidate(); err != nil {
17+
t.Errorf("failed to validate provider: %+v", err)
18+
}
19+
}

0 commit comments

Comments
 (0)