mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-27 16:52:14 +01:00
1d332342db
* Added package store settings. * Added models. * Added generic package registry. * Added tests. * Added NuGet package registry. * Moved service index to api file. * Added NPM package registry. * Added Maven package registry. * Added PyPI package registry. * Summary is deprecated. * Changed npm name. * Sanitize project url. * Allow only scoped packages. * Added user interface. * Changed method name. * Added missing migration file. * Set page info. * Added documentation. * Added documentation links. * Fixed wrong error message. * Lint template files. * Fixed merge errors. * Fixed unit test storage path. * Switch to json module. * Added suggestions. * Added package webhook. * Add package api. * Fixed swagger file. * Fixed enum and comments. * Fixed NuGet pagination. * Print test names. * Added api tests. * Fixed access level. * Fix User unmarshal. * Added RubyGems package registry. * Fix lint. * Implemented io.Writer. * Added support for sha256/sha512 checksum files. * Improved maven-metadata.xml support. * Added support for symbol package uploads. * Added tests. * Added overview docs. * Added npm dependencies and keywords. * Added no-packages information. * Display file size. * Display asset count. * Fixed filter alignment. * Added package icons. * Formatted instructions. * Allow anonymous package downloads. * Fixed comments. * Fixed postgres test. * Moved file. * Moved models to models/packages. * Use correct error response format per client. * Use simpler search form. * Fixed IsProd. * Restructured data model. * Prevent empty filename. * Fix swagger. * Implemented user/org registry. * Implemented UI. * Use GetUserByIDCtx. * Use table for dependencies. * make svg * Added support for unscoped npm packages. * Add support for npm dist tags. * Added tests for npm tags. * Unlink packages if repository gets deleted. * Prevent user/org delete if a packages exist. * Use package unlink in repository service. * Added support for composer packages. * Restructured package docs. * Added missing tests. * Fixed generic content page. * Fixed docs. * Fixed swagger. * Added missing type. * Fixed ambiguous column. * Organize content store by sha256 hash. * Added admin package management. * Added support for sorting. * Add support for multiple identical versions/files. * Added missing repository unlink. * Added file properties. * make fmt * lint * Added Conan package registry. * Updated docs. * Unify package names. * Added swagger enum. * Use longer TEXT column type. * Removed version composite key. * Merged package and container registry. * Removed index. * Use dedicated package router. * Moved files to new location. * Updated docs. * Fixed JOIN order. * Fixed GROUP BY statement. * Fixed GROUP BY #2. * Added symbol server support. * Added more tests. * Set NOT NULL. * Added setting to disable package registries. * Moved auth into service. * refactor * Use ctx everywhere. * Added package cleanup task. * Changed packages path. * Added container registry. * Refactoring * Updated comparison. * Fix swagger. * Fixed table order. * Use token auth for npm routes. * Enabled ReverseProxy auth. * Added packages link for orgs. * Fixed anonymous org access. * Enable copy button for setup instructions. * Merge error * Added suggestions. * Fixed merge. * Handle "generic". * Added link for TODO. * Added suggestions. * Changed temporary buffer filename. * Added suggestions. * Apply suggestions from code review Co-authored-by: Thomas Boerger <thomas@webhippie.de> * Update docs/content/doc/packages/nuget.en-us.md Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Thomas Boerger <thomas@webhippie.de>
818 lines
23 KiB
Go
818 lines
23 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package conan
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
packages_model "code.gitea.io/gitea/models/packages"
|
|
conan_model "code.gitea.io/gitea/models/packages/conan"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/json"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/notification"
|
|
packages_module "code.gitea.io/gitea/modules/packages"
|
|
conan_module "code.gitea.io/gitea/modules/packages/conan"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/routers/api/packages/helper"
|
|
packages_service "code.gitea.io/gitea/services/packages"
|
|
)
|
|
|
|
const (
|
|
conanfileFile = "conanfile.py"
|
|
conaninfoFile = "conaninfo.txt"
|
|
|
|
recipeReferenceKey = "RecipeReference"
|
|
packageReferenceKey = "PackageReference"
|
|
)
|
|
|
|
type stringSet map[string]struct{}
|
|
|
|
var (
|
|
recipeFileList = stringSet{
|
|
conanfileFile: struct{}{},
|
|
"conanmanifest.txt": struct{}{},
|
|
"conan_sources.tgz": struct{}{},
|
|
"conan_export.tgz": struct{}{},
|
|
}
|
|
packageFileList = stringSet{
|
|
conaninfoFile: struct{}{},
|
|
"conanmanifest.txt": struct{}{},
|
|
"conan_package.tgz": struct{}{},
|
|
}
|
|
)
|
|
|
|
func jsonResponse(ctx *context.Context, status int, obj interface{}) {
|
|
// https://github.com/conan-io/conan/issues/6613
|
|
ctx.Resp.Header().Set("Content-Type", "application/json")
|
|
ctx.Status(status)
|
|
if err := json.NewEncoder(ctx.Resp).Encode(obj); err != nil {
|
|
log.Error("JSON encode: %v", err)
|
|
}
|
|
}
|
|
|
|
func apiError(ctx *context.Context, status int, obj interface{}) {
|
|
helper.LogAndProcessError(ctx, status, obj, func(message string) {
|
|
jsonResponse(ctx, status, map[string]string{
|
|
"message": message,
|
|
})
|
|
})
|
|
}
|
|
|
|
func baseURL(ctx *context.Context) string {
|
|
return setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/conan"
|
|
}
|
|
|
|
// ExtractPathParameters is a middleware to extract common parameters from path
|
|
func ExtractPathParameters(ctx *context.Context) {
|
|
rref, err := conan_module.NewRecipeReference(
|
|
ctx.Params("name"),
|
|
ctx.Params("version"),
|
|
ctx.Params("user"),
|
|
ctx.Params("channel"),
|
|
ctx.Params("recipe_revision"),
|
|
)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
ctx.Data[recipeReferenceKey] = rref
|
|
|
|
reference := ctx.Params("package_reference")
|
|
|
|
var pref *conan_module.PackageReference
|
|
if reference != "" {
|
|
pref, err = conan_module.NewPackageReference(
|
|
rref,
|
|
reference,
|
|
ctx.Params("package_revision"),
|
|
)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.Data[packageReferenceKey] = pref
|
|
}
|
|
|
|
// Ping reports the server capabilities
|
|
func Ping(ctx *context.Context) {
|
|
ctx.RespHeader().Add("X-Conan-Server-Capabilities", "revisions") // complex_search,checksum_deploy,matrix_params
|
|
|
|
ctx.Status(http.StatusOK)
|
|
}
|
|
|
|
// Authenticate creates an authentication token for the user
|
|
func Authenticate(ctx *context.Context) {
|
|
if ctx.Doer == nil {
|
|
apiError(ctx, http.StatusBadRequest, nil)
|
|
return
|
|
}
|
|
|
|
token, err := packages_service.CreateAuthorizationToken(ctx.Doer)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
ctx.PlainText(http.StatusOK, token)
|
|
}
|
|
|
|
// CheckCredentials tests if the provided authentication token is valid
|
|
func CheckCredentials(ctx *context.Context) {
|
|
if ctx.Doer == nil {
|
|
ctx.Status(http.StatusUnauthorized)
|
|
} else {
|
|
ctx.Status(http.StatusOK)
|
|
}
|
|
}
|
|
|
|
// RecipeSnapshot displays the recipe files with their md5 hash
|
|
func RecipeSnapshot(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
serveSnapshot(ctx, rref.AsKey())
|
|
}
|
|
|
|
// RecipeSnapshot displays the package files with their md5 hash
|
|
func PackageSnapshot(ctx *context.Context) {
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
serveSnapshot(ctx, pref.AsKey())
|
|
}
|
|
|
|
func serveSnapshot(ctx *context.Context, fileKey string) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
|
|
if err != nil {
|
|
if err == packages_model.ErrPackageNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
|
|
VersionID: pv.ID,
|
|
CompositeKey: fileKey,
|
|
})
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
if len(pfs) == 0 {
|
|
apiError(ctx, http.StatusNotFound, nil)
|
|
return
|
|
}
|
|
|
|
files := make(map[string]string)
|
|
for _, pf := range pfs {
|
|
pb, err := packages_model.GetBlobByID(ctx, pf.BlobID)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
files[pf.Name] = pb.HashMD5
|
|
}
|
|
|
|
jsonResponse(ctx, http.StatusOK, files)
|
|
}
|
|
|
|
// RecipeDownloadURLs displays the recipe files with their download url
|
|
func RecipeDownloadURLs(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
serveDownloadURLs(
|
|
ctx,
|
|
rref.AsKey(),
|
|
fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/recipe", rref.LinkName()),
|
|
)
|
|
}
|
|
|
|
// PackageDownloadURLs displays the package files with their download url
|
|
func PackageDownloadURLs(ctx *context.Context) {
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
serveDownloadURLs(
|
|
ctx,
|
|
pref.AsKey(),
|
|
fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/package/%s", pref.Recipe.LinkName(), pref.LinkName()),
|
|
)
|
|
}
|
|
|
|
func serveDownloadURLs(ctx *context.Context, fileKey, downloadURL string) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
|
|
if err != nil {
|
|
if err == packages_model.ErrPackageNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
|
|
VersionID: pv.ID,
|
|
CompositeKey: fileKey,
|
|
})
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
if len(pfs) == 0 {
|
|
apiError(ctx, http.StatusNotFound, nil)
|
|
return
|
|
}
|
|
|
|
urls := make(map[string]string)
|
|
for _, pf := range pfs {
|
|
urls[pf.Name] = fmt.Sprintf("%s/%s", downloadURL, pf.Name)
|
|
}
|
|
|
|
jsonResponse(ctx, http.StatusOK, urls)
|
|
}
|
|
|
|
// RecipeUploadURLs displays the upload urls for the provided recipe files
|
|
func RecipeUploadURLs(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
serveUploadURLs(
|
|
ctx,
|
|
recipeFileList,
|
|
fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/recipe", rref.LinkName()),
|
|
)
|
|
}
|
|
|
|
// PackageUploadURLs displays the upload urls for the provided package files
|
|
func PackageUploadURLs(ctx *context.Context) {
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
serveUploadURLs(
|
|
ctx,
|
|
packageFileList,
|
|
fmt.Sprintf(baseURL(ctx)+"/v1/files/%s/package/%s", pref.Recipe.LinkName(), pref.LinkName()),
|
|
)
|
|
}
|
|
|
|
func serveUploadURLs(ctx *context.Context, fileFilter stringSet, uploadURL string) {
|
|
defer ctx.Req.Body.Close()
|
|
|
|
var files map[string]int64
|
|
if err := json.NewDecoder(ctx.Req.Body).Decode(&files); err != nil {
|
|
apiError(ctx, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
urls := make(map[string]string)
|
|
for file := range files {
|
|
if _, ok := fileFilter[file]; ok {
|
|
urls[file] = fmt.Sprintf("%s/%s", uploadURL, file)
|
|
}
|
|
}
|
|
|
|
jsonResponse(ctx, http.StatusOK, urls)
|
|
}
|
|
|
|
// UploadRecipeFile handles the upload of a recipe file
|
|
func UploadRecipeFile(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
uploadFile(ctx, recipeFileList, rref.AsKey())
|
|
}
|
|
|
|
// UploadPackageFile handles the upload of a package file
|
|
func UploadPackageFile(ctx *context.Context) {
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
uploadFile(ctx, packageFileList, pref.AsKey())
|
|
}
|
|
|
|
func uploadFile(ctx *context.Context, fileFilter stringSet, fileKey string) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
filename := ctx.Params("filename")
|
|
if _, ok := fileFilter[filename]; !ok {
|
|
apiError(ctx, http.StatusBadRequest, nil)
|
|
return
|
|
}
|
|
|
|
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, 32*1024*1024)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
defer buf.Close()
|
|
|
|
if buf.Size() == 0 {
|
|
// ignore empty uploads, second request contains content
|
|
jsonResponse(ctx, http.StatusOK, nil)
|
|
return
|
|
}
|
|
|
|
isConanfileFile := filename == conanfileFile
|
|
|
|
pci := &packages_service.PackageCreationInfo{
|
|
PackageInfo: packages_service.PackageInfo{
|
|
Owner: ctx.Package.Owner,
|
|
PackageType: packages_model.TypeConan,
|
|
Name: rref.Name,
|
|
Version: rref.Version,
|
|
},
|
|
SemverCompatible: true,
|
|
Creator: ctx.Doer,
|
|
}
|
|
pfci := &packages_service.PackageFileCreationInfo{
|
|
PackageFileInfo: packages_service.PackageFileInfo{
|
|
Filename: strings.ToLower(filename),
|
|
CompositeKey: fileKey,
|
|
},
|
|
Data: buf,
|
|
IsLead: isConanfileFile,
|
|
Properties: map[string]string{
|
|
conan_module.PropertyRecipeUser: rref.User,
|
|
conan_module.PropertyRecipeChannel: rref.Channel,
|
|
conan_module.PropertyRecipeRevision: rref.RevisionOrDefault(),
|
|
},
|
|
OverwriteExisting: true,
|
|
}
|
|
|
|
if pref != nil {
|
|
pfci.Properties[conan_module.PropertyPackageReference] = pref.Reference
|
|
pfci.Properties[conan_module.PropertyPackageRevision] = pref.RevisionOrDefault()
|
|
}
|
|
|
|
if isConanfileFile || filename == conaninfoFile {
|
|
if isConanfileFile {
|
|
metadata, err := conan_module.ParseConanfile(buf)
|
|
if err != nil {
|
|
log.Error("Error parsing package metadata: %v", err)
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
pv, err := packages_model.GetVersionByNameAndVersion(ctx, pci.Owner.ID, pci.PackageType, pci.Name, pci.Version)
|
|
if err != nil && err != packages_model.ErrPackageNotExist {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
if pv != nil {
|
|
raw, err := json.Marshal(metadata)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
pv.MetadataJSON = string(raw)
|
|
if err := packages_model.UpdateVersion(ctx, pv); err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
} else {
|
|
pci.Metadata = metadata
|
|
}
|
|
} else {
|
|
info, err := conan_module.ParseConaninfo(buf)
|
|
if err != nil {
|
|
log.Error("Error parsing conan info: %v", err)
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
raw, err := json.Marshal(info)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
pfci.Properties[conan_module.PropertyPackageInfo] = string(raw)
|
|
}
|
|
|
|
if _, err := buf.Seek(0, io.SeekStart); err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
_, _, err = packages_service.CreatePackageOrAddFileToExisting(
|
|
pci,
|
|
pfci,
|
|
)
|
|
if err != nil {
|
|
if err == packages_model.ErrDuplicatePackageFile {
|
|
apiError(ctx, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
ctx.Status(http.StatusCreated)
|
|
}
|
|
|
|
// DownloadRecipeFile serves the conent of the requested recipe file
|
|
func DownloadRecipeFile(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
downloadFile(ctx, recipeFileList, rref.AsKey())
|
|
}
|
|
|
|
// DownloadPackageFile serves the conent of the requested package file
|
|
func DownloadPackageFile(ctx *context.Context) {
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
downloadFile(ctx, packageFileList, pref.AsKey())
|
|
}
|
|
|
|
func downloadFile(ctx *context.Context, fileFilter stringSet, fileKey string) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
filename := ctx.Params("filename")
|
|
if _, ok := fileFilter[filename]; !ok {
|
|
apiError(ctx, http.StatusBadRequest, nil)
|
|
return
|
|
}
|
|
|
|
s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
|
|
ctx,
|
|
&packages_service.PackageInfo{
|
|
Owner: ctx.Package.Owner,
|
|
PackageType: packages_model.TypeConan,
|
|
Name: rref.Name,
|
|
Version: rref.Version,
|
|
},
|
|
&packages_service.PackageFileInfo{
|
|
Filename: filename,
|
|
CompositeKey: fileKey,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
defer s.Close()
|
|
|
|
ctx.ServeStream(s, pf.Name)
|
|
}
|
|
|
|
// DeleteRecipeV1 deletes the requested recipe(s)
|
|
func DeleteRecipeV1(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
if err := deleteRecipeOrPackage(ctx, rref, true, nil, false); err != nil {
|
|
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
return
|
|
}
|
|
ctx.Status(http.StatusOK)
|
|
}
|
|
|
|
// DeleteRecipeV2 deletes the requested recipe(s) respecting its revisions
|
|
func DeleteRecipeV2(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
if err := deleteRecipeOrPackage(ctx, rref, rref.Revision == "", nil, false); err != nil {
|
|
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
return
|
|
}
|
|
ctx.Status(http.StatusOK)
|
|
}
|
|
|
|
// DeletePackageV1 deletes the requested package(s)
|
|
func DeletePackageV1(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
type PackageReferences struct {
|
|
References []string `json:"package_ids"`
|
|
}
|
|
|
|
var ids *PackageReferences
|
|
if err := json.NewDecoder(ctx.Req.Body).Decode(&ids); err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
revisions, err := conan_model.GetRecipeRevisions(ctx, ctx.Package.Owner.ID, rref)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
for _, revision := range revisions {
|
|
currentRref := rref.WithRevision(revision.Value)
|
|
|
|
var references []*conan_model.PropertyValue
|
|
if len(ids.References) == 0 {
|
|
if references, err = conan_model.GetPackageReferences(ctx, ctx.Package.Owner.ID, currentRref); err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
} else {
|
|
for _, reference := range ids.References {
|
|
references = append(references, &conan_model.PropertyValue{Value: reference})
|
|
}
|
|
}
|
|
|
|
for _, reference := range references {
|
|
pref, _ := conan_module.NewPackageReference(currentRref, reference.Value, conan_module.DefaultRevision)
|
|
if err := deleteRecipeOrPackage(ctx, currentRref, true, pref, true); err != nil {
|
|
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
ctx.Status(http.StatusOK)
|
|
}
|
|
|
|
// DeletePackageV2 deletes the requested package(s) respecting its revisions
|
|
func DeletePackageV2(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
if pref != nil { // has package reference
|
|
if err := deleteRecipeOrPackage(ctx, rref, false, pref, pref.Revision == ""); err != nil {
|
|
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
} else {
|
|
ctx.Status(http.StatusOK)
|
|
}
|
|
return
|
|
}
|
|
|
|
references, err := conan_model.GetPackageReferences(ctx, ctx.Package.Owner.ID, rref)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
if len(references) == 0 {
|
|
apiError(ctx, http.StatusNotFound, conan_model.ErrPackageReferenceNotExist)
|
|
return
|
|
}
|
|
|
|
for _, reference := range references {
|
|
pref, _ := conan_module.NewPackageReference(rref, reference.Value, conan_module.DefaultRevision)
|
|
|
|
if err := deleteRecipeOrPackage(ctx, rref, false, pref, true); err != nil {
|
|
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.Status(http.StatusOK)
|
|
}
|
|
|
|
func deleteRecipeOrPackage(apictx *context.Context, rref *conan_module.RecipeReference, ignoreRecipeRevision bool, pref *conan_module.PackageReference, ignorePackageRevision bool) error {
|
|
ctx, committer, err := db.TxContext()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer committer.Close()
|
|
|
|
pv, err := packages_model.GetVersionByNameAndVersion(ctx, apictx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pd, err := packages_model.GetPackageDescriptor(ctx, pv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
filter := map[string]string{
|
|
conan_module.PropertyRecipeUser: rref.User,
|
|
conan_module.PropertyRecipeChannel: rref.Channel,
|
|
}
|
|
if !ignoreRecipeRevision {
|
|
filter[conan_module.PropertyRecipeRevision] = rref.RevisionOrDefault()
|
|
}
|
|
if pref != nil {
|
|
filter[conan_module.PropertyPackageReference] = pref.Reference
|
|
if !ignorePackageRevision {
|
|
filter[conan_module.PropertyPackageRevision] = pref.RevisionOrDefault()
|
|
}
|
|
}
|
|
|
|
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
|
|
VersionID: pv.ID,
|
|
Properties: filter,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(pfs) == 0 {
|
|
return conan_model.ErrPackageReferenceNotExist
|
|
}
|
|
|
|
for _, pf := range pfs {
|
|
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
|
|
return err
|
|
}
|
|
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
versionDeleted := false
|
|
has, err := packages_model.HasVersionFileReferences(ctx, pv.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !has {
|
|
versionDeleted = true
|
|
|
|
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeVersion, pv.ID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := packages_model.DeleteVersionByID(ctx, pv.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := committer.Commit(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if versionDeleted {
|
|
notification.NotifyPackageDelete(apictx.Doer, pd)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ListRecipeRevisions gets a list of all recipe revisions
|
|
func ListRecipeRevisions(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
revisions, err := conan_model.GetRecipeRevisions(ctx, ctx.Package.Owner.ID, rref)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
listRevisions(ctx, revisions)
|
|
}
|
|
|
|
// ListPackageRevisions gets a list of all package revisions
|
|
func ListPackageRevisions(ctx *context.Context) {
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
revisions, err := conan_model.GetPackageRevisions(ctx, ctx.Package.Owner.ID, pref)
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
listRevisions(ctx, revisions)
|
|
}
|
|
|
|
type revisionInfo struct {
|
|
Revision string `json:"revision"`
|
|
Time time.Time `json:"time"`
|
|
}
|
|
|
|
func listRevisions(ctx *context.Context, revisions []*conan_model.PropertyValue) {
|
|
if len(revisions) == 0 {
|
|
apiError(ctx, http.StatusNotFound, conan_model.ErrRecipeReferenceNotExist)
|
|
return
|
|
}
|
|
|
|
type RevisionList struct {
|
|
Revisions []*revisionInfo `json:"revisions"`
|
|
}
|
|
|
|
revs := make([]*revisionInfo, 0, len(revisions))
|
|
for _, rev := range revisions {
|
|
revs = append(revs, &revisionInfo{Revision: rev.Value, Time: time.Unix(int64(rev.CreatedUnix), 0)})
|
|
}
|
|
|
|
jsonResponse(ctx, http.StatusOK, &RevisionList{revs})
|
|
}
|
|
|
|
// LatestRecipeRevision gets the latest recipe revision
|
|
func LatestRecipeRevision(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
revision, err := conan_model.GetLastRecipeRevision(ctx, ctx.Package.Owner.ID, rref)
|
|
if err != nil {
|
|
if err == conan_model.ErrRecipeReferenceNotExist || err == conan_model.ErrPackageReferenceNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
jsonResponse(ctx, http.StatusOK, &revisionInfo{Revision: revision.Value, Time: time.Unix(int64(revision.CreatedUnix), 0)})
|
|
}
|
|
|
|
// LatestPackageRevision gets the latest package revision
|
|
func LatestPackageRevision(ctx *context.Context) {
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
revision, err := conan_model.GetLastPackageRevision(ctx, ctx.Package.Owner.ID, pref)
|
|
if err != nil {
|
|
if err == conan_model.ErrRecipeReferenceNotExist || err == conan_model.ErrPackageReferenceNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
jsonResponse(ctx, http.StatusOK, &revisionInfo{Revision: revision.Value, Time: time.Unix(int64(revision.CreatedUnix), 0)})
|
|
}
|
|
|
|
// ListRecipeRevisionFiles gets a list of all recipe revision files
|
|
func ListRecipeRevisionFiles(ctx *context.Context) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
listRevisionFiles(ctx, rref.AsKey())
|
|
}
|
|
|
|
// ListPackageRevisionFiles gets a list of all package revision files
|
|
func ListPackageRevisionFiles(ctx *context.Context) {
|
|
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
|
|
|
listRevisionFiles(ctx, pref.AsKey())
|
|
}
|
|
|
|
func listRevisionFiles(ctx *context.Context, fileKey string) {
|
|
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
|
|
|
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
|
|
if err != nil {
|
|
if err == packages_model.ErrPackageNotExist {
|
|
apiError(ctx, http.StatusNotFound, err)
|
|
} else {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
|
|
VersionID: pv.ID,
|
|
CompositeKey: fileKey,
|
|
})
|
|
if err != nil {
|
|
apiError(ctx, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
if len(pfs) == 0 {
|
|
apiError(ctx, http.StatusNotFound, nil)
|
|
return
|
|
}
|
|
|
|
files := make(map[string]interface{})
|
|
for _, pf := range pfs {
|
|
files[pf.Name] = nil
|
|
}
|
|
|
|
type FileList struct {
|
|
Files map[string]interface{} `json:"files"`
|
|
}
|
|
|
|
jsonResponse(ctx, http.StatusOK, &FileList{
|
|
Files: files,
|
|
})
|
|
}
|