mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-09-11 04:30:06 +02:00
Add Docker /v2/_catalog endpoint (#20469)
* Added properties for packages. * Fixed authenticate header format. * Added _catalog endpoint. * Check owner visibility. * Extracted condition. * Added test for _catalog. Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
4604048010
commit
86e5268c39
21 changed files with 341 additions and 78 deletions
|
@ -27,6 +27,7 @@ import (
|
||||||
|
|
||||||
func TestPackageContainer(t *testing.T) {
|
func TestPackageContainer(t *testing.T) {
|
||||||
defer prepareTestEnv(t)()
|
defer prepareTestEnv(t)()
|
||||||
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||||
|
|
||||||
has := func(l packages_model.PackagePropertyList, name string) bool {
|
has := func(l packages_model.PackagePropertyList, name string) bool {
|
||||||
|
@ -37,6 +38,15 @@ func TestPackageContainer(t *testing.T) {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
getAllByName := func(l packages_model.PackagePropertyList, name string) []string {
|
||||||
|
values := make([]string, 0, len(l))
|
||||||
|
for _, pp := range l {
|
||||||
|
if pp.Name == name {
|
||||||
|
values = append(values, pp.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
images := []string{"test", "te/st"}
|
images := []string{"test", "te/st"}
|
||||||
tags := []string{"latest", "main"}
|
tags := []string{"latest", "main"}
|
||||||
|
@ -67,7 +77,7 @@ func TestPackageContainer(t *testing.T) {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token"`}
|
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`}
|
||||||
|
|
||||||
t.Run("Anonymous", func(t *testing.T) {
|
t.Run("Anonymous", func(t *testing.T) {
|
||||||
defer PrintCurrentTest(t)()
|
defer PrintCurrentTest(t)()
|
||||||
|
@ -237,7 +247,8 @@ func TestPackageContainer(t *testing.T) {
|
||||||
assert.Nil(t, pd.SemVer)
|
assert.Nil(t, pd.SemVer)
|
||||||
assert.Equal(t, image, pd.Package.Name)
|
assert.Equal(t, image, pd.Package.Name)
|
||||||
assert.Equal(t, tag, pd.Version.Version)
|
assert.Equal(t, tag, pd.Version.Version)
|
||||||
assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged))
|
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
|
||||||
|
assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
|
||||||
|
|
||||||
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
|
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
|
||||||
metadata := pd.Metadata.(*container_module.Metadata)
|
metadata := pd.Metadata.(*container_module.Metadata)
|
||||||
|
@ -331,7 +342,8 @@ func TestPackageContainer(t *testing.T) {
|
||||||
assert.Nil(t, pd.SemVer)
|
assert.Nil(t, pd.SemVer)
|
||||||
assert.Equal(t, image, pd.Package.Name)
|
assert.Equal(t, image, pd.Package.Name)
|
||||||
assert.Equal(t, untaggedManifestDigest, pd.Version.Version)
|
assert.Equal(t, untaggedManifestDigest, pd.Version.Version)
|
||||||
assert.False(t, has(pd.Properties, container_module.PropertyManifestTagged))
|
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
|
||||||
|
assert.False(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
|
||||||
|
|
||||||
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
|
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
|
||||||
|
|
||||||
|
@ -363,18 +375,10 @@ func TestPackageContainer(t *testing.T) {
|
||||||
assert.Nil(t, pd.SemVer)
|
assert.Nil(t, pd.SemVer)
|
||||||
assert.Equal(t, image, pd.Package.Name)
|
assert.Equal(t, image, pd.Package.Name)
|
||||||
assert.Equal(t, multiTag, pd.Version.Version)
|
assert.Equal(t, multiTag, pd.Version.Version)
|
||||||
assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged))
|
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
|
||||||
|
assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
|
||||||
|
|
||||||
getAllByName := func(l packages_model.PackagePropertyList, name string) []string {
|
assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference))
|
||||||
values := make([]string, 0, len(l))
|
|
||||||
for _, pp := range l {
|
|
||||||
if pp.Name == name {
|
|
||||||
values = append(values, pp.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.Properties, container_module.PropertyManifestReference))
|
|
||||||
|
|
||||||
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
|
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
|
||||||
metadata := pd.Metadata.(*container_module.Metadata)
|
metadata := pd.Metadata.(*container_module.Metadata)
|
||||||
|
@ -536,4 +540,56 @@ func TestPackageContainer(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("OwnerNameChange", func(t *testing.T) {
|
||||||
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
checkCatalog := func(owner string) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
defer PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL))
|
||||||
|
addTokenAuthHeader(req, userToken)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
type RepositoryList struct {
|
||||||
|
Repositories []string `json:"repositories"`
|
||||||
|
}
|
||||||
|
|
||||||
|
repoList := &RepositoryList{}
|
||||||
|
DecodeJSON(t, resp, &repoList)
|
||||||
|
|
||||||
|
assert.Len(t, repoList.Repositories, len(images))
|
||||||
|
names := make([]string, 0, len(images))
|
||||||
|
for _, image := range images {
|
||||||
|
names = append(names, strings.ToLower(owner+"/"+image))
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, names, repoList.Repositories)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(fmt.Sprintf("Catalog[%s]", user.LowerName), checkCatalog(user.LowerName))
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
|
||||||
|
newOwnerName := "newUsername"
|
||||||
|
|
||||||
|
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||||
|
"_csrf": GetCSRF(t, session, "/user/settings"),
|
||||||
|
"name": newOwnerName,
|
||||||
|
"email": "user2@example.com",
|
||||||
|
"language": "en-US",
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
|
||||||
|
t.Run(fmt.Sprintf("Catalog[%s]", newOwnerName), checkCatalog(newOwnerName))
|
||||||
|
|
||||||
|
req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||||
|
"_csrf": GetCSRF(t, session, "/user/settings"),
|
||||||
|
"name": user.Name,
|
||||||
|
"email": "user2@example.com",
|
||||||
|
"language": "en-US",
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,9 +85,9 @@ func TestPackageNpm(t *testing.T) {
|
||||||
assert.IsType(t, &npm.Metadata{}, pd.Metadata)
|
assert.IsType(t, &npm.Metadata{}, pd.Metadata)
|
||||||
assert.Equal(t, packageName, pd.Package.Name)
|
assert.Equal(t, packageName, pd.Package.Name)
|
||||||
assert.Equal(t, packageVersion, pd.Version.Version)
|
assert.Equal(t, packageVersion, pd.Version.Version)
|
||||||
assert.Len(t, pd.Properties, 1)
|
assert.Len(t, pd.VersionProperties, 1)
|
||||||
assert.Equal(t, npm.TagProperty, pd.Properties[0].Name)
|
assert.Equal(t, npm.TagProperty, pd.VersionProperties[0].Name)
|
||||||
assert.Equal(t, packageTag, pd.Properties[0].Value)
|
assert.Equal(t, packageTag, pd.VersionProperties[0].Value)
|
||||||
|
|
||||||
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -398,6 +398,8 @@ var migrations = []Migration{
|
||||||
NewMigration("Improve Action table indices v2", improveActionTableIndices),
|
NewMigration("Improve Action table indices v2", improveActionTableIndices),
|
||||||
// v219 -> v220
|
// v219 -> v220
|
||||||
NewMigration("Add sync_on_commit column to push_mirror table", addSyncOnCommitColForPushMirror),
|
NewMigration("Add sync_on_commit column to push_mirror table", addSyncOnCommitColForPushMirror),
|
||||||
|
// v220 -> v221
|
||||||
|
NewMigration("Add container repository property", addContainerRepositoryProperty),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current db version
|
// GetCurrentDBVersion returns the current db version
|
||||||
|
|
29
models/migrations/v220.go
Normal file
29
models/migrations/v220.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// 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 migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
|
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addContainerRepositoryProperty(x *xorm.Engine) error {
|
||||||
|
switch x.Dialect().URI().DBType {
|
||||||
|
case schemas.SQLITE:
|
||||||
|
_, err := x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, u.lower_name || '/' || p.lower_name FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?", packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_, err := x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, CONCAT(u.lower_name, '/', p.lower_name) FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?", packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/packages"
|
"code.gitea.io/gitea/models/packages"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
@ -210,6 +211,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack
|
||||||
return pvs, count, err
|
return pvs, count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchExpiredUploadedBlobs gets all uploaded blobs which are older than specified
|
||||||
func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) {
|
func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) {
|
||||||
var cond builder.Cond = builder.Eq{
|
var cond builder.Cond = builder.Eq{
|
||||||
"package_version.is_internal": true,
|
"package_version.is_internal": true,
|
||||||
|
@ -225,3 +227,37 @@ func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([
|
||||||
Where(cond).
|
Where(cond).
|
||||||
Find(&pfs)
|
Find(&pfs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRepositories gets a sorted list of all repositories
|
||||||
|
func GetRepositories(ctx context.Context, actor *user_model.User, n int, last string) ([]string, error) {
|
||||||
|
var cond builder.Cond = builder.Eq{
|
||||||
|
"package.type": packages.TypeContainer,
|
||||||
|
"package_property.ref_type": packages.PropertyTypePackage,
|
||||||
|
"package_property.name": container_module.PropertyRepository,
|
||||||
|
}
|
||||||
|
|
||||||
|
cond = cond.And(builder.Exists(
|
||||||
|
builder.
|
||||||
|
Select("package_version.id").
|
||||||
|
Where(builder.Eq{"package_version.is_internal": false}.And(builder.Expr("package.id = package_version.package_id"))).
|
||||||
|
From("package_version"),
|
||||||
|
))
|
||||||
|
|
||||||
|
if last != "" {
|
||||||
|
cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)})
|
||||||
|
}
|
||||||
|
|
||||||
|
cond = cond.And(user_model.BuildCanSeeUserCondition(actor))
|
||||||
|
|
||||||
|
sess := db.GetEngine(ctx).
|
||||||
|
Table("package").
|
||||||
|
Select("package_property.value").
|
||||||
|
Join("INNER", "user", "`user`.id = package.owner_id").
|
||||||
|
Join("INNER", "package_property", "package_property.ref_id = package.id").
|
||||||
|
Where(cond).
|
||||||
|
Asc("package_property.value").
|
||||||
|
Limit(n)
|
||||||
|
|
||||||
|
repositories := make([]string, 0, n)
|
||||||
|
return repositories, sess.Find(&repositories)
|
||||||
|
}
|
||||||
|
|
|
@ -40,15 +40,16 @@ func (l PackagePropertyList) GetByName(name string) string {
|
||||||
|
|
||||||
// PackageDescriptor describes a package
|
// PackageDescriptor describes a package
|
||||||
type PackageDescriptor struct {
|
type PackageDescriptor struct {
|
||||||
Package *Package
|
Package *Package
|
||||||
Owner *user_model.User
|
Owner *user_model.User
|
||||||
Repository *repo_model.Repository
|
Repository *repo_model.Repository
|
||||||
Version *PackageVersion
|
Version *PackageVersion
|
||||||
SemVer *version.Version
|
SemVer *version.Version
|
||||||
Creator *user_model.User
|
Creator *user_model.User
|
||||||
Properties PackagePropertyList
|
PackageProperties PackagePropertyList
|
||||||
Metadata interface{}
|
VersionProperties PackagePropertyList
|
||||||
Files []*PackageFileDescriptor
|
Metadata interface{}
|
||||||
|
Files []*PackageFileDescriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackageFileDescriptor describes a package file
|
// PackageFileDescriptor describes a package file
|
||||||
|
@ -102,6 +103,10 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pps, err := GetProperties(ctx, PropertyTypePackage, p.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
pvps, err := GetProperties(ctx, PropertyTypeVersion, pv.ID)
|
pvps, err := GetProperties(ctx, PropertyTypeVersion, pv.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -152,15 +157,16 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PackageDescriptor{
|
return &PackageDescriptor{
|
||||||
Package: p,
|
Package: p,
|
||||||
Owner: o,
|
Owner: o,
|
||||||
Repository: repository,
|
Repository: repository,
|
||||||
Version: pv,
|
Version: pv,
|
||||||
SemVer: semVer,
|
SemVer: semVer,
|
||||||
Creator: creator,
|
Creator: creator,
|
||||||
Properties: PackagePropertyList(pvps),
|
PackageProperties: PackagePropertyList(pps),
|
||||||
Metadata: metadata,
|
VersionProperties: PackagePropertyList(pvps),
|
||||||
Files: pfds,
|
Metadata: metadata,
|
||||||
|
Files: pfds,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,12 @@ func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
|
||||||
return p, nil
|
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
|
// SetRepositoryLink sets the linked repository
|
||||||
func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error {
|
func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error {
|
||||||
_, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: repoID})
|
_, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: repoID})
|
||||||
|
@ -192,21 +198,20 @@ func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([]
|
||||||
Find(&ps)
|
Find(&ps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePackagesIfUnreferenced deletes a package if there are no associated versions
|
// FindUnreferencedPackages gets all packages without associated versions
|
||||||
func DeletePackagesIfUnreferenced(ctx context.Context) error {
|
func FindUnreferencedPackages(ctx context.Context) ([]*Package, error) {
|
||||||
in := builder.
|
in := builder.
|
||||||
Select("package.id").
|
Select("package.id").
|
||||||
From("package").
|
From("package").
|
||||||
LeftJoin("package_version", "package_version.package_id = package.id").
|
LeftJoin("package_version", "package_version.package_id = package.id").
|
||||||
Where(builder.Expr("package_version.id IS NULL"))
|
Where(builder.Expr("package_version.id IS NULL"))
|
||||||
|
|
||||||
_, err := db.GetEngine(ctx).
|
ps := make([]*Package, 0, 10)
|
||||||
|
return ps, db.GetEngine(ctx).
|
||||||
// double select workaround for MySQL
|
// double select workaround for MySQL
|
||||||
// https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition
|
// https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition
|
||||||
Where(builder.In("package.id", builder.Select("id").From(in, "temp"))).
|
Where(builder.In("package.id", builder.Select("id").From(in, "temp"))).
|
||||||
Delete(&Package{})
|
Find(&ps)
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasOwnerPackages tests if a user/org has packages
|
// HasOwnerPackages tests if a user/org has packages
|
||||||
|
|
|
@ -21,9 +21,11 @@ const (
|
||||||
PropertyTypeVersion PropertyType = iota // 0
|
PropertyTypeVersion PropertyType = iota // 0
|
||||||
// PropertyTypeFile means the reference is a package file
|
// PropertyTypeFile means the reference is a package file
|
||||||
PropertyTypeFile // 1
|
PropertyTypeFile // 1
|
||||||
|
// PropertyTypePackage means the reference is a package
|
||||||
|
PropertyTypePackage // 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// PackageProperty represents a property of a package version or file
|
// PackageProperty represents a property of a package, version or file
|
||||||
type PackageProperty struct {
|
type PackageProperty struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
RefType PropertyType `xorm:"INDEX NOT NULL"`
|
RefType PropertyType `xorm:"INDEX NOT NULL"`
|
||||||
|
@ -68,3 +70,9 @@ func DeletePropertyByID(ctx context.Context, propertyID int64) error {
|
||||||
_, err := db.GetEngine(ctx).ID(propertyID).Delete(&PackageProperty{})
|
_, err := db.GetEngine(ctx).ID(propertyID).Delete(&PackageProperty{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletePropertyByName deletes properties by name
|
||||||
|
func DeletePropertyByName(ctx context.Context, refType PropertyType, refID int64, name string) error {
|
||||||
|
_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -58,24 +58,7 @@ func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
|
||||||
cond = cond.And(builder.In("visibility", opts.Visible))
|
cond = cond.And(builder.In("visibility", opts.Visible))
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Actor != nil {
|
cond = cond.And(BuildCanSeeUserCondition(opts.Actor))
|
||||||
// If Admin - they see all users!
|
|
||||||
if !opts.Actor.IsAdmin {
|
|
||||||
// Users can see an organization they are a member of
|
|
||||||
accessCond := builder.In("id", builder.Select("org_id").From("org_user").Where(builder.Eq{"uid": opts.Actor.ID}))
|
|
||||||
if !opts.Actor.IsRestricted {
|
|
||||||
// Not-Restricted users can see public and limited users/organizations
|
|
||||||
accessCond = accessCond.Or(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
|
|
||||||
}
|
|
||||||
// Don't forget about self
|
|
||||||
accessCond = accessCond.Or(builder.Eq{"id": opts.Actor.ID})
|
|
||||||
cond = cond.And(accessCond)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Force visibility for privacy
|
|
||||||
// Not logged in - only public users
|
|
||||||
cond = cond.And(builder.In("visibility", structs.VisibleTypePublic))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.UID > 0 {
|
if opts.UID > 0 {
|
||||||
cond = cond.And(builder.Eq{"id": opts.UID})
|
cond = cond.And(builder.Eq{"id": opts.UID})
|
||||||
|
@ -163,3 +146,26 @@ func IterateUser(f func(user *User) error) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildCanSeeUserCondition creates a condition which can be used to restrict results to users/orgs the actor can see
|
||||||
|
func BuildCanSeeUserCondition(actor *User) builder.Cond {
|
||||||
|
if actor != nil {
|
||||||
|
// If Admin - they see all users!
|
||||||
|
if !actor.IsAdmin {
|
||||||
|
// Users can see an organization they are a member of
|
||||||
|
cond := builder.In("`user`.id", builder.Select("org_id").From("org_user").Where(builder.Eq{"uid": actor.ID}))
|
||||||
|
if !actor.IsRestricted {
|
||||||
|
// Not-Restricted users can see public and limited users/organizations
|
||||||
|
cond = cond.Or(builder.In("`user`.visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
|
||||||
|
}
|
||||||
|
// Don't forget about self
|
||||||
|
return cond.Or(builder.Eq{"`user`.id": actor.ID})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force visibility for privacy
|
||||||
|
// Not logged in - only public users
|
||||||
|
return builder.In("`user`.visibility", structs.VisibleTypePublic)
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
PropertyRepository = "container.repository"
|
||||||
PropertyDigest = "container.digest"
|
PropertyDigest = "container.digest"
|
||||||
PropertyMediaType = "container.mediatype"
|
PropertyMediaType = "container.mediatype"
|
||||||
PropertyManifestTagged = "container.manifest.tagged"
|
PropertyManifestTagged = "container.manifest.tagged"
|
||||||
|
|
|
@ -257,6 +257,7 @@ func ContainerRoutes() *web.Route {
|
||||||
|
|
||||||
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
|
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
|
||||||
r.Get("/token", container.Authenticate)
|
r.Get("/token", container.Authenticate)
|
||||||
|
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
|
||||||
r.Group("/{username}", func() {
|
r.Group("/{username}", func() {
|
||||||
r.Group("/{image}", func() {
|
r.Group("/{image}", func() {
|
||||||
r.Group("/blobs/uploads", func() {
|
r.Group("/blobs/uploads", func() {
|
||||||
|
|
|
@ -88,7 +88,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
|
||||||
|
|
||||||
for _, pd := range pds {
|
for _, pd := range pds {
|
||||||
packageType := ""
|
packageType := ""
|
||||||
for _, pvp := range pd.Properties {
|
for _, pvp := range pd.VersionProperties {
|
||||||
if pvp.Name == composer_module.TypeProperty {
|
if pvp.Name == composer_module.TypeProperty {
|
||||||
packageType = pvp.Value
|
packageType = pvp.Value
|
||||||
break
|
break
|
||||||
|
|
|
@ -227,7 +227,7 @@ func UploadPackage(ctx *context.Context) {
|
||||||
SemverCompatible: true,
|
SemverCompatible: true,
|
||||||
Creator: ctx.Doer,
|
Creator: ctx.Doer,
|
||||||
Metadata: cp.Metadata,
|
Metadata: cp.Metadata,
|
||||||
Properties: map[string]string{
|
VersionProperties: map[string]string{
|
||||||
composer_module.TypeProperty: cp.Type,
|
composer_module.TypeProperty: cp.Type,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,6 +29,7 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
||||||
contentStore := packages_module.NewContentStore()
|
contentStore := packages_module.NewContentStore()
|
||||||
|
|
||||||
err := db.WithTx(func(ctx context.Context) error {
|
err := db.WithTx(func(ctx context.Context) error {
|
||||||
|
created := true
|
||||||
p := &packages_model.Package{
|
p := &packages_model.Package{
|
||||||
OwnerID: pi.Owner.ID,
|
OwnerID: pi.Owner.ID,
|
||||||
Type: packages_model.TypeContainer,
|
Type: packages_model.TypeContainer,
|
||||||
|
@ -37,12 +38,21 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
|
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
|
||||||
if err != packages_model.ErrDuplicatePackage {
|
if err == packages_model.ErrDuplicatePackage {
|
||||||
|
created = false
|
||||||
|
} else {
|
||||||
log.Error("Error inserting package: %v", err)
|
log.Error("Error inserting package: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if created {
|
||||||
|
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(pi.Owner.LowerName+"/"+pi.Name)); err != nil {
|
||||||
|
log.Error("Error setting package property: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pv := &packages_model.PackageVersion{
|
pv := &packages_model.PackageVersion{
|
||||||
PackageID: p.ID,
|
PackageID: p.ID,
|
||||||
CreatorID: pi.Owner.ID,
|
CreatorID: pi.Owner.ID,
|
||||||
|
|
|
@ -112,7 +112,7 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
|
||||||
// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost for anonymous access)
|
// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost for anonymous access)
|
||||||
func ReqContainerAccess(ctx *context.Context) {
|
func ReqContainerAccess(ctx *context.Context) {
|
||||||
if ctx.Doer == nil {
|
if ctx.Doer == nil {
|
||||||
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token"`)
|
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*"`)
|
||||||
apiErrorDefined(ctx, errUnauthorized)
|
apiErrorDefined(ctx, errUnauthorized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,39 @@ func Authenticate(ctx *context.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://docs.docker.com/registry/spec/api/#listing-repositories
|
||||||
|
func GetRepositoryList(ctx *context.Context) {
|
||||||
|
n := ctx.FormInt("n")
|
||||||
|
if n <= 0 || n > 100 {
|
||||||
|
n = 100
|
||||||
|
}
|
||||||
|
last := ctx.FormTrim("last")
|
||||||
|
|
||||||
|
repositories, err := container_model.GetRepositories(ctx, ctx.Doer, n, last)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type RepositoryList struct {
|
||||||
|
Repositories []string `json:"repositories"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(repositories) == n {
|
||||||
|
v := url.Values{}
|
||||||
|
if n > 0 {
|
||||||
|
v.Add("n", strconv.Itoa(n))
|
||||||
|
}
|
||||||
|
v.Add("last", repositories[len(repositories)-1])
|
||||||
|
|
||||||
|
ctx.Resp.Header().Set("Link", fmt.Sprintf(`</v2/_catalog?%s>; rel="next"`, v.Encode()))
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonResponse(ctx, http.StatusOK, RepositoryList{
|
||||||
|
Repositories: repositories,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#mounting-a-blob-from-another-repository
|
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#mounting-a-blob-from-another-repository
|
||||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post
|
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post
|
||||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
|
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
|
||||||
|
|
|
@ -267,6 +267,7 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, metadata *container_module.Metadata) (*packages_model.PackageVersion, error) {
|
func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, metadata *container_module.Metadata) (*packages_model.PackageVersion, error) {
|
||||||
|
created := true
|
||||||
p := &packages_model.Package{
|
p := &packages_model.Package{
|
||||||
OwnerID: mci.Owner.ID,
|
OwnerID: mci.Owner.ID,
|
||||||
Type: packages_model.TypeContainer,
|
Type: packages_model.TypeContainer,
|
||||||
|
@ -275,12 +276,21 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
|
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
|
||||||
if err != packages_model.ErrDuplicatePackage {
|
if err == packages_model.ErrDuplicatePackage {
|
||||||
|
created = false
|
||||||
|
} else {
|
||||||
log.Error("Error inserting package: %v", err)
|
log.Error("Error inserting package: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if created {
|
||||||
|
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(mci.Owner.LowerName+"/"+mci.Image)); err != nil {
|
||||||
|
log.Error("Error setting package property: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
metadata.IsTagged = mci.IsTagged
|
metadata.IsTagged = mci.IsTagged
|
||||||
|
|
||||||
metadataJSON, err := json.Marshal(metadata)
|
metadataJSON, err := json.Marshal(metadata)
|
||||||
|
|
|
@ -25,7 +25,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
|
||||||
for _, pd := range pds {
|
for _, pd := range pds {
|
||||||
versions[pd.SemVer.String()] = createPackageMetadataVersion(registryURL, pd)
|
versions[pd.SemVer.String()] = createPackageMetadataVersion(registryURL, pd)
|
||||||
|
|
||||||
for _, pvp := range pd.Properties {
|
for _, pvp := range pd.VersionProperties {
|
||||||
if pvp.Name == npm_module.TagProperty {
|
if pvp.Name == npm_module.TagProperty {
|
||||||
distTags[pvp.Value] = pd.Version.Version
|
distTags[pvp.Value] = pd.Version.Version
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
user_setting "code.gitea.io/gitea/routers/web/user/setting"
|
user_setting "code.gitea.io/gitea/routers/web/user/setting"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
"code.gitea.io/gitea/services/org"
|
"code.gitea.io/gitea/services/org"
|
||||||
|
container_service "code.gitea.io/gitea/services/packages/container"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
)
|
)
|
||||||
|
@ -88,6 +89,12 @@ func SettingsPost(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := container_service.UpdateRepositoryNames(ctx, org.AsUser(), form.Name); err != nil {
|
||||||
|
ctx.ServerError("UpdateRepositoryNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// reset ctx.org.OrgLink with new name
|
// reset ctx.org.OrgLink with new name
|
||||||
ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(form.Name)
|
ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(form.Name)
|
||||||
log.Trace("Organization name changed: %s -> %s", org.Name, form.Name)
|
log.Trace("Organization name changed: %s -> %s", org.Name, form.Name)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
"code.gitea.io/gitea/services/agit"
|
"code.gitea.io/gitea/services/agit"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
container_service "code.gitea.io/gitea/services/packages/container"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -90,6 +91,11 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := container_service.UpdateRepositoryNames(ctx, user, newName); err != nil {
|
||||||
|
ctx.ServerError("UpdateRepositoryNames", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
log.Trace("User name changed: %s -> %s", user.Name, newName)
|
log.Trace("User name changed: %s -> %s", user.Name, newName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,13 @@ package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
container_model "code.gitea.io/gitea/models/packages/container"
|
container_model "code.gitea.io/gitea/models/packages/container"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -78,3 +81,25 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateRepositoryNames updates the repository name property for all packages of the specific owner
|
||||||
|
func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwnerName string) error {
|
||||||
|
ps, err := packages_model.GetPackagesByType(ctx, owner.ID, packages_model.TypeContainer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newOwnerName = strings.ToLower(newOwnerName)
|
||||||
|
|
||||||
|
for _, p := range ps {
|
||||||
|
if err := packages_model.DeletePropertyByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, newOwnerName+"/"+p.LowerName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -34,10 +34,11 @@ type PackageInfo struct {
|
||||||
// PackageCreationInfo describes a package to create
|
// PackageCreationInfo describes a package to create
|
||||||
type PackageCreationInfo struct {
|
type PackageCreationInfo struct {
|
||||||
PackageInfo
|
PackageInfo
|
||||||
SemverCompatible bool
|
SemverCompatible bool
|
||||||
Creator *user_model.User
|
Creator *user_model.User
|
||||||
Metadata interface{}
|
Metadata interface{}
|
||||||
Properties map[string]string
|
PackageProperties map[string]string
|
||||||
|
VersionProperties map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackageFileInfo describes a package file
|
// PackageFileInfo describes a package file
|
||||||
|
@ -110,8 +111,9 @@ func createPackageAndAddFile(pvci *PackageCreationInfo, pfci *PackageFileCreatio
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, allowDuplicate bool) (*packages_model.PackageVersion, bool, error) {
|
func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, allowDuplicate bool) (*packages_model.PackageVersion, bool, error) {
|
||||||
log.Trace("Creating package: %v, %v, %v, %s, %s, %+v, %v", pvci.Creator.ID, pvci.Owner.ID, pvci.PackageType, pvci.Name, pvci.Version, pvci.Properties, allowDuplicate)
|
log.Trace("Creating package: %v, %v, %v, %s, %s, %+v, %+v, %v", pvci.Creator.ID, pvci.Owner.ID, pvci.PackageType, pvci.Name, pvci.Version, pvci.PackageProperties, pvci.VersionProperties, allowDuplicate)
|
||||||
|
|
||||||
|
packageCreated := true
|
||||||
p := &packages_model.Package{
|
p := &packages_model.Package{
|
||||||
OwnerID: pvci.Owner.ID,
|
OwnerID: pvci.Owner.ID,
|
||||||
Type: pvci.PackageType,
|
Type: pvci.PackageType,
|
||||||
|
@ -121,18 +123,29 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
|
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
|
||||||
if err != packages_model.ErrDuplicatePackage {
|
if err == packages_model.ErrDuplicatePackage {
|
||||||
|
packageCreated = false
|
||||||
|
} else {
|
||||||
log.Error("Error inserting package: %v", err)
|
log.Error("Error inserting package: %v", err)
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if packageCreated {
|
||||||
|
for name, value := range pvci.PackageProperties {
|
||||||
|
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, name, value); err != nil {
|
||||||
|
log.Error("Error setting package property: %v", err)
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
metadataJSON, err := json.Marshal(pvci.Metadata)
|
metadataJSON, err := json.Marshal(pvci.Metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
created := true
|
versionCreated := true
|
||||||
pv := &packages_model.PackageVersion{
|
pv := &packages_model.PackageVersion{
|
||||||
PackageID: p.ID,
|
PackageID: p.ID,
|
||||||
CreatorID: pvci.Creator.ID,
|
CreatorID: pvci.Creator.ID,
|
||||||
|
@ -142,7 +155,7 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
|
||||||
}
|
}
|
||||||
if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
|
if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
|
||||||
if err == packages_model.ErrDuplicatePackageVersion {
|
if err == packages_model.ErrDuplicatePackageVersion {
|
||||||
created = false
|
versionCreated = false
|
||||||
}
|
}
|
||||||
if err != packages_model.ErrDuplicatePackageVersion || !allowDuplicate {
|
if err != packages_model.ErrDuplicatePackageVersion || !allowDuplicate {
|
||||||
log.Error("Error inserting package: %v", err)
|
log.Error("Error inserting package: %v", err)
|
||||||
|
@ -150,8 +163,8 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if created {
|
if versionCreated {
|
||||||
for name, value := range pvci.Properties {
|
for name, value := range pvci.VersionProperties {
|
||||||
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, name, value); err != nil {
|
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, name, value); err != nil {
|
||||||
log.Error("Error setting package version property: %v", err)
|
log.Error("Error setting package version property: %v", err)
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
|
@ -159,7 +172,7 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pv, created, nil
|
return pv, versionCreated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddFileToExistingPackage adds a file to an existing package. If the package does not exist, ErrPackageNotExist is returned
|
// AddFileToExistingPackage adds a file to an existing package. If the package does not exist, ErrPackageNotExist is returned
|
||||||
|
@ -350,9 +363,18 @@ func Cleanup(unused context.Context, olderThan time.Duration) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := packages_model.DeletePackagesIfUnreferenced(ctx); err != nil {
|
ps, err := packages_model.FindUnreferencedPackages(ctx)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, p := range ps {
|
||||||
|
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypePackage, p.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := packages_model.DeletePackageByID(ctx, p.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pbs, err := packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan)
|
pbs, err := packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue