diff --git a/api/agent.go b/api/agent.go index 35c0676b5..34cfaa7ee 100644 --- a/api/agent.go +++ b/api/agent.go @@ -67,17 +67,19 @@ type AgentCheckRegistration struct { // AgentServiceCheck is used to define a node or service level check type AgentServiceCheck struct { - Script string `json:",omitempty"` - DockerContainerID string `json:",omitempty"` - Shell string `json:",omitempty"` // Only supported for Docker. - Interval string `json:",omitempty"` - Timeout string `json:",omitempty"` - TTL string `json:",omitempty"` - HTTP string `json:",omitempty"` - TCP string `json:",omitempty"` - Status string `json:",omitempty"` - Notes string `json:",omitempty"` - TLSSkipVerify bool `json:",omitempty"` + Script string `json:",omitempty"` + DockerContainerID string `json:",omitempty"` + Shell string `json:",omitempty"` // Only supported for Docker. + Interval string `json:",omitempty"` + Timeout string `json:",omitempty"` + TTL string `json:",omitempty"` + HTTP string `json:",omitempty"` + Header map[string][]string `json:",omitempty"` + Method string `json:",omitempty"` + TCP string `json:",omitempty"` + Status string `json:",omitempty"` + Notes string `json:",omitempty"` + TLSSkipVerify bool `json:",omitempty"` // In Consul 0.7 and later, checks that are associated with a service // may also contain this optional DeregisterCriticalServiceAfter field, diff --git a/command/agent/agent.go b/command/agent/agent.go index fd3cd88b7..32639da91 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -1575,6 +1575,8 @@ func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *CheckType, persist Notify: &a.state, CheckID: check.CheckID, HTTP: chkType.HTTP, + Header: chkType.Header, + Method: chkType.Method, Interval: chkType.Interval, Timeout: chkType.Timeout, Logger: a.logger, diff --git a/command/agent/check.go b/command/agent/check.go index 7fbeacfe2..50a67172a 100644 --- a/command/agent/check.go +++ b/command/agent/check.go @@ -57,6 +57,8 @@ type CheckType struct { Script string HTTP string + Header map[string][]string + Method string TCP string Interval time.Duration DockerContainerID string @@ -347,6 +349,8 @@ type CheckHTTP struct { Notify CheckNotifier CheckID types.CheckID HTTP string + Header map[string][]string + Method string Interval time.Duration Timeout time.Duration Logger *log.Logger @@ -429,15 +433,31 @@ func (c *CheckHTTP) run() { // check is invoked periodically to perform the HTTP check func (c *CheckHTTP) check() { - req, err := http.NewRequest("GET", c.HTTP, nil) + method := c.Method + if method == "" { + method = "GET" + } + + req, err := http.NewRequest(method, c.HTTP, nil) if err != nil { c.Logger.Printf("[WARN] agent: http request failed '%s': %s", c.HTTP, err) c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, err.Error()) return } - req.Header.Set("User-Agent", UserAgent) - req.Header.Set("Accept", "text/plain, text/*, */*") + req.Header = http.Header(c.Header) + + // this happens during testing but not in prod + if req.Header == nil { + req.Header = make(http.Header) + } + + if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", UserAgent) + } + if req.Header.Get("Accept") == "" { + req.Header.Set("Accept", "text/plain, text/*, */*") + } resp, err := c.httpClient.Do(req) if err != nil { diff --git a/command/agent/check_test.go b/command/agent/check_test.go index 9f182fcc5..a1fdba8fc 100644 --- a/command/agent/check_test.go +++ b/command/agent/check_test.go @@ -4,12 +4,14 @@ import ( "bytes" "errors" "fmt" + "io/ioutil" "log" "net" "net/http" "net/http/httptest" "os" "os/exec" + "reflect" "strings" "testing" "time" @@ -28,7 +30,7 @@ func expectStatus(t *testing.T, script, status string) { CheckID: types.CheckID("foo"), Script: script, Interval: 10 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), } check.Start() defer check.Stop() @@ -71,7 +73,7 @@ func TestCheckMonitor_Timeout(t *testing.T) { Script: "sleep 1 && exit 0", Interval: 10 * time.Millisecond, Timeout: 5 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), } check.Start() defer check.Stop() @@ -95,7 +97,7 @@ func TestCheckMonitor_RandomStagger(t *testing.T) { CheckID: types.CheckID("foo"), Script: "exit 0", Interval: 25 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), } check.Start() defer check.Stop() @@ -120,7 +122,7 @@ func TestCheckMonitor_LimitOutput(t *testing.T) { CheckID: types.CheckID("foo"), Script: "od -N 81920 /dev/urandom", Interval: 25 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), } check.Start() defer check.Stop() @@ -140,7 +142,7 @@ func TestCheckTTL(t *testing.T) { Notify: notif, CheckID: types.CheckID("foo"), TTL: 100 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), } check.Start() defer check.Stop() @@ -178,126 +180,98 @@ func TestCheckTTL(t *testing.T) { } } -func mockHTTPServer(responseCode int) *httptest.Server { - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - // Body larger than 4k limit - body := bytes.Repeat([]byte{'a'}, 2*CheckBufSize) - w.WriteHeader(responseCode) - w.Write(body) - return - }) +func TestCheckHTTP(t *testing.T) { + t.Parallel() - return httptest.NewServer(mux) -} + tests := []struct { + desc string + code int + method string + header http.Header + status string + }{ + // passing + {code: 200, status: api.HealthPassing}, + {code: 201, status: api.HealthPassing}, + {code: 250, status: api.HealthPassing}, + {code: 299, status: api.HealthPassing}, -func mockTLSHTTPServer(responseCode int) *httptest.Server { - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - // Body larger than 4k limit - body := bytes.Repeat([]byte{'a'}, 2*CheckBufSize) - w.WriteHeader(responseCode) - w.Write(body) - return - }) + // warning + {code: 429, status: api.HealthWarning}, - return httptest.NewTLSServer(mux) -} + // critical + {code: 150, status: api.HealthCritical}, + {code: 199, status: api.HealthCritical}, + {code: 300, status: api.HealthCritical}, + {code: 400, status: api.HealthCritical}, + {code: 500, status: api.HealthCritical}, -func expectHTTPStatus(t *testing.T, url string, status string) { - notif := mock.NewNotify() - check := &CheckHTTP{ - Notify: notif, - CheckID: types.CheckID("foo"), - HTTP: url, - Interval: 10 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + // custom method + {desc: "custom method GET", code: 200, method: "GET", status: api.HealthPassing}, + {desc: "custom method POST", code: 200, method: "POST", status: api.HealthPassing}, + {desc: "custom method abc", code: 200, method: "abc", status: api.HealthPassing}, + + // custom header + {desc: "custom header", code: 200, header: http.Header{"A": []string{"b", "c"}}, status: api.HealthPassing}, } - check.Start() - defer check.Stop() - retry.Run(t, func(r *retry.R) { - if got, want := notif.Updates("foo"), 2; got < want { - r.Fatalf("got %d updates want at least %d", got, want) + + for _, tt := range tests { + desc := tt.desc + if desc == "" { + desc = fmt.Sprintf("code %d -> status %s", tt.code, tt.status) } - if got, want := notif.State("foo"), status; got != want { - r.Fatalf("got state %q want %q", got, want) - } - // Allow slightly more data than CheckBufSize, for the header - if n := len(notif.Output("foo")); n > (CheckBufSize + 256) { - r.Fatalf("output too long: %d (%d-byte limit)", n, CheckBufSize) - } - }) -} + t.Run(desc, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if tt.method != "" && tt.method != r.Method { + w.WriteHeader(999) + return + } + if len(tt.header) > 0 && !reflect.DeepEqual(tt.header, r.Header) { + w.WriteHeader(999) + return + } + // Body larger than 4k limit + body := bytes.Repeat([]byte{'a'}, 2*CheckBufSize) + w.WriteHeader(tt.code) + w.Write(body) + })) + defer server.Close() -func TestCheckHTTPCritical(t *testing.T) { - t.Parallel() - // var server *httptest.Server + notif := mock.NewNotify() + check := &CheckHTTP{ + Notify: notif, + CheckID: types.CheckID("foo"), + HTTP: server.URL, + Method: tt.method, + Header: tt.header, + Interval: 10 * time.Millisecond, + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), + } + check.Start() + defer check.Stop() - server := mockHTTPServer(150) - expectHTTPStatus(t, server.URL, api.HealthCritical) - server.Close() - - // 2xx - 1 - server = mockHTTPServer(199) - expectHTTPStatus(t, server.URL, api.HealthCritical) - server.Close() - - // 2xx + 1 - server = mockHTTPServer(300) - expectHTTPStatus(t, server.URL, api.HealthCritical) - server.Close() - - server = mockHTTPServer(400) - expectHTTPStatus(t, server.URL, api.HealthCritical) - server.Close() - - server = mockHTTPServer(500) - expectHTTPStatus(t, server.URL, api.HealthCritical) - server.Close() -} - -func TestCheckHTTPPassing(t *testing.T) { - t.Parallel() - var server *httptest.Server - - server = mockHTTPServer(200) - expectHTTPStatus(t, server.URL, api.HealthPassing) - server.Close() - - server = mockHTTPServer(201) - expectHTTPStatus(t, server.URL, api.HealthPassing) - server.Close() - - server = mockHTTPServer(250) - expectHTTPStatus(t, server.URL, api.HealthPassing) - server.Close() - - server = mockHTTPServer(299) - expectHTTPStatus(t, server.URL, api.HealthPassing) - server.Close() -} - -func TestCheckHTTPWarning(t *testing.T) { - t.Parallel() - server := mockHTTPServer(429) - expectHTTPStatus(t, server.URL, api.HealthWarning) - server.Close() -} - -func mockSlowHTTPServer(responseCode int, sleep time.Duration) *httptest.Server { - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - time.Sleep(sleep) - w.WriteHeader(responseCode) - return - }) - - return httptest.NewServer(mux) + retry.Run(t, func(r *retry.R) { + if got, want := notif.Updates("foo"), 2; got < want { + r.Fatalf("got %d updates want at least %d", got, want) + } + if got, want := notif.State("foo"), tt.status; got != want { + r.Fatalf("got state %q want %q", got, want) + } + // Allow slightly more data than CheckBufSize, for the header + if n := len(notif.Output("foo")); n > (CheckBufSize + 256) { + r.Fatalf("output too long: %d (%d-byte limit)", n, CheckBufSize) + } + }) + }) + } } func TestCheckHTTPTimeout(t *testing.T) { t.Parallel() - server := mockSlowHTTPServer(200, 10*time.Millisecond) + timeout := 5 * time.Millisecond + server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + time.Sleep(2 * timeout) + })) defer server.Close() notif := mock.NewNotify() @@ -305,9 +279,9 @@ func TestCheckHTTPTimeout(t *testing.T) { Notify: notif, CheckID: types.CheckID("bar"), HTTP: server.URL, - Timeout: 5 * time.Millisecond, + Timeout: timeout, Interval: 10 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), } check.Start() @@ -328,7 +302,7 @@ func TestCheckHTTP_disablesKeepAlives(t *testing.T) { CheckID: types.CheckID("foo"), HTTP: "http://foo.bar/baz", Interval: 10 * time.Second, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), } check.Start() @@ -345,7 +319,7 @@ func TestCheckHTTP_TLSSkipVerify_defaultFalse(t *testing.T) { CheckID: "foo", HTTP: "https://foo.bar/baz", Interval: 10 * time.Second, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), } check.Start() @@ -356,6 +330,15 @@ func TestCheckHTTP_TLSSkipVerify_defaultFalse(t *testing.T) { } } +func mockTLSHTTPServer(code int) *httptest.Server { + return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Body larger than 4k limit + body := bytes.Repeat([]byte{'a'}, 2*CheckBufSize) + w.WriteHeader(code) + w.Write(body) + })) +} + func TestCheckHTTP_TLSSkipVerify_true_pass(t *testing.T) { t.Parallel() server := mockTLSHTTPServer(200) @@ -368,7 +351,7 @@ func TestCheckHTTP_TLSSkipVerify_true_pass(t *testing.T) { CheckID: types.CheckID("skipverify_true"), HTTP: server.URL, Interval: 5 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), TLSSkipVerify: true, } @@ -397,7 +380,7 @@ func TestCheckHTTP_TLSSkipVerify_true_fail(t *testing.T) { CheckID: types.CheckID("skipverify_true"), HTTP: server.URL, Interval: 5 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), TLSSkipVerify: true, } check.Start() @@ -425,7 +408,7 @@ func TestCheckHTTP_TLSSkipVerify_false(t *testing.T) { CheckID: types.CheckID("skipverify_false"), HTTP: server.URL, Interval: 100 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), TLSSkipVerify: false, } @@ -472,7 +455,7 @@ func expectTCPStatus(t *testing.T, tcp string, status string) { CheckID: types.CheckID("foo"), TCP: tcp, Interval: 10 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), } check.Start() defer check.Stop() @@ -649,7 +632,7 @@ func expectDockerCheckStatus(t *testing.T, dockerClient DockerClient, status str DockerContainerID: "54432bad1fc7", Shell: "/bin/sh", Interval: 10 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), dockerClient: dockerClient, } check.Start() @@ -711,7 +694,7 @@ func TestDockerCheckDefaultToSh(t *testing.T) { Script: "/health.sh", DockerContainerID: "54432bad1fc7", Interval: 10 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), dockerClient: &fakeDockerClientWithNoErrors{}, } check.Start() @@ -733,7 +716,7 @@ func TestDockerCheckUseShellFromEnv(t *testing.T) { Script: "/health.sh", DockerContainerID: "54432bad1fc7", Interval: 10 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), dockerClient: &fakeDockerClientWithNoErrors{}, } check.Start() @@ -756,7 +739,7 @@ func TestDockerCheckTruncateOutput(t *testing.T) { DockerContainerID: "54432bad1fc7", Shell: "/bin/sh", Interval: 10 * time.Millisecond, - Logger: log.New(os.Stderr, UniqueID(), log.LstdFlags), + Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags), dockerClient: &fakeDockerClientWithLongOutput{}, } check.Start() @@ -768,5 +751,4 @@ func TestDockerCheckTruncateOutput(t *testing.T) { if len(notif.Output("foo")) > CheckBufSize+100 { t.Fatalf("output size is too long") } - } diff --git a/command/agent/config.go b/command/agent/config.go index 411c34127..372d86910 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" + "errors" "fmt" "io" "net" @@ -1413,83 +1414,97 @@ AFTER_FIX: return &result, nil } -func FixupCheckType(raw interface{}) error { - var ttlKey, intervalKey, timeoutKey string - const deregisterKey = "DeregisterCriticalServiceAfter" +var errInvalidHeaderFormat = errors.New("agent: invalid format of 'header' field") - // Handle decoding of time durations +func FixupCheckType(raw interface{}) error { rawMap, ok := raw.(map[string]interface{}) if !ok { return nil } + parseDuration := func(v interface{}) (time.Duration, error) { + if v == nil { + return 0, nil + } + if d, ok := v.(time.Duration); ok { + return d, nil + } + s, ok := v.(string) + if !ok { + return 0, fmt.Errorf("invalid format") + } + d, err := time.ParseDuration(s) + if err != nil { + return 0, err + } + return d, nil + } + + parseHeaderMap := func(v interface{}) (map[string][]string, error) { + if v == nil { + return nil, nil + } + vm, ok := v.(map[string]interface{}) + if !ok { + return nil, errInvalidHeaderFormat + } + m := map[string][]string{} + for k, vv := range vm { + vs, ok := vv.([]interface{}) + if !ok { + return nil, errInvalidHeaderFormat + } + for _, vs := range vs { + s, ok := vs.(string) + if !ok { + return nil, errInvalidHeaderFormat + } + m[k] = append(m[k], s) + } + } + return m, nil + } + + replace := func(oldKey, newKey string, val interface{}) { + rawMap[newKey] = val + if oldKey != newKey { + delete(rawMap, oldKey) + } + } + for k, v := range rawMap { switch strings.ToLower(k) { - case "ttl": - ttlKey = k - case "interval": - intervalKey = k - case "timeout": - timeoutKey = k - case "deregister_critical_service_after": - rawMap[deregisterKey] = v - delete(rawMap, k) - case "service_id": - rawMap["serviceid"] = v - delete(rawMap, k) + case "header": + h, err := parseHeaderMap(v) + if err != nil { + return fmt.Errorf("invalid %q: %s", k, err) + } + rawMap[k] = h + + case "ttl", "interval", "timeout": + d, err := parseDuration(v) + if err != nil { + return fmt.Errorf("invalid %q: %v", k, err) + } + rawMap[k] = d + + case "deregister_critical_service_after", "deregistercriticalserviceafter": + d, err := parseDuration(v) + if err != nil { + return fmt.Errorf("invalid %q: %v", k, err) + } + replace(k, "DeregisterCriticalServiceAfter", d) + case "docker_container_id": - rawMap["DockerContainerID"] = v - delete(rawMap, k) + replace(k, "DockerContainerID", v) + + case "service_id": + replace(k, "ServiceID", v) + case "tls_skip_verify": - rawMap["TLSSkipVerify"] = v - delete(rawMap, k) + replace(k, "TLSSkipVerify", v) } } - - if ttl, ok := rawMap[ttlKey]; ok { - ttlS, ok := ttl.(string) - if ok { - dur, err := time.ParseDuration(ttlS) - if err != nil { - return err - } - rawMap[ttlKey] = dur - } - } - - if interval, ok := rawMap[intervalKey]; ok { - intervalS, ok := interval.(string) - if ok { - dur, err := time.ParseDuration(intervalS) - if err != nil { - return err - } - rawMap[intervalKey] = dur - } - } - - if timeout, ok := rawMap[timeoutKey]; ok { - timeoutS, ok := timeout.(string) - if ok { - dur, err := time.ParseDuration(timeoutS) - if err != nil { - return err - } - rawMap[timeoutKey] = dur - } - } - - if deregister, ok := rawMap[deregisterKey]; ok { - timeoutS, ok := deregister.(string) - if ok { - dur, err := time.ParseDuration(timeoutS) - if err != nil { - return err - } - rawMap[deregisterKey] = dur - } - } - return nil } diff --git a/command/agent/config_test.go b/command/agent/config_test.go index ef1ed0c63..1a802ffe6 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -724,6 +724,8 @@ func TestDecodeConfig(t *testing.T) { "Notes": "j", "Script": "k", "HTTP": "l", + "Header": {"a":["b"], "c":["d", "e"]}, + "Method": "x", "TCP": "m", "DockerContainerID": "n", "Shell": "o", @@ -752,6 +754,8 @@ func TestDecodeConfig(t *testing.T) { Notes: "j", Script: "k", HTTP: "l", + Header: map[string][]string{"a": []string{"b"}, "c": []string{"d", "e"}}, + Method: "x", TCP: "m", DockerContainerID: "n", Shell: "o", @@ -784,6 +788,8 @@ func TestDecodeConfig(t *testing.T) { "Notes": "j", "Script": "k", "HTTP": "l", + "Header": {"a":["b"], "c":["d", "e"]}, + "Method": "x", "TCP": "m", "DockerContainerID": "n", "Shell": "o", @@ -800,6 +806,8 @@ func TestDecodeConfig(t *testing.T) { "Notes": "jj", "Script": "kk", "HTTP": "ll", + "Header": {"aa":["bb"], "cc":["dd", "ee"]}, + "Method": "xx", "TCP": "mm", "DockerContainerID": "nn", "Shell": "oo", @@ -830,6 +838,8 @@ func TestDecodeConfig(t *testing.T) { Notes: "j", Script: "k", HTTP: "l", + Header: map[string][]string{"a": []string{"b"}, "c": []string{"d", "e"}}, + Method: "x", TCP: "m", DockerContainerID: "n", Shell: "o", @@ -846,6 +856,8 @@ func TestDecodeConfig(t *testing.T) { Notes: "jj", Script: "kk", HTTP: "ll", + Header: map[string][]string{"aa": []string{"bb"}, "cc": []string{"dd", "ee"}}, + Method: "xx", TCP: "mm", DockerContainerID: "nn", Shell: "oo", @@ -879,6 +891,8 @@ func TestDecodeConfig(t *testing.T) { "Notes": "j", "Script": "k", "HTTP": "l", + "Header": {"a":["b"], "c":["d", "e"]}, + "Method": "x", "TCP": "m", "DockerContainerID": "n", "Shell": "o", @@ -904,6 +918,8 @@ func TestDecodeConfig(t *testing.T) { "Notes": "jj", "Script": "kk", "HTTP": "ll", + "Header": {"aa":["bb"], "cc":["dd", "ee"]}, + "Method": "xx", "TCP": "mm", "DockerContainerID": "nn", "Shell": "oo", @@ -933,6 +949,8 @@ func TestDecodeConfig(t *testing.T) { Notes: "j", Script: "k", HTTP: "l", + Header: map[string][]string{"a": []string{"b"}, "c": []string{"d", "e"}}, + Method: "x", TCP: "m", DockerContainerID: "n", Shell: "o", @@ -958,6 +976,8 @@ func TestDecodeConfig(t *testing.T) { Notes: "jj", Script: "kk", HTTP: "ll", + Header: map[string][]string{"aa": []string{"bb"}, "cc": []string{"dd", "ee"}}, + Method: "xx", TCP: "mm", DockerContainerID: "nn", Shell: "oo", @@ -985,6 +1005,8 @@ func TestDecodeConfig(t *testing.T) { "script": "d", "shell": "e", "http": "f", + "Header": {"a":["b"], "c":["d", "e"]}, + "Method": "x", "tcp": "g", "docker_container_id": "h", "tls_skip_verify": true, @@ -1006,6 +1028,8 @@ func TestDecodeConfig(t *testing.T) { Script: "d", Shell: "e", HTTP: "f", + Header: map[string][]string{"a": []string{"b"}, "c": []string{"d", "e"}}, + Method: "x", TCP: "g", DockerContainerID: "h", TLSSkipVerify: true, @@ -1031,6 +1055,8 @@ func TestDecodeConfig(t *testing.T) { "script": "g", "shell": "h", "http": "i", + "Header": {"a":["b"], "c":["d", "e"]}, + "Method": "x", "tcp": "j", "docker_container_id": "k", "tls_skip_verify": true, @@ -1049,6 +1075,8 @@ func TestDecodeConfig(t *testing.T) { "script": "gg", "shell": "hh", "http": "ii", + "Header": {"aa":["bb"], "cc":["dd", "ee"]}, + "Method": "xx", "tcp": "jj", "docker_container_id": "kk", "tls_skip_verify": false, @@ -1071,6 +1099,8 @@ func TestDecodeConfig(t *testing.T) { Script: "g", Shell: "h", HTTP: "i", + Header: map[string][]string{"a": []string{"b"}, "c": []string{"d", "e"}}, + Method: "x", TCP: "j", DockerContainerID: "k", TLSSkipVerify: true, @@ -1089,6 +1119,8 @@ func TestDecodeConfig(t *testing.T) { Script: "gg", Shell: "hh", HTTP: "ii", + Header: map[string][]string{"aa": []string{"bb"}, "cc": []string{"dd", "ee"}}, + Method: "xx", TCP: "jj", DockerContainerID: "kk", TLSSkipVerify: false, diff --git a/command/agent/structs.go b/command/agent/structs.go index 8d950352b..d269fbbdc 100644 --- a/command/agent/structs.go +++ b/command/agent/structs.go @@ -62,6 +62,8 @@ type CheckDefinition struct { // Script string HTTP string + Header map[string][]string + Method string TCP string Interval time.Duration DockerContainerID string diff --git a/website/source/docs/agent/checks.html.md b/website/source/docs/agent/checks.html.md index 8664c386b..d9ae67acc 100644 --- a/website/source/docs/agent/checks.html.md +++ b/website/source/docs/agent/checks.html.md @@ -27,17 +27,21 @@ There are five different kinds of checks: `timeout` field in the check definition. * HTTP + Interval - These checks make an HTTP `GET` request every Interval (e.g. - every 30 seconds) to the specified URL. The status of the service depends on the HTTP response code: - any `2xx` code is considered passing, a `429 Too Many Requests` is a warning, and anything else is a failure. - This type of check should be preferred over a script that uses `curl` or another external process - to check a simple HTTP operation. By default, HTTP checks will be configured - with a request timeout equal to the check interval, with a max of 10 seconds. - It is possible to configure a custom HTTP check timeout value by specifying - the `timeout` field in the check definition. The output of the check is - limited to roughly 4K. Responses larger than this will be truncated. HTTP checks - also support SSL. By default, a valid SSL certificate is expected. Certificate - verification can be turned off by setting the `tls_skip_verify` field to `true` - in the check definition. + every 30 seconds) to the specified URL. The status of the service depends on + the HTTP response code: any `2xx` code is considered passing, a `429 Too Many + Requests` is a warning, and anything else is a failure. This type of check + should be preferred over a script that uses `curl` or another external process + to check a simple HTTP operation. By default, HTTP checks are `GET` requests + unless the `method` field specifies a different method. Additional header + fields can be set through the `header` field which is a map of lists of + strings, e.g. `{"x-foo": ["bar", "baz"]}`. By default, HTTP checks will be + configured with a request timeout equal to the check interval, with a max of + 10 seconds. It is possible to configure a custom HTTP check timeout value by + specifying the `timeout` field in the check definition. The output of the + check is limited to roughly 4K. Responses larger than this will be truncated. + HTTP checks also support SSL. By default, a valid SSL certificate is expected. + Certificate verification can be turned off by setting the `tls_skip_verify` + field to `true` in the check definition. * TCP + Interval - These checks make an TCP connection attempt every Interval (e.g. every 30 seconds) to the specified IP/hostname and port. If no hostname @@ -104,6 +108,8 @@ A HTTP check: "id": "api", "name": "HTTP API on port 5000", "http": "http://localhost:5000/health", + "method": "POST", + "header": {"x-foo":["bar", "baz"]}, "interval": "10s", "timeout": "1s" }