Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for TCP checks [sc-23026] #124

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading