Skip to content

Commit e28028d

Browse files
committed
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: Add health check endpoint (go-gitea#18465) Only check for non-finished migrating task (go-gitea#19601) Make .cs highlighting legible on dark themes. (go-gitea#19604)
2 parents 5b8948d + e933f31 commit e28028d

File tree

10 files changed

+273
-25
lines changed

10 files changed

+273
-25
lines changed

docs/content/doc/installation/on-kubernetes.en-us.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,47 @@ helm install gitea gitea-charts/gitea
2525
```
2626

2727
If you would like to customize your install, which includes kubernetes ingress, please refer to the complete [Gitea helm chart configuration details](https://gitea.com/gitea/helm-chart/)
28+
29+
## Health check endpoint
30+
31+
Gitea comes with a health check endpoint `/api/healthz`, you can configure it in kubernetes like this:
32+
33+
```yaml
34+
livenessProbe:
35+
httpGet:
36+
path: /api/healthz
37+
port: http
38+
initialDelaySeconds: 200
39+
timeoutSeconds: 5
40+
periodSeconds: 10
41+
successThreshold: 1
42+
failureThreshold: 10
43+
```
44+
45+
a successful health check response will respond with http code `200`, here's example:
46+
47+
```
48+
HTTP/1.1 200 OK
49+
50+
51+
{
52+
"status": "pass",
53+
"description": "Gitea: Git with a cup of tea",
54+
"checks": {
55+
"cache:ping": [
56+
{
57+
"status": "pass",
58+
"time": "2022-02-19T09:16:08Z"
59+
}
60+
],
61+
"database:ping": [
62+
{
63+
"status": "pass",
64+
"time": "2022-02-19T09:16:08Z"
65+
}
66+
]
67+
}
68+
}
69+
```
70+
71+
for more information, please reference to kubernetes documentation [Define a liveness HTTP request](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-http-request)

docs/content/doc/installation/on-kubernetes.zh-tw.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,47 @@ helm install gitea gitea-charts/gitea
2525
```
2626

2727
若您想自訂安裝(包括使用 kubernetes ingress),請前往完整的 [Gitea helm chart configuration details](https://gitea.com/gitea/helm-chart/)
28+
29+
##運行狀況檢查終端節點
30+
31+
Gitea 附帶了一個運行狀況檢查端點 `/api/healthz`,你可以像這樣在 kubernetes 中配置它:
32+
33+
```yaml
34+
livenessProbe:
35+
httpGet:
36+
path: /api/healthz
37+
port: http
38+
initialDelaySeconds: 200
39+
timeoutSeconds: 5
40+
periodSeconds: 10
41+
successThreshold: 1
42+
failureThreshold: 10
43+
```
44+
45+
成功的運行狀況檢查回應將使用 HTTP 代碼 `200` 進行回應,下面是示例:
46+
47+
```
48+
HTTP/1.1 200 OK
49+
50+
51+
{
52+
"status": "pass",
53+
"description": "Gitea: Git with a cup of tea",
54+
"checks": {
55+
"cache:ping": [
56+
{
57+
"status": "pass",
58+
"time": "2022-02-19T09:16:08Z"
59+
}
60+
],
61+
"database:ping": [
62+
{
63+
"status": "pass",
64+
"time": "2022-02-19T09:16:08Z"
65+
}
66+
]
67+
}
68+
}
69+
```
70+
71+
有關更多信息,請參考kubernetes文檔[定義一個存活態 HTTP請求接口](https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)

models/task.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,12 @@ func GetMigratingTask(repoID int64) (*Task, error) {
181181
return &task, nil
182182
}
183183

184-
// HasMigratingTask returns if migrating task exist for repo.
185-
func HasMigratingTask(repoID int64) (bool, error) {
186-
return db.GetEngine(db.DefaultContext).Exist(&Task{
187-
RepoID: repoID,
188-
Type: structs.TaskTypeMigrateRepo,
189-
})
184+
// HasFinishedMigratingTask returns if a finished migration task exists for the repo.
185+
func HasFinishedMigratingTask(repoID int64) (bool, error) {
186+
return db.GetEngine(db.DefaultContext).
187+
Where("repo_id=? AND type=? AND status=?", repoID, structs.TaskTypeMigrateRepo, structs.TaskStatusFinished).
188+
Table("task").
189+
Exist()
190190
}
191191

192192
// GetMigratingTaskByID returns the migrating task by repo's id

modules/cache/cache.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package cache
66

77
import (
8+
"errors"
89
"fmt"
910
"strconv"
1011

@@ -34,25 +35,37 @@ func NewContext() error {
3435
if conn, err = newCache(setting.CacheService.Cache); err != nil {
3536
return err
3637
}
37-
const testKey = "__gitea_cache_test"
38-
const testVal = "test-value"
39-
if err = conn.Put(testKey, testVal, 10); err != nil {
38+
if err = Ping(); err != nil {
4039
return err
4140
}
42-
val := conn.Get(testKey)
43-
if valStr, ok := val.(string); !ok || valStr != testVal {
44-
// If the cache is full, the Get may not read the expected value stored by Put.
45-
// Since we have checked that Put can success, so we just show a warning here, do not return an error to panic.
46-
log.Warn("cache (adapter:%s, config:%s) doesn't seem to work correctly, set test value '%v' but get '%v'",
47-
setting.CacheService.Cache.Adapter, setting.CacheService.Cache.Conn,
48-
testVal, val,
49-
)
50-
}
5141
}
5242

5343
return err
5444
}
5545

46+
// Ping checks if the cache service works or not, it not, it returns an error
47+
func Ping() error {
48+
if conn == nil {
49+
return errors.New("cache not available")
50+
}
51+
var err error
52+
const testKey = "__gitea_cache_test"
53+
const testVal = "test-value"
54+
if err = conn.Put(testKey, testVal, 10); err != nil {
55+
return err
56+
}
57+
val := conn.Get(testKey)
58+
if valStr, ok := val.(string); !ok || valStr != testVal {
59+
// If the cache is full, the Get may not read the expected value stored by Put.
60+
// Since we have checked that Put can success, so we just show a warning here, do not return an error to panic.
61+
log.Warn("cache (adapter:%s, config:%s) doesn't seem to work correctly, set test value '%v' but get '%v'",
62+
setting.CacheService.Cache.Adapter, setting.CacheService.Cache.Conn,
63+
testVal, val,
64+
)
65+
}
66+
return nil
67+
}
68+
5669
// GetCache returns the currently configured cache
5770
func GetCache() mc.Cache {
5871
return conn

modules/context/repo.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -371,14 +371,14 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
371371

372372
if repo.IsMirror {
373373

374-
// Check if there's a migrating task.
375-
// If it does exist, don't fetch the Mirror from the database as it doesn't exist yet.
376-
hasTask, err := models.HasMigratingTask(repo.ID)
374+
// Check if the mirror has finsihed migrationg, only then we can
375+
// lookup the mirror informtation the database.
376+
finishedMigrating, err := models.HasFinishedMigratingTask(repo.ID)
377377
if err != nil {
378-
ctx.ServerError("GetMirrorByRepoID", err)
378+
ctx.ServerError("HasFinishedMigratingTask", err)
379379
return
380380
}
381-
if !hasTask {
381+
if finishedMigrating {
382382
ctx.Repo.Mirror, err = repo_model.GetMirrorByRepoID(repo.ID)
383383
if err != nil {
384384
ctx.ServerError("GetMirrorByRepoID", err)

routers/install/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"code.gitea.io/gitea/modules/web"
1717
"code.gitea.io/gitea/modules/web/middleware"
1818
"code.gitea.io/gitea/routers/common"
19+
"code.gitea.io/gitea/routers/web/healthcheck"
1920
"code.gitea.io/gitea/services/forms"
2021

2122
"gitea.com/go-chi/session"
@@ -106,6 +107,7 @@ func Routes() *web.Route {
106107
r.Use(Init)
107108
r.Get("/", Install)
108109
r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall)
110+
r.Get("/api/healthz", healthcheck.Check)
109111

110112
r.NotFound(web.Wrap(installNotFound))
111113
return r

routers/install/routes_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
func TestRoutes(t *testing.T) {
1414
routes := Routes()
1515
assert.NotNil(t, routes)
16-
assert.Len(t, routes.R.Routes(), 1)
1716
assert.EqualValues(t, "/", routes.R.Routes()[0].Pattern)
1817
assert.Nil(t, routes.R.Routes()[0].SubRoutes)
1918
assert.Len(t, routes.R.Routes()[0].Handlers, 2)

routers/web/healthcheck/check.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package healthcheck
6+
7+
import (
8+
"net/http"
9+
"os"
10+
"time"
11+
12+
"code.gitea.io/gitea/models/db"
13+
"code.gitea.io/gitea/modules/cache"
14+
"code.gitea.io/gitea/modules/json"
15+
"code.gitea.io/gitea/modules/log"
16+
"code.gitea.io/gitea/modules/setting"
17+
)
18+
19+
type status string
20+
21+
const (
22+
// pass healthy (acceptable aliases: "ok" to support Node's Terminus and "up" for Java's SpringBoot)
23+
// fail unhealthy (acceptable aliases: "error" to support Node's Terminus and "down" for Java's SpringBoot), and
24+
// warn healthy, with some concerns.
25+
//
26+
// ref https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check#section-3.1
27+
// status: (required) indicates whether the service status is acceptable
28+
// or not. API publishers SHOULD use following values for the field:
29+
// The value of the status field is case-insensitive and is tightly
30+
// related with the HTTP response code returned by the health endpoint.
31+
// For "pass" status, HTTP response code in the 2xx-3xx range MUST be
32+
// used. For "fail" status, HTTP response code in the 4xx-5xx range
33+
// MUST be used. In case of the "warn" status, endpoints MUST return
34+
// HTTP status in the 2xx-3xx range, and additional information SHOULD
35+
// be provided, utilizing optional fields of the response.
36+
pass status = "pass"
37+
fail status = "fail"
38+
warn status = "warn"
39+
)
40+
41+
func (s status) ToHTTPStatus() int {
42+
if s == pass || s == warn {
43+
return http.StatusOK
44+
}
45+
return http.StatusFailedDependency
46+
}
47+
48+
type checks map[string][]componentStatus
49+
50+
// response is the data returned by the health endpoint, which will be marshaled to JSON format
51+
type response struct {
52+
Status status `json:"status"`
53+
Description string `json:"description"` // a human-friendly description of the service
54+
Checks checks `json:"checks,omitempty"` // The Checks Object, should be omitted on installation route
55+
}
56+
57+
// componentStatus presents one status of a single check object
58+
// an object that provides detailed health statuses of additional downstream systems and endpoints
59+
// which can affect the overall health of the main API.
60+
type componentStatus struct {
61+
Status status `json:"status"`
62+
Time string `json:"time"` // the date-time, in ISO8601 format
63+
Output string `json:"output,omitempty"` // this field SHOULD be omitted for "pass" state.
64+
}
65+
66+
// Check is the health check API handler
67+
func Check(w http.ResponseWriter, r *http.Request) {
68+
rsp := response{
69+
Status: pass,
70+
Description: setting.AppName,
71+
Checks: make(checks),
72+
}
73+
74+
statuses := make([]status, 0)
75+
if setting.InstallLock {
76+
statuses = append(statuses, checkDatabase(rsp.Checks))
77+
statuses = append(statuses, checkCache(rsp.Checks))
78+
}
79+
for _, s := range statuses {
80+
if s != pass {
81+
rsp.Status = fail
82+
break
83+
}
84+
}
85+
86+
data, _ := json.MarshalIndent(rsp, "", " ")
87+
w.Header().Set("Content-Type", "application/json")
88+
w.WriteHeader(rsp.Status.ToHTTPStatus())
89+
_, _ = w.Write(data)
90+
}
91+
92+
// database checks gitea database status
93+
func checkDatabase(checks checks) status {
94+
st := componentStatus{}
95+
if err := db.GetEngine(db.DefaultContext).Ping(); err != nil {
96+
st.Status = fail
97+
st.Time = getCheckTime()
98+
log.Error("database ping failed with error: %v", err)
99+
} else {
100+
st.Status = pass
101+
st.Time = getCheckTime()
102+
}
103+
104+
if setting.Database.UseSQLite3 && st.Status == pass {
105+
if !setting.EnableSQLite3 {
106+
st.Status = fail
107+
st.Time = getCheckTime()
108+
log.Error("SQLite3 health check failed with error: %v", "this Gitea binary is built without SQLite3 enabled")
109+
} else {
110+
if _, err := os.Stat(setting.Database.Path); err != nil {
111+
st.Status = fail
112+
st.Time = getCheckTime()
113+
log.Error("SQLite3 file exists check failed with error: %v", err)
114+
}
115+
}
116+
}
117+
118+
checks["database:ping"] = []componentStatus{st}
119+
return st.Status
120+
}
121+
122+
// cache checks gitea cache status
123+
func checkCache(checks checks) status {
124+
if !setting.CacheService.Enabled {
125+
return pass
126+
}
127+
128+
st := componentStatus{}
129+
if err := cache.Ping(); err != nil {
130+
st.Status = fail
131+
st.Time = getCheckTime()
132+
log.Error("cache ping failed with error: %v", err)
133+
} else {
134+
st.Status = pass
135+
st.Time = getCheckTime()
136+
}
137+
checks["cache:ping"] = []componentStatus{st}
138+
return st.Status
139+
}
140+
141+
func getCheckTime() string {
142+
return time.Now().UTC().Format(time.RFC3339)
143+
}

routers/web/web.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"code.gitea.io/gitea/routers/web/events"
3232
"code.gitea.io/gitea/routers/web/explore"
3333
"code.gitea.io/gitea/routers/web/feed"
34+
"code.gitea.io/gitea/routers/web/healthcheck"
3435
"code.gitea.io/gitea/routers/web/misc"
3536
"code.gitea.io/gitea/routers/web/org"
3637
"code.gitea.io/gitea/routers/web/repo"
@@ -191,6 +192,8 @@ func Routes() *web.Route {
191192
rw.WriteHeader(http.StatusOK)
192193
})
193194

195+
routes.Get("/api/healthz", healthcheck.Check)
196+
194197
// Removed: toolbox.Toolboxer middleware will provide debug information which seems unnecessary
195198
common = append(common, context.Contexter())
196199

web_src/less/chroma/dark.less

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
.chroma .ch { color: #6a737d; } /* CommentHashbang */
5353
.chroma .cm { color: #6a737d; } /* CommentMultiline */
5454
.chroma .c1 { color: #6a737d; } /* CommentSingle */
55-
.chroma .cs { color: #637d; } /* CommentSpecial */
55+
.chroma .cs { color: #95ad; } /* CommentSpecial */
5656
.chroma .cp { color: #fc6; } /* CommentPreproc */
5757
.chroma .cpf { color: #03dfff; } /* CommentPreprocFile */
5858
.chroma .gd { color: #fff; background-color: #5f3737; } /* GenericDeleted */

0 commit comments

Comments
 (0)