code.gitea.io/gitea@v1.21.7/routers/api/packages/conda/conda.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package conda 5 6 import ( 7 "errors" 8 "fmt" 9 "io" 10 "net/http" 11 "strings" 12 13 packages_model "code.gitea.io/gitea/models/packages" 14 conda_model "code.gitea.io/gitea/models/packages/conda" 15 "code.gitea.io/gitea/modules/context" 16 "code.gitea.io/gitea/modules/json" 17 "code.gitea.io/gitea/modules/log" 18 packages_module "code.gitea.io/gitea/modules/packages" 19 conda_module "code.gitea.io/gitea/modules/packages/conda" 20 "code.gitea.io/gitea/modules/util" 21 "code.gitea.io/gitea/routers/api/packages/helper" 22 packages_service "code.gitea.io/gitea/services/packages" 23 24 "github.com/dsnet/compress/bzip2" 25 ) 26 27 func apiError(ctx *context.Context, status int, obj any) { 28 helper.LogAndProcessError(ctx, status, obj, func(message string) { 29 ctx.JSON(status, struct { 30 Reason string `json:"reason"` 31 Message string `json:"message"` 32 }{ 33 Reason: http.StatusText(status), 34 Message: message, 35 }) 36 }) 37 } 38 39 func EnumeratePackages(ctx *context.Context) { 40 type Info struct { 41 Subdir string `json:"subdir"` 42 } 43 44 type PackageInfo struct { 45 Name string `json:"name"` 46 Version string `json:"version"` 47 NoArch string `json:"noarch"` 48 Subdir string `json:"subdir"` 49 Timestamp int64 `json:"timestamp"` 50 Build string `json:"build"` 51 BuildNumber int64 `json:"build_number"` 52 Dependencies []string `json:"depends"` 53 License string `json:"license"` 54 LicenseFamily string `json:"license_family"` 55 HashMD5 string `json:"md5"` 56 HashSHA256 string `json:"sha256"` 57 Size int64 `json:"size"` 58 } 59 60 type RepoData struct { 61 Info Info `json:"info"` 62 Packages map[string]*PackageInfo `json:"packages"` 63 PackagesConda map[string]*PackageInfo `json:"packages.conda"` 64 Removed map[string]*PackageInfo `json:"removed"` 65 } 66 67 repoData := &RepoData{ 68 Info: Info{ 69 Subdir: ctx.Params("architecture"), 70 }, 71 Packages: make(map[string]*PackageInfo), 72 PackagesConda: make(map[string]*PackageInfo), 73 Removed: make(map[string]*PackageInfo), 74 } 75 76 pfs, err := conda_model.SearchFiles(ctx, &conda_model.FileSearchOptions{ 77 OwnerID: ctx.Package.Owner.ID, 78 Channel: ctx.Params("channel"), 79 Subdir: repoData.Info.Subdir, 80 }) 81 if err != nil { 82 apiError(ctx, http.StatusInternalServerError, err) 83 return 84 } 85 86 if len(pfs) == 0 { 87 apiError(ctx, http.StatusNotFound, nil) 88 return 89 } 90 91 pds := make(map[int64]*packages_model.PackageDescriptor) 92 93 for _, pf := range pfs { 94 pd, exists := pds[pf.VersionID] 95 if !exists { 96 pv, err := packages_model.GetVersionByID(ctx, pf.VersionID) 97 if err != nil { 98 apiError(ctx, http.StatusInternalServerError, err) 99 return 100 } 101 102 pd, err = packages_model.GetPackageDescriptor(ctx, pv) 103 if err != nil { 104 apiError(ctx, http.StatusInternalServerError, err) 105 return 106 } 107 108 pds[pf.VersionID] = pd 109 } 110 111 var pfd *packages_model.PackageFileDescriptor 112 for _, d := range pd.Files { 113 if d.File.ID == pf.ID { 114 pfd = d 115 break 116 } 117 } 118 119 var fileMetadata *conda_module.FileMetadata 120 if err := json.Unmarshal([]byte(pfd.Properties.GetByName(conda_module.PropertyMetadata)), &fileMetadata); err != nil { 121 apiError(ctx, http.StatusInternalServerError, err) 122 return 123 } 124 125 versionMetadata := pd.Metadata.(*conda_module.VersionMetadata) 126 127 pi := &PackageInfo{ 128 Name: pd.PackageProperties.GetByName(conda_module.PropertyName), 129 Version: pd.Version.Version, 130 NoArch: fileMetadata.NoArch, 131 Subdir: repoData.Info.Subdir, 132 Timestamp: fileMetadata.Timestamp, 133 Build: fileMetadata.Build, 134 BuildNumber: fileMetadata.BuildNumber, 135 Dependencies: fileMetadata.Dependencies, 136 License: versionMetadata.License, 137 LicenseFamily: versionMetadata.LicenseFamily, 138 HashMD5: pfd.Blob.HashMD5, 139 HashSHA256: pfd.Blob.HashSHA256, 140 Size: pfd.Blob.Size, 141 } 142 143 if fileMetadata.IsCondaPackage { 144 repoData.PackagesConda[pfd.File.Name] = pi 145 } else { 146 repoData.Packages[pfd.File.Name] = pi 147 } 148 } 149 150 resp := ctx.Resp 151 152 var w io.Writer = resp 153 154 if strings.HasSuffix(ctx.Params("filename"), ".json") { 155 resp.Header().Set("Content-Type", "application/json") 156 } else { 157 resp.Header().Set("Content-Type", "application/x-bzip2") 158 159 zw, err := bzip2.NewWriter(w, nil) 160 if err != nil { 161 apiError(ctx, http.StatusInternalServerError, err) 162 return 163 } 164 defer zw.Close() 165 166 w = zw 167 } 168 169 resp.WriteHeader(http.StatusOK) 170 171 if err := json.NewEncoder(w).Encode(repoData); err != nil { 172 log.Error("JSON encode: %v", err) 173 } 174 } 175 176 func UploadPackageFile(ctx *context.Context) { 177 upload, close, err := ctx.UploadStream() 178 if err != nil { 179 apiError(ctx, http.StatusInternalServerError, err) 180 return 181 } 182 if close { 183 defer upload.Close() 184 } 185 186 buf, err := packages_module.CreateHashedBufferFromReader(upload) 187 if err != nil { 188 apiError(ctx, http.StatusInternalServerError, err) 189 return 190 } 191 defer buf.Close() 192 193 var pck *conda_module.Package 194 if strings.HasSuffix(strings.ToLower(ctx.Params("filename")), ".tar.bz2") { 195 pck, err = conda_module.ParsePackageBZ2(buf) 196 } else { 197 pck, err = conda_module.ParsePackageConda(buf, buf.Size()) 198 } 199 if err != nil { 200 if errors.Is(err, util.ErrInvalidArgument) { 201 apiError(ctx, http.StatusBadRequest, err) 202 } else { 203 apiError(ctx, http.StatusInternalServerError, err) 204 } 205 return 206 } 207 208 if _, err := buf.Seek(0, io.SeekStart); err != nil { 209 apiError(ctx, http.StatusInternalServerError, err) 210 return 211 } 212 213 fullName := pck.Name 214 215 channel := ctx.Params("channel") 216 if channel != "" { 217 fullName = channel + "/" + pck.Name 218 } 219 220 extension := ".tar.bz2" 221 if pck.FileMetadata.IsCondaPackage { 222 extension = ".conda" 223 } 224 225 fileMetadataRaw, err := json.Marshal(pck.FileMetadata) 226 if err != nil { 227 apiError(ctx, http.StatusInternalServerError, err) 228 return 229 } 230 231 _, _, err = packages_service.CreatePackageOrAddFileToExisting( 232 ctx, 233 &packages_service.PackageCreationInfo{ 234 PackageInfo: packages_service.PackageInfo{ 235 Owner: ctx.Package.Owner, 236 PackageType: packages_model.TypeConda, 237 Name: fullName, 238 Version: pck.Version, 239 }, 240 SemverCompatible: false, 241 Creator: ctx.Doer, 242 Metadata: pck.VersionMetadata, 243 PackageProperties: map[string]string{ 244 conda_module.PropertyName: pck.Name, 245 conda_module.PropertyChannel: channel, 246 }, 247 }, 248 &packages_service.PackageFileCreationInfo{ 249 PackageFileInfo: packages_service.PackageFileInfo{ 250 Filename: fmt.Sprintf("%s-%s-%s%s", pck.Name, pck.Version, pck.FileMetadata.Build, extension), 251 CompositeKey: pck.Subdir, 252 }, 253 Creator: ctx.Doer, 254 Data: buf, 255 IsLead: true, 256 Properties: map[string]string{ 257 conda_module.PropertySubdir: pck.Subdir, 258 conda_module.PropertyMetadata: string(fileMetadataRaw), 259 }, 260 }, 261 ) 262 if err != nil { 263 switch err { 264 case packages_model.ErrDuplicatePackageFile: 265 apiError(ctx, http.StatusConflict, err) 266 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 267 apiError(ctx, http.StatusForbidden, err) 268 default: 269 apiError(ctx, http.StatusInternalServerError, err) 270 } 271 return 272 } 273 274 ctx.Status(http.StatusCreated) 275 } 276 277 func DownloadPackageFile(ctx *context.Context) { 278 pfs, err := conda_model.SearchFiles(ctx, &conda_model.FileSearchOptions{ 279 OwnerID: ctx.Package.Owner.ID, 280 Channel: ctx.Params("channel"), 281 Subdir: ctx.Params("architecture"), 282 Filename: ctx.Params("filename"), 283 }) 284 if err != nil { 285 apiError(ctx, http.StatusInternalServerError, err) 286 return 287 } 288 289 if len(pfs) != 1 { 290 apiError(ctx, http.StatusNotFound, nil) 291 return 292 } 293 294 pf := pfs[0] 295 296 s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 297 if err != nil { 298 apiError(ctx, http.StatusInternalServerError, err) 299 return 300 } 301 302 helper.ServePackageFile(ctx, s, u, pf) 303 }