Skip to content

Commit 24d52fa

Browse files
authored
feat: add support for status pages [sc-23757] (#309)
* feat: add support for status pages * feat: add tests for required arguments * feat: add tests for status pages and services Fixes default_theme's default value so that plans are clean. * feat: update sdk to v1.11.0 which includes status page support * fix: custom domains don't allow example domains * feat: add descriptions for status pages and services
1 parent c213e3d commit 24d52fa

File tree

11 files changed

+778
-3
lines changed

11 files changed

+778
-3
lines changed

checkly/provider.go

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ func Provider() *schema.Provider {
4444
"checkly_environment_variable": resourceEnvironmentVariable(),
4545
"checkly_private_location": resourcePrivateLocation(),
4646
"checkly_client_certificate": resourceClientCertificate(),
47+
"checkly_status_page": resourceStatusPage(),
48+
"checkly_status_page_service": resourceStatusPageService(),
4749
},
4850
DataSourcesMap: map[string]*schema.Resource{
4951
"checkly_static_ips": dataSourceStaticIPs(),

checkly/resource_status_page.go

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
package checkly
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
10+
checkly "github.com/checkly/checkly-go-sdk"
11+
)
12+
13+
func resourceStatusPage() *schema.Resource {
14+
return &schema.Resource{
15+
Create: resourceStatusPageCreate,
16+
Read: resourceStatusPageRead,
17+
Update: resourceStatusPageUpdate,
18+
Delete: resourceStatusPageDelete,
19+
Importer: &schema.ResourceImporter{
20+
StateContext: schema.ImportStatePassthroughContext,
21+
},
22+
Description: "Checkly status pages allow you to easily communicate " +
23+
"the uptime and health of your applications and services to your customers.",
24+
Schema: map[string]*schema.Schema{
25+
"name": {
26+
Type: schema.TypeString,
27+
Required: true,
28+
Description: "The name of the status page.",
29+
},
30+
"url": {
31+
Type: schema.TypeString,
32+
Required: true,
33+
Description: "The URL of the status page.",
34+
},
35+
"custom_domain": {
36+
Type: schema.TypeString,
37+
Optional: true,
38+
Description: "A custom user domain, e.g. \"status.example.com\". See the docs on updating your DNS and SSL usage.",
39+
ValidateFunc: func(value interface{}, key string) (warns []string, errs []error) {
40+
unsupportedSuffixes := []string{
41+
"example.com",
42+
"example.net",
43+
"example.org",
44+
}
45+
v := value.(string)
46+
for _, suffix := range unsupportedSuffixes {
47+
if strings.HasSuffix(strings.ToLower(v), suffix) || strings.EqualFold(v, suffix) {
48+
errs = append(errs, fmt.Errorf("custom domains ending in %s are not supported", suffix))
49+
break
50+
}
51+
}
52+
return warns, errs
53+
},
54+
},
55+
"logo": {
56+
Type: schema.TypeString,
57+
Optional: true,
58+
Description: "A URL to an image file to use as the logo for the status page.",
59+
},
60+
"redirect_to": {
61+
Type: schema.TypeString,
62+
Optional: true,
63+
Description: "The URL the user should be redirected to when clicking the logo.",
64+
},
65+
"favicon": {
66+
Type: schema.TypeString,
67+
Optional: true,
68+
Description: "A URL to an image file to use as the favicon of the status page.",
69+
},
70+
"default_theme": {
71+
Type: schema.TypeString,
72+
Optional: true,
73+
Description: "Possible values are `AUTO`, `DARK`, and `LIGHT`. (Default `AUTO`).",
74+
Default: "AUTO",
75+
ValidateFunc: func(value interface{}, key string) (warns []string, errs []error) {
76+
v := value.(string)
77+
isValid := false
78+
options := []string{"AUTO", "DARK", "LIGHT"}
79+
for _, option := range options {
80+
if v == option {
81+
isValid = true
82+
}
83+
}
84+
if !isValid {
85+
errs = append(errs, fmt.Errorf("%q must be one of %v, got %s", key, options, v))
86+
}
87+
return warns, errs
88+
},
89+
},
90+
"card": {
91+
Type: schema.TypeList,
92+
Required: true,
93+
MinItems: 1,
94+
Description: "A list of cards to include on the status page.",
95+
Elem: &schema.Resource{
96+
Schema: map[string]*schema.Schema{
97+
"name": {
98+
Type: schema.TypeString,
99+
Required: true,
100+
Description: "The name of the card.",
101+
},
102+
"service_attachment": {
103+
Type: schema.TypeList,
104+
Required: true,
105+
MinItems: 1,
106+
Description: "A list of services to attach to the card.",
107+
Elem: &schema.Resource{
108+
Schema: map[string]*schema.Schema{
109+
"service_id": {
110+
Type: schema.TypeString,
111+
Required: true,
112+
Description: "The ID of the service.",
113+
},
114+
},
115+
},
116+
},
117+
},
118+
},
119+
},
120+
},
121+
}
122+
}
123+
124+
func resourceStatusPageCreate(d *schema.ResourceData, client interface{}) error {
125+
statusPage, err := statusPageFromResourceData(d)
126+
if err != nil {
127+
return fmt.Errorf("resourceStatusPageCreate: translation error: %w", err)
128+
}
129+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
130+
defer cancel()
131+
result, err := client.(checkly.Client).CreateStatusPage(ctx, statusPage)
132+
if err != nil {
133+
return fmt.Errorf("CreateStatusPage: API error: %w", err)
134+
}
135+
d.SetId(result.ID)
136+
return resourceStatusPageRead(d, client)
137+
}
138+
139+
func statusPageFromResourceData(d *schema.ResourceData) (checkly.StatusPage, error) {
140+
return checkly.StatusPage{
141+
ID: d.Id(),
142+
Name: d.Get("name").(string),
143+
URL: d.Get("url").(string),
144+
CustomDomain: d.Get("custom_domain").(string),
145+
Logo: d.Get("logo").(string),
146+
RedirectTo: d.Get("redirect_to").(string),
147+
Favicon: d.Get("favicon").(string),
148+
DefaultTheme: checkly.StatusPageTheme(d.Get("default_theme").(string)),
149+
Cards: statusPageCardsFromList(d.Get("card").([]interface{})),
150+
}, nil
151+
}
152+
153+
func statusPageCardsFromList(l []interface{}) []checkly.StatusPageCard {
154+
res := []checkly.StatusPageCard{}
155+
if len(l) == 0 {
156+
return res
157+
}
158+
for _, it := range l {
159+
tm := it.(tfMap)
160+
name := tm["name"].(string)
161+
serviceAttachments := statusPageCardServiceAttachmentsFromList(tm["service_attachment"].([]interface{}))
162+
res = append(res, checkly.StatusPageCard{
163+
Name: name,
164+
Services: serviceAttachments,
165+
})
166+
}
167+
return res
168+
}
169+
170+
func listFromStatusPageCards(cards []checkly.StatusPageCard) []tfMap {
171+
result := make([]tfMap, 0, len(cards))
172+
173+
for _, card := range cards {
174+
result = append(result, tfMap{
175+
"name": card.Name,
176+
"service_attachment": listFromStatusPageCardServiceAttachments(card.Services),
177+
})
178+
}
179+
180+
return result
181+
}
182+
183+
func statusPageCardServiceAttachmentsFromList(l []interface{}) []checkly.StatusPageService {
184+
res := []checkly.StatusPageService{}
185+
if len(l) == 0 {
186+
return res
187+
}
188+
for _, it := range l {
189+
tm := it.(tfMap)
190+
id := tm["service_id"].(string)
191+
res = append(res, checkly.StatusPageService{
192+
ID: id,
193+
})
194+
}
195+
return res
196+
}
197+
198+
func listFromStatusPageCardServiceAttachments(attachments []checkly.StatusPageService) []tfMap {
199+
result := make([]tfMap, 0, len(attachments))
200+
201+
for _, attachment := range attachments {
202+
result = append(result, tfMap{
203+
"service_id": attachment.ID,
204+
})
205+
}
206+
207+
return result
208+
}
209+
210+
func resourceDataFromStatusPage(p *checkly.StatusPage, d *schema.ResourceData) error {
211+
d.Set("name", p.Name)
212+
d.Set("url", p.URL)
213+
d.Set("custom_domain", p.CustomDomain)
214+
d.Set("logo", p.Logo)
215+
d.Set("redirect_to", p.RedirectTo)
216+
d.Set("favicon", p.Favicon)
217+
d.Set("default_theme", p.DefaultTheme)
218+
d.Set("card", listFromStatusPageCards(p.Cards))
219+
return nil
220+
}
221+
222+
func resourceStatusPageRead(d *schema.ResourceData, client interface{}) error {
223+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
224+
defer cancel()
225+
statusPage, err := client.(checkly.Client).GetStatusPage(ctx, d.Id())
226+
if err != nil {
227+
if strings.Contains(err.Error(), "404") {
228+
//if resource is deleted remotely, then mark it as
229+
//successfully gone by unsetting it's ID
230+
d.SetId("")
231+
return nil
232+
}
233+
return fmt.Errorf("resourceStatusPageRead: API error: %w", err)
234+
}
235+
return resourceDataFromStatusPage(statusPage, d)
236+
}
237+
238+
func resourceStatusPageUpdate(d *schema.ResourceData, client interface{}) error {
239+
statusPage, err := statusPageFromResourceData(d)
240+
if err != nil {
241+
return fmt.Errorf("resourceStatusPageUpdate: translation error: %w", err)
242+
}
243+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
244+
defer cancel()
245+
_, err = client.(checkly.Client).UpdateStatusPage(ctx, statusPage.ID, statusPage)
246+
if err != nil {
247+
return fmt.Errorf("resourceStatusPageUpdate: API error: %w", err)
248+
}
249+
d.SetId(statusPage.ID)
250+
return resourceStatusPageRead(d, client)
251+
}
252+
253+
func resourceStatusPageDelete(d *schema.ResourceData, client interface{}) error {
254+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
255+
defer cancel()
256+
err := client.(checkly.Client).DeleteStatusPage(ctx, d.Id())
257+
if err != nil {
258+
return fmt.Errorf("resourceStatusPageDelete: API error: %w", err)
259+
}
260+
return nil
261+
}
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package checkly
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
10+
checkly "github.com/checkly/checkly-go-sdk"
11+
)
12+
13+
func resourceStatusPageService() *schema.Resource {
14+
return &schema.Resource{
15+
Create: resourceStatusPageServiceCreate,
16+
Read: resourceStatusPageServiceRead,
17+
Update: resourceStatusPageServiceUpdate,
18+
Delete: resourceStatusPageServiceDelete,
19+
Importer: &schema.ResourceImporter{
20+
StateContext: schema.ImportStatePassthroughContext,
21+
},
22+
Description: "Status page services represent functional pieces of " +
23+
"your application or website, such as landing page, API, support portal etc.",
24+
Schema: map[string]*schema.Schema{
25+
"name": {
26+
Type: schema.TypeString,
27+
Required: true,
28+
Description: "The name of the service.",
29+
},
30+
},
31+
}
32+
}
33+
34+
func resourceStatusPageServiceCreate(d *schema.ResourceData, client interface{}) error {
35+
service, err := statusPageServiceFromResourceData(d)
36+
if err != nil {
37+
return fmt.Errorf("resourceStatusPageServiceCreate: translation error: %w", err)
38+
}
39+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
40+
defer cancel()
41+
result, err := client.(checkly.Client).CreateStatusPageService(ctx, service)
42+
if err != nil {
43+
return fmt.Errorf("CreateStatusPageService: API error: %w", err)
44+
}
45+
d.SetId(result.ID)
46+
return resourceStatusPageServiceRead(d, client)
47+
}
48+
49+
func statusPageServiceFromResourceData(d *schema.ResourceData) (checkly.StatusPageService, error) {
50+
return checkly.StatusPageService{
51+
ID: d.Id(),
52+
Name: d.Get("name").(string),
53+
}, nil
54+
}
55+
56+
func resourceDataFromStatusPageService(s *checkly.StatusPageService, d *schema.ResourceData) error {
57+
d.Set("name", s.Name)
58+
return nil
59+
}
60+
61+
func resourceStatusPageServiceRead(d *schema.ResourceData, client interface{}) error {
62+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
63+
defer cancel()
64+
service, err := client.(checkly.Client).GetStatusPageService(ctx, d.Id())
65+
if err != nil {
66+
if strings.Contains(err.Error(), "404") {
67+
//if resource is deleted remotely, then mark it as
68+
//successfully gone by unsetting it's ID
69+
d.SetId("")
70+
return nil
71+
}
72+
return fmt.Errorf("resourceStatusPageServiceRead: API error: %w", err)
73+
}
74+
return resourceDataFromStatusPageService(service, d)
75+
}
76+
77+
func resourceStatusPageServiceUpdate(d *schema.ResourceData, client interface{}) error {
78+
service, err := statusPageServiceFromResourceData(d)
79+
if err != nil {
80+
return fmt.Errorf("resourceStatusPageServiceUpdate: translation error: %w", err)
81+
}
82+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
83+
defer cancel()
84+
_, err = client.(checkly.Client).UpdateStatusPageService(ctx, service.ID, service)
85+
if err != nil {
86+
return fmt.Errorf("resourceStatusPageServiceUpdate: API error: %w", err)
87+
}
88+
d.SetId(service.ID)
89+
return resourceStatusPageServiceRead(d, client)
90+
}
91+
92+
func resourceStatusPageServiceDelete(d *schema.ResourceData, client interface{}) error {
93+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
94+
defer cancel()
95+
err := client.(checkly.Client).DeleteStatusPageService(ctx, d.Id())
96+
if err != nil {
97+
return fmt.Errorf("resourceStatusPageServiceDelete: API error: %w", err)
98+
}
99+
return nil
100+
}

0 commit comments

Comments
 (0)