Skip to content

Commit dac9fad

Browse files
authored
feat: add support for TCP checks (#124)
1 parent c7573ff commit dac9fad

File tree

3 files changed

+215
-4
lines changed

3 files changed

+215
-4
lines changed

checkly.go

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,14 +185,19 @@ func (c *client) CreateCheck(
185185
return nil, err
186186
}
187187
var checkType string
188-
if check.Type == "BROWSER" {
188+
switch check.Type {
189+
case "BROWSER":
189190
checkType = "checks/browser"
190-
} else if check.Type == "API" {
191+
case "API":
191192
checkType = "checks/api"
192-
} else if check.Type == "HEARTBEAT" {
193+
case "HEARTBEAT":
193194
checkType = "checks/heartbeat"
194-
} else if check.Type == "MULTI_STEP" {
195+
case "MULTI_STEP":
195196
checkType = "checks/multistep"
197+
case "TCP":
198+
return nil, fmt.Errorf("user error: use CreateTCPCheck to create TCP checks")
199+
default:
200+
return nil, fmt.Errorf("unknown check type: %s", checkType)
196201
}
197202
status, res, err := c.apiCall(
198203
ctx,
@@ -240,6 +245,33 @@ func (c *client) CreateHeartbeat(
240245
return &result, nil
241246
}
242247

248+
func (c *client) CreateTCPCheck(
249+
ctx context.Context,
250+
check TCPCheck,
251+
) (*TCPCheck, error) {
252+
data, err := json.Marshal(check)
253+
if err != nil {
254+
return nil, err
255+
}
256+
status, res, err := c.apiCall(
257+
ctx,
258+
http.MethodPost,
259+
withAutoAssignAlertsFlag("checks/tcp"),
260+
data,
261+
)
262+
if err != nil {
263+
return nil, err
264+
}
265+
if status != http.StatusCreated {
266+
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
267+
}
268+
var result TCPCheck
269+
if err = json.NewDecoder(strings.NewReader(res)).Decode(&result); err != nil {
270+
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
271+
}
272+
return &result, nil
273+
}
274+
243275
// Update updates an existing check with the specified details. It returns the
244276
// updated check, or an error.
245277
func (c *client) UpdateCheck(
@@ -300,6 +332,44 @@ func (c *client) UpdateHeartbeat(
300332
return &result, nil
301333
}
302334

335+
func (c *client) UpdateTCPCheck(
336+
ctx context.Context,
337+
ID string,
338+
check TCPCheck,
339+
) (*TCPCheck, error) {
340+
// Unfortunately `checkType` is required for this endpoint, so sneak it in
341+
// using an anonymous struct.
342+
payload := struct {
343+
TCPCheck
344+
Type string `json:"checkType"`
345+
}{
346+
TCPCheck: check,
347+
Type: "TCP",
348+
}
349+
data, err := json.Marshal(payload)
350+
if err != nil {
351+
return nil, err
352+
}
353+
status, res, err := c.apiCall(
354+
ctx,
355+
http.MethodPut,
356+
withAutoAssignAlertsFlag(fmt.Sprintf("checks/tcp/%s", ID)),
357+
data,
358+
)
359+
if err != nil {
360+
return nil, err
361+
}
362+
if status != http.StatusOK {
363+
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
364+
}
365+
var result TCPCheck
366+
err = json.NewDecoder(strings.NewReader(res)).Decode(&result)
367+
if err != nil {
368+
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
369+
}
370+
return &result, nil
371+
}
372+
303373
// Delete deletes the check with the specified ID.
304374
func (c *client) DeleteCheck(
305375
ctx context.Context,
@@ -372,6 +442,32 @@ func (c *client) GetHeartbeatCheck(
372442
return &result, nil
373443
}
374444

445+
// GetTCPCheck takes the ID of an existing TCP check, and returns the check
446+
// parameters, or an error.
447+
func (c *client) GetTCPCheck(
448+
ctx context.Context,
449+
ID string,
450+
) (*TCPCheck, error) {
451+
status, res, err := c.apiCall(
452+
ctx,
453+
http.MethodGet,
454+
fmt.Sprintf("checks/%s", ID),
455+
nil,
456+
)
457+
if err != nil {
458+
return nil, err
459+
}
460+
if status != http.StatusOK {
461+
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
462+
}
463+
var result TCPCheck
464+
err = json.NewDecoder(strings.NewReader(res)).Decode(&result)
465+
if err != nil {
466+
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
467+
}
468+
return &result, nil
469+
}
470+
375471
// CreateGroup creates a new check group with the specified details. It returns
376472
// the newly-created group, or an error.
377473
func (c *client) CreateGroup(

checkly_integration_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,57 @@ func TestGetPrivateLocationIntegration(t *testing.T) {
325325
t.Error(cmp.Diff(testPrivateLocation, *gotPrivateLocation, ignorePrivateLocationFields))
326326
}
327327
}
328+
329+
func TestTCPCheckCRUD(t *testing.T) {
330+
ctx := context.TODO()
331+
332+
client := setupClient(t)
333+
334+
pendingCheck := checkly.TCPCheck{
335+
Name: "TestTCPCheckCRUD",
336+
Muted: false,
337+
Locations: []string{"eu-west-1"},
338+
Request: checkly.TCPRequest{
339+
Hostname: "api.checklyhq.com",
340+
Port: 443,
341+
},
342+
}
343+
344+
createdCheck, err := client.CreateTCPCheck(ctx, pendingCheck)
345+
if err != nil {
346+
t.Fatalf("failed to create TCP check: %v", err)
347+
}
348+
var didDelete bool
349+
defer func() {
350+
if !didDelete {
351+
_ = client.DeleteCheck(ctx, createdCheck.ID)
352+
}
353+
}()
354+
355+
if createdCheck.Muted != false {
356+
t.Fatalf("expected Muted to be false after creation")
357+
}
358+
359+
_, err = client.GetTCPCheck(ctx, createdCheck.ID)
360+
if err != nil {
361+
t.Fatalf("failed to get TCP check: %v", err)
362+
}
363+
364+
updateCheck := *createdCheck
365+
updateCheck.Muted = true
366+
367+
updatedCheck, err := client.UpdateTCPCheck(ctx, createdCheck.ID, updateCheck)
368+
if err != nil {
369+
t.Fatalf("failed to update TCP check: %v", err)
370+
}
371+
372+
if updatedCheck.Muted != true {
373+
t.Fatalf("expected Muted to be true after update")
374+
}
375+
376+
didDelete = true
377+
err = client.DeleteCheck(ctx, createdCheck.ID)
378+
if err != nil {
379+
t.Fatalf("failed to delete TCP check: %v", err)
380+
}
381+
}

types.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ type Client interface {
7272
check HeartbeatCheck,
7373
) (*HeartbeatCheck, error)
7474

75+
// CreateTCPCheck creates a new TCP check with the specified details.
76+
// It returns the newly-created check, or an error.
77+
CreateTCPCheck(
78+
ctx context.Context,
79+
check TCPCheck,
80+
) (*TCPCheck, error)
81+
7582
// Update updates an existing check with the specified details.
7683
// It returns the updated check, or an error.
7784
UpdateCheck(
@@ -88,6 +95,14 @@ type Client interface {
8895
check HeartbeatCheck,
8996
) (*HeartbeatCheck, error)
9097

98+
// UpdateTCPCheck updates an existing TCP check with the specified details.
99+
// It returns the updated check, or an error.
100+
UpdateTCPCheck(
101+
ctx context.Context,
102+
ID string,
103+
check TCPCheck,
104+
) (*TCPCheck, error)
105+
91106
// Delete deletes the check with the specified ID.
92107
DeleteCheck(
93108
ctx context.Context,
@@ -101,6 +116,13 @@ type Client interface {
101116
ID string,
102117
) (*Check, error)
103118

119+
// Get takes the ID of an existing TCP check, and returns the check
120+
// parameters, or an error.
121+
GetTCPCheck(
122+
ctx context.Context,
123+
ID string,
124+
) (*TCPCheck, error)
125+
104126
// CreateGroup creates a new check group with the specified details.
105127
// It returns the newly-created group, or an error.
106128
CreateGroup(
@@ -412,6 +434,9 @@ const Headers = "HEADERS"
412434
// ResponseTime identifies the response time as an assertion source.
413435
const ResponseTime = "RESPONSE_TIME"
414436

437+
// ResponseData identifies the response data of a TCP check as an assertion source.
438+
const ResponseData = "RESPONSE_DATA"
439+
415440
// Assertion comparison constants
416441

417442
// Equals asserts that the source and target are equal.
@@ -524,6 +549,33 @@ type HeartbeatCheck struct {
524549
UpdatedAt time.Time `json:"updatedAt"`
525550
}
526551

552+
// TCPCheck represents a TCP check.
553+
type TCPCheck struct {
554+
ID string `json:"id,omitempty"`
555+
Name string `json:"name"`
556+
Frequency int `json:"frequency,omitempty"`
557+
FrequencyOffset int `json:"frequencyOffset,omitempty"`
558+
Activated bool `json:"activated"`
559+
Muted bool `json:"muted"`
560+
ShouldFail bool `json:"shouldFail"`
561+
RunParallel bool `json:"runParallel"`
562+
Locations []string `json:"locations,omitempty"`
563+
DegradedResponseTime int `json:"degradedResponseTime,omitempty"`
564+
MaxResponseTime int `json:"maxResponseTime,omitempty"`
565+
Tags []string `json:"tags,omitempty"`
566+
AlertSettings *AlertSettings `json:"alertSettings,omitempty"`
567+
UseGlobalAlertSettings bool `json:"useGlobalAlertSettings"`
568+
Request TCPRequest `json:"request"`
569+
GroupID int64 `json:"groupId,omitempty"`
570+
GroupOrder int `json:"groupOrder,omitempty"`
571+
AlertChannelSubscriptions []AlertChannelSubscription `json:"alertChannelSubscriptions,omitempty"`
572+
PrivateLocations *[]string `json:"privateLocations,omitempty"`
573+
RuntimeID *string `json:"runtimeId"`
574+
RetryStrategy *RetryStrategy `json:"retryStrategy,omitempty"`
575+
CreatedAt time.Time `json:"created_at,omitempty"`
576+
UpdatedAt time.Time `json:"updated_at,omitempty"`
577+
}
578+
527579
// Heartbeat represents the parameter for the heartbeat check.
528580
type Heartbeat struct {
529581
Period int `json:"period"`
@@ -575,6 +627,15 @@ type KeyValue struct {
575627
Locked bool `json:"locked"`
576628
}
577629

630+
// TCPRequest represents the parameters for a TCP check's connection.
631+
type TCPRequest struct {
632+
Hostname string `json:"hostname"`
633+
Port uint16 `json:"port"`
634+
Data string `json:"data,omitempty"`
635+
Assertions []Assertion `json:"assertions,omitempty"`
636+
IPFamily string `json:"ipFamily,omitempty"`
637+
}
638+
578639
// EnvironmentVariable represents a key-value pair for setting environment
579640
// values during check execution.
580641
type EnvironmentVariable struct {

0 commit comments

Comments
 (0)