Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ When trusted proxies are configured, forwarded headers are honored only for trus
Response:
- JSON object: `status`, `message`, optional `client`

### `GET /healthz`

Unauthenticated liveness endpoint for orchestration probes.

### `GET /readyz`

Unauthenticated readiness endpoint for orchestration probes.

## Config

Use one YAML file.
Expand Down
14 changes: 14 additions & 0 deletions internal/httpapi/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,23 @@ func New(cfg *appconfig.Config, resolver *ipresolver.Resolver, updater *provider
}

func (h *Handler) Register(mux *http.ServeMux) {
mux.HandleFunc("GET /healthz", h.Healthz)
mux.HandleFunc("GET /readyz", h.Readyz)
mux.HandleFunc("POST /update", h.Update)
}

func (h *Handler) Healthz(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, response{Status: "ok", Message: "alive"})
}

func (h *Handler) Readyz(w http.ResponseWriter, _ *http.Request) {
if h.cfg == nil || h.resolver == nil || h.updater == nil {
writeJSON(w, http.StatusServiceUnavailable, response{Status: "error", Message: "not ready"})
return
}
writeJSON(w, http.StatusOK, response{Status: "ok", Message: "ready"})
}

func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
token, err := bearerToken(r.Header.Get("Authorization"))
if err != nil {
Expand Down
53 changes: 53 additions & 0 deletions internal/httpapi/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,56 @@ func TestWriteJSON(t *testing.T) {
t.Fatalf("unmarshal: %v", err)
}
}

func TestHealthzRoute(t *testing.T) {
cfg := testConfig()
resolver, _ := ipresolver.New(nil)
h := New(cfg, resolver, provider.NewUpdater())
mux := http.NewServeMux()
h.Register(mux)

r := httptest.NewRequest(http.MethodGet, "/healthz", nil)
w := httptest.NewRecorder()
mux.ServeHTTP(w, r)

if w.Code != http.StatusOK {
t.Fatalf("unexpected code: %d", w.Code)
}
var body response
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if body.Status != "ok" || body.Message != "alive" {
t.Fatalf("unexpected body: %+v", body)
}
}

func TestReadyzRoute(t *testing.T) {
cfg := testConfig()
resolver, _ := ipresolver.New(nil)
h := New(cfg, resolver, provider.NewUpdater())
mux := http.NewServeMux()
h.Register(mux)

r := httptest.NewRequest(http.MethodGet, "/readyz", nil)
w := httptest.NewRecorder()
mux.ServeHTTP(w, r)

if w.Code != http.StatusOK {
t.Fatalf("unexpected code: %d", w.Code)
}
}

func TestReadyzNotReady(t *testing.T) {
h := New(nil, nil, nil)
mux := http.NewServeMux()
h.Register(mux)

r := httptest.NewRequest(http.MethodGet, "/readyz", nil)
w := httptest.NewRecorder()
mux.ServeHTTP(w, r)

if w.Code != http.StatusServiceUnavailable {
t.Fatalf("unexpected code: %d", w.Code)
}
}