You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mirror.go 11KB

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