157 lines
4.2 KiB
Go
157 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,
|
||
|
}
|
||
|
}
|