gitea源码

helm.go 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package helm
  4. import (
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "net/url"
  10. "strings"
  11. "time"
  12. packages_model "code.gitea.io/gitea/models/packages"
  13. "code.gitea.io/gitea/modules/json"
  14. "code.gitea.io/gitea/modules/optional"
  15. packages_module "code.gitea.io/gitea/modules/packages"
  16. helm_module "code.gitea.io/gitea/modules/packages/helm"
  17. "code.gitea.io/gitea/modules/setting"
  18. "code.gitea.io/gitea/modules/util"
  19. "code.gitea.io/gitea/routers/api/packages/helper"
  20. "code.gitea.io/gitea/services/context"
  21. packages_service "code.gitea.io/gitea/services/packages"
  22. "gopkg.in/yaml.v3"
  23. )
  24. func apiError(ctx *context.Context, status int, obj any) {
  25. message := helper.ProcessErrorForUser(ctx, status, obj)
  26. type Error struct {
  27. Error string `json:"error"`
  28. }
  29. ctx.JSON(status, Error{
  30. Error: message,
  31. })
  32. }
  33. // Index generates the Helm charts index
  34. func Index(ctx *context.Context) {
  35. pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
  36. OwnerID: ctx.Package.Owner.ID,
  37. Type: packages_model.TypeHelm,
  38. IsInternal: optional.Some(false),
  39. })
  40. if err != nil {
  41. apiError(ctx, http.StatusInternalServerError, err)
  42. return
  43. }
  44. baseURL := setting.AppURL + "api/packages/" + url.PathEscape(ctx.Package.Owner.Name) + "/helm"
  45. type ChartVersion struct {
  46. helm_module.Metadata `yaml:",inline"`
  47. URLs []string `yaml:"urls"`
  48. Created time.Time `yaml:"created,omitempty"`
  49. Removed bool `yaml:"removed,omitempty"`
  50. Digest string `yaml:"digest,omitempty"`
  51. }
  52. type ServerInfo struct {
  53. ContextPath string `yaml:"contextPath,omitempty"`
  54. }
  55. type Index struct {
  56. APIVersion string `yaml:"apiVersion"`
  57. Entries map[string][]*ChartVersion `yaml:"entries"`
  58. Generated time.Time `yaml:"generated,omitempty"`
  59. ServerInfo *ServerInfo `yaml:"serverInfo,omitempty"`
  60. }
  61. entries := make(map[string][]*ChartVersion)
  62. for _, pv := range pvs {
  63. metadata := &helm_module.Metadata{}
  64. if err := json.Unmarshal([]byte(pv.MetadataJSON), &metadata); err != nil {
  65. apiError(ctx, http.StatusInternalServerError, err)
  66. return
  67. }
  68. entries[metadata.Name] = append(entries[metadata.Name], &ChartVersion{
  69. Metadata: *metadata,
  70. Created: pv.CreatedUnix.AsTime(),
  71. URLs: []string{fmt.Sprintf("%s/%s", baseURL, url.PathEscape(createFilename(metadata)))},
  72. })
  73. }
  74. ctx.Resp.WriteHeader(http.StatusOK)
  75. _ = yaml.NewEncoder(ctx.Resp).Encode(&Index{
  76. APIVersion: "v1",
  77. Entries: entries,
  78. Generated: time.Now(),
  79. ServerInfo: &ServerInfo{
  80. ContextPath: setting.AppSubURL + "/api/packages/" + url.PathEscape(ctx.Package.Owner.Name) + "/helm",
  81. },
  82. })
  83. }
  84. // DownloadPackageFile serves the content of a package
  85. func DownloadPackageFile(ctx *context.Context) {
  86. filename := ctx.PathParam("filename")
  87. pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
  88. OwnerID: ctx.Package.Owner.ID,
  89. Type: packages_model.TypeHelm,
  90. Name: packages_model.SearchValue{
  91. ExactMatch: true,
  92. Value: ctx.PathParam("package"),
  93. },
  94. HasFileWithName: filename,
  95. IsInternal: optional.Some(false),
  96. })
  97. if err != nil {
  98. apiError(ctx, http.StatusInternalServerError, err)
  99. return
  100. }
  101. if len(pvs) != 1 {
  102. apiError(ctx, http.StatusNotFound, nil)
  103. return
  104. }
  105. s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
  106. ctx,
  107. pvs[0],
  108. &packages_service.PackageFileInfo{
  109. Filename: filename,
  110. },
  111. ctx.Req.Method,
  112. )
  113. if err != nil {
  114. if errors.Is(err, packages_model.ErrPackageFileNotExist) {
  115. apiError(ctx, http.StatusNotFound, err)
  116. return
  117. }
  118. apiError(ctx, http.StatusInternalServerError, err)
  119. return
  120. }
  121. helper.ServePackageFile(ctx, s, u, pf)
  122. }
  123. // UploadPackage creates a new package
  124. func UploadPackage(ctx *context.Context) {
  125. upload, needToClose, err := ctx.UploadStream()
  126. if err != nil {
  127. apiError(ctx, http.StatusInternalServerError, err)
  128. return
  129. }
  130. if needToClose {
  131. defer upload.Close()
  132. }
  133. buf, err := packages_module.CreateHashedBufferFromReader(upload)
  134. if err != nil {
  135. apiError(ctx, http.StatusInternalServerError, err)
  136. return
  137. }
  138. defer buf.Close()
  139. metadata, err := helm_module.ParseChartArchive(buf)
  140. if err != nil {
  141. if errors.Is(err, util.ErrInvalidArgument) {
  142. apiError(ctx, http.StatusBadRequest, err)
  143. } else {
  144. apiError(ctx, http.StatusInternalServerError, err)
  145. }
  146. return
  147. }
  148. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  149. apiError(ctx, http.StatusInternalServerError, err)
  150. return
  151. }
  152. _, _, err = packages_service.CreatePackageOrAddFileToExisting(
  153. ctx,
  154. &packages_service.PackageCreationInfo{
  155. PackageInfo: packages_service.PackageInfo{
  156. Owner: ctx.Package.Owner,
  157. PackageType: packages_model.TypeHelm,
  158. Name: metadata.Name,
  159. Version: metadata.Version,
  160. },
  161. SemverCompatible: true,
  162. Creator: ctx.Doer,
  163. Metadata: metadata,
  164. },
  165. &packages_service.PackageFileCreationInfo{
  166. PackageFileInfo: packages_service.PackageFileInfo{
  167. Filename: createFilename(metadata),
  168. },
  169. Creator: ctx.Doer,
  170. Data: buf,
  171. IsLead: true,
  172. OverwriteExisting: true,
  173. },
  174. )
  175. if err != nil {
  176. switch err {
  177. case packages_model.ErrDuplicatePackageVersion:
  178. apiError(ctx, http.StatusConflict, err)
  179. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  180. apiError(ctx, http.StatusForbidden, err)
  181. default:
  182. apiError(ctx, http.StatusInternalServerError, err)
  183. }
  184. return
  185. }
  186. ctx.Status(http.StatusCreated)
  187. }
  188. func createFilename(metadata *helm_module.Metadata) string {
  189. return strings.ToLower(fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version))
  190. }