go-healthchecks/pingclient.go

156 lines
4.2 KiB
Go

package gohealthchecks
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
)
func handleResponse(resp *http.Response) error {
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated {
return nil // don't do anything more here, we would just have a `OK` or `Created` in the body
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
switch resp.StatusCode {
case http.StatusNotFound:
return ErrCheckNotFound
case http.StatusConflict:
return ErrSlugConflict
case http.StatusTooManyRequests:
return ErrRateLimit
case http.StatusBadRequest:
return fmt.Errorf("invalid URL format: %s", string(respBody))
default:
return fmt.Errorf("unexpected statuscode %d (%s)", resp.StatusCode, string(respBody))
}
}
func addQueryParams(reqURL *url.URL, check Check) {
q := reqURL.Query()
for param, value := range check.params() {
q.Add(param, value)
}
reqURL.RawQuery = q.Encode()
}
type PingClient interface {
ReportSuccess(ctx context.Context, check Check) error
ReportStart(ctx context.Context, check Check) error
ReportFailure(ctx context.Context, check Check) error
LogMessage(ctx context.Context, check Check, log []byte) error
ReportExitCode(ctx context.Context, check Check, exitCode int) error
}
type pingClient struct {
httpClt http.Client
host string
}
func (c *pingClient) ReportSuccess(ctx context.Context, check Check) error {
url, err := url.JoinPath(c.host, check.path())
if err != nil {
return fmt.Errorf("invalid check or hostname provided: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return fmt.Errorf("failed to build ping request: %w", err)
}
addQueryParams(req.URL, check)
resp, err := c.httpClt.Do(req)
if err != nil {
return fmt.Errorf("failed to send ping: %w", err)
}
return handleResponse(resp)
}
func (c *pingClient) ReportStart(ctx context.Context, check Check) error {
url, err := url.JoinPath(c.host, check.path(), "start")
if err != nil {
return fmt.Errorf("invalid check or hostname provided: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return fmt.Errorf("failed to build ping request: %w", err)
}
addQueryParams(req.URL, check)
resp, err := c.httpClt.Do(req)
if err != nil {
return fmt.Errorf("failed to notify start: %w", err)
}
return handleResponse(resp)
}
func (c *pingClient) ReportFailure(ctx context.Context, check Check) error {
url, err := url.JoinPath(c.host, check.path(), "fail")
if err != nil {
return fmt.Errorf("invalid check or hostname provided: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return fmt.Errorf("failed to build ping request: %w", err)
}
addQueryParams(req.URL, check)
resp, err := c.httpClt.Do(req)
if err != nil {
return fmt.Errorf("failed to send ping: %w", err)
}
return handleResponse(resp)
}
func (c *pingClient) LogMessage(ctx context.Context, check Check, log []byte) error {
url, err := url.JoinPath(c.host, check.path(), "log")
if err != nil {
return fmt.Errorf("invalid check or hostname provided: %w", err)
}
reqBody := bytes.NewBuffer(log)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, reqBody)
if err != nil {
return fmt.Errorf("failed to build log request: %w", err)
}
addQueryParams(req.URL, check)
resp, err := c.httpClt.Do(req)
if err != nil {
return fmt.Errorf("failed to send log: %w", err)
}
return handleResponse(resp)
}
func (c *pingClient) ReportExitCode(ctx context.Context, check Check, exitCode int) error {
url, err := url.JoinPath(c.host, check.path(), strconv.FormatInt(int64(exitCode), 10))
if err != nil {
return fmt.Errorf("invalid check or hostname provided: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return fmt.Errorf("failed to build exitcode request: %w", err)
}
addQueryParams(req.URL, check)
resp, err := c.httpClt.Do(req)
if err != nil {
return fmt.Errorf("failed to send exitcode: %w", err)
}
return handleResponse(resp)
}
func NewPingClient(host string) PingClient {
return &pingClient{
httpClt: *http.DefaultClient,
host: host,
}
}