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