KDOC 55: giteaのコードを読んだメモ
DONE プロジェクトステータス
プロジェクトは終了である。
概要
go-gitea/giteaを読んだメモ。バックエンドはGoで書かれている。
memo
コンパイル
コンパイルしている部分。タスク名が動的になっていてわかりにくい。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/Makefile#L788-L789
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
routerを起動している部分
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/cmd/web.go#L211
webRoutes := routers.NormalRoutes()
DB設定部分
setting packageにまとめられている。パッケージで変数化されている。
https://github.com/go-gitea/gitea/blob/0999721c7b4d124c1f14bbd067acdcf767542abb/modules/setting/database.go#L27-L28
// Database holds the database settings Database = struct {
変数はグローバル変数にしつつ↑、セットはパッケージ内の関数でやる。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/modules/setting/database.go#L59-L65
func loadDBSetting(rootCfg ConfigProvider) { sec := rootCfg.Section("database") Database.Type = DatabaseType(sec.Key("DB_TYPE").String()) Database.Host = sec.Key("HOST").String() Database.Name = sec.Key("NAME").String() Database.User = sec.Key("USER").String()
DB初期化部分
routerでのDBの初期化は↓でやっている。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/db/engine.go#L132-L133
// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext func InitEngine(ctx context.Context) error {
InitEngineの中でデータベースを初期化する。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/db/engine.go#L134
xormEngine, err := newXORMEngine()
InitEngineの中でSetDefaultEngine()して初期化したデータベースをDefaultContextに設定する。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/db/engine.go#L153-L160
// SetDefaultEngine sets the default engine for db func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) { x = eng DefaultContext = &Context{ Context: ctx, e: x, } }
セットできているかの確認文。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/routers/init.go#L136
mustInitCtx(ctx, common.InitDBEngine)
変数default contextに設定する
設定している部分。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/db/engine.go#L153-L160
// SetDefaultEngine sets the default engine for db func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) { x = eng DefaultContext = &Context{ Context: ctx, e: x, } }
テストとかではdefault contextを使って、dbを簡単に起動している。そうでない部分では、起動時に明示して上書きしているようだ。
APIContextをセットしている部分
DBのデータなどはcontext.APIContextで伝達している。APIContextにはBaseが埋め込まれている。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/modules/context/api.go#L28-L43
// APIContext is a specific context for API service type APIContext struct { *Base Cache cache.Cache Doer *user_model.User // current signed-in user IsSigned bool IsBasicAuth bool ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer Repo *Repository Org *APIOrganization Package *Package }
Baseにはcontext.Contextが埋め込まれている。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/modules/context/base.go#L31-L44
type Base struct { originCtx context.Context contextValues []contextValuePair Resp ResponseWriter Req *http.Request // Data is prepared by ContextDataStore middleware, this field only refers to the pre-created/prepared ContextData. // Although it's mainly used for MVC templates, sometimes it's also used to pass data between middlewares/handler Data middleware.ContextData // Locale is mainly for Web context, although the API context also uses it in some cases: message response, form validation Locale translation.Locale }
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/modules/context/api.go#L213-L223
// APIContexter returns apicontext as middleware func APIContexter() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { base, baseCleanUp := NewBaseContext(w, req) ctx := &APIContext{ Base: base, Cache: mc.GetCache(), Repo: &Repository{PullRequest: &PullRequest{}}, Org: &APIOrganization{}, }
contextからdbインスタンスを取り出す
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/admin/task.go#L56
has, err := db.GetEngine(ctx).ID(task.RepoID).Get(&repo)
DBコンテキストは異なる
DBのためだけにありそう。defaultContextが入るのはこれ。contextを構造体に埋め込むのはよくないとされている。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/db/context.go#L29-L34
// Context represents a db context type Context struct { context.Context e Engine transaction bool }
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/db/context.go#L14-L16
// DefaultContext is the default context to run xorm queries in // will be overwritten by Init with HammerContext var DefaultContext context.Context
context.Contextはインターフェースなので、DefaultContextにはdb.Contextが入る。
このDBコンテキストは、Engine()を実装しているのでEnginedインターフェースを満たす。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/db/context.go#L67-L90
// Engined structs provide an Engine type Engined interface { Engine() Engine } // GetEngine will get a db Engine from this context or return an Engine restricted to this context func GetEngine(ctx context.Context) Engine { if e := getEngine(ctx); e != nil { return e } return x.Context(ctx) } // getEngine will get a db Engine from this context or return nil func getEngine(ctx context.Context) Engine { if engined, ok := ctx.(Engined); ok { return engined.Engine() } enginedInterface := ctx.Value(enginedContextKey) if enginedInterface != nil { return enginedInterface.(Engined).Engine() } return nil }
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/db/context.go#L49-L52
// Engine returns db engine func (ctx *Context) Engine() Engine { return ctx.e }
DBオブジェクト宣言
xormのオブジェクト作成はxorm.NewEngine()で行う。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/db/engine.go#L107
engine, err = xorm.NewEngine("postgresschema", connStr)
テストで、どうやってDefaultContextをセットしているか
テストでたくさん使っている。便利だ。これはいつセットされているか。
最初にunittest.PrepareTestDatabase()する。この中でDefaultContextに対してクエリが実行されている。そのあとDefaultContextを直に取得して実行する。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/actions/runner_token_test.go#L16-L18
assert.NoError(t, unittest.PrepareTestDatabase()) token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3}) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
dbはcontextのvalueで持つべきじゃないらしい。リクエストスコープでないから。ダメじゃんと思ったけれども、contextの内部で持ってるわけではなく構造体で持ってるからいいのか。
APIコンテキストはどうやって生成しているか
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/modules/context/api.go#L213-L243
// APIContexter returns apicontext as middleware func APIContexter() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { base, baseCleanUp := NewBaseContext(w, req) ctx := &APIContext{ Base: base, Cache: mc.GetCache(), Repo: &Repository{PullRequest: &PullRequest{}}, Org: &APIOrganization{}, } defer baseCleanUp() ctx.Base.AppendContextValue(apiContextKey, ctx) ctx.Base.AppendContextValueFunc(git.RepositoryContextKey, func() any { return ctx.Repo.GitRepo }) // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size ctx.InternalServerError(err) return } } httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform") ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) next.ServeHTTP(ctx.Resp, ctx.Req) }) } }
ミドルウェアとして登録されている。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/routers/api/v1/api.go#L807
m.Use(context.APIContexter())
DB関係なくないか。どこでセットされているか。
トランザクションコンテキストは必要。なぜ。
マイグレーションコマンド
マイグレーションはタスクで行う。あるいは、Webの起動時にも関数が用意されている。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/cmd/migrate.go#L25
func runMigrate(ctx *cli.Context) error {
テスト時にどうやってDB初期化されているか
- マイグレーション
- DefaultContext初期化
が必要なはずだが見当たらない。
unittest.PrepareTestDatabase() を見るが、そのときにはすでにDefaultContextからDBが取得できている。
各test packageにあるtest_main.goにある、TestMain関数内でunittest.MainTest()している。MainTest内でDBを初期化してる。テスト時に必ず実行される。
https://github.com/go-gitea/gitea/blob/8ef169a173c24e5ab57de8d6638bb6786e378a3d/models/unittest/testdb.go#L73
func MainTest(m *testing.M, testOpts ...*TestOptions) {