diff --git a/README.md b/README.md index ae2bce6..c7ff87a 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,7 @@ probe "probe-name" { } http { + method = "post" scheme = "http" host = { hostname = "localhost" @@ -296,6 +297,13 @@ probe "probe-name" { } path = "/status" timeout = "5s" + payload = "{\"body\": 123}" + # regex to match against the response status line + # (e.g. 403 Forbidden) + expectStatus = "(200|201)" + headers = { + Content-Type = "application/json" + } } } ``` diff --git a/internal/config/types.go b/internal/config/types.go index e4c8386..7c95fb3 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -43,11 +43,15 @@ type SMTP struct { Host } -type HttpGet struct { +type HTTP struct { + Method string Scheme string Host - Path string - Timeout string + Path string + Timeout string + Payload string + ExpectStatus string + Headers map[string]string } type Probe struct { @@ -58,7 +62,7 @@ type Probe struct { Redis *Redis MongoDB *MongoDB Amqp *Amqp - HTTP *HttpGet + HTTP *HTTP SMTP *SMTP } diff --git a/pkg/probe/probe_http.go b/pkg/probe/probe_http.go index ff01e37..4ee8e61 100644 --- a/pkg/probe/probe_http.go +++ b/pkg/probe/probe_http.go @@ -2,8 +2,11 @@ package probe import ( "fmt" + "net" "net/http" "net/url" + "regexp" + "strings" "time" "github.com/mittwald/mittnite/internal/config" @@ -11,69 +14,86 @@ import ( log "github.com/sirupsen/logrus" ) -type httpGetProbe struct { +type httpProbe struct { + method string scheme string host string path string - timeout string + payload string + headers map[string]string + timeout time.Duration + status *regexp.Regexp } -func NewHttpProbe(cfg *config.HttpGet) *httpGetProbe { - cfg.Scheme = helper.ResolveEnv(cfg.Scheme) +func NewHttpProbe(cfg *config.HTTP) (*httpProbe, error) { + cfg.Method = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.Method), "GET", "method", "http") + cfg.Scheme = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.Scheme), "http", "scheme", "http") cfg.Hostname = helper.ResolveEnv(cfg.Hostname) cfg.Port = helper.ResolveEnv(cfg.Port) cfg.Path = helper.ResolveEnv(cfg.Path) - cfg.Timeout = helper.ResolveEnv(cfg.Timeout) - - if cfg.Scheme == "" { - cfg.Scheme = "http" - } + cfg.Timeout = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.Timeout), "5s", "timeout", "http") + cfg.ExpectStatus = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.ExpectStatus), `(1|2|3)\d\d\s`, "expectStatus", "http") + method := strings.ToUpper(cfg.Method) host := cfg.Hostname if cfg.Port != "" { - host = fmt.Sprintf("%s:%s", cfg.Hostname, cfg.Port) + host = net.JoinHostPort(cfg.Hostname, cfg.Port) + } + + status, err := regexp.Compile(cfg.ExpectStatus) + if err != nil { + return nil, fmt.Errorf("invalid HTTP status line regexp: %w", err) } - connCfg := httpGetProbe{ + timeout, err := time.ParseDuration(cfg.Timeout) + if err != nil { + return nil, fmt.Errorf("invalid timeout duration: %w", err) + } + + connCfg := &httpProbe{ + method: method, scheme: cfg.Scheme, host: host, path: cfg.Path, - timeout: cfg.Timeout, + status: status, + timeout: timeout, + payload: cfg.Payload, + headers: cfg.Headers, } - return &connCfg + return connCfg, nil } -func (h *httpGetProbe) Exec() error { - timeout := time.Second * 5 - if h.timeout != "" { - duration, err := time.ParseDuration(h.timeout) - if err == nil { - timeout = duration - } else { - return fmt.Errorf("invalid timeout duration: %s", err) - } - } - +func (h *httpProbe) Exec() error { u := url.URL{ Scheme: h.scheme, Host: h.host, Path: h.path, } urlStr := u.String() - client := &http.Client{ - Timeout: timeout, + Timeout: h.timeout, } - res, err := client.Get(urlStr) + + data := strings.NewReader(h.payload) + req, err := http.NewRequest(h.method, u.String(), data) + if err != nil { + return err + } + + for k, v := range h.headers { + req.Header.Set(k, v) + } + + res, err := client.Do(req) if err != nil { return err } - if res.StatusCode >= 200 && res.StatusCode < 400 { - log.WithFields(log.Fields{"kind": "probe", "name": "http", "status": "alive", "host": urlStr}).Debug() - return nil + if !h.status.MatchString(res.Status) { + return fmt.Errorf("http service %q returned status %q", urlStr, res.Status) } - return fmt.Errorf("http service '%s' returned status code %d", urlStr, res.StatusCode) + log.WithFields(log.Fields{"kind": "probe", "name": "http", "status": "alive", "host": urlStr}).Debug() + return nil } diff --git a/pkg/probe/server.go b/pkg/probe/server.go index d4d9ca0..e2cbdf3 100644 --- a/pkg/probe/server.go +++ b/pkg/probe/server.go @@ -143,29 +143,15 @@ func filterWaitProbes(cfg *config.Ignition, probes map[string]Probe) map[string] } func buildProbesFromConfig(cfg *config.Ignition) (map[string]Probe, error) { - result := make(map[string]Probe) - var errs []error + result := make(map[string]Probe) for i := range cfg.Probes { - if cfg.Probes[i].Filesystem != "" { - result[cfg.Probes[i].Name] = &filesystemProbe{cfg.Probes[i].Filesystem} - } else if cfg.Probes[i].MySQL != nil { - result[cfg.Probes[i].Name] = NewMySQLProbe(cfg.Probes[i].MySQL) - } else if cfg.Probes[i].Redis != nil { - result[cfg.Probes[i].Name] = NewRedisProbe(cfg.Probes[i].Redis) - } else if cfg.Probes[i].MongoDB != nil { - var err error - result[cfg.Probes[i].Name], err = NewMongoDBProbe(cfg.Probes[i].MongoDB) - if err != nil { - errs = append(errs, err) - } - } else if cfg.Probes[i].Amqp != nil { - result[cfg.Probes[i].Name] = NewAmqpProbe(cfg.Probes[i].Amqp) - } else if cfg.Probes[i].HTTP != nil { - result[cfg.Probes[i].Name] = NewHttpProbe(cfg.Probes[i].HTTP) - } else if cfg.Probes[i].SMTP != nil { - result[cfg.Probes[i].Name] = NewSmtpProbe(cfg.Probes[i].SMTP) + p, err := newProbe(cfg.Probes[i]) + if err != nil { + errs = append(errs, err) + } else if p != nil { + result[cfg.Probes[i].Name] = p } } @@ -176,3 +162,23 @@ func buildProbesFromConfig(cfg *config.Ignition) (map[string]Probe, error) { return result, err } + +func newProbe(p config.Probe) (Probe, error) { + if p.Filesystem != "" { + return &filesystemProbe{p.Filesystem}, nil + } else if p.MySQL != nil { + return NewMySQLProbe(p.MySQL), nil + } else if p.Redis != nil { + return NewRedisProbe(p.Redis), nil + } else if p.MongoDB != nil { + return NewMongoDBProbe(p.MongoDB) + } else if p.Amqp != nil { + return NewAmqpProbe(p.Amqp), nil + } else if p.HTTP != nil { + return NewHttpProbe(p.HTTP) + } else if p.SMTP != nil { + return NewSmtpProbe(p.SMTP), nil + } + + return nil, nil +}