Repository: hashicorp/nomad (Go)

Issue:
Nomad service health checks urlencode the path
### Nomad version
Nomad v1.6.0-beta.1+ent
BuildDate 2023-06-27T16:32:15Z
Revision b26558fbf8f4cf89dbaa29068620241746c583bb

### Issue

A Nomad service health check configured as so:
```hcl
  service {
        name = "vault-api"
        port = "http"
        provider = "nomad"
        check {
          name = "healthy"
          type = "http"
          path = "/v1/sys/health?sealedcode=200"
          interval = "10s"
          timeout  = "5s"
}
```

results in the path being url encoded by Nomad:
(example access log from nginx)
```
192.168.1.203 - - [03/Jul/2023:10:10:33 +0000] "GET /v1/sys/health%3Fsealedcode=200 HTTP/1.1" 502 157 "-" "Nomad/1.6.0-beta.1+ent (+https://www.nomadproject.io/; go1.20.5)" "-"
```

Which results in failing healthchecks.

### Reproduction steps

Use a Nomad health check with a query string.

#### Expected Result

The health check should be on the path as configured.

#### Actual Result

URL encoded paths being used by the health check.




Discussion:
I'm going to lock this issue because it has been closed for _120 days_ ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.



Patch:
diff --git a/.changelog/17936.txt b/.changelog/17936.txt
new file mode 100644
index 00000000000..5861a694503
--- /dev/null
+++ b/.changelog/17936.txt
@@ -0,0 +1,3 @@
+```release-note:bug
+services: Fixed a bug that prevented passing query parameters in Nomad native service discovery HTTP health check paths
+```
diff --git a/client/serviceregistration/checks/client.go b/client/serviceregistration/checks/client.go
index 4ebc6ac879a..cdaec49d8f2 100644
--- a/client/serviceregistration/checks/client.go
+++ b/client/serviceregistration/checks/client.go
@@ -155,11 +155,18 @@ func (c *checker) checkHTTP(ctx context.Context, qc *QueryContext, q *Query) *st
 		return qr
 	}
 
-	u := (&url.URL{
+	relative, err := url.Parse(q.Path)
+	if err != nil {
+		qr.Output = err.Error()
+		qr.Status = structs.CheckFailure
+		return qr
+	}
+
+	base := url.URL{
 		Scheme: q.Protocol,
 		Host:   addr,
-		Path:   q.Path,
-	}).String()
+	}
+	u := base.ResolveReference(relative).String()
 
 	request, err := http.NewRequest(q.Method, u, nil)
 	if err != nil {


Test patch:
diff --git a/client/serviceregistration/checks/client_test.go b/client/serviceregistration/checks/client_test.go
index 5a81ad0ab0f..03df2ce227b 100644
--- a/client/serviceregistration/checks/client_test.go
+++ b/client/serviceregistration/checks/client_test.go
@@ -39,6 +39,19 @@ func TestChecker_Do_HTTP(t *testing.T) {
 
 	// create an http server with various responses
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// handle query param requests with string match because we want to
+		// test the path is set correctly instead of with escaped query params.
+		if strings.Contains(r.URL.Path, "query-param") {
+			if r.URL.RawQuery == "" {
+				w.WriteHeader(400)
+				_, _ = io.WriteString(w, "400 bad request")
+			} else {
+				w.WriteHeader(200)
+				_, _ = io.WriteString(w, "200 ok")
+			}
+			return
+		}
+
 		switch r.URL.Path {
 		case "/fail":
 			w.WriteHeader(500)
@@ -182,6 +195,16 @@ func TestChecker_Do_HTTP(t *testing.T) {
 			http.StatusCreated,
 			truncate,
 		),
+	}, {
+		name: "query param",
+		qc:   makeQueryContext(),
+		q:    makeQuery(structs.Healthiness, "query-param?a=b"),
+		expResult: makeExpResult(
+			structs.Healthiness,
+			structs.CheckSuccess,
+			http.StatusOK,
+			"nomad: http ok",
+		),
 	}}
 
 	for _, tc := range cases {