Skip to content

Commit

Permalink
feat: add support for TCP checks (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
sorccu authored Jan 28, 2025
1 parent c7573ff commit dac9fad
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 4 deletions.
104 changes: 100 additions & 4 deletions checkly.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,19 @@ func (c *client) CreateCheck(
return nil, err
}
var checkType string
if check.Type == "BROWSER" {
switch check.Type {
case "BROWSER":
checkType = "checks/browser"
} else if check.Type == "API" {
case "API":
checkType = "checks/api"
} else if check.Type == "HEARTBEAT" {
case "HEARTBEAT":
checkType = "checks/heartbeat"
} else if check.Type == "MULTI_STEP" {
case "MULTI_STEP":
checkType = "checks/multistep"
case "TCP":
return nil, fmt.Errorf("user error: use CreateTCPCheck to create TCP checks")
default:
return nil, fmt.Errorf("unknown check type: %s", checkType)
}
status, res, err := c.apiCall(
ctx,
Expand Down Expand Up @@ -240,6 +245,33 @@ func (c *client) CreateHeartbeat(
return &result, nil
}

func (c *client) CreateTCPCheck(
ctx context.Context,
check TCPCheck,
) (*TCPCheck, error) {
data, err := json.Marshal(check)
if err != nil {
return nil, err
}
status, res, err := c.apiCall(
ctx,
http.MethodPost,
withAutoAssignAlertsFlag("checks/tcp"),
data,
)
if err != nil {
return nil, err
}
if status != http.StatusCreated {
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
}
var result TCPCheck
if err = json.NewDecoder(strings.NewReader(res)).Decode(&result); err != nil {
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
}
return &result, nil
}

// Update updates an existing check with the specified details. It returns the
// updated check, or an error.
func (c *client) UpdateCheck(
Expand Down Expand Up @@ -300,6 +332,44 @@ func (c *client) UpdateHeartbeat(
return &result, nil
}

func (c *client) UpdateTCPCheck(
ctx context.Context,
ID string,
check TCPCheck,
) (*TCPCheck, error) {
// Unfortunately `checkType` is required for this endpoint, so sneak it in
// using an anonymous struct.
payload := struct {
TCPCheck
Type string `json:"checkType"`
}{
TCPCheck: check,
Type: "TCP",
}
data, err := json.Marshal(payload)
if err != nil {
return nil, err
}
status, res, err := c.apiCall(
ctx,
http.MethodPut,
withAutoAssignAlertsFlag(fmt.Sprintf("checks/tcp/%s", ID)),
data,
)
if err != nil {
return nil, err
}
if status != http.StatusOK {
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
}
var result TCPCheck
err = json.NewDecoder(strings.NewReader(res)).Decode(&result)
if err != nil {
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
}
return &result, nil
}

// Delete deletes the check with the specified ID.
func (c *client) DeleteCheck(
ctx context.Context,
Expand Down Expand Up @@ -372,6 +442,32 @@ func (c *client) GetHeartbeatCheck(
return &result, nil
}

// GetTCPCheck takes the ID of an existing TCP check, and returns the check
// parameters, or an error.
func (c *client) GetTCPCheck(
ctx context.Context,
ID string,
) (*TCPCheck, error) {
status, res, err := c.apiCall(
ctx,
http.MethodGet,
fmt.Sprintf("checks/%s", ID),
nil,
)
if err != nil {
return nil, err
}
if status != http.StatusOK {
return nil, fmt.Errorf("unexpected response status %d: %q", status, res)
}
var result TCPCheck
err = json.NewDecoder(strings.NewReader(res)).Decode(&result)
if err != nil {
return nil, fmt.Errorf("decoding error for data %s: %v", res, err)
}
return &result, nil
}

// CreateGroup creates a new check group with the specified details. It returns
// the newly-created group, or an error.
func (c *client) CreateGroup(
Expand Down
54 changes: 54 additions & 0 deletions checkly_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,57 @@ func TestGetPrivateLocationIntegration(t *testing.T) {
t.Error(cmp.Diff(testPrivateLocation, *gotPrivateLocation, ignorePrivateLocationFields))
}
}

func TestTCPCheckCRUD(t *testing.T) {
ctx := context.TODO()

client := setupClient(t)

pendingCheck := checkly.TCPCheck{
Name: "TestTCPCheckCRUD",
Muted: false,
Locations: []string{"eu-west-1"},
Request: checkly.TCPRequest{
Hostname: "api.checklyhq.com",
Port: 443,
},
}

createdCheck, err := client.CreateTCPCheck(ctx, pendingCheck)
if err != nil {
t.Fatalf("failed to create TCP check: %v", err)
}
var didDelete bool
defer func() {
if !didDelete {
_ = client.DeleteCheck(ctx, createdCheck.ID)
}
}()

if createdCheck.Muted != false {
t.Fatalf("expected Muted to be false after creation")
}

_, err = client.GetTCPCheck(ctx, createdCheck.ID)
if err != nil {
t.Fatalf("failed to get TCP check: %v", err)
}

updateCheck := *createdCheck
updateCheck.Muted = true

updatedCheck, err := client.UpdateTCPCheck(ctx, createdCheck.ID, updateCheck)
if err != nil {
t.Fatalf("failed to update TCP check: %v", err)
}

if updatedCheck.Muted != true {
t.Fatalf("expected Muted to be true after update")
}

didDelete = true
err = client.DeleteCheck(ctx, createdCheck.ID)
if err != nil {
t.Fatalf("failed to delete TCP check: %v", err)
}
}
61 changes: 61 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ type Client interface {
check HeartbeatCheck,
) (*HeartbeatCheck, error)

// CreateTCPCheck creates a new TCP check with the specified details.
// It returns the newly-created check, or an error.
CreateTCPCheck(
ctx context.Context,
check TCPCheck,
) (*TCPCheck, error)

// Update updates an existing check with the specified details.
// It returns the updated check, or an error.
UpdateCheck(
Expand All @@ -88,6 +95,14 @@ type Client interface {
check HeartbeatCheck,
) (*HeartbeatCheck, error)

// UpdateTCPCheck updates an existing TCP check with the specified details.
// It returns the updated check, or an error.
UpdateTCPCheck(
ctx context.Context,
ID string,
check TCPCheck,
) (*TCPCheck, error)

// Delete deletes the check with the specified ID.
DeleteCheck(
ctx context.Context,
Expand All @@ -101,6 +116,13 @@ type Client interface {
ID string,
) (*Check, error)

// Get takes the ID of an existing TCP check, and returns the check
// parameters, or an error.
GetTCPCheck(
ctx context.Context,
ID string,
) (*TCPCheck, error)

// CreateGroup creates a new check group with the specified details.
// It returns the newly-created group, or an error.
CreateGroup(
Expand Down Expand Up @@ -412,6 +434,9 @@ const Headers = "HEADERS"
// ResponseTime identifies the response time as an assertion source.
const ResponseTime = "RESPONSE_TIME"

// ResponseData identifies the response data of a TCP check as an assertion source.
const ResponseData = "RESPONSE_DATA"

// Assertion comparison constants

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

// TCPCheck represents a TCP check.
type TCPCheck struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Frequency int `json:"frequency,omitempty"`
FrequencyOffset int `json:"frequencyOffset,omitempty"`
Activated bool `json:"activated"`
Muted bool `json:"muted"`
ShouldFail bool `json:"shouldFail"`
RunParallel bool `json:"runParallel"`
Locations []string `json:"locations,omitempty"`
DegradedResponseTime int `json:"degradedResponseTime,omitempty"`
MaxResponseTime int `json:"maxResponseTime,omitempty"`
Tags []string `json:"tags,omitempty"`
AlertSettings *AlertSettings `json:"alertSettings,omitempty"`
UseGlobalAlertSettings bool `json:"useGlobalAlertSettings"`
Request TCPRequest `json:"request"`
GroupID int64 `json:"groupId,omitempty"`
GroupOrder int `json:"groupOrder,omitempty"`
AlertChannelSubscriptions []AlertChannelSubscription `json:"alertChannelSubscriptions,omitempty"`
PrivateLocations *[]string `json:"privateLocations,omitempty"`
RuntimeID *string `json:"runtimeId"`
RetryStrategy *RetryStrategy `json:"retryStrategy,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}

// Heartbeat represents the parameter for the heartbeat check.
type Heartbeat struct {
Period int `json:"period"`
Expand Down Expand Up @@ -575,6 +627,15 @@ type KeyValue struct {
Locked bool `json:"locked"`
}

// TCPRequest represents the parameters for a TCP check's connection.
type TCPRequest struct {
Hostname string `json:"hostname"`
Port uint16 `json:"port"`
Data string `json:"data,omitempty"`
Assertions []Assertion `json:"assertions,omitempty"`
IPFamily string `json:"ipFamily,omitempty"`
}

// EnvironmentVariable represents a key-value pair for setting environment
// values during check execution.
type EnvironmentVariable struct {
Expand Down

0 comments on commit dac9fad

Please sign in to comment.