From 0e61a74e5a3fc14ff26d1c85065ba336d3f3994b Mon Sep 17 00:00:00 2001 From: Mohamed Sekour Date: Sat, 30 Jul 2022 18:45:59 +0200 Subject: [PATCH] Add new API endpoints for push mirrors management (#19841) - Add a new push mirror to specific repository - Sync now ( send all the changes to the configured push mirrors ) - Get list of all push mirrors of a repository - Get a push mirror by ID - Delete push mirror by ID Signed-off-by: Mohamed Sekour Signed-off-by: Andrew Thornton Co-authored-by: zeripath --- integrations/mirror_push_test.go | 5 +- models/repo/pushmirror.go | 73 ++++--- models/repo/pushmirror_test.go | 9 +- modules/context/repo.go | 2 +- modules/convert/mirror.go | 39 ++++ modules/structs/mirror.go | 25 +++ routers/api/v1/api.go | 9 + routers/api/v1/repo/mirror.go | 326 ++++++++++++++++++++++++++++++ routers/api/v1/swagger/options.go | 3 + routers/api/v1/swagger/repo.go | 14 ++ routers/web/repo/setting.go | 18 +- services/mirror/mirror.go | 2 +- services/mirror/mirror_push.go | 4 +- templates/swagger/v1_json.tmpl | 302 ++++++++++++++++++++++++++- 14 files changed, 787 insertions(+), 44 deletions(-) create mode 100644 modules/convert/mirror.go create mode 100644 modules/structs/mirror.go diff --git a/integrations/mirror_push_test.go b/integrations/mirror_push_test.go index a73b69e786..3a22b00754 100644 --- a/integrations/mirror_push_test.go +++ b/integrations/mirror_push_test.go @@ -13,6 +13,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -47,7 +48,7 @@ func testMirrorPush(t *testing.T, u *url.URL) { doCreatePushMirror(ctx, fmt.Sprintf("%s%s/%s", u.String(), url.PathEscape(ctx.Username), url.PathEscape(mirrorRepo.Name)), user.LowerName, userPassword)(t) - mirrors, err := repo_model.GetPushMirrorsByRepoID(srcRepo.ID) + mirrors, _, err := repo_model.GetPushMirrorsByRepoID(db.DefaultContext, srcRepo.ID, db.ListOptions{}) assert.NoError(t, err) assert.Len(t, mirrors, 1) @@ -72,7 +73,7 @@ func testMirrorPush(t *testing.T, u *url.URL) { // Cleanup doRemovePushMirror(ctx, fmt.Sprintf("%s%s/%s", u.String(), url.PathEscape(ctx.Username), url.PathEscape(mirrorRepo.Name)), user.LowerName, userPassword, int(mirrors[0].ID))(t) - mirrors, err = repo_model.GetPushMirrorsByRepoID(srcRepo.ID) + mirrors, _, err = repo_model.GetPushMirrorsByRepoID(db.DefaultContext, srcRepo.ID, db.ListOptions{}) assert.NoError(t, err) assert.Len(t, mirrors, 0) } diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go index 0a7dea79c9..02ffcbee76 100644 --- a/models/repo/pushmirror.go +++ b/models/repo/pushmirror.go @@ -5,12 +5,15 @@ package repo import ( + "context" "errors" "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/builder" ) // ErrPushMirrorNotExist mirror does not exist error @@ -29,6 +32,25 @@ type PushMirror struct { LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"` LastError string `xorm:"text"` } +type PushMirrorOptions struct { + ID int64 + RepoID int64 + RemoteName string +} + +func (opts *PushMirrorOptions) toConds() builder.Cond { + cond := builder.NewCond() + if opts.RepoID > 0 { + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) + } + if opts.RemoteName != "" { + cond = cond.And(builder.Eq{"remote_name": opts.RemoteName}) + } + if opts.ID > 0 { + cond = cond.And(builder.Eq{"id": opts.ID}) + } + return cond +} func init() { db.RegisterModel(new(PushMirror)) @@ -53,45 +75,48 @@ func (m *PushMirror) GetRemoteName() string { } // InsertPushMirror inserts a push-mirror to database -func InsertPushMirror(m *PushMirror) error { - _, err := db.GetEngine(db.DefaultContext).Insert(m) +func InsertPushMirror(ctx context.Context, m *PushMirror) error { + _, err := db.GetEngine(ctx).Insert(m) return err } // UpdatePushMirror updates the push-mirror -func UpdatePushMirror(m *PushMirror) error { - _, err := db.GetEngine(db.DefaultContext).ID(m.ID).AllCols().Update(m) +func UpdatePushMirror(ctx context.Context, m *PushMirror) error { + _, err := db.GetEngine(ctx).ID(m.ID).AllCols().Update(m) return err } -// DeletePushMirrorByID deletes a push-mirrors by ID -func DeletePushMirrorByID(ID int64) error { - _, err := db.GetEngine(db.DefaultContext).ID(ID).Delete(&PushMirror{}) - return err +func DeletePushMirrors(ctx context.Context, opts PushMirrorOptions) error { + if opts.RepoID > 0 { + _, err := db.GetEngine(ctx).Where(opts.toConds()).Delete(&PushMirror{}) + return err + } + return errors.New("repoID required and must be set") } -// DeletePushMirrorsByRepoID deletes all push-mirrors by repoID -func DeletePushMirrorsByRepoID(repoID int64) error { - _, err := db.GetEngine(db.DefaultContext).Delete(&PushMirror{RepoID: repoID}) - return err -} - -// GetPushMirrorByID returns push-mirror information. -func GetPushMirrorByID(ID int64) (*PushMirror, error) { - m := &PushMirror{} - has, err := db.GetEngine(db.DefaultContext).ID(ID).Get(m) +func GetPushMirror(ctx context.Context, opts PushMirrorOptions) (*PushMirror, error) { + mirror := &PushMirror{} + exist, err := db.GetEngine(ctx).Where(opts.toConds()).Get(mirror) if err != nil { return nil, err - } else if !has { + } else if !exist { return nil, ErrPushMirrorNotExist } - return m, nil + return mirror, nil } // GetPushMirrorsByRepoID returns push-mirror information of a repository. -func GetPushMirrorsByRepoID(repoID int64) ([]*PushMirror, error) { +func GetPushMirrorsByRepoID(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*PushMirror, int64, error) { + sess := db.GetEngine(ctx).Where("repo_id = ?", repoID) + if listOptions.Page != 0 { + sess = db.SetSessionPagination(sess, &listOptions) + mirrors := make([]*PushMirror, 0, listOptions.PageSize) + count, err := sess.FindAndCount(&mirrors) + return mirrors, count, err + } mirrors := make([]*PushMirror, 0, 10) - return mirrors, db.GetEngine(db.DefaultContext).Where("repo_id=?", repoID).Find(&mirrors) + count, err := sess.FindAndCount(&mirrors) + return mirrors, count, err } // GetPushMirrorsSyncedOnCommit returns push-mirrors for this repo that should be updated by new commits @@ -103,8 +128,8 @@ func GetPushMirrorsSyncedOnCommit(repoID int64) ([]*PushMirror, error) { } // PushMirrorsIterate iterates all push-mirror repositories. -func PushMirrorsIterate(limit int, f func(idx int, bean interface{}) error) error { - return db.GetEngine(db.DefaultContext). +func PushMirrorsIterate(ctx context.Context, limit int, f func(idx int, bean interface{}) error) error { + return db.GetEngine(ctx). Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()). And("`interval` != 0"). OrderBy("last_update ASC"). diff --git a/models/repo/pushmirror_test.go b/models/repo/pushmirror_test.go index d36a48547e..5087e30095 100644 --- a/models/repo/pushmirror_test.go +++ b/models/repo/pushmirror_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/timeutil" @@ -20,20 +21,20 @@ func TestPushMirrorsIterate(t *testing.T) { now := timeutil.TimeStampNow() - repo_model.InsertPushMirror(&repo_model.PushMirror{ + repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{ RemoteName: "test-1", LastUpdateUnix: now, Interval: 1, }) long, _ := time.ParseDuration("24h") - repo_model.InsertPushMirror(&repo_model.PushMirror{ + repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{ RemoteName: "test-2", LastUpdateUnix: now, Interval: long, }) - repo_model.InsertPushMirror(&repo_model.PushMirror{ + repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{ RemoteName: "test-3", LastUpdateUnix: now, Interval: 0, @@ -41,7 +42,7 @@ func TestPushMirrorsIterate(t *testing.T) { time.Sleep(1 * time.Millisecond) - repo_model.PushMirrorsIterate(1, func(idx int, bean interface{}) error { + repo_model.PushMirrorsIterate(db.DefaultContext, 1, func(idx int, bean interface{}) error { m, ok := bean.(*repo_model.PushMirror) assert.True(t, ok) assert.Equal(t, "test-1", m.RemoteName) diff --git a/modules/context/repo.go b/modules/context/repo.go index 1d9f98158e..ea40542069 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -393,7 +393,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) { } } - pushMirrors, err := repo_model.GetPushMirrorsByRepoID(repo.ID) + pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{}) if err != nil { ctx.ServerError("GetPushMirrorsByRepoID", err) return diff --git a/modules/convert/mirror.go b/modules/convert/mirror.go new file mode 100644 index 0000000000..b2414f4677 --- /dev/null +++ b/modules/convert/mirror.go @@ -0,0 +1,39 @@ +// 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 convert + +import ( + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" +) + +// ToPushMirror convert from repo_model.PushMirror and remoteAddress to api.TopicResponse +func ToPushMirror(pm *repo_model.PushMirror) (*api.PushMirror, error) { + repo := pm.GetRepository() + remoteAddress, err := getRemoteAddress(repo, pm.RemoteName) + if err != nil { + return nil, err + } + return &api.PushMirror{ + RepoName: repo.Name, + RemoteName: pm.RemoteName, + RemoteAddress: remoteAddress, + CreatedUnix: pm.CreatedUnix.FormatLong(), + LastUpdateUnix: pm.LastUpdateUnix.FormatLong(), + LastError: pm.LastError, + Interval: pm.Interval.String(), + }, nil +} + +func getRemoteAddress(repo *repo_model.Repository, remoteName string) (string, error) { + url, err := git.GetRemoteURL(git.DefaultContext, repo.RepoPath(), remoteName) + if err != nil { + return "", err + } + // remove confidential information + url.User = nil + return url.String(), nil +} diff --git a/modules/structs/mirror.go b/modules/structs/mirror.go new file mode 100644 index 0000000000..8e8a8a2705 --- /dev/null +++ b/modules/structs/mirror.go @@ -0,0 +1,25 @@ +// Copyright 2021 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 structs + +// CreatePushMirrorOption represents need information to create a push mirror of a repository. +type CreatePushMirrorOption struct { + RemoteAddress string `json:"remote_address"` + RemoteUsername string `json:"remote_username"` + RemotePassword string `json:"remote_password"` + Interval string `json:"interval"` +} + +// PushMirror represents information of a push mirror +// swagger:model +type PushMirror struct { + RepoName string `json:"repo_name"` + RemoteName string `json:"remote_name"` + RemoteAddress string `json:"remote_address"` + CreatedUnix string `json:"created"` + LastUpdateUnix string `json:"last_update"` + LastError string `json:"last_error"` + Interval string `json:"interval"` +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 44e0c290a0..e1478fa2aa 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -982,6 +982,15 @@ func Routes() *web.Route { }) }, reqRepoReader(unit.TypeReleases)) m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), repo.MirrorSync) + m.Post("/push_mirrors-sync", reqAdmin(), repo.PushMirrorSync) + m.Group("/push_mirrors", func() { + m.Combo("").Get(repo.ListPushMirrors). + Post(bind(api.CreatePushMirrorOption{}), repo.AddPushMirror) + m.Combo("/{name}"). + Delete(repo.DeletePushMirrorByRemoteName). + Get(repo.GetPushMirrorByName) + }, reqAdmin()) + m.Get("/editorconfig/{filename}", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetEditorconfig) m.Group("/pulls", func() { m.Combo("").Get(repo.ListPullRequests). diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index 3d29383550..91e5e0c031 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -6,13 +6,25 @@ package repo import ( "errors" + "fmt" "net/http" + "time" + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" mirror_module "code.gitea.io/gitea/modules/mirror" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/forms" + "code.gitea.io/gitea/services/migrations" + mirror_service "code.gitea.io/gitea/services/mirror" ) // MirrorSync adds a mirrored repository to the sync queue @@ -63,3 +75,317 @@ func MirrorSync(ctx *context.APIContext) { ctx.Status(http.StatusOK) } + +// PushMirrorSync adds all push mirrored repositories to the sync queue +func PushMirrorSync(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/push_mirrors-sync repository repoPushMirrorSync + // --- + // summary: Sync all push mirrored repository + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo to sync + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo to sync + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/empty" + // "400": + // "$ref": "#/responses/error" + // "403": + // "$ref": "#/responses/forbidden" + + if !setting.Mirror.Enabled { + ctx.Error(http.StatusBadRequest, "PushMirrorSync", "Mirror feature is disabled") + return + } + // Get All push mirrors of a specific repo + pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, ctx.Repo.Repository.ID, db.ListOptions{}) + if err != nil { + ctx.Error(http.StatusNotFound, "PushMirrorSync", err) + return + } + for _, mirror := range pushMirrors { + ok := mirror_service.SyncPushMirror(ctx, mirror.ID) + if !ok { + ctx.Error(http.StatusInternalServerError, "PushMirrorSync", "error occurred when syncing push mirror "+mirror.RemoteName) + return + } + } + + ctx.Status(http.StatusOK) +} + +// ListPushMirrors get list of push mirrors of a repository +func ListPushMirrors(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/push_mirrors repository repoListPushMirrors + // --- + // summary: Get all push mirrors of the repository + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/PushMirrorList" + // "400": + // "$ref": "#/responses/error" + // "403": + // "$ref": "#/responses/forbidden" + + if !setting.Mirror.Enabled { + ctx.Error(http.StatusBadRequest, "GetPushMirrorsByRepoID", "Mirror feature is disabled") + return + } + + repo := ctx.Repo.Repository + // Get all push mirrors for the specified repository. + pushMirrors, count, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, utils.GetListOptions(ctx)) + if err != nil { + ctx.Error(http.StatusNotFound, "GetPushMirrorsByRepoID", err) + return + } + + responsePushMirrors := make([]*api.PushMirror, 0, len(pushMirrors)) + for _, mirror := range pushMirrors { + m, err := convert.ToPushMirror(mirror) + if err == nil { + responsePushMirrors = append(responsePushMirrors, m) + } + + } + ctx.SetLinkHeader(len(responsePushMirrors), utils.GetListOptions(ctx).PageSize) + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, responsePushMirrors) +} + +// GetPushMirrorByName get push mirror of a repository by name +func GetPushMirrorByName(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/push_mirrors/{name} repository repoGetPushMirrorByRemoteName + // --- + // summary: Get push mirror of the repository by remoteName + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: name + // in: path + // description: remote name of push mirror + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/PushMirror" + // "400": + // "$ref": "#/responses/error" + // "403": + // "$ref": "#/responses/forbidden" + + if !setting.Mirror.Enabled { + ctx.Error(http.StatusBadRequest, "GetPushMirrorByRemoteName", "Mirror feature is disabled") + return + } + + mirrorName := ctx.Params(":name") + // Get push mirror of a specific repo by remoteName + pushMirror, err := repo_model.GetPushMirror(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: mirrorName}) + if err != nil { + ctx.Error(http.StatusNotFound, "GetPushMirrors", err) + return + } + m, err := convert.ToPushMirror(pushMirror) + if err != nil { + ctx.ServerError("GetPushMirrorByRemoteName", err) + return + } + ctx.JSON(http.StatusOK, m) +} + +// AddPushMirror adds a push mirror to a repository +func AddPushMirror(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/push_mirrors repository repoAddPushMirror + // --- + // summary: add a push mirror to the repository + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/CreatePushMirrorOption" + // responses: + // "201": + // "$ref": "#/responses/PushMirror" + // "403": + // "$ref": "#/responses/forbidden" + // "400": + // "$ref": "#/responses/error" + + if !setting.Mirror.Enabled { + ctx.Error(http.StatusBadRequest, "AddPushMirror", "Mirror feature is disabled") + return + } + + pushMirror := web.GetForm(ctx).(*api.CreatePushMirrorOption) + CreatePushMirror(ctx, pushMirror) +} + +// DeletePushMirrorByRemoteName deletes a push mirror from a repository by remoteName +func DeletePushMirrorByRemoteName(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/push_mirrors/{name} repository repoDeletePushMirror + // --- + // summary: deletes a push mirror from a repository by remoteName + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: name + // in: path + // description: remote name of the pushMirror + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "404": + // "$ref": "#/responses/notFound" + // "400": + // "$ref": "#/responses/error" + + if !setting.Mirror.Enabled { + ctx.Error(http.StatusBadRequest, "DeletePushMirrorByName", "Mirror feature is disabled") + return + } + + remoteName := ctx.Params(":name") + // Delete push mirror on repo by name. + err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: remoteName}) + if err != nil { + ctx.Error(http.StatusNotFound, "DeletePushMirrors", err) + return + } + ctx.Status(http.StatusNoContent) +} + +func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirrorOption) { + repo := ctx.Repo.Repository + + interval, err := time.ParseDuration(mirrorOption.Interval) + if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { + ctx.Error(http.StatusBadRequest, "CreatePushMirror", err) + return + } + + address, err := forms.ParseRemoteAddr(mirrorOption.RemoteAddress, mirrorOption.RemoteUsername, mirrorOption.RemotePassword) + if err == nil { + err = migrations.IsMigrateURLAllowed(address, ctx.ContextUser) + } + if err != nil { + HandleRemoteAddressError(ctx, err) + return + } + + remoteSuffix, err := util.CryptoRandomString(10) + if err != nil { + ctx.ServerError("CryptoRandomString", err) + return + } + + pushMirror := &repo_model.PushMirror{ + RepoID: repo.ID, + Repo: repo, + RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), + Interval: interval, + } + + if err = repo_model.InsertPushMirror(ctx, pushMirror); err != nil { + ctx.ServerError("InsertPushMirror", err) + return + } + + // if the registration of the push mirrorOption fails remove it from the database + if err = mirror_service.AddPushMirrorRemote(ctx, pushMirror, address); err != nil { + if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: pushMirror.ID, RepoID: pushMirror.RepoID}); err != nil { + ctx.ServerError("DeletePushMirrors", err) + } + ctx.ServerError("AddPushMirrorRemote", err) + return + } + m, err := convert.ToPushMirror(pushMirror) + if err != nil { + ctx.ServerError("ToPushMirror", err) + return + } + ctx.JSON(http.StatusOK, m) +} + +func HandleRemoteAddressError(ctx *context.APIContext, err error) { + if models.IsErrInvalidCloneAddr(err) { + addrErr := err.(*models.ErrInvalidCloneAddr) + switch { + case addrErr.IsProtocolInvalid: + ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Invalid mirror protocol") + case addrErr.IsURLError: + ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Invalid Url ") + case addrErr.IsPermissionDenied: + ctx.Error(http.StatusUnauthorized, "CreatePushMirror", "Permission denied") + default: + ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Unknown error") + } + return + } +} diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index df3d011246..e8cfc0706f 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -169,4 +169,7 @@ type swaggerParameterBodies struct { // in:body CreateWikiPageOptions api.CreateWikiPageOptions + + // in:body + CreatePushMirrorOption api.CreatePushMirrorOption } diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index ab802db781..3522e24276 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -345,6 +345,20 @@ type swaggerWikiCommitList struct { Body api.WikiCommitList `json:"body"` } +// PushMirror +// swagger:response PushMirror +type swaggerPushMirror struct { + // in:body + Body api.PushMirror `json:"body"` +} + +// PushMirrorList +// swagger:response PushMirrorList +type swaggerPushMirrorList struct { + // in:body + Body []api.PushMirror `json:"body"` +} + // RepoCollaboratorPermission // swagger:response RepoCollaboratorPermission type swaggerRepoCollaboratorPermission struct { diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index 7f6b0feafb..a59824cecd 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -90,7 +90,7 @@ func SettingsCtxData(ctx *context.Context) { } ctx.Data["StatsIndexerStatus"] = status } - pushMirrors, err := repo_model.GetPushMirrorsByRepoID(ctx.Repo.Repository.ID) + pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, ctx.Repo.Repository.ID, db.ListOptions{}) if err != nil { ctx.ServerError("GetPushMirrorsByRepoID", err) return @@ -284,7 +284,7 @@ func SettingsPost(ctx *context.Context) { return } - m, err := selectPushMirrorByForm(form, repo) + m, err := selectPushMirrorByForm(ctx, form, repo) if err != nil { ctx.NotFound("", nil) return @@ -305,7 +305,7 @@ func SettingsPost(ctx *context.Context) { // as an error on the UI for this action ctx.Data["Err_RepoName"] = nil - m, err := selectPushMirrorByForm(form, repo) + m, err := selectPushMirrorByForm(ctx, form, repo) if err != nil { ctx.NotFound("", nil) return @@ -316,7 +316,7 @@ func SettingsPost(ctx *context.Context) { return } - if err = repo_model.DeletePushMirrorByID(m.ID); err != nil { + if err = repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil { ctx.ServerError("DeletePushMirrorByID", err) return } @@ -364,14 +364,14 @@ func SettingsPost(ctx *context.Context) { SyncOnCommit: form.PushMirrorSyncOnCommit, Interval: interval, } - if err := repo_model.InsertPushMirror(m); err != nil { + if err := repo_model.InsertPushMirror(ctx, m); err != nil { ctx.ServerError("InsertPushMirror", err) return } if err := mirror_service.AddPushMirrorRemote(ctx, m, address); err != nil { - if err := repo_model.DeletePushMirrorByID(m.ID); err != nil { - log.Error("DeletePushMirrorByID %v", err) + if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil { + log.Error("DeletePushMirrors %v", err) } ctx.ServerError("AddPushMirrorRemote", err) return @@ -1222,13 +1222,13 @@ func SettingsDeleteAvatar(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/settings") } -func selectPushMirrorByForm(form *forms.RepoSettingForm, repo *repo_model.Repository) (*repo_model.PushMirror, error) { +func selectPushMirrorByForm(ctx *context.Context, form *forms.RepoSettingForm, repo *repo_model.Repository) (*repo_model.PushMirror, error) { id, err := strconv.ParseInt(form.PushMirrorID, 10, 64) if err != nil { return nil, err } - pushMirrors, err := repo_model.GetPushMirrorsByRepoID(repo.ID) + pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{}) if err != nil { return nil, err } diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index 8321829ad2..3b4a8e5f8a 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -106,7 +106,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { pushMirrorsRequested := 0 if pushLimit != 0 { - if err := repo_model.PushMirrorsIterate(pushLimit, func(idx int, bean interface{}) error { + if err := repo_model.PushMirrorsIterate(ctx, pushLimit, func(idx int, bean interface{}) error { if err := handler(idx, bean); err != nil { return err } diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go index 2927bed72b..0c8960d78b 100644 --- a/services/mirror/mirror_push.go +++ b/services/mirror/mirror_push.go @@ -94,7 +94,7 @@ func SyncPushMirror(ctx context.Context, mirrorID int64) bool { log.Error("PANIC whilst syncPushMirror[%d] Panic: %v\nStacktrace: %s", mirrorID, err, log.Stack(2)) }() - m, err := repo_model.GetPushMirrorByID(mirrorID) + m, err := repo_model.GetPushMirror(ctx, repo_model.PushMirrorOptions{ID: mirrorID}) if err != nil { log.Error("GetPushMirrorByID [%d]: %v", mirrorID, err) return false @@ -116,7 +116,7 @@ func SyncPushMirror(ctx context.Context, mirrorID int64) bool { m.LastUpdateUnix = timeutil.TimeStampNow() - if err := repo_model.UpdatePushMirror(m); err != nil { + if err := repo_model.UpdatePushMirror(ctx, m); err != nil { log.Error("UpdatePushMirror [%d]: %v", m.ID, err) return false diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index c0dd50a7b9..daec38044f 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -8774,6 +8774,233 @@ } } }, + "/repos/{owner}/{repo}/push_mirrors": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get all push mirrors of the repository", + "operationId": "repoListPushMirrors", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/PushMirrorList" + }, + "400": { + "$ref": "#/responses/error" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "add a push mirror to the repository", + "operationId": "repoAddPushMirror", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/CreatePushMirrorOption" + } + } + ], + "responses": { + "201": { + "$ref": "#/responses/PushMirror" + }, + "400": { + "$ref": "#/responses/error" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + } + }, + "/repos/{owner}/{repo}/push_mirrors-sync": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Sync all push mirrored repository", + "operationId": "repoPushMirrorSync", + "parameters": [ + { + "type": "string", + "description": "owner of the repo to sync", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo to sync", + "name": "repo", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/empty" + }, + "400": { + "$ref": "#/responses/error" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + } + }, + "/repos/{owner}/{repo}/push_mirrors/{name}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get push mirror of the repository by remoteName", + "operationId": "repoGetPushMirrorByRemoteName", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "remote name of push mirror", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/PushMirror" + }, + "400": { + "$ref": "#/responses/error" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "deletes a push mirror from a repository by remoteName", + "operationId": "repoDeletePushMirror", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "remote name of the pushMirror", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/repos/{owner}/{repo}/raw/{filepath}": { "get": { "produces": [ @@ -14441,6 +14668,29 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "CreatePushMirrorOption": { + "type": "object", + "title": "CreatePushMirrorOption represents need information to create a push mirror of a repository.", + "properties": { + "interval": { + "type": "string", + "x-go-name": "Interval" + }, + "remote_address": { + "type": "string", + "x-go-name": "RemoteAddress" + }, + "remote_password": { + "type": "string", + "x-go-name": "RemotePassword" + }, + "remote_username": { + "type": "string", + "x-go-name": "RemoteUsername" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "CreateReleaseOption": { "description": "CreateReleaseOption options when creating a release", "type": "object", @@ -17516,6 +17766,41 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "PushMirror": { + "description": "PushMirror represents information of a push mirror", + "type": "object", + "properties": { + "created": { + "type": "string", + "x-go-name": "CreatedUnix" + }, + "interval": { + "type": "string", + "x-go-name": "Interval" + }, + "last_error": { + "type": "string", + "x-go-name": "LastError" + }, + "last_update": { + "type": "string", + "x-go-name": "LastUpdateUnix" + }, + "remote_address": { + "type": "string", + "x-go-name": "RemoteAddress" + }, + "remote_name": { + "type": "string", + "x-go-name": "RemoteName" + }, + "repo_name": { + "type": "string", + "x-go-name": "RepoName" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "Reaction": { "description": "Reaction contain one reaction", "type": "object", @@ -19293,6 +19578,21 @@ } } }, + "PushMirror": { + "description": "PushMirror", + "schema": { + "$ref": "#/definitions/PushMirror" + } + }, + "PushMirrorList": { + "description": "PushMirrorList", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/PushMirror" + } + } + }, "Reaction": { "description": "Reaction", "schema": { @@ -19572,7 +19872,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/CreateWikiPageOptions" + "$ref": "#/definitions/CreatePushMirrorOption" } }, "redirect": {