Skip to content

Commit a901c8f

Browse files
authored
feat: add support for ICMP monitors (#148)
1 parent 9208025 commit a901c8f

File tree

7 files changed

+693
-0
lines changed

7 files changed

+693
-0
lines changed

checkly.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ func (c *client) CreateCheck(
147147
return nil, fmt.Errorf("user error: use CreateURLMonitor to create URL monitors")
148148
case "DNS":
149149
return nil, fmt.Errorf("user error: use CreateDNSMonitor to create DNS monitors")
150+
case "ICMP":
151+
return nil, fmt.Errorf("user error: use CreateICMPMonitor to create ICMP monitors")
150152
default:
151153
return nil, fmt.Errorf("unknown check type: %s", check.Type)
152154
}
@@ -443,6 +445,70 @@ func (c *client) CreateDNSMonitor(
443445
return &result, nil
444446
}
445447

448+
type icmpMonitorPayload struct {
449+
ICMPMonitor
450+
Type string `json:"checkType"`
451+
DoubleCheck bool `json:"doubleCheck"`
452+
ShouldFail bool `json:"shouldFail"`
453+
GroupID *int64 `json:"groupId"`
454+
ID *string `json:"id,omitempty"` // Skip, can't be changed.
455+
CreatedAt *time.Time `json:"created_at,omitempty"` // Skip, can't be changed.
456+
UpdatedAt *time.Time `json:"updated_at,omitempty"` // Skip, can't be changed.
457+
}
458+
459+
func createICMPMonitorPayload(monitor ICMPMonitor) icmpMonitorPayload {
460+
payload := icmpMonitorPayload{
461+
ICMPMonitor: monitor,
462+
// Unfortunately `checkType` is required for this endpoint.
463+
Type: "ICMP",
464+
// Unfortunately, this will default to true if not set.
465+
DoubleCheck: false,
466+
// Unfortunately, this will default to true if not set.
467+
ShouldFail: false,
468+
}
469+
470+
// GroupID must be null if empty or the group will not get unset on update.
471+
if monitor.GroupID != 0 {
472+
payload.GroupID = &monitor.GroupID
473+
}
474+
475+
// A nil value for a list will cause the backend to not update the value.
476+
// We must send empty lists instead.
477+
if monitor.Locations == nil {
478+
payload.Locations = []string{}
479+
}
480+
481+
return payload
482+
}
483+
484+
func (c *client) CreateICMPMonitor(
485+
ctx context.Context,
486+
monitor ICMPMonitor,
487+
) (*ICMPMonitor, error) {
488+
payload := createICMPMonitorPayload(monitor)
489+
data, err := json.Marshal(payload)
490+
if err != nil {
491+
return nil, err
492+
}
493+
status, res, err := c.apiCall(
494+
ctx,
495+
http.MethodPost,
496+
withAutoAssignAlertsFlag("checks/icmp"),
497+
data,
498+
)
499+
if err != nil {
500+
return nil, err
501+
}
502+
if status != http.StatusCreated {
503+
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
504+
}
505+
var result ICMPMonitor
506+
if err = json.NewDecoder(strings.NewReader(res)).Decode(&result); err != nil {
507+
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
508+
}
509+
return &result, nil
510+
}
511+
446512
type playwrightCheckPayload struct {
447513
PlaywrightCheck
448514
Type string `json:"checkType"`
@@ -661,6 +727,36 @@ func (c *client) UpdateDNSMonitor(
661727
return &result, nil
662728
}
663729

730+
func (c *client) UpdateICMPMonitor(
731+
ctx context.Context,
732+
ID string,
733+
monitor ICMPMonitor,
734+
) (*ICMPMonitor, error) {
735+
payload := createICMPMonitorPayload(monitor)
736+
data, err := json.Marshal(payload)
737+
if err != nil {
738+
return nil, err
739+
}
740+
status, res, err := c.apiCall(
741+
ctx,
742+
http.MethodPut,
743+
withAutoAssignAlertsFlag(fmt.Sprintf("checks/icmp/%s", ID)),
744+
data,
745+
)
746+
if err != nil {
747+
return nil, err
748+
}
749+
if status != http.StatusOK {
750+
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
751+
}
752+
var result ICMPMonitor
753+
err = json.NewDecoder(strings.NewReader(res)).Decode(&result)
754+
if err != nil {
755+
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
756+
}
757+
return &result, nil
758+
}
759+
664760
func (c *client) UpdatePlaywrightCheck(
665761
ctx context.Context,
666762
ID string,
@@ -739,6 +835,13 @@ func (c *client) DeleteDNSMonitor(
739835
return c.DeleteCheck(ctx, ID)
740836
}
741837

838+
func (c *client) DeleteICMPMonitor(
839+
ctx context.Context,
840+
ID string,
841+
) error {
842+
return c.DeleteCheck(ctx, ID)
843+
}
844+
742845
func (c *client) DeletePlaywrightCheck(
743846
ctx context.Context,
744847
ID string,
@@ -893,6 +996,32 @@ func (c *client) GetDNSMonitor(
893996
return &result, nil
894997
}
895998

999+
// GetICMPMonitor takes the ID of an existing ICMP monitor, and returns the
1000+
// monitor parameters, or an error.
1001+
func (c *client) GetICMPMonitor(
1002+
ctx context.Context,
1003+
ID string,
1004+
) (*ICMPMonitor, error) {
1005+
status, res, err := c.apiCall(
1006+
ctx,
1007+
http.MethodGet,
1008+
fmt.Sprintf("checks/%s", ID),
1009+
nil,
1010+
)
1011+
if err != nil {
1012+
return nil, err
1013+
}
1014+
if status != http.StatusOK {
1015+
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
1016+
}
1017+
var result ICMPMonitor
1018+
err = json.NewDecoder(strings.NewReader(res)).Decode(&result)
1019+
if err != nil {
1020+
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
1021+
}
1022+
return &result, nil
1023+
}
1024+
8961025
// GetPlaywrightCheck retrieves an existing Playwright check.
8971026
func (c *client) GetPlaywrightCheck(
8981027
ctx context.Context,

checkly_integration_test.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,210 @@ func TestDNSMonitorCRUD(t *testing.T) {
957957
})
958958
}
959959

960+
func TestICMPMonitorCRUD(t *testing.T) {
961+
ctx := context.TODO()
962+
963+
client := setupClient(t)
964+
965+
pendingMonitor := checkly.ICMPMonitor{
966+
Name: "Foo ICMP monitor",
967+
Frequency: 1,
968+
Request: checkly.ICMPRequest{
969+
Hostname: "welcome.checklyhq.com",
970+
IPFamily: "IPv4",
971+
PingCount: 5,
972+
Assertions: []checkly.Assertion{
973+
{
974+
Source: "LATENCY",
975+
Property: "avg",
976+
Target: "500",
977+
Comparison: "LESS_THAN",
978+
},
979+
},
980+
},
981+
Locations: []string{"us-east-1"},
982+
}
983+
984+
createdMonitor, err := client.CreateICMPMonitor(ctx, pendingMonitor)
985+
if err != nil {
986+
t.Fatalf("failed to create ICMP monitor: %v", err)
987+
}
988+
989+
var deleteOnce sync.Once
990+
defer func() {
991+
deleteOnce.Do(func() {
992+
_ = client.DeleteICMPMonitor(ctx, createdMonitor.ID)
993+
})
994+
}()
995+
996+
readMonitor, err := client.GetICMPMonitor(ctx, createdMonitor.ID)
997+
if err != nil {
998+
t.Fatalf("failed to get ICMP monitor: %v", err)
999+
}
1000+
1001+
if readMonitor.Name != "Foo ICMP monitor" {
1002+
t.Fatalf("wrong Name after creation")
1003+
}
1004+
1005+
if readMonitor.Request.Hostname != "welcome.checklyhq.com" {
1006+
t.Fatalf("wrong Hostname after creation")
1007+
}
1008+
1009+
if readMonitor.Request.PingCount != 5 {
1010+
t.Fatalf("wrong PingCount after creation, got %d", readMonitor.Request.PingCount)
1011+
}
1012+
1013+
if len(readMonitor.Request.Assertions) != 1 {
1014+
t.Fatalf("wrong Assertion count after creation")
1015+
}
1016+
1017+
if readMonitor.Request.Assertions[0].Source != "LATENCY" {
1018+
t.Fatalf("wrong Assertion.Source after creation, got %s", readMonitor.Request.Assertions[0].Source)
1019+
}
1020+
1021+
if readMonitor.Request.Assertions[0].Property != "avg" {
1022+
t.Fatalf("wrong Assertion.Property after creation, got %s", readMonitor.Request.Assertions[0].Property)
1023+
}
1024+
1025+
if readMonitor.Request.Assertions[0].Target != "500" {
1026+
t.Fatalf("wrong Assertion.Target after creation")
1027+
}
1028+
1029+
if len(readMonitor.Locations) != 1 {
1030+
t.Fatalf("wrong Locations count after creation")
1031+
}
1032+
1033+
if readMonitor.Locations[0] != "us-east-1" {
1034+
t.Fatalf("wrong Location after creation")
1035+
}
1036+
1037+
updateMonitor := checkly.ICMPMonitor{
1038+
Name: "Bar ICMP monitor",
1039+
Frequency: 1,
1040+
Request: checkly.ICMPRequest{
1041+
Hostname: "api.checklyhq.com",
1042+
IPFamily: "IPv4",
1043+
PingCount: 3,
1044+
Assertions: []checkly.Assertion{
1045+
{
1046+
Source: "LATENCY",
1047+
Property: "max",
1048+
Target: "200",
1049+
Comparison: "LESS_THAN",
1050+
},
1051+
},
1052+
},
1053+
Locations: []string{"us-west-1"},
1054+
}
1055+
1056+
updatedMonitor, err := client.UpdateICMPMonitor(ctx, createdMonitor.ID, updateMonitor)
1057+
if err != nil {
1058+
t.Fatalf("failed to update ICMP monitor: %v", err)
1059+
}
1060+
1061+
if updatedMonitor.Name != "Bar ICMP monitor" {
1062+
t.Fatalf("wrong Name after update")
1063+
}
1064+
1065+
if updatedMonitor.Request.Hostname != "api.checklyhq.com" {
1066+
t.Fatalf("wrong Hostname after update")
1067+
}
1068+
1069+
if updatedMonitor.Request.PingCount != 3 {
1070+
t.Fatalf("wrong PingCount after update, got %d", updatedMonitor.Request.PingCount)
1071+
}
1072+
1073+
if len(updatedMonitor.Request.Assertions) != 1 {
1074+
t.Fatalf("wrong Assertion count after update")
1075+
}
1076+
1077+
if updatedMonitor.Request.Assertions[0].Property != "max" {
1078+
t.Fatalf("wrong Assertion.Property after update, got %s", updatedMonitor.Request.Assertions[0].Property)
1079+
}
1080+
1081+
if updatedMonitor.Request.Assertions[0].Target != "200" {
1082+
t.Fatalf("wrong Assertion.Target after update")
1083+
}
1084+
1085+
if len(updatedMonitor.Locations) != 1 {
1086+
t.Fatalf("wrong Locations count after update")
1087+
}
1088+
1089+
if updatedMonitor.Locations[0] != "us-west-1" {
1090+
t.Fatalf("wrong Location after update")
1091+
}
1092+
1093+
deleteOnce.Do(func() {
1094+
err := client.DeleteICMPMonitor(ctx, createdMonitor.ID)
1095+
if err != nil {
1096+
t.Fatalf("failed to delete ICMP monitor: %v", err)
1097+
}
1098+
})
1099+
}
1100+
1101+
func TestICMPMonitorGroupUnset(t *testing.T) {
1102+
ctx := context.TODO()
1103+
1104+
client := setupClient(t)
1105+
1106+
group, err := client.CreateGroup(ctx, checkly.Group{
1107+
Name: "Test Group",
1108+
Concurrency: 3,
1109+
Tags: []string{},
1110+
AlertSettings: checkly.AlertSettings{
1111+
EscalationType: checkly.RunBased,
1112+
RunBasedEscalation: checkly.RunBasedEscalation{
1113+
FailedRunThreshold: 1,
1114+
},
1115+
},
1116+
Locations: []string{"us-east-1"},
1117+
})
1118+
1119+
if err != nil {
1120+
t.Fatalf("failed to create group for ICMP monitor: %v", err)
1121+
}
1122+
1123+
defer func() {
1124+
_ = client.DeleteGroup(ctx, group.ID)
1125+
}()
1126+
1127+
pendingMonitor := checkly.ICMPMonitor{
1128+
Name: "Foo ICMP monitor",
1129+
Frequency: 1,
1130+
Request: checkly.ICMPRequest{
1131+
Hostname: "welcome.checklyhq.com",
1132+
Assertions: []checkly.Assertion{},
1133+
},
1134+
Locations: []string{"us-east-1"},
1135+
GroupID: group.ID,
1136+
}
1137+
1138+
createdMonitor, err := client.CreateICMPMonitor(ctx, pendingMonitor)
1139+
if err != nil {
1140+
t.Fatalf("failed to create ICMP monitor: %v", err)
1141+
}
1142+
1143+
defer func() {
1144+
_ = client.DeleteICMPMonitor(ctx, createdMonitor.ID)
1145+
}()
1146+
1147+
if createdMonitor.GroupID != group.ID {
1148+
t.Fatalf("wrong GroupID after creation")
1149+
}
1150+
1151+
updateMonitor := pendingMonitor
1152+
updateMonitor.GroupID = 0
1153+
1154+
updatedMonitor, err := client.UpdateICMPMonitor(ctx, createdMonitor.ID, updateMonitor)
1155+
if err != nil {
1156+
t.Fatalf("failed to update ICMP monitor: %v", err)
1157+
}
1158+
1159+
if updatedMonitor.GroupID != 0 {
1160+
t.Fatalf("wrong GroupID after update")
1161+
}
1162+
}
1163+
9601164
func TestDNSMonitorGroupUnset(t *testing.T) {
9611165
ctx := context.TODO()
9621166

0 commit comments

Comments
 (0)