diff options
author | KN4CK3R <admin@oldschoolhack.me> | 2023-05-22 04:57:49 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-22 10:57:49 +0800 |
commit | cdb088cec288a20e14240f86a689dd14f4cd603b (patch) | |
tree | 0a491ea8dcffa22b5b1033d04f5402425fbeb73c /routers | |
parent | ec2a01d1e20c0d33c1ea7e362a7dfd5b653dd15f (diff) | |
download | gitea-cdb088cec288a20e14240f86a689dd14f4cd603b.tar.gz gitea-cdb088cec288a20e14240f86a689dd14f4cd603b.zip |
Add CRAN package registry (#22331)
This PR adds a [CRAN](https://cran.r-project.org/) package registry.
![grafik](https://user-images.githubusercontent.com/1666336/210450039-d6fa6f77-20cd-4741-89a8-1624def267f7.png)
Diffstat (limited to 'routers')
-rw-r--r-- | routers/api/packages/api.go | 19 | ||||
-rw-r--r-- | routers/api/packages/cran/cran.go | 267 | ||||
-rw-r--r-- | routers/api/v1/packages/package.go | 2 |
3 files changed, 287 insertions, 1 deletions
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index e715997e82..4f0f637fa5 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/routers/api/packages/conan" "code.gitea.io/gitea/routers/api/packages/conda" "code.gitea.io/gitea/routers/api/packages/container" + "code.gitea.io/gitea/routers/api/packages/cran" "code.gitea.io/gitea/routers/api/packages/debian" "code.gitea.io/gitea/routers/api/packages/generic" "code.gitea.io/gitea/routers/api/packages/goproxy" @@ -295,6 +296,24 @@ func CommonRoutes(ctx gocontext.Context) *web.Route { conda.UploadPackageFile(ctx) }) }, reqPackageAccess(perm.AccessModeRead)) + r.Group("/cran", func() { + r.Group("/src", func() { + r.Group("/contrib", func() { + r.Get("/PACKAGES", cran.EnumerateSourcePackages) + r.Get("/PACKAGES{format}", cran.EnumerateSourcePackages) + r.Get("/{filename}", cran.DownloadSourcePackageFile) + }) + r.Put("", reqPackageAccess(perm.AccessModeWrite), cran.UploadSourcePackageFile) + }) + r.Group("/bin", func() { + r.Group("/{platform}/contrib/{rversion}", func() { + r.Get("/PACKAGES", cran.EnumerateBinaryPackages) + r.Get("/PACKAGES{format}", cran.EnumerateBinaryPackages) + r.Get("/{filename}", cran.DownloadBinaryPackageFile) + }) + r.Put("", reqPackageAccess(perm.AccessModeWrite), cran.UploadBinaryPackageFile) + }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/debian", func() { r.Get("/repository.key", debian.GetRepositoryKey) r.Group("/dists/{distribution}", func() { diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go new file mode 100644 index 0000000000..eb3f9a452b --- /dev/null +++ b/routers/api/packages/cran/cran.go @@ -0,0 +1,267 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cran + +import ( + "compress/gzip" + "errors" + "fmt" + "io" + "net/http" + "strings" + + packages_model "code.gitea.io/gitea/models/packages" + cran_model "code.gitea.io/gitea/models/packages/cran" + "code.gitea.io/gitea/modules/context" + packages_module "code.gitea.io/gitea/modules/packages" + cran_module "code.gitea.io/gitea/modules/packages/cran" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers/api/packages/helper" + packages_service "code.gitea.io/gitea/services/packages" +) + +func apiError(ctx *context.Context, status int, obj interface{}) { + helper.LogAndProcessError(ctx, status, obj, func(message string) { + ctx.PlainText(status, message) + }) +} + +func EnumerateSourcePackages(ctx *context.Context) { + enumeratePackages(ctx, ctx.Params("format"), &cran_model.SearchOptions{ + OwnerID: ctx.Package.Owner.ID, + FileType: cran_module.TypeSource, + }) +} + +func EnumerateBinaryPackages(ctx *context.Context) { + enumeratePackages(ctx, ctx.Params("format"), &cran_model.SearchOptions{ + OwnerID: ctx.Package.Owner.ID, + FileType: cran_module.TypeBinary, + Platform: ctx.Params("platform"), + RVersion: ctx.Params("rversion"), + }) +} + +func enumeratePackages(ctx *context.Context, format string, opts *cran_model.SearchOptions) { + if format != "" && format != ".gz" { + apiError(ctx, http.StatusNotFound, nil) + return + } + + pvs, err := cran_model.SearchLatestVersions(ctx, opts) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if len(pvs) == 0 { + apiError(ctx, http.StatusNotFound, nil) + return + } + + pds, err := packages_model.GetPackageDescriptors(ctx, pvs) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + var w io.Writer = ctx.Resp + + if format == ".gz" { + ctx.Resp.Header().Set("Content-Type", "application/x-gzip") + + gzw := gzip.NewWriter(w) + defer gzw.Close() + + w = gzw + } else { + ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") + } + ctx.Resp.WriteHeader(http.StatusOK) + + for i, pd := range pds { + if i > 0 { + fmt.Fprintln(w) + } + + var pfd *packages_model.PackageFileDescriptor + for _, d := range pd.Files { + if d.Properties.GetByName(cran_module.PropertyType) == opts.FileType && + d.Properties.GetByName(cran_module.PropertyPlatform) == opts.Platform && + d.Properties.GetByName(cran_module.PropertyRVersion) == opts.RVersion { + pfd = d + break + } + } + + metadata := pd.Metadata.(*cran_module.Metadata) + + fmt.Fprintln(w, "Package:", pd.Package.Name) + fmt.Fprintln(w, "Version:", pd.Version.Version) + if metadata.License != "" { + fmt.Fprintln(w, "License:", metadata.License) + } + if len(metadata.Depends) > 0 { + fmt.Fprintln(w, "Depends:", strings.Join(metadata.Depends, ", ")) + } + if len(metadata.Imports) > 0 { + fmt.Fprintln(w, "Imports:", strings.Join(metadata.Imports, ", ")) + } + if len(metadata.LinkingTo) > 0 { + fmt.Fprintln(w, "LinkingTo:", strings.Join(metadata.LinkingTo, ", ")) + } + if len(metadata.Suggests) > 0 { + fmt.Fprintln(w, "Suggests:", strings.Join(metadata.Suggests, ", ")) + } + needsCompilation := "no" + if metadata.NeedsCompilation { + needsCompilation = "yes" + } + fmt.Fprintln(w, "NeedsCompilation:", needsCompilation) + fmt.Fprintln(w, "MD5sum:", pfd.Blob.HashMD5) + } +} + +func UploadSourcePackageFile(ctx *context.Context) { + uploadPackageFile( + ctx, + packages_model.EmptyFileKey, + map[string]string{ + cran_module.PropertyType: cran_module.TypeSource, + }, + ) +} + +func UploadBinaryPackageFile(ctx *context.Context) { + platform, rversion := ctx.FormTrim("platform"), ctx.FormTrim("rversion") + if platform == "" || rversion == "" { + apiError(ctx, http.StatusBadRequest, nil) + return + } + + uploadPackageFile( + ctx, + platform+"|"+rversion, + map[string]string{ + cran_module.PropertyType: cran_module.TypeBinary, + cran_module.PropertyPlatform: platform, + cran_module.PropertyRVersion: rversion, + }, + ) +} + +func uploadPackageFile(ctx *context.Context, compositeKey string, properties map[string]string) { + upload, close, err := ctx.UploadStream() + if err != nil { + apiError(ctx, http.StatusBadRequest, err) + return + } + if close { + defer upload.Close() + } + + buf, err := packages_module.CreateHashedBufferFromReader(upload) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + defer buf.Close() + + pck, err := cran_module.ParsePackage(buf, buf.Size()) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + apiError(ctx, http.StatusBadRequest, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + if _, err := buf.Seek(0, io.SeekStart); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + _, _, err = packages_service.CreatePackageOrAddFileToExisting( + &packages_service.PackageCreationInfo{ + PackageInfo: packages_service.PackageInfo{ + Owner: ctx.Package.Owner, + PackageType: packages_model.TypeCran, + Name: pck.Name, + Version: pck.Version, + }, + SemverCompatible: false, + Creator: ctx.Doer, + Metadata: pck.Metadata, + }, + &packages_service.PackageFileCreationInfo{ + PackageFileInfo: packages_service.PackageFileInfo{ + Filename: fmt.Sprintf("%s_%s%s", pck.Name, pck.Version, pck.FileExtension), + CompositeKey: compositeKey, + }, + Creator: ctx.Doer, + Data: buf, + IsLead: true, + Properties: properties, + }, + ) + if err != nil { + switch err { + case packages_model.ErrDuplicatePackageFile: + apiError(ctx, http.StatusConflict, err) + case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: + apiError(ctx, http.StatusForbidden, err) + default: + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + ctx.Status(http.StatusCreated) +} + +func DownloadSourcePackageFile(ctx *context.Context) { + downloadPackageFile(ctx, &cran_model.SearchOptions{ + OwnerID: ctx.Package.Owner.ID, + FileType: cran_module.TypeSource, + Filename: ctx.Params("filename"), + }) +} + +func DownloadBinaryPackageFile(ctx *context.Context) { + downloadPackageFile(ctx, &cran_model.SearchOptions{ + OwnerID: ctx.Package.Owner.ID, + FileType: cran_module.TypeBinary, + Platform: ctx.Params("platform"), + RVersion: ctx.Params("rversion"), + Filename: ctx.Params("filename"), + }) +} + +func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) { + pf, err := cran_model.SearchFile(ctx, opts) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + s, _, err := packages_service.GetPackageFileStream(ctx, pf) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + defer s.Close() + + ctx.ServeContent(s, &context.ServeHeaderOptions{ + Filename: pf.Name, + LastModified: pf.CreatedUnix.AsLocalTime(), + }) +} diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index 0c9a134281..5129c7d4f0 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -40,7 +40,7 @@ func ListPackages(ctx *context.APIContext) { // in: query // description: package type filter // type: string - // enum: [alpine, cargo, chef, composer, conan, conda, container, debian, generic, go, helm, maven, npm, nuget, pub, pypi, rpm, rubygems, swift, vagrant] + // enum: [alpine, cargo, chef, composer, conan, conda, container, cran, debian, generic, go, helm, maven, npm, nuget, pub, pypi, rpm, rubygems, swift, vagrant] // - name: q // in: query // description: name filter |