gitea源码

mirror.go 11KB

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