From 7f437754555d6a43608614f6ddb6e2c874dd32d4 Mon Sep 17 00:00:00 2001 From: NopeCharon <115794254+NopeCharon@users.noreply.github.com> Date: Thu, 29 Oct 2026 06:07:36 +0530 Subject: [PATCH] backend: serve /healthz and /api/healthz from a single handler The backend had two independent health endpoints: - /healthz, registered in backend.NewServer right after the web server is created, so it is always available. The Helm chart probes use it. - /api/healthz, registered in handler.AddRoutes, which only runs when the frontend is enabled. It returned an empty body, while /healthz returned "ok" without the no-cache headers. Register both paths in http.NewServer and point them at one handleHealthz function that sets the no-cache headers and writes "ok". This removes the duplicate implementation, makes both endpoints behave the same, and keeps /api/healthz available in --frontend-disabled mode. Both paths stay registered so existing probes and API clients keep working. Add a unit test that checks status, body and cache headers for both paths on a freshly created server. Signed-off-by: NopeCharon <115794254+NopeCharon@users.noreply.github.com> --- backend/http/handler.go | 6 ------ backend/http/server.go | 12 +++++++++++ backend/http/server_test.go | 43 +++++++++++++++++++++++++++++++++++++ backend/server.go | 7 ------ 4 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 backend/http/server_test.go diff --git a/backend/http/handler.go b/backend/http/handler.go index 9e73238d7..3cd476722 100644 --- a/backend/http/handler.go +++ b/backend/http/handler.go @@ -152,7 +152,6 @@ func NewHandler( func (h *handler) AddRoutes(mux *mux.Router) error { // Public API routes (no authentication required) - mux.HandleFunc("/api/healthz", h.handleHealthz).Methods(http.MethodGet) mux.HandleFunc("/api/bindable-resources", h.handleBindableResources).Methods(http.MethodGet) // Intentionally unauthenticated: serves static, deterministic deployment YAML // (konnector image tag is the only variable, derived from the server's own version). @@ -197,11 +196,6 @@ func (h *handler) AddRoutes(mux *mux.Router) error { return nil } -func (h *handler) handleHealthz(w http.ResponseWriter, r *http.Request) { - prepareNoCache(w) - w.WriteHeader(http.StatusOK) -} - func (h *handler) handlePing(w http.ResponseWriter, r *http.Request) { prepareNoCache(w) w.WriteHeader(http.StatusOK) diff --git a/backend/http/server.go b/backend/http/server.go index a7a5208ca..97e20c22f 100644 --- a/backend/http/server.go +++ b/backend/http/server.go @@ -41,9 +41,21 @@ func NewServer(options *options.Serve) (*Server, error) { listener: options.Listener, } + // Health endpoints are registered here, rather than with the API routes, + // so they are served even when the frontend is disabled. /healthz is used + // by the Kubernetes probes, /api/healthz is kept for API clients. + server.Router.HandleFunc("/healthz", handleHealthz).Methods(http.MethodGet) + server.Router.HandleFunc("/api/healthz", handleHealthz).Methods(http.MethodGet) + return server, nil } +func handleHealthz(w http.ResponseWriter, r *http.Request) { + prepareNoCache(w) + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) //nolint:errcheck +} + func (s *Server) Addr() net.Addr { return s.listener.Addr() } diff --git a/backend/http/server_test.go b/backend/http/server_test.go new file mode 100644 index 000000000..475ce4e4f --- /dev/null +++ b/backend/http/server_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2026 The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package http + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/kube-bind/kube-bind/backend/options" +) + +func TestHealthzEndpoints(t *testing.T) { + server, err := NewServer(options.NewServe()) + require.NoError(t, err) + + for _, path := range []string{"/healthz", "/api/healthz"} { + t.Run(path, func(t *testing.T) { + rec := httptest.NewRecorder() + server.Router.ServeHTTP(rec, httptest.NewRequestWithContext(t.Context(), http.MethodGet, path, nil)) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "ok", rec.Body.String()) + require.Equal(t, noCacheHeaders["Cache-Control"], rec.Header().Get("Cache-Control")) + }) + } +} diff --git a/backend/server.go b/backend/server.go index bd8b2fbf4..a8e5a2186 100644 --- a/backend/server.go +++ b/backend/server.go @@ -21,7 +21,6 @@ import ( "encoding/base64" "fmt" "net" - nethttp "net/http" "strings" "sync" @@ -118,12 +117,6 @@ func NewServer(ctx context.Context, c *Config) (*Server, error) { return nil, fmt.Errorf("error setting up HTTP Server: %w", err) } - // Register health check endpoint - s.WebServer.Router.HandleFunc("/healthz", func(w nethttp.ResponseWriter, r *nethttp.Request) { - w.WriteHeader(nethttp.StatusOK) - w.Write([]byte("ok")) //nolint:errcheck - }).Methods("GET") - if c.Options.FrontendDisabled { logger.Info("Frontend is disabled; running in backend only mode") } else {