From 3eab81fdea8f8a4d2e4a5c4301a9d76a59a13587 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 5 Apr 2024 18:20:04 +0200 Subject: [PATCH 1/4] Fix #3030 add Cache-Control header for health-check (cherry picked from commit b210a3ebd59ffd59a82bbddcf9b9cee3fee43598) --- routers/web/healthcheck/check.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/web/healthcheck/check.go b/routers/web/healthcheck/check.go index d278ee1709..6be3be86d8 100644 --- a/routers/web/healthcheck/check.go +++ b/routers/web/healthcheck/check.go @@ -85,6 +85,7 @@ func Check(w http.ResponseWriter, r *http.Request) { data, _ := json.MarshalIndent(rsp, "", " ") w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate") w.WriteHeader(rsp.Status.ToHTTPStatus()) _, _ = w.Write(data) } From 12c28641abec976167c111d1cd3700c325dbb93a Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 5 Apr 2024 22:08:22 +0200 Subject: [PATCH 2/4] Remove old proxy backwards compatibility (cherry picked from commit d2ff8f8720a5d3cc65b8d94c1458a8b4c569bb4c) --- routers/web/healthcheck/check.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/healthcheck/check.go b/routers/web/healthcheck/check.go index 6be3be86d8..b21971c9e2 100644 --- a/routers/web/healthcheck/check.go +++ b/routers/web/healthcheck/check.go @@ -85,7 +85,7 @@ func Check(w http.ResponseWriter, r *http.Request) { data, _ := json.MarshalIndent(rsp, "", " ") w.Header().Set("Content-Type", "application/json") - w.Header().Set("Cache-Control", "no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate") + w.Header().Set("Cache-Control", "no-store") w.WriteHeader(rsp.Status.ToHTTPStatus()) _, _ = w.Write(data) } From 8ddfd26d97058bcf9431ca93047d297d87e3fd25 Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 6 Apr 2024 00:25:30 +0200 Subject: [PATCH 3/4] Add health-check test (cherry picked from commit 84f5115bd1b8ed32b0160bf53acb47bbe54b75f0) --- routers/web/healthcheck/check.go | 42 ++++++++++++++-------------- tests/integration/api_health_test.go | 28 +++++++++++++++++++ 2 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 tests/integration/api_health_test.go diff --git a/routers/web/healthcheck/check.go b/routers/web/healthcheck/check.go index b21971c9e2..83dfe62537 100644 --- a/routers/web/healthcheck/check.go +++ b/routers/web/healthcheck/check.go @@ -19,7 +19,7 @@ import ( type status string const ( - // pass healthy (acceptable aliases: "ok" to support Node's Terminus and "up" for Java's SpringBoot) + // Pass healthy (acceptable aliases: "ok" to support Node's Terminus and "up" for Java's SpringBoot) // fail unhealthy (acceptable aliases: "error" to support Node's Terminus and "down" for Java's SpringBoot), and // warn healthy, with some concerns. // @@ -27,19 +27,19 @@ const ( // status: (required) indicates whether the service status is acceptable // or not. API publishers SHOULD use following values for the field: // The value of the status field is case-insensitive and is tightly - // related with the HTTP response code returned by the health endpoint. - // For "pass" status, HTTP response code in the 2xx-3xx range MUST be - // used. For "fail" status, HTTP response code in the 4xx-5xx range + // related with the HTTP Response code returned by the health endpoint. + // For "pass" status, HTTP Response code in the 2xx-3xx range MUST be + // used. For "fail" status, HTTP Response code in the 4xx-5xx range // MUST be used. In case of the "warn" status, endpoints MUST return // HTTP status in the 2xx-3xx range, and additional information SHOULD - // be provided, utilizing optional fields of the response. - pass status = "pass" - fail status = "fail" + // be provided, utilizing optional fields of the Response. + Pass status = "pass" + Fail status = "fail" warn status = "warn" ) func (s status) ToHTTPStatus() int { - if s == pass || s == warn { + if s == Pass || s == warn { return http.StatusOK } return http.StatusFailedDependency @@ -47,8 +47,8 @@ func (s status) ToHTTPStatus() int { type checks map[string][]componentStatus -// response is the data returned by the health endpoint, which will be marshaled to JSON format -type response struct { +// Response is the data returned by the health endpoint, which will be marshaled to JSON format +type Response struct { Status status `json:"status"` Description string `json:"description"` // a human-friendly description of the service Checks checks `json:"checks,omitempty"` // The Checks Object, should be omitted on installation route @@ -65,8 +65,8 @@ type componentStatus struct { // Check is the health check API handler func Check(w http.ResponseWriter, r *http.Request) { - rsp := response{ - Status: pass, + rsp := Response{ + Status: Pass, Description: setting.AppName, Checks: make(checks), } @@ -77,8 +77,8 @@ func Check(w http.ResponseWriter, r *http.Request) { statuses = append(statuses, checkCache(rsp.Checks)) } for _, s := range statuses { - if s != pass { - rsp.Status = fail + if s != Pass { + rsp.Status = Fail break } } @@ -94,22 +94,22 @@ func Check(w http.ResponseWriter, r *http.Request) { func checkDatabase(ctx context.Context, checks checks) status { st := componentStatus{} if err := db.GetEngine(ctx).Ping(); err != nil { - st.Status = fail + st.Status = Fail st.Time = getCheckTime() log.Error("database ping failed with error: %v", err) } else { - st.Status = pass + st.Status = Pass st.Time = getCheckTime() } - if setting.Database.Type.IsSQLite3() && st.Status == pass { + if setting.Database.Type.IsSQLite3() && st.Status == Pass { if !setting.EnableSQLite3 { - st.Status = fail + st.Status = Fail st.Time = getCheckTime() log.Error("SQLite3 health check failed with error: %v", "this Forgejo binary is built without SQLite3 enabled") } else { if _, err := os.Stat(setting.Database.Path); err != nil { - st.Status = fail + st.Status = Fail st.Time = getCheckTime() log.Error("SQLite3 file exists check failed with error: %v", err) } @@ -124,11 +124,11 @@ func checkDatabase(ctx context.Context, checks checks) status { func checkCache(checks checks) status { st := componentStatus{} if err := cache.GetCache().Ping(); err != nil { - st.Status = fail + st.Status = Fail st.Time = getCheckTime() log.Error("cache ping failed with error: %v", err) } else { - st.Status = pass + st.Status = Pass st.Time = getCheckTime() } checks["cache:ping"] = []componentStatus{st} diff --git a/tests/integration/api_health_test.go b/tests/integration/api_health_test.go new file mode 100644 index 0000000000..d351fcd26c --- /dev/null +++ b/tests/integration/api_health_test.go @@ -0,0 +1,28 @@ +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/routers/web/healthcheck" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestApiHeatlhCheck(t *testing.T) { + defer tests.PrepareTestEnv(t)() + t.Run("Test health-check pass", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/api/healthz") + resp := MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Header().Values("Cache-Control"), "no-store") + + var status healthcheck.Response + DecodeJSON(t, resp, &status) + assert.Equal(t, healthcheck.Pass, status.Status) + assert.Equal(t, setting.AppName, status.Description) + }) +} From 7aa0999c9fe8ba31ab90c0903308a53e02cb35a9 Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 6 Apr 2024 00:43:22 +0200 Subject: [PATCH 4/4] Remove useless t.run (cherry picked from commit 323d7ad50719e69b5969d059415ba027075232c9) --- tests/integration/api_health_test.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/integration/api_health_test.go b/tests/integration/api_health_test.go index d351fcd26c..5657f4fd06 100644 --- a/tests/integration/api_health_test.go +++ b/tests/integration/api_health_test.go @@ -13,16 +13,13 @@ import ( func TestApiHeatlhCheck(t *testing.T) { defer tests.PrepareTestEnv(t)() - t.Run("Test health-check pass", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", "/api/healthz") - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Header().Values("Cache-Control"), "no-store") + req := NewRequest(t, "GET", "/api/healthz") + resp := MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Header().Values("Cache-Control"), "no-store") - var status healthcheck.Response - DecodeJSON(t, resp, &status) - assert.Equal(t, healthcheck.Pass, status.Status) - assert.Equal(t, setting.AppName, status.Description) - }) + var status healthcheck.Response + DecodeJSON(t, resp, &status) + assert.Equal(t, healthcheck.Pass, status.Status) + assert.Equal(t, setting.AppName, status.Description) }