code.gitea.io/gitea@v1.22.3/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/setting" 17 api "code.gitea.io/gitea/modules/structs" 18 "code.gitea.io/gitea/modules/util" 19 "code.gitea.io/gitea/modules/web" 20 "code.gitea.io/gitea/routers/api/v1/utils" 21 "code.gitea.io/gitea/services/context" 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(ctx, mirror) 180 if err == nil { 181 responsePushMirrors = append(responsePushMirrors, m) 182 } 183 } 184 ctx.SetLinkHeader(len(responsePushMirrors), utils.GetListOptions(ctx).PageSize) 185 ctx.SetTotalCountHeader(count) 186 ctx.JSON(http.StatusOK, responsePushMirrors) 187 } 188 189 // GetPushMirrorByName get push mirror of a repository by name 190 func GetPushMirrorByName(ctx *context.APIContext) { 191 // swagger:operation GET /repos/{owner}/{repo}/push_mirrors/{name} repository repoGetPushMirrorByRemoteName 192 // --- 193 // summary: Get push mirror of the repository by remoteName 194 // produces: 195 // - application/json 196 // parameters: 197 // - name: owner 198 // in: path 199 // description: owner of the repo 200 // type: string 201 // required: true 202 // - name: repo 203 // in: path 204 // description: name of the repo 205 // type: string 206 // required: true 207 // - name: name 208 // in: path 209 // description: remote name of push mirror 210 // type: string 211 // required: true 212 // responses: 213 // "200": 214 // "$ref": "#/responses/PushMirror" 215 // "400": 216 // "$ref": "#/responses/error" 217 // "403": 218 // "$ref": "#/responses/forbidden" 219 // "404": 220 // "$ref": "#/responses/notFound" 221 222 if !setting.Mirror.Enabled { 223 ctx.Error(http.StatusBadRequest, "GetPushMirrorByRemoteName", "Mirror feature is disabled") 224 return 225 } 226 227 mirrorName := ctx.Params(":name") 228 // Get push mirror of a specific repo by remoteName 229 pushMirror, exist, err := db.Get[repo_model.PushMirror](ctx, repo_model.PushMirrorOptions{ 230 RepoID: ctx.Repo.Repository.ID, 231 RemoteName: mirrorName, 232 }.ToConds()) 233 if err != nil { 234 ctx.Error(http.StatusInternalServerError, "GetPushMirrors", err) 235 return 236 } else if !exist { 237 ctx.Error(http.StatusNotFound, "GetPushMirrors", nil) 238 return 239 } 240 241 m, err := convert.ToPushMirror(ctx, pushMirror) 242 if err != nil { 243 ctx.ServerError("GetPushMirrorByRemoteName", err) 244 return 245 } 246 ctx.JSON(http.StatusOK, m) 247 } 248 249 // AddPushMirror adds a push mirror to a repository 250 func AddPushMirror(ctx *context.APIContext) { 251 // swagger:operation POST /repos/{owner}/{repo}/push_mirrors repository repoAddPushMirror 252 // --- 253 // summary: add a push mirror to the repository 254 // consumes: 255 // - application/json 256 // produces: 257 // - application/json 258 // parameters: 259 // - name: owner 260 // in: path 261 // description: owner of the repo 262 // type: string 263 // required: true 264 // - name: repo 265 // in: path 266 // description: name of the repo 267 // type: string 268 // required: true 269 // - name: body 270 // in: body 271 // schema: 272 // "$ref": "#/definitions/CreatePushMirrorOption" 273 // responses: 274 // "200": 275 // "$ref": "#/responses/PushMirror" 276 // "403": 277 // "$ref": "#/responses/forbidden" 278 // "400": 279 // "$ref": "#/responses/error" 280 // "404": 281 // "$ref": "#/responses/notFound" 282 283 if !setting.Mirror.Enabled { 284 ctx.Error(http.StatusBadRequest, "AddPushMirror", "Mirror feature is disabled") 285 return 286 } 287 288 pushMirror := web.GetForm(ctx).(*api.CreatePushMirrorOption) 289 CreatePushMirror(ctx, pushMirror) 290 } 291 292 // DeletePushMirrorByRemoteName deletes a push mirror from a repository by remoteName 293 func DeletePushMirrorByRemoteName(ctx *context.APIContext) { 294 // swagger:operation DELETE /repos/{owner}/{repo}/push_mirrors/{name} repository repoDeletePushMirror 295 // --- 296 // summary: deletes a push mirror from a repository by remoteName 297 // produces: 298 // - application/json 299 // parameters: 300 // - name: owner 301 // in: path 302 // description: owner of the repo 303 // type: string 304 // required: true 305 // - name: repo 306 // in: path 307 // description: name of the repo 308 // type: string 309 // required: true 310 // - name: name 311 // in: path 312 // description: remote name of the pushMirror 313 // type: string 314 // required: true 315 // responses: 316 // "204": 317 // "$ref": "#/responses/empty" 318 // "404": 319 // "$ref": "#/responses/notFound" 320 // "400": 321 // "$ref": "#/responses/error" 322 323 if !setting.Mirror.Enabled { 324 ctx.Error(http.StatusBadRequest, "DeletePushMirrorByName", "Mirror feature is disabled") 325 return 326 } 327 328 remoteName := ctx.Params(":name") 329 // Delete push mirror on repo by name. 330 err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: remoteName}) 331 if err != nil { 332 ctx.Error(http.StatusNotFound, "DeletePushMirrors", err) 333 return 334 } 335 ctx.Status(http.StatusNoContent) 336 } 337 338 func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirrorOption) { 339 repo := ctx.Repo.Repository 340 341 interval, err := time.ParseDuration(mirrorOption.Interval) 342 if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { 343 ctx.Error(http.StatusBadRequest, "CreatePushMirror", err) 344 return 345 } 346 347 address, err := forms.ParseRemoteAddr(mirrorOption.RemoteAddress, mirrorOption.RemoteUsername, mirrorOption.RemotePassword) 348 if err == nil { 349 err = migrations.IsMigrateURLAllowed(address, ctx.ContextUser) 350 } 351 if err != nil { 352 HandleRemoteAddressError(ctx, err) 353 return 354 } 355 356 remoteSuffix, err := util.CryptoRandomString(10) 357 if err != nil { 358 ctx.ServerError("CryptoRandomString", err) 359 return 360 } 361 362 remoteAddress, err := util.SanitizeURL(mirrorOption.RemoteAddress) 363 if err != nil { 364 ctx.ServerError("SanitizeURL", err) 365 return 366 } 367 368 pushMirror := &repo_model.PushMirror{ 369 RepoID: repo.ID, 370 Repo: repo, 371 RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), 372 Interval: interval, 373 SyncOnCommit: mirrorOption.SyncOnCommit, 374 RemoteAddress: remoteAddress, 375 } 376 377 if err = db.Insert(ctx, pushMirror); err != nil { 378 ctx.ServerError("InsertPushMirror", err) 379 return 380 } 381 382 // if the registration of the push mirrorOption fails remove it from the database 383 if err = mirror_service.AddPushMirrorRemote(ctx, pushMirror, address); err != nil { 384 if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: pushMirror.ID, RepoID: pushMirror.RepoID}); err != nil { 385 ctx.ServerError("DeletePushMirrors", err) 386 return 387 } 388 ctx.ServerError("AddPushMirrorRemote", err) 389 return 390 } 391 m, err := convert.ToPushMirror(ctx, pushMirror) 392 if err != nil { 393 ctx.ServerError("ToPushMirror", err) 394 return 395 } 396 ctx.JSON(http.StatusOK, m) 397 } 398 399 func HandleRemoteAddressError(ctx *context.APIContext, err error) { 400 if models.IsErrInvalidCloneAddr(err) { 401 addrErr := err.(*models.ErrInvalidCloneAddr) 402 switch { 403 case addrErr.IsProtocolInvalid: 404 ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Invalid mirror protocol") 405 case addrErr.IsURLError: 406 ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Invalid Url ") 407 case addrErr.IsPermissionDenied: 408 ctx.Error(http.StatusUnauthorized, "CreatePushMirror", "Permission denied") 409 default: 410 ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Unknown error") 411 } 412 return 413 } 414 }