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