mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-29 13:16:10 +01:00
32db62515f
Fixes #20514 Fixes #20766 Fixes #20631 This PR adds Cleanup Rules for the package registry. This allows to delete unneeded packages automatically. Cleanup rules can be set up from the user or org settings. Please have a look at the documentation because I'm not a native english speaker. Rule Form ![grafik](https://user-images.githubusercontent.com/1666336/199330792-c13918a6-e196-4e71-9f53-18554515edca.png) Rule List ![grafik](https://user-images.githubusercontent.com/1666336/199331261-5f6878e8-a80c-4985-800d-ebb3524b1a8d.png) Rule Preview ![grafik](https://user-images.githubusercontent.com/1666336/199330917-c95e4017-cf64-4142-a3e4-af18c4f127c3.png) Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
258 lines
6.3 KiB
Go
258 lines
6.3 KiB
Go
// Copyright 2021 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 packages
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
func init() {
|
|
db.RegisterModel(new(Package))
|
|
}
|
|
|
|
var (
|
|
// ErrDuplicatePackage indicates a duplicated package error
|
|
ErrDuplicatePackage = errors.New("Package does exist already")
|
|
// ErrPackageNotExist indicates a package not exist error
|
|
ErrPackageNotExist = errors.New("Package does not exist")
|
|
)
|
|
|
|
// Type of a package
|
|
type Type string
|
|
|
|
// List of supported packages
|
|
const (
|
|
TypeComposer Type = "composer"
|
|
TypeConan Type = "conan"
|
|
TypeContainer Type = "container"
|
|
TypeGeneric Type = "generic"
|
|
TypeHelm Type = "helm"
|
|
TypeMaven Type = "maven"
|
|
TypeNpm Type = "npm"
|
|
TypeNuGet Type = "nuget"
|
|
TypePub Type = "pub"
|
|
TypePyPI Type = "pypi"
|
|
TypeRubyGems Type = "rubygems"
|
|
TypeVagrant Type = "vagrant"
|
|
)
|
|
|
|
var TypeList = []Type{
|
|
TypeComposer,
|
|
TypeConan,
|
|
TypeContainer,
|
|
TypeGeneric,
|
|
TypeHelm,
|
|
TypeMaven,
|
|
TypeNpm,
|
|
TypeNuGet,
|
|
TypePub,
|
|
TypePyPI,
|
|
TypeRubyGems,
|
|
TypeVagrant,
|
|
}
|
|
|
|
// Name gets the name of the package type
|
|
func (pt Type) Name() string {
|
|
switch pt {
|
|
case TypeComposer:
|
|
return "Composer"
|
|
case TypeConan:
|
|
return "Conan"
|
|
case TypeContainer:
|
|
return "Container"
|
|
case TypeGeneric:
|
|
return "Generic"
|
|
case TypeHelm:
|
|
return "Helm"
|
|
case TypeMaven:
|
|
return "Maven"
|
|
case TypeNpm:
|
|
return "npm"
|
|
case TypeNuGet:
|
|
return "NuGet"
|
|
case TypePub:
|
|
return "Pub"
|
|
case TypePyPI:
|
|
return "PyPI"
|
|
case TypeRubyGems:
|
|
return "RubyGems"
|
|
case TypeVagrant:
|
|
return "Vagrant"
|
|
}
|
|
panic(fmt.Sprintf("unknown package type: %s", string(pt)))
|
|
}
|
|
|
|
// SVGName gets the name of the package type svg image
|
|
func (pt Type) SVGName() string {
|
|
switch pt {
|
|
case TypeComposer:
|
|
return "gitea-composer"
|
|
case TypeConan:
|
|
return "gitea-conan"
|
|
case TypeContainer:
|
|
return "octicon-container"
|
|
case TypeGeneric:
|
|
return "octicon-package"
|
|
case TypeHelm:
|
|
return "gitea-helm"
|
|
case TypeMaven:
|
|
return "gitea-maven"
|
|
case TypeNpm:
|
|
return "gitea-npm"
|
|
case TypeNuGet:
|
|
return "gitea-nuget"
|
|
case TypePub:
|
|
return "gitea-pub"
|
|
case TypePyPI:
|
|
return "gitea-python"
|
|
case TypeRubyGems:
|
|
return "gitea-rubygems"
|
|
case TypeVagrant:
|
|
return "gitea-vagrant"
|
|
}
|
|
panic(fmt.Sprintf("unknown package type: %s", string(pt)))
|
|
}
|
|
|
|
// Package represents a package
|
|
type Package struct {
|
|
ID int64 `xorm:"pk autoincr"`
|
|
OwnerID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
|
RepoID int64 `xorm:"INDEX"`
|
|
Type Type `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
|
Name string `xorm:"NOT NULL"`
|
|
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
|
SemverCompatible bool `xorm:"NOT NULL DEFAULT false"`
|
|
}
|
|
|
|
// TryInsertPackage inserts a package. If a package exists already, ErrDuplicatePackage is returned
|
|
func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
|
|
e := db.GetEngine(ctx)
|
|
|
|
key := &Package{
|
|
OwnerID: p.OwnerID,
|
|
Type: p.Type,
|
|
LowerName: p.LowerName,
|
|
}
|
|
|
|
has, err := e.Get(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if has {
|
|
return key, ErrDuplicatePackage
|
|
}
|
|
if _, err = e.Insert(p); err != nil {
|
|
return nil, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// DeletePackageByID deletes a package by id
|
|
func DeletePackageByID(ctx context.Context, packageID int64) error {
|
|
_, err := db.GetEngine(ctx).ID(packageID).Delete(&Package{})
|
|
return err
|
|
}
|
|
|
|
// SetRepositoryLink sets the linked repository
|
|
func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error {
|
|
_, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: repoID})
|
|
return err
|
|
}
|
|
|
|
// UnlinkRepositoryFromAllPackages unlinks every package from the repository
|
|
func UnlinkRepositoryFromAllPackages(ctx context.Context, repoID int64) error {
|
|
_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Cols("repo_id").Update(&Package{})
|
|
return err
|
|
}
|
|
|
|
// GetPackageByID gets a package by id
|
|
func GetPackageByID(ctx context.Context, packageID int64) (*Package, error) {
|
|
p := &Package{}
|
|
|
|
has, err := db.GetEngine(ctx).ID(packageID).Get(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !has {
|
|
return nil, ErrPackageNotExist
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// GetPackageByName gets a package by name
|
|
func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) {
|
|
var cond builder.Cond = builder.Eq{
|
|
"package.owner_id": ownerID,
|
|
"package.type": packageType,
|
|
"package.lower_name": strings.ToLower(name),
|
|
}
|
|
|
|
p := &Package{}
|
|
|
|
has, err := db.GetEngine(ctx).
|
|
Where(cond).
|
|
Get(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !has {
|
|
return nil, ErrPackageNotExist
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// GetPackagesByType gets all packages of a specific type
|
|
func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([]*Package, error) {
|
|
var cond builder.Cond = builder.Eq{
|
|
"package.owner_id": ownerID,
|
|
"package.type": packageType,
|
|
}
|
|
|
|
ps := make([]*Package, 0, 10)
|
|
return ps, db.GetEngine(ctx).
|
|
Where(cond).
|
|
Find(&ps)
|
|
}
|
|
|
|
// FindUnreferencedPackages gets all packages without associated versions
|
|
func FindUnreferencedPackages(ctx context.Context) ([]*Package, error) {
|
|
in := builder.
|
|
Select("package.id").
|
|
From("package").
|
|
LeftJoin("package_version", "package_version.package_id = package.id").
|
|
Where(builder.Expr("package_version.id IS NULL"))
|
|
|
|
ps := make([]*Package, 0, 10)
|
|
return ps, db.GetEngine(ctx).
|
|
// double select workaround for MySQL
|
|
// https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition
|
|
Where(builder.In("package.id", builder.Select("id").From(in, "temp"))).
|
|
Find(&ps)
|
|
}
|
|
|
|
// HasOwnerPackages tests if a user/org has accessible packages
|
|
func HasOwnerPackages(ctx context.Context, ownerID int64) (bool, error) {
|
|
return db.GetEngine(ctx).
|
|
Table("package_version").
|
|
Join("INNER", "package", "package.id = package_version.package_id").
|
|
Where(builder.Eq{
|
|
"package_version.is_internal": false,
|
|
"package.owner_id": ownerID,
|
|
}).
|
|
Exist(&PackageVersion{})
|
|
}
|
|
|
|
// HasRepositoryPackages tests if a repository has packages
|
|
func HasRepositoryPackages(ctx context.Context, repositoryID int64) (bool, error) {
|
|
return db.GetEngine(ctx).Where("repo_id = ?", repositoryID).Exist(&Package{})
|
|
}
|