code.gitea.io/gitea@v1.21.7/routers/api/v1/repo/mirror.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package repo 5 6 import ( 7 "errors" 8 "fmt" 9 "net/http" 10 "time" 11 12 "code.gitea.io/gitea/models" 13 "code.gitea.io/gitea/models/db" 14 repo_model "code.gitea.io/gitea/models/repo" 15 "code.gitea.io/gitea/models/unit" 16 "code.gitea.io/gitea/modules/context" 17 "code.gitea.io/gitea/modules/setting" 18 api "code.gitea.io/gitea/modules/structs" 19 "code.gitea.io/gitea/modules/util" 20 "code.gitea.io/gitea/modules/web" 21 "code.gitea.io/gitea/routers/api/v1/utils" 22 "code.gitea.io/gitea/services/convert" 23 "code.gitea.io/gitea/services/forms" 24 "code.gitea.io/gitea/services/migrations" 25 mirror_service "code.gitea.io/gitea/services/mirror" 26 ) 27 28 // MirrorSync adds a mirrored repository to the sync queue 29 func MirrorSync(ctx *context.APIContext) { 30 // swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync 31 // --- 32 // summary: Sync a mirrored repository 33 // produces: 34 // - application/json 35 // parameters: 36 // - name: owner 37 // in: path 38 // description: owner of the repo to sync 39 // type: string 40 // required: true 41 // - name: repo 42 // in: path 43 // description: name of the repo to sync 44 // type: string 45 // required: true 46 // responses: 47 // "200": 48 // "$ref": "#/responses/empty" 49 // "403": 50 // "$ref": "#/responses/forbidden" 51 // "404": 52 // "$ref": "#/responses/notFound" 53 54 repo := ctx.Repo.Repository 55 56 if !ctx.Repo.CanWrite(unit.TypeCode) { 57 ctx.Error(http.StatusForbidden, "MirrorSync", "Must have write access") 58 } 59 60 if !setting.Mirror.Enabled { 61 ctx.Error(http.StatusBadRequest, "MirrorSync", "Mirror feature is disabled") 62 return 63 } 64 65 if _, err := repo_model.GetMirrorByRepoID(ctx, repo.ID); err != nil { 66 if errors.Is(err, repo_model.ErrMirrorNotExist) { 67 ctx.Error(http.StatusBadRequest, "MirrorSync", "Repository is not a mirror") 68 return 69 } 70 ctx.Error(http.StatusInternalServerError, "MirrorSync", err) 71 return 72 } 73 74 mirror_service.AddPullMirrorToQueue(repo.ID) 75 76 ctx.Status(http.StatusOK) 77 } 78 79 // PushMirrorSync adds all push mirrored repositories to the sync queue 80 func PushMirrorSync(ctx *context.APIContext) { 81 // swagger:operation POST /repos/{owner}/{repo}/push_mirrors-sync repository repoPushMirrorSync 82 // --- 83 // summary: Sync all push mirrored repository 84 // produces: 85 // - application/json 86 // parameters: 87 // - name: owner 88 // in: path 89 // description: owner of the repo to sync 90 // type: string 91 // required: true 92 // - name: repo 93 // in: path 94 // description: name of the repo to sync 95 // type: string 96 // required: true 97 // responses: 98 // "200": 99 // "$ref": "#/responses/empty" 100 // "400": 101 // "$ref": "#/responses/error" 102 // "403": 103 // "$ref": "#/responses/forbidden" 104 // "404": 105 // "$ref": "#/responses/notFound" 106 107 if !setting.Mirror.Enabled { 108 ctx.Error(http.StatusBadRequest, "PushMirrorSync", "Mirror feature is disabled") 109 return 110 } 111 // Get All push mirrors of a specific repo 112 pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, ctx.Repo.Repository.ID, db.ListOptions{}) 113 if err != nil { 114 ctx.Error(http.StatusNotFound, "PushMirrorSync", err) 115 return 116 } 117 for _, mirror := range pushMirrors { 118 ok := mirror_service.SyncPushMirror(ctx, mirror.ID) 119 if !ok { 120 ctx.Error(http.StatusInternalServerError, "PushMirrorSync", "error occurred when syncing push mirror "+mirror.RemoteName) 121 return 122 } 123 } 124 125 ctx.Status(http.StatusOK) 126 } 127 128 // ListPushMirrors get list of push mirrors of a repository 129 func ListPushMirrors(ctx *context.APIContext) { 130 // swagger:operation GET /repos/{owner}/{repo}/push_mirrors repository repoListPushMirrors 131 // --- 132 // summary: Get all push mirrors of the repository 133 // produces: 134 // - application/json 135 // parameters: 136 // - name: owner 137 // in: path 138 // description: owner of the repo 139 // type: string 140 // required: true 141 // - name: repo 142 // in: path 143 // description: name of the repo 144 // type: string 145 // required: true 146 // - name: page 147 // in: query 148 // description: page number of results to return (1-based) 149 // type: integer 150 // - name: limit 151 // in: query 152 // description: page size of results 153 // type: integer 154 // responses: 155 // "200": 156 // "$ref": "#/responses/PushMirrorList" 157 // "400": 158 // "$ref": "#/responses/error" 159 // "403": 160 // "$ref": "#/responses/forbidden" 161 // "404": 162 // "$ref": "#/responses/notFound" 163 164 if !setting.Mirror.Enabled { 165 ctx.Error(http.StatusBadRequest, "GetPushMirrorsByRepoID", "Mirror feature is disabled") 166 return 167 } 168 169 repo := ctx.Repo.Repository 170 // Get all push mirrors for the specified repository. 171 pushMirrors, count, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, utils.GetListOptions(ctx)) 172 if err != nil { 173 ctx.Error(http.StatusNotFound, "GetPushMirrorsByRepoID", err) 174 return 175 } 176 177 responsePushMirrors := make([]*api.PushMirror, 0, len(pushMirrors)) 178 for _, mirror := range pushMirrors { 179 m, err := convert.ToPushMirror(mirror) 180 if err == nil { 181 responsePushMirrors = append(responsePushMirrors, m) 182 } 183 184 } 185 ctx.SetLinkHeader(len(responsePushMirrors), utils.GetListOptions(ctx).PageSize) 186 ctx.SetTotalCountHeader(count) 187 ctx.JSON(http.StatusOK, responsePushMirrors) 188 } 189 190 // GetPushMirrorByName get push mirror of a repository by name 191 func GetPushMirrorByName(ctx *context.APIContext) { 192 // swagger:operation GET /repos/{owner}/{repo}/push_mirrors/{name} repository repoGetPushMirrorByRemoteName 193 // --- 194 // summary: Get push mirror of the repository by remoteName 195 // produces: 196 // - application/json 197 // parameters: 198 // - name: owner 199 // in: path 200 // description: owner of the repo 201 // type: string 202 // required: true 203 // - name: repo 204 // in: path 205 // description: name of the repo 206 // type: string 207 // required: true 208 // - name: name 209 // in: path 210 // description: remote name of push mirror 211 // type: string 212 // required: true 213 // responses: 214 // "200": 215 // "$ref": "#/responses/PushMirror" 216 // "400": 217 // "$ref": "#/responses/error" 218 // "403": 219 // "$ref": "#/responses/forbidden" 220 // "404": 221 // "$ref": "#/responses/notFound" 222 223 if !setting.Mirror.Enabled { 224 ctx.Error(http.StatusBadRequest, "GetPushMirrorByRemoteName", "Mirror feature is disabled") 225 return 226 } 227 228 mirrorName := ctx.Params(":name") 229 // Get push mirror of a specific repo by remoteName 230 pushMirror, err := repo_model.GetPushMirror(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: mirrorName}) 231 if err != nil { 232 ctx.Error(http.StatusNotFound, "GetPushMirrors", err) 233 return 234 } 235 m, err := convert.ToPushMirror(pushMirror) 236 if err != nil { 237 ctx.ServerError("GetPushMirrorByRemoteName", err) 238 return 239 } 240 ctx.JSON(http.StatusOK, m) 241 } 242 243 // AddPushMirror adds a push mirror to a repository 244 func AddPushMirror(ctx *context.APIContext) { 245 // swagger:operation POST /repos/{owner}/{repo}/push_mirrors repository repoAddPushMirror 246 // --- 247 // summary: add a push mirror to the repository 248 // consumes: 249 // - application/json 250 // produces: 251 // - application/json 252 // parameters: 253 // - name: owner 254 // in: path 255 // description: owner of the repo 256 // type: string 257 // required: true 258 // - name: repo 259 // in: path 260 // description: name of the repo 261 // type: string 262 // required: true 263 // - name: body 264 // in: body 265 // schema: 266 // "$ref": "#/definitions/CreatePushMirrorOption" 267 // responses: 268 // "200": 269 // "$ref": "#/responses/PushMirror" 270 // "403": 271 // "$ref": "#/responses/forbidden" 272 // "400": 273 // "$ref": "#/responses/error" 274 // "404": 275 // "$ref": "#/responses/notFound" 276 277 if !setting.Mirror.Enabled { 278 ctx.Error(http.StatusBadRequest, "AddPushMirror", "Mirror feature is disabled") 279 return 280 } 281 282 pushMirror := web.GetForm(ctx).(*api.CreatePushMirrorOption) 283 CreatePushMirror(ctx, pushMirror) 284 } 285 286 // DeletePushMirrorByRemoteName deletes a push mirror from a repository by remoteName 287 func DeletePushMirrorByRemoteName(ctx *context.APIContext) { 288 // swagger:operation DELETE /repos/{owner}/{repo}/push_mirrors/{name} repository repoDeletePushMirror 289 // --- 290 // summary: deletes a push mirror from a repository by remoteName 291 // produces: 292 // - application/json 293 // parameters: 294 // - name: owner 295 // in: path 296 // description: owner of the repo 297 // type: string 298 // required: true 299 // - name: repo 300 // in: path 301 // description: name of the repo 302 // type: string 303 // required: true 304 // - name: name 305 // in: path 306 // description: remote name of the pushMirror 307 // type: string 308 // required: true 309 // responses: 310 // "204": 311 // "$ref": "#/responses/empty" 312 // "404": 313 // "$ref": "#/responses/notFound" 314 // "400": 315 // "$ref": "#/responses/error" 316 317 if !setting.Mirror.Enabled { 318 ctx.Error(http.StatusBadRequest, "DeletePushMirrorByName", "Mirror feature is disabled") 319 return 320 } 321 322 remoteName := ctx.Params(":name") 323 // Delete push mirror on repo by name. 324 err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: remoteName}) 325 if err != nil { 326 ctx.Error(http.StatusNotFound, "DeletePushMirrors", err) 327 return 328 } 329 ctx.Status(http.StatusNoContent) 330 } 331 332 func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirrorOption) { 333 repo := ctx.Repo.Repository 334 335 interval, err := time.ParseDuration(mirrorOption.Interval) 336 if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { 337 ctx.Error(http.StatusBadRequest, "CreatePushMirror", err) 338 return 339 } 340 341 address, err := forms.ParseRemoteAddr(mirrorOption.RemoteAddress, mirrorOption.RemoteUsername, mirrorOption.RemotePassword) 342 if err == nil { 343 err = migrations.IsMigrateURLAllowed(address, ctx.ContextUser) 344 } 345 if err != nil { 346 HandleRemoteAddressError(ctx, err) 347 return 348 } 349 350 remoteSuffix, err := util.CryptoRandomString(10) 351 if err != nil { 352 ctx.ServerError("CryptoRandomString", err) 353 return 354 } 355 356 remoteAddress, err := util.SanitizeURL(mirrorOption.RemoteAddress) 357 if err != nil { 358 ctx.ServerError("SanitizeURL", err) 359 return 360 } 361 362 pushMirror := &repo_model.PushMirror{ 363 RepoID: repo.ID, 364 Repo: repo, 365 RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), 366 Interval: interval, 367 SyncOnCommit: mirrorOption.SyncOnCommit, 368 RemoteAddress: remoteAddress, 369 } 370 371 if err = repo_model.InsertPushMirror(ctx, pushMirror); err != nil { 372 ctx.ServerError("InsertPushMirror", err) 373 return 374 } 375 376 // if the registration of the push mirrorOption fails remove it from the database 377 if err = mirror_service.AddPushMirrorRemote(ctx, pushMirror, address); err != nil { 378 if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: pushMirror.ID, RepoID: pushMirror.RepoID}); err != nil { 379 ctx.ServerError("DeletePushMirrors", err) 380 } 381 ctx.ServerError("AddPushMirrorRemote", err) 382 return 383 } 384 m, err := convert.ToPushMirror(pushMirror) 385 if err != nil { 386 ctx.ServerError("ToPushMirror", err) 387 return 388 } 389 ctx.JSON(http.StatusOK, m) 390 } 391 392 func HandleRemoteAddressError(ctx *context.APIContext, err error) { 393 if models.IsErrInvalidCloneAddr(err) { 394 addrErr := err.(*models.ErrInvalidCloneAddr) 395 switch { 396 case addrErr.IsProtocolInvalid: 397 ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Invalid mirror protocol") 398 case addrErr.IsURLError: 399 ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Invalid Url ") 400 case addrErr.IsPermissionDenied: 401 ctx.Error(http.StatusUnauthorized, "CreatePushMirror", "Permission denied") 402 default: 403 ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Unknown error") 404 } 405 return 406 } 407 }