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.

alpine.go 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package alpine
  4. import (
  5. "crypto/x509"
  6. "encoding/hex"
  7. "encoding/pem"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "net/http"
  12. "strings"
  13. packages_model "code.gitea.io/gitea/models/packages"
  14. "code.gitea.io/gitea/modules/json"
  15. packages_module "code.gitea.io/gitea/modules/packages"
  16. alpine_module "code.gitea.io/gitea/modules/packages/alpine"
  17. "code.gitea.io/gitea/modules/util"
  18. "code.gitea.io/gitea/routers/api/packages/helper"
  19. "code.gitea.io/gitea/services/context"
  20. packages_service "code.gitea.io/gitea/services/packages"
  21. alpine_service "code.gitea.io/gitea/services/packages/alpine"
  22. )
  23. func apiError(ctx *context.Context, status int, obj any) {
  24. helper.LogAndProcessError(ctx, status, obj, func(message string) {
  25. ctx.PlainText(status, message)
  26. })
  27. }
  28. func GetRepositoryKey(ctx *context.Context) {
  29. _, pub, err := alpine_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
  30. if err != nil {
  31. apiError(ctx, http.StatusInternalServerError, err)
  32. return
  33. }
  34. pubPem, _ := pem.Decode([]byte(pub))
  35. if pubPem == nil {
  36. apiError(ctx, http.StatusInternalServerError, "failed to decode private key pem")
  37. return
  38. }
  39. pubKey, err := x509.ParsePKIXPublicKey(pubPem.Bytes)
  40. if err != nil {
  41. apiError(ctx, http.StatusInternalServerError, err)
  42. return
  43. }
  44. fingerprint, err := util.CreatePublicKeyFingerprint(pubKey)
  45. if err != nil {
  46. apiError(ctx, http.StatusInternalServerError, err)
  47. return
  48. }
  49. ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{
  50. ContentType: "application/x-pem-file",
  51. Filename: fmt.Sprintf("%s@%s.rsa.pub", ctx.Package.Owner.LowerName, hex.EncodeToString(fingerprint)),
  52. })
  53. }
  54. func GetRepositoryFile(ctx *context.Context) {
  55. pv, err := alpine_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
  56. if err != nil {
  57. apiError(ctx, http.StatusInternalServerError, err)
  58. return
  59. }
  60. s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
  61. ctx,
  62. pv,
  63. &packages_service.PackageFileInfo{
  64. Filename: alpine_service.IndexArchiveFilename,
  65. CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")),
  66. },
  67. )
  68. if err != nil {
  69. if errors.Is(err, util.ErrNotExist) {
  70. apiError(ctx, http.StatusNotFound, err)
  71. } else {
  72. apiError(ctx, http.StatusInternalServerError, err)
  73. }
  74. return
  75. }
  76. helper.ServePackageFile(ctx, s, u, pf)
  77. }
  78. func UploadPackageFile(ctx *context.Context) {
  79. branch := strings.TrimSpace(ctx.Params("branch"))
  80. repository := strings.TrimSpace(ctx.Params("repository"))
  81. if branch == "" || repository == "" {
  82. apiError(ctx, http.StatusBadRequest, "invalid branch or repository")
  83. return
  84. }
  85. upload, needToClose, err := ctx.UploadStream()
  86. if err != nil {
  87. apiError(ctx, http.StatusInternalServerError, err)
  88. return
  89. }
  90. if needToClose {
  91. defer upload.Close()
  92. }
  93. buf, err := packages_module.CreateHashedBufferFromReader(upload)
  94. if err != nil {
  95. apiError(ctx, http.StatusInternalServerError, err)
  96. return
  97. }
  98. defer buf.Close()
  99. pck, err := alpine_module.ParsePackage(buf)
  100. if err != nil {
  101. if errors.Is(err, util.ErrInvalidArgument) || err == io.EOF {
  102. apiError(ctx, http.StatusBadRequest, err)
  103. } else {
  104. apiError(ctx, http.StatusInternalServerError, err)
  105. }
  106. return
  107. }
  108. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  109. apiError(ctx, http.StatusInternalServerError, err)
  110. return
  111. }
  112. fileMetadataRaw, err := json.Marshal(pck.FileMetadata)
  113. if err != nil {
  114. apiError(ctx, http.StatusInternalServerError, err)
  115. return
  116. }
  117. _, _, err = packages_service.CreatePackageOrAddFileToExisting(
  118. ctx,
  119. &packages_service.PackageCreationInfo{
  120. PackageInfo: packages_service.PackageInfo{
  121. Owner: ctx.Package.Owner,
  122. PackageType: packages_model.TypeAlpine,
  123. Name: pck.Name,
  124. Version: pck.Version,
  125. },
  126. Creator: ctx.Doer,
  127. Metadata: pck.VersionMetadata,
  128. },
  129. &packages_service.PackageFileCreationInfo{
  130. PackageFileInfo: packages_service.PackageFileInfo{
  131. Filename: fmt.Sprintf("%s-%s.apk", pck.Name, pck.Version),
  132. CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, pck.FileMetadata.Architecture),
  133. },
  134. Creator: ctx.Doer,
  135. Data: buf,
  136. IsLead: true,
  137. Properties: map[string]string{
  138. alpine_module.PropertyBranch: branch,
  139. alpine_module.PropertyRepository: repository,
  140. alpine_module.PropertyArchitecture: pck.FileMetadata.Architecture,
  141. alpine_module.PropertyMetadata: string(fileMetadataRaw),
  142. },
  143. },
  144. )
  145. if err != nil {
  146. switch err {
  147. case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
  148. apiError(ctx, http.StatusConflict, err)
  149. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  150. apiError(ctx, http.StatusForbidden, err)
  151. default:
  152. apiError(ctx, http.StatusInternalServerError, err)
  153. }
  154. return
  155. }
  156. if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, pck.FileMetadata.Architecture); err != nil {
  157. apiError(ctx, http.StatusInternalServerError, err)
  158. return
  159. }
  160. ctx.Status(http.StatusCreated)
  161. }
  162. func DownloadPackageFile(ctx *context.Context) {
  163. branch := ctx.Params("branch")
  164. repository := ctx.Params("repository")
  165. architecture := ctx.Params("architecture")
  166. opts := &packages_model.PackageFileSearchOptions{
  167. OwnerID: ctx.Package.Owner.ID,
  168. PackageType: packages_model.TypeAlpine,
  169. Query: ctx.Params("filename"),
  170. CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
  171. }
  172. pfs, _, err := packages_model.SearchFiles(ctx, opts)
  173. if err != nil {
  174. apiError(ctx, http.StatusInternalServerError, err)
  175. return
  176. }
  177. if len(pfs) == 0 {
  178. // Try again with architecture 'noarch'
  179. if architecture == alpine_module.NoArch {
  180. apiError(ctx, http.StatusNotFound, nil)
  181. return
  182. }
  183. opts.CompositeKey = fmt.Sprintf("%s|%s|%s", branch, repository, alpine_module.NoArch)
  184. if pfs, _, err = packages_model.SearchFiles(ctx, opts); err != nil {
  185. apiError(ctx, http.StatusInternalServerError, err)
  186. return
  187. }
  188. if len(pfs) == 0 {
  189. apiError(ctx, http.StatusNotFound, nil)
  190. return
  191. }
  192. }
  193. s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
  194. if err != nil {
  195. if errors.Is(err, util.ErrNotExist) {
  196. apiError(ctx, http.StatusNotFound, err)
  197. } else {
  198. apiError(ctx, http.StatusInternalServerError, err)
  199. }
  200. return
  201. }
  202. helper.ServePackageFile(ctx, s, u, pf)
  203. }
  204. func DeletePackageFile(ctx *context.Context) {
  205. branch, repository, architecture := ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")
  206. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  207. OwnerID: ctx.Package.Owner.ID,
  208. PackageType: packages_model.TypeAlpine,
  209. Query: ctx.Params("filename"),
  210. CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
  211. })
  212. if err != nil {
  213. apiError(ctx, http.StatusInternalServerError, err)
  214. return
  215. }
  216. if len(pfs) != 1 {
  217. apiError(ctx, http.StatusNotFound, nil)
  218. return
  219. }
  220. if err := packages_service.RemovePackageFileAndVersionIfUnreferenced(ctx, ctx.Doer, pfs[0]); err != nil {
  221. if errors.Is(err, util.ErrNotExist) {
  222. apiError(ctx, http.StatusNotFound, err)
  223. } else {
  224. apiError(ctx, http.StatusInternalServerError, err)
  225. }
  226. return
  227. }
  228. if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, architecture); err != nil {
  229. apiError(ctx, http.StatusInternalServerError, err)
  230. return
  231. }
  232. ctx.Status(http.StatusNoContent)
  233. }