Skip to content

Commit 4df14b8

Browse files
authored
fix: high frequency intervals could not be used with some monitor/check types (#339)
* feat: make frequency and frequency_offset consistent * feat: add tests to make sure frequency and frequency offset work * chore: bump Go SDK to v1.18.1 which contains the fix * chore: clarify documentation
1 parent e804cc8 commit 4df14b8

20 files changed

+1360
-126
lines changed

checkly/attribute_frequency.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package checkly
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
7+
)
8+
9+
const frequencyAttributeName = "frequency"
10+
11+
type FrequencyAttributeSchemaOptions struct {
12+
Monitor bool
13+
AllowHighFrequency bool
14+
Disclaimer string
15+
}
16+
17+
func makeFrequencyAttributeSchema(options FrequencyAttributeSchemaOptions) *schema.Schema {
18+
name := "check"
19+
if options.Monitor {
20+
name = "monitor"
21+
}
22+
23+
allow := allowedValues[int]{
24+
{
25+
Value: 0,
26+
Description: "high frequency - use `frequency_offset` to define the actual frequency",
27+
},
28+
{
29+
Value: 1,
30+
Description: "1 minute",
31+
},
32+
{
33+
Value: 2,
34+
Description: "2 minutes",
35+
},
36+
{
37+
Value: 5,
38+
Description: "5 minutes",
39+
},
40+
{
41+
Value: 10,
42+
Description: "10 minutes",
43+
},
44+
{
45+
Value: 15,
46+
Description: "15 minutes",
47+
},
48+
{
49+
Value: 30,
50+
Description: "30 minutes",
51+
},
52+
{
53+
Value: 60,
54+
Description: "1 hour",
55+
},
56+
{
57+
Value: 120,
58+
Description: "2 hours",
59+
},
60+
{
61+
Value: 180,
62+
Description: "3 hours",
63+
},
64+
{
65+
Value: 360,
66+
Description: "6 hours",
67+
},
68+
{
69+
Value: 720,
70+
Description: "12 hours",
71+
},
72+
{
73+
Value: 1440,
74+
Description: "24 hours",
75+
},
76+
}
77+
78+
if !options.AllowHighFrequency {
79+
allow = allow[1:]
80+
}
81+
82+
var disclaimer string
83+
if options.Disclaimer != "" {
84+
disclaimer = options.Disclaimer + " "
85+
}
86+
87+
return &schema.Schema{
88+
Description: disclaimer + fmt.Sprintf("Controls how often the %s should run. Defined in minutes. %s", name, allow.String()),
89+
Type: schema.TypeInt,
90+
Required: true,
91+
ValidateFunc: validateOneOf(allow.Values()),
92+
}
93+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package checkly
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
)
9+
10+
const frequencyOffsetAttributeName = "frequency_offset"
11+
12+
type FrequencyOffsetAttributeSchemaOptions struct {
13+
Monitor bool
14+
Disclaimer string
15+
}
16+
17+
func makeFrequencyOffsetAttributeSchema(options FrequencyOffsetAttributeSchemaOptions) *schema.Schema {
18+
name := "check"
19+
if options.Monitor {
20+
name = "monitor"
21+
}
22+
23+
allow := allowedValues[int]{
24+
{
25+
Value: 0,
26+
Description: "disabled - use `frequency` to define the actual frequency",
27+
},
28+
{
29+
Value: 10,
30+
Description: "10 seconds",
31+
},
32+
{
33+
Value: 20,
34+
Description: "20 seconds",
35+
},
36+
{
37+
Value: 30,
38+
Description: "30 seconds",
39+
},
40+
}
41+
42+
var disclaimer string
43+
if options.Disclaimer != "" {
44+
disclaimer = options.Disclaimer + " "
45+
}
46+
47+
return &schema.Schema{
48+
Description: disclaimer + fmt.Sprintf("When `frequency` is `0` (high frequency), `frequency_offset` is required and it alone controls how often the %s should run. Defined in seconds. %s", name, allow.String()),
49+
Type: schema.TypeInt,
50+
Optional: true,
51+
ValidateFunc: validateOneOf(allow.Values()),
52+
}
53+
}
54+
55+
func FrequencyOffsetCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, meta any) error {
56+
frequency := diff.Get(frequencyAttributeName).(int)
57+
offset := diff.Get(frequencyOffsetAttributeName).(int)
58+
59+
switch {
60+
case frequency > 0 && offset != 0:
61+
return fmt.Errorf(`"frequency_offset" can only be set when "frequency" is 0`)
62+
case frequency == 0 && offset == 0:
63+
return fmt.Errorf(`"frequency_offset" is required when "frequency" is 0`)
64+
}
65+
66+
return nil
67+
}

checkly/helpers.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package checkly
33
import (
44
"crypto/sha256"
55
"encoding/hex"
6+
"fmt"
67
"io"
78
"os"
89
"strconv"
10+
"strings"
911
"time"
1012
)
1113

@@ -34,3 +36,59 @@ func checksumSha256(r io.Reader) string {
3436

3537
return checksum
3638
}
39+
40+
type allowedValue[T any] struct {
41+
Value T
42+
Description string
43+
}
44+
45+
func (v *allowedValue[T]) String() string {
46+
if v.Description != "" {
47+
return fmt.Sprintf("`%v` (%s)", v.Value, v.Description)
48+
}
49+
50+
return fmt.Sprintf("`%v`", v.Value)
51+
}
52+
53+
type allowedValues[T any] []allowedValue[T]
54+
55+
func (v *allowedValues[T]) Values() []T {
56+
s := make([]T, 0, len(*v))
57+
58+
for _, value := range *v {
59+
s = append(s, value.Value)
60+
}
61+
62+
return s
63+
}
64+
65+
func (v *allowedValues[T]) String() string {
66+
l := len(*v)
67+
switch l {
68+
case 0:
69+
return "There are no allowed values."
70+
case 1:
71+
return fmt.Sprintf("The only allowed value is %s.", (*v)[0].String())
72+
default:
73+
head := (*v)[:l-1]
74+
last := (*v)[l-1]
75+
76+
var buf strings.Builder
77+
78+
buf.WriteString("The allowed values are ")
79+
80+
for i, value := range head {
81+
if i > 0 {
82+
buf.WriteString(", ")
83+
}
84+
85+
buf.WriteString(value.String())
86+
}
87+
88+
buf.WriteString(" and ")
89+
buf.WriteString(last.String())
90+
buf.WriteString(".")
91+
92+
return buf.String()
93+
}
94+
}

checkly/resource_check.go

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,30 +38,14 @@ func resourceCheck() *schema.Resource {
3838
Required: true,
3939
Description: "The type of the check. Possible values are `API`, `BROWSER`, and `MULTI_STEP`.",
4040
},
41-
"frequency": {
42-
Type: schema.TypeInt,
43-
Required: true,
44-
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
45-
v := val.(int)
46-
valid := false
47-
validFreqs := []int{0, 1, 2, 5, 10, 15, 30, 60, 120, 180, 360, 720, 1440}
48-
for _, i := range validFreqs {
49-
if v == i {
50-
valid = true
51-
}
52-
}
53-
if !valid {
54-
errs = append(errs, fmt.Errorf("%q must be one of %v, got %d", key, validFreqs, v))
55-
}
56-
return warns, errs
57-
},
58-
Description: "The frequency in minutes to run the check. Possible values are `0`, `1`, `2`, `5`, `10`, `15`, `30`, `60`, `120`, `180`, `360`, `720`, and `1440`.",
59-
},
60-
"frequency_offset": {
61-
Type: schema.TypeInt,
62-
Optional: true,
63-
Description: "This property only valid for API high frequency checks. To create a hight frequency check, the property `frequency` must be `0` and `frequency_offset` could be `10`, `20` or `30`.",
64-
},
41+
frequencyAttributeName: makeFrequencyAttributeSchema(FrequencyAttributeSchemaOptions{
42+
Monitor: false,
43+
AllowHighFrequency: true,
44+
}),
45+
frequencyOffsetAttributeName: makeFrequencyOffsetAttributeSchema(FrequencyOffsetAttributeSchemaOptions{
46+
Monitor: true,
47+
Disclaimer: "Only relevant when `type` is `API`.",
48+
}),
6549
"activated": {
6650
Type: schema.TypeBool,
6751
Required: true,
@@ -392,6 +376,7 @@ func resourceCheck() *schema.Resource {
392376
},
393377
CustomizeDiff: customdiff.Sequence(
394378
RetryStrategyCustomizeDiff,
379+
FrequencyOffsetCustomizeDiff,
395380
),
396381
}
397382
}
@@ -487,9 +472,11 @@ func resourceDataFromCheck(c *checkly.Check, d *schema.ResourceData) error {
487472
sort.Strings(c.Tags)
488473
d.Set("tags", c.Tags)
489474

490-
d.Set("frequency", c.Frequency)
475+
d.Set(frequencyAttributeName, c.Frequency)
491476
if c.Frequency == 0 {
492-
d.Set("frequency_offset", c.FrequencyOffset)
477+
d.Set(frequencyOffsetAttributeName, c.FrequencyOffset)
478+
} else {
479+
d.Set(frequencyOffsetAttributeName, nil)
493480
}
494481

495482
if c.RuntimeID != nil {
@@ -591,7 +578,7 @@ func checkFromResourceData(d *schema.ResourceData) (checkly.Check, error) {
591578
ID: d.Id(),
592579
Name: d.Get("name").(string),
593580
Type: d.Get("type").(string),
594-
Frequency: d.Get("frequency").(int),
581+
Frequency: d.Get(frequencyAttributeName).(int),
595582
Activated: d.Get("activated").(bool),
596583
Muted: d.Get("muted").(bool),
597584
ShouldFail: d.Get("should_fail").(bool),
@@ -636,10 +623,10 @@ func checkFromResourceData(d *schema.ResourceData) (checkly.Check, error) {
636623
if check.Type == checkly.TypeAPI {
637624
// this will prevent subsequent apply from causing a tf config change in browser checks
638625
check.Request = requestFromSet(d.Get("request").(*schema.Set))
639-
check.FrequencyOffset = d.Get("frequency_offset").(int)
626+
check.FrequencyOffset = d.Get(frequencyOffsetAttributeName).(int)
640627

641628
if check.Frequency == 0 && (check.FrequencyOffset != 10 && check.FrequencyOffset != 20 && check.FrequencyOffset != 30) {
642-
return check, errors.New("when property frequency is 0, frequency_offset must be 10, 20 or 30")
629+
return check, errors.New("when `frequency` is 0, `frequency_offset` must be 10, 20 or 30")
643630
}
644631

645632
if check.SSLCheckDomain != "" {

0 commit comments

Comments
 (0)