code.gitea.io/gitea@v1.21.7/routers/api/packages/rpm/rpm.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package rpm 5 6 import ( 7 stdctx "context" 8 "errors" 9 "fmt" 10 "io" 11 "net/http" 12 "strings" 13 14 "code.gitea.io/gitea/models/db" 15 packages_model "code.gitea.io/gitea/models/packages" 16 "code.gitea.io/gitea/modules/context" 17 "code.gitea.io/gitea/modules/json" 18 packages_module "code.gitea.io/gitea/modules/packages" 19 rpm_module "code.gitea.io/gitea/modules/packages/rpm" 20 "code.gitea.io/gitea/modules/setting" 21 "code.gitea.io/gitea/modules/util" 22 "code.gitea.io/gitea/routers/api/packages/helper" 23 notify_service "code.gitea.io/gitea/services/notify" 24 packages_service "code.gitea.io/gitea/services/packages" 25 rpm_service "code.gitea.io/gitea/services/packages/rpm" 26 ) 27 28 func apiError(ctx *context.Context, status int, obj any) { 29 helper.LogAndProcessError(ctx, status, obj, func(message string) { 30 ctx.PlainText(status, message) 31 }) 32 } 33 34 // https://dnf.readthedocs.io/en/latest/conf_ref.html 35 func GetRepositoryConfig(ctx *context.Context) { 36 url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name) 37 38 ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+`] 39 name=`+ctx.Package.Owner.Name+` - `+setting.AppName+` 40 baseurl=`+url+` 41 enabled=1 42 gpgcheck=1 43 gpgkey=`+url+`/repository.key`) 44 } 45 46 // Gets or creates the PGP public key used to sign repository metadata files 47 func GetRepositoryKey(ctx *context.Context) { 48 _, pub, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) 49 if err != nil { 50 apiError(ctx, http.StatusInternalServerError, err) 51 return 52 } 53 54 ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{ 55 ContentType: "application/pgp-keys", 56 Filename: "repository.key", 57 }) 58 } 59 60 func CheckRepositoryFileExistence(ctx *context.Context) { 61 pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID) 62 if err != nil { 63 apiError(ctx, http.StatusInternalServerError, err) 64 return 65 } 66 67 pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), packages_model.EmptyFileKey) 68 if err != nil { 69 if errors.Is(err, util.ErrNotExist) { 70 ctx.Status(http.StatusNotFound) 71 } else { 72 apiError(ctx, http.StatusInternalServerError, err) 73 } 74 return 75 } 76 77 ctx.SetServeHeaders(&context.ServeHeaderOptions{ 78 Filename: pf.Name, 79 LastModified: pf.CreatedUnix.AsLocalTime(), 80 }) 81 ctx.Status(http.StatusOK) 82 } 83 84 // Gets a pre-generated repository metadata file 85 func GetRepositoryFile(ctx *context.Context) { 86 pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID) 87 if err != nil { 88 apiError(ctx, http.StatusInternalServerError, err) 89 return 90 } 91 92 s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 93 ctx, 94 pv, 95 &packages_service.PackageFileInfo{ 96 Filename: ctx.Params("filename"), 97 }, 98 ) 99 if err != nil { 100 if errors.Is(err, util.ErrNotExist) { 101 apiError(ctx, http.StatusNotFound, err) 102 } else { 103 apiError(ctx, http.StatusInternalServerError, err) 104 } 105 return 106 } 107 108 helper.ServePackageFile(ctx, s, u, pf) 109 } 110 111 func UploadPackageFile(ctx *context.Context) { 112 upload, close, err := ctx.UploadStream() 113 if err != nil { 114 apiError(ctx, http.StatusInternalServerError, err) 115 return 116 } 117 if close { 118 defer upload.Close() 119 } 120 121 buf, err := packages_module.CreateHashedBufferFromReader(upload) 122 if err != nil { 123 apiError(ctx, http.StatusInternalServerError, err) 124 return 125 } 126 defer buf.Close() 127 128 pck, err := rpm_module.ParsePackage(buf) 129 if err != nil { 130 if errors.Is(err, util.ErrInvalidArgument) { 131 apiError(ctx, http.StatusBadRequest, err) 132 } else { 133 apiError(ctx, http.StatusInternalServerError, err) 134 } 135 return 136 } 137 138 if _, err := buf.Seek(0, io.SeekStart); err != nil { 139 apiError(ctx, http.StatusInternalServerError, err) 140 return 141 } 142 143 fileMetadataRaw, err := json.Marshal(pck.FileMetadata) 144 if err != nil { 145 apiError(ctx, http.StatusInternalServerError, err) 146 return 147 } 148 149 _, _, err = packages_service.CreatePackageOrAddFileToExisting( 150 ctx, 151 &packages_service.PackageCreationInfo{ 152 PackageInfo: packages_service.PackageInfo{ 153 Owner: ctx.Package.Owner, 154 PackageType: packages_model.TypeRpm, 155 Name: pck.Name, 156 Version: pck.Version, 157 }, 158 Creator: ctx.Doer, 159 Metadata: pck.VersionMetadata, 160 }, 161 &packages_service.PackageFileCreationInfo{ 162 PackageFileInfo: packages_service.PackageFileInfo{ 163 Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture), 164 }, 165 Creator: ctx.Doer, 166 Data: buf, 167 IsLead: true, 168 Properties: map[string]string{ 169 rpm_module.PropertyMetadata: string(fileMetadataRaw), 170 }, 171 }, 172 ) 173 if err != nil { 174 switch err { 175 case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile: 176 apiError(ctx, http.StatusConflict, err) 177 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 178 apiError(ctx, http.StatusForbidden, err) 179 default: 180 apiError(ctx, http.StatusInternalServerError, err) 181 } 182 return 183 } 184 185 if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID); err != nil { 186 apiError(ctx, http.StatusInternalServerError, err) 187 return 188 } 189 190 ctx.Status(http.StatusCreated) 191 } 192 193 func DownloadPackageFile(ctx *context.Context) { 194 name := ctx.Params("name") 195 version := ctx.Params("version") 196 197 s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 198 ctx, 199 &packages_service.PackageInfo{ 200 Owner: ctx.Package.Owner, 201 PackageType: packages_model.TypeRpm, 202 Name: name, 203 Version: version, 204 }, 205 &packages_service.PackageFileInfo{ 206 Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")), 207 }, 208 ) 209 if err != nil { 210 if errors.Is(err, util.ErrNotExist) { 211 apiError(ctx, http.StatusNotFound, err) 212 } else { 213 apiError(ctx, http.StatusInternalServerError, err) 214 } 215 return 216 } 217 218 helper.ServePackageFile(ctx, s, u, pf) 219 } 220 221 func DeletePackageFile(webctx *context.Context) { 222 name := webctx.Params("name") 223 version := webctx.Params("version") 224 architecture := webctx.Params("architecture") 225 226 var pd *packages_model.PackageDescriptor 227 228 err := db.WithTx(webctx, func(ctx stdctx.Context) error { 229 pv, err := packages_model.GetVersionByNameAndVersion(ctx, webctx.Package.Owner.ID, packages_model.TypeRpm, name, version) 230 if err != nil { 231 return err 232 } 233 234 pf, err := packages_model.GetFileForVersionByName( 235 ctx, 236 pv.ID, 237 fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture), 238 packages_model.EmptyFileKey, 239 ) 240 if err != nil { 241 return err 242 } 243 244 if err := packages_service.DeletePackageFile(ctx, pf); err != nil { 245 return err 246 } 247 248 has, err := packages_model.HasVersionFileReferences(ctx, pv.ID) 249 if err != nil { 250 return err 251 } 252 if !has { 253 pd, err = packages_model.GetPackageDescriptor(ctx, pv) 254 if err != nil { 255 return err 256 } 257 258 if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { 259 return err 260 } 261 } 262 263 return nil 264 }) 265 if err != nil { 266 if errors.Is(err, util.ErrNotExist) { 267 apiError(webctx, http.StatusNotFound, err) 268 } else { 269 apiError(webctx, http.StatusInternalServerError, err) 270 } 271 return 272 } 273 274 if pd != nil { 275 notify_service.PackageDelete(webctx, webctx.Doer, pd) 276 } 277 278 if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID); err != nil { 279 apiError(webctx, http.StatusInternalServerError, err) 280 return 281 } 282 283 webctx.Status(http.StatusNoContent) 284 }