Skip to content

Commit 9dedaac

Browse files
committed
HTTP probe: add support for HTTP methods, request payload and status validation
this extends the HTTP probe with additional properties related to the outgoing HTTP request. additionally a response status validation can be configured which is matched against the status line. also the initialization has been refactored to error out on invalid duration definitions instead of failing the probe execution.
1 parent bb72ca4 commit 9dedaac

File tree

4 files changed

+93
-55
lines changed

4 files changed

+93
-55
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,13 +342,21 @@ probe "mongodb" {
342342
probe "http" {
343343
wait = true
344344
http {
345+
method = "post"
345346
scheme = "http"
346347
host = {
347348
hostname = "localhost"
348349
port = 8080
349350
}
350351
path = "/status"
351352
timeout = "5s"
353+
payload = "{\"body\": 123}"
354+
# regex to match against the response status line
355+
# (e.g. 403 Forbidden)
356+
expectStatus = "(200|201)"
357+
headers = {
358+
Content-Type = "application/json"
359+
}
352360
}
353361
}
354362

internal/config/types.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,15 @@ type SMTP struct {
4343
Host
4444
}
4545

46-
type HttpGet struct {
46+
type HTTP struct {
47+
Method string
4748
Scheme string
4849
Host
49-
Path string
50-
Timeout string
50+
Path string
51+
Timeout string
52+
Payload string
53+
ExpectStatus string
54+
Headers map[string]string
5155
}
5256

5357
type Probe struct {
@@ -58,7 +62,7 @@ type Probe struct {
5862
Redis *Redis
5963
MongoDB *MongoDB
6064
Amqp *Amqp
61-
HTTP *HttpGet
65+
HTTP *HTTP
6266
SMTP *SMTP
6367
}
6468

pkg/probe/probe_http.go

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,98 @@ package probe
22

33
import (
44
"fmt"
5+
"net"
56
"net/http"
67
"net/url"
8+
"regexp"
9+
"strings"
710
"time"
811

912
"github.com/mittwald/mittnite/internal/config"
1013
"github.com/mittwald/mittnite/internal/helper"
1114
log "github.com/sirupsen/logrus"
1215
)
1316

14-
type httpGetProbe struct {
17+
type httpProbe struct {
18+
method string
1519
scheme string
1620
host string
1721
path string
18-
timeout string
22+
payload string
23+
headers map[string]string
24+
timeout time.Duration
25+
status *regexp.Regexp
1926
}
2027

21-
func NewHttpProbe(cfg *config.HttpGet) *httpGetProbe {
22-
cfg.Scheme = helper.ResolveEnv(cfg.Scheme)
28+
func NewHttpProbe(cfg *config.HTTP) (*httpProbe, error) {
29+
cfg.Method = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.Method), "GET", "method", "http")
30+
cfg.Scheme = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.Scheme), "http", "scheme", "http")
2331
cfg.Hostname = helper.ResolveEnv(cfg.Hostname)
2432
cfg.Port = helper.ResolveEnv(cfg.Port)
2533
cfg.Path = helper.ResolveEnv(cfg.Path)
26-
cfg.Timeout = helper.ResolveEnv(cfg.Timeout)
27-
28-
if cfg.Scheme == "" {
29-
cfg.Scheme = "http"
30-
}
34+
cfg.Timeout = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.Timeout), "5s", "timeout", "http")
35+
cfg.ExpectStatus = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.ExpectStatus), `(1|2|3)\d\d\s`, "expectStatus", "http")
3136

37+
method := strings.ToUpper(cfg.Method)
3238
host := cfg.Hostname
3339
if cfg.Port != "" {
34-
host = fmt.Sprintf("%s:%s", cfg.Hostname, cfg.Port)
40+
host = net.JoinHostPort(cfg.Hostname, cfg.Port)
41+
}
42+
43+
status, err := regexp.Compile(cfg.ExpectStatus)
44+
if err != nil {
45+
return nil, fmt.Errorf("invalid HTTP status line regexp: %w", err)
3546
}
3647

37-
connCfg := httpGetProbe{
48+
timeout, err := time.ParseDuration(cfg.Timeout)
49+
if err != nil {
50+
return nil, fmt.Errorf("invalid timeout duration: %w", err)
51+
}
52+
53+
connCfg := &httpProbe{
54+
method: method,
3855
scheme: cfg.Scheme,
3956
host: host,
4057
path: cfg.Path,
41-
timeout: cfg.Timeout,
58+
status: status,
59+
timeout: timeout,
60+
payload: cfg.Payload,
61+
headers: cfg.Headers,
4262
}
4363

44-
return &connCfg
64+
return connCfg, nil
4565
}
4666

47-
func (h *httpGetProbe) Exec() error {
48-
timeout := time.Second * 5
49-
if h.timeout != "" {
50-
duration, err := time.ParseDuration(h.timeout)
51-
if err == nil {
52-
timeout = duration
53-
} else {
54-
return fmt.Errorf("invalid timeout duration: %s", err)
55-
}
56-
}
57-
67+
func (h *httpProbe) Exec() error {
5868
u := url.URL{
5969
Scheme: h.scheme,
6070
Host: h.host,
6171
Path: h.path,
6272
}
6373
urlStr := u.String()
64-
6574
client := &http.Client{
66-
Timeout: timeout,
75+
Timeout: h.timeout,
6776
}
68-
res, err := client.Get(urlStr)
77+
78+
data := strings.NewReader(h.payload)
79+
req, err := http.NewRequest(h.method, u.String(), data)
80+
if err != nil {
81+
return err
82+
}
83+
84+
for k, v := range h.headers {
85+
req.Header.Set(k, v)
86+
}
87+
88+
res, err := client.Do(req)
6989
if err != nil {
7090
return err
7191
}
7292

73-
if res.StatusCode >= 200 && res.StatusCode < 400 {
74-
log.WithFields(log.Fields{"kind": "probe", "name": "http", "status": "alive", "host": urlStr}).Debug()
75-
return nil
93+
if !h.status.MatchString(res.Status) {
94+
return fmt.Errorf("http service %q returned status %q", urlStr, res.Status)
7695
}
7796

78-
return fmt.Errorf("http service '%s' returned status code %d", urlStr, res.StatusCode)
97+
log.WithFields(log.Fields{"kind": "probe", "name": "http", "status": "alive", "host": urlStr}).Debug()
98+
return nil
7999
}

pkg/probe/server.go

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -148,29 +148,15 @@ func filterWaitProbes(cfg *config.Ignition, probes map[string]Probe) map[string]
148148
}
149149

150150
func buildProbesFromConfig(cfg *config.Ignition) (map[string]Probe, error) {
151-
result := make(map[string]Probe)
152-
153151
var errs []error
154152

153+
result := make(map[string]Probe)
155154
for i := range cfg.Probes {
156-
if cfg.Probes[i].Filesystem != "" {
157-
result[cfg.Probes[i].Name] = &filesystemProbe{cfg.Probes[i].Filesystem}
158-
} else if cfg.Probes[i].MySQL != nil {
159-
result[cfg.Probes[i].Name] = NewMySQLProbe(cfg.Probes[i].MySQL)
160-
} else if cfg.Probes[i].Redis != nil {
161-
result[cfg.Probes[i].Name] = NewRedisProbe(cfg.Probes[i].Redis)
162-
} else if cfg.Probes[i].MongoDB != nil {
163-
var err error
164-
result[cfg.Probes[i].Name], err = NewMongoDBProbe(cfg.Probes[i].MongoDB)
165-
if err != nil {
166-
errs = append(errs, err)
167-
}
168-
} else if cfg.Probes[i].Amqp != nil {
169-
result[cfg.Probes[i].Name] = NewAmqpProbe(cfg.Probes[i].Amqp)
170-
} else if cfg.Probes[i].HTTP != nil {
171-
result[cfg.Probes[i].Name] = NewHttpProbe(cfg.Probes[i].HTTP)
172-
} else if cfg.Probes[i].SMTP != nil {
173-
result[cfg.Probes[i].Name] = NewSmtpProbe(cfg.Probes[i].SMTP)
155+
p, err := newProbe(cfg.Probes[i])
156+
if err != nil {
157+
errs = append(errs, err)
158+
} else if p != nil {
159+
result[cfg.Probes[i].Name] = p
174160
}
175161
}
176162

@@ -181,3 +167,23 @@ func buildProbesFromConfig(cfg *config.Ignition) (map[string]Probe, error) {
181167

182168
return result, err
183169
}
170+
171+
func newProbe(p config.Probe) (Probe, error) {
172+
if p.Filesystem != "" {
173+
return &filesystemProbe{p.Filesystem}, nil
174+
} else if p.MySQL != nil {
175+
return NewMySQLProbe(p.MySQL), nil
176+
} else if p.Redis != nil {
177+
return NewRedisProbe(p.Redis), nil
178+
} else if p.MongoDB != nil {
179+
return NewMongoDBProbe(p.MongoDB)
180+
} else if p.Amqp != nil {
181+
return NewAmqpProbe(p.Amqp), nil
182+
} else if p.HTTP != nil {
183+
return NewHttpProbe(p.HTTP)
184+
} else if p.SMTP != nil {
185+
return NewSmtpProbe(p.SMTP), nil
186+
}
187+
188+
return nil, nil
189+
}

0 commit comments

Comments
 (0)