Skip to content

Commit

Permalink
Merge remote-tracking branch 'giteaofficial/main'
Browse files Browse the repository at this point in the history
* 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)
  • Loading branch information
zjjhot committed May 4, 2022
2 parents 5b8948d + e933f31 commit e28028d
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 25 deletions.
44 changes: 44 additions & 0 deletions docs/content/doc/installation/on-kubernetes.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,47 @@ helm install gitea gitea-charts/gitea
```

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/)

## Health check endpoint

Gitea comes with a health check endpoint `/api/healthz`, you can configure it in kubernetes like this:

```yaml
livenessProbe:
httpGet:
path: /api/healthz
port: http
initialDelaySeconds: 200
timeoutSeconds: 5
periodSeconds: 10
successThreshold: 1
failureThreshold: 10
```

a successful health check response will respond with http code `200`, here's example:

```
HTTP/1.1 200 OK
{
"status": "pass",
"description": "Gitea: Git with a cup of tea",
"checks": {
"cache:ping": [
{
"status": "pass",
"time": "2022-02-19T09:16:08Z"
}
],
"database:ping": [
{
"status": "pass",
"time": "2022-02-19T09:16:08Z"
}
]
}
}
```

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)
44 changes: 44 additions & 0 deletions docs/content/doc/installation/on-kubernetes.zh-tw.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,47 @@ helm install gitea gitea-charts/gitea
```

若您想自訂安裝(包括使用 kubernetes ingress),請前往完整的 [Gitea helm chart configuration details](https://gitea.com/gitea/helm-chart/)

##運行狀況檢查終端節點

Gitea 附帶了一個運行狀況檢查端點 `/api/healthz`,你可以像這樣在 kubernetes 中配置它:

```yaml
livenessProbe:
httpGet:
path: /api/healthz
port: http
initialDelaySeconds: 200
timeoutSeconds: 5
periodSeconds: 10
successThreshold: 1
failureThreshold: 10
```

成功的運行狀況檢查回應將使用 HTTP 代碼 `200` 進行回應,下面是示例:

```
HTTP/1.1 200 OK
{
"status": "pass",
"description": "Gitea: Git with a cup of tea",
"checks": {
"cache:ping": [
{
"status": "pass",
"time": "2022-02-19T09:16:08Z"
}
],
"database:ping": [
{
"status": "pass",
"time": "2022-02-19T09:16:08Z"
}
]
}
}
```

有關更多信息,請參考kubernetes文檔[定義一個存活態 HTTP請求接口]https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)
12 changes: 6 additions & 6 deletions models/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,12 @@ func GetMigratingTask(repoID int64) (*Task, error) {
return &task, nil
}

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

// GetMigratingTaskByID returns the migrating task by repo's id
Expand Down
37 changes: 25 additions & 12 deletions modules/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package cache

import (
"errors"
"fmt"
"strconv"

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

return err
}

// Ping checks if the cache service works or not, it not, it returns an error
func Ping() error {
if conn == nil {
return errors.New("cache not available")
}
var err error
const testKey = "__gitea_cache_test"
const testVal = "test-value"
if err = conn.Put(testKey, testVal, 10); err != nil {
return err
}
val := conn.Get(testKey)
if valStr, ok := val.(string); !ok || valStr != testVal {
// If the cache is full, the Get may not read the expected value stored by Put.
// Since we have checked that Put can success, so we just show a warning here, do not return an error to panic.
log.Warn("cache (adapter:%s, config:%s) doesn't seem to work correctly, set test value '%v' but get '%v'",
setting.CacheService.Cache.Adapter, setting.CacheService.Cache.Conn,
testVal, val,
)
}
return nil
}

// GetCache returns the currently configured cache
func GetCache() mc.Cache {
return conn
Expand Down
10 changes: 5 additions & 5 deletions modules/context/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,14 +371,14 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {

if repo.IsMirror {

// Check if there's a migrating task.
// If it does exist, don't fetch the Mirror from the database as it doesn't exist yet.
hasTask, err := models.HasMigratingTask(repo.ID)
// Check if the mirror has finsihed migrationg, only then we can
// lookup the mirror informtation the database.
finishedMigrating, err := models.HasFinishedMigratingTask(repo.ID)
if err != nil {
ctx.ServerError("GetMirrorByRepoID", err)
ctx.ServerError("HasFinishedMigratingTask", err)
return
}
if !hasTask {
if finishedMigrating {
ctx.Repo.Mirror, err = repo_model.GetMirrorByRepoID(repo.ID)
if err != nil {
ctx.ServerError("GetMirrorByRepoID", err)
Expand Down
2 changes: 2 additions & 0 deletions routers/install/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/routers/web/healthcheck"
"code.gitea.io/gitea/services/forms"

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

r.NotFound(web.Wrap(installNotFound))
return r
Expand Down
1 change: 0 additions & 1 deletion routers/install/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
func TestRoutes(t *testing.T) {
routes := Routes()
assert.NotNil(t, routes)
assert.Len(t, routes.R.Routes(), 1)
assert.EqualValues(t, "/", routes.R.Routes()[0].Pattern)
assert.Nil(t, routes.R.Routes()[0].SubRoutes)
assert.Len(t, routes.R.Routes()[0].Handlers, 2)
Expand Down
143 changes: 143 additions & 0 deletions routers/web/healthcheck/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package healthcheck

import (
"net/http"
"os"
"time"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)

type status string

const (
// 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.
//
// ref https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check#section-3.1
// 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
// 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"
warn status = "warn"
)

func (s status) ToHTTPStatus() int {
if s == pass || s == warn {
return http.StatusOK
}
return http.StatusFailedDependency
}

type checks map[string][]componentStatus

// 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
}

// componentStatus presents one status of a single check object
// an object that provides detailed health statuses of additional downstream systems and endpoints
// which can affect the overall health of the main API.
type componentStatus struct {
Status status `json:"status"`
Time string `json:"time"` // the date-time, in ISO8601 format
Output string `json:"output,omitempty"` // this field SHOULD be omitted for "pass" state.
}

// Check is the health check API handler
func Check(w http.ResponseWriter, r *http.Request) {
rsp := response{
Status: pass,
Description: setting.AppName,
Checks: make(checks),
}

statuses := make([]status, 0)
if setting.InstallLock {
statuses = append(statuses, checkDatabase(rsp.Checks))
statuses = append(statuses, checkCache(rsp.Checks))
}
for _, s := range statuses {
if s != pass {
rsp.Status = fail
break
}
}

data, _ := json.MarshalIndent(rsp, "", " ")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(rsp.Status.ToHTTPStatus())
_, _ = w.Write(data)
}

// database checks gitea database status
func checkDatabase(checks checks) status {
st := componentStatus{}
if err := db.GetEngine(db.DefaultContext).Ping(); err != nil {
st.Status = fail
st.Time = getCheckTime()
log.Error("database ping failed with error: %v", err)
} else {
st.Status = pass
st.Time = getCheckTime()
}

if setting.Database.UseSQLite3 && st.Status == pass {
if !setting.EnableSQLite3 {
st.Status = fail
st.Time = getCheckTime()
log.Error("SQLite3 health check failed with error: %v", "this Gitea binary is built without SQLite3 enabled")
} else {
if _, err := os.Stat(setting.Database.Path); err != nil {
st.Status = fail
st.Time = getCheckTime()
log.Error("SQLite3 file exists check failed with error: %v", err)
}
}
}

checks["database:ping"] = []componentStatus{st}
return st.Status
}

// cache checks gitea cache status
func checkCache(checks checks) status {
if !setting.CacheService.Enabled {
return pass
}

st := componentStatus{}
if err := cache.Ping(); err != nil {
st.Status = fail
st.Time = getCheckTime()
log.Error("cache ping failed with error: %v", err)
} else {
st.Status = pass
st.Time = getCheckTime()
}
checks["cache:ping"] = []componentStatus{st}
return st.Status
}

func getCheckTime() string {
return time.Now().UTC().Format(time.RFC3339)
}
3 changes: 3 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"code.gitea.io/gitea/routers/web/events"
"code.gitea.io/gitea/routers/web/explore"
"code.gitea.io/gitea/routers/web/feed"
"code.gitea.io/gitea/routers/web/healthcheck"
"code.gitea.io/gitea/routers/web/misc"
"code.gitea.io/gitea/routers/web/org"
"code.gitea.io/gitea/routers/web/repo"
Expand Down Expand Up @@ -191,6 +192,8 @@ func Routes() *web.Route {
rw.WriteHeader(http.StatusOK)
})

routes.Get("/api/healthz", healthcheck.Check)

// Removed: toolbox.Toolboxer middleware will provide debug information which seems unnecessary
common = append(common, context.Contexter())

Expand Down
2 changes: 1 addition & 1 deletion web_src/less/chroma/dark.less
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
.chroma .ch { color: #6a737d; } /* CommentHashbang */
.chroma .cm { color: #6a737d; } /* CommentMultiline */
.chroma .c1 { color: #6a737d; } /* CommentSingle */
.chroma .cs { color: #637d; } /* CommentSpecial */
.chroma .cs { color: #95ad; } /* CommentSpecial */
.chroma .cp { color: #fc6; } /* CommentPreproc */
.chroma .cpf { color: #03dfff; } /* CommentPreprocFile */
.chroma .gd { color: #fff; background-color: #5f3737; } /* GenericDeleted */
Expand Down

0 comments on commit e28028d

Please sign in to comment.