promtool: fix check healthy/ready failing with trailing slash in URL

CheckServerStatus built the request URL by concatenating the
user-provided URL with the endpoint path:

    serverURL.String() + checkEndpoint

When the URL ends with a slash (e.g. "http://localhost:9090/"), this
produces a doubled slash like "http://localhost:9090//-/healthy". The
Prometheus router registers "/-/healthy" and "/-/ready" exactly and
does not collapse repeated slashes, so the request 404s and
"promtool check healthy" / "promtool check ready" fail.

Use url.JoinPath to append the endpoint, which normalises the slash and
also handles a configured route prefix correctly. JoinPath returns a new
URL, so the original is left untouched for the error message.

Signed-off-by: kigland <shuaizhicheng336@gmail.com>
This commit is contained in:
kigland 2026-06-04 18:29:00 +08:00
parent 14b9a0e222
commit f6dae733c5
2 changed files with 33 additions and 1 deletions

View file

@ -569,8 +569,11 @@ func CheckServerStatus(serverURL *url.URL, checkEndpoint string, roundTripper ht
serverURL.Scheme = "http"
}
// Join the endpoint onto the server URL path so that a trailing slash in
// the user-provided URL does not result in a doubled slash (e.g.
// "//-/healthy"), which the Prometheus router does not match.
config := api.Config{
Address: serverURL.String() + checkEndpoint,
Address: serverURL.JoinPath(checkEndpoint).String(),
RoundTripper: roundTripper,
}

View file

@ -120,6 +120,35 @@ func TestQueryInstantHeaders(t *testing.T) {
require.Equal(t, "value", getRequest().Header.Get("X-Custom"))
}
func TestCheckServerStatus(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
urlSuffix string
}{
{name: "no trailing slash", urlSuffix: ""},
{name: "trailing slash", urlSuffix: "/"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
s, getRequest := mockServer(200, "Prometheus is Healthy.\n")
defer s.Close()
urlObject, err := url.Parse(s.URL + tc.urlSuffix)
require.NoError(t, err)
err = CheckServerStatus(urlObject, checkHealth, http.DefaultTransport)
require.NoError(t, err)
// The endpoint path must be requested verbatim regardless of a
// trailing slash in the user-provided URL. A naive string
// concatenation would request "//-/healthy" here, which the
// Prometheus router does not match.
require.Equal(t, checkHealth, getRequest().URL.Path)
})
}
}
func mockServer(code int, body string) (*httptest.Server, func() *http.Request) {
var req *http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {