summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--integrations/api_packages_container_test.go84
-rw-r--r--integrations/api_packages_npm_test.go6
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v220.go29
-rw-r--r--models/packages/container/search.go36
-rw-r--r--models/packages/descriptor.go42
-rw-r--r--models/packages/package.go17
-rw-r--r--models/packages/package_property.go10
-rw-r--r--models/user/search.go42
-rw-r--r--modules/packages/container/metadata.go1
-rw-r--r--routers/api/packages/api.go1
-rw-r--r--routers/api/packages/composer/api.go2
-rw-r--r--routers/api/packages/composer/composer.go2
-rw-r--r--routers/api/packages/container/blob.go12
-rw-r--r--routers/api/packages/container/container.go35
-rw-r--r--routers/api/packages/container/manifest.go12
-rw-r--r--routers/api/packages/npm/api.go2
-rw-r--r--routers/web/org/setting.go7
-rw-r--r--routers/web/user/setting/profile.go6
-rw-r--r--services/packages/container/cleanup.go25
-rw-r--r--services/packages/packages.go46
21 files changed, 341 insertions, 78 deletions
diff --git a/integrations/api_packages_container_test.go b/integrations/api_packages_container_test.go
index bdb8e2e90e..5e073f313f 100644
--- a/integrations/api_packages_container_test.go
+++ b/integrations/api_packages_container_test.go
@@ -27,6 +27,7 @@ import (
func TestPackageContainer(t *testing.T) {
defer prepareTestEnv(t)()
+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
has := func(l packages_model.PackagePropertyList, name string) bool {
@@ -37,6 +38,15 @@ func TestPackageContainer(t *testing.T) {
}
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"}
tags := []string{"latest", "main"}
@@ -67,7 +77,7 @@ func TestPackageContainer(t *testing.T) {
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) {
defer PrintCurrentTest(t)()
@@ -237,7 +247,8 @@ func TestPackageContainer(t *testing.T) {
assert.Nil(t, pd.SemVer)
assert.Equal(t, image, pd.Package.Name)
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)
metadata := pd.Metadata.(*container_module.Metadata)
@@ -331,7 +342,8 @@ func TestPackageContainer(t *testing.T) {
assert.Nil(t, pd.SemVer)
assert.Equal(t, image, pd.Package.Name)
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)
@@ -363,18 +375,10 @@ func TestPackageContainer(t *testing.T) {
assert.Nil(t, pd.SemVer)
assert.Equal(t, image, pd.Package.Name)
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 {
- 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.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference))
assert.IsType(t, &container_module.Metadata{}, pd.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)
+ })
}
diff --git a/integrations/api_packages_npm_test.go b/integrations/api_packages_npm_test.go
index 28a3711939..ad88ac5da6 100644
--- a/integrations/api_packages_npm_test.go
+++ b/integrations/api_packages_npm_test.go
@@ -85,9 +85,9 @@ func TestPackageNpm(t *testing.T) {
assert.IsType(t, &npm.Metadata{}, pd.Metadata)
assert.Equal(t, packageName, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version)
- assert.Len(t, pd.Properties, 1)
- assert.Equal(t, npm.TagProperty, pd.Properties[0].Name)
- assert.Equal(t, packageTag, pd.Properties[0].Value)
+ assert.Len(t, pd.VersionProperties, 1)
+ assert.Equal(t, npm.TagProperty, pd.VersionProperties[0].Name)
+ assert.Equal(t, packageTag, pd.VersionProperties[0].Value)
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
assert.NoError(t, err)
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 1b2a743b6d..beeba866dc 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -398,6 +398,8 @@ var migrations = []Migration{
NewMigration("Improve Action table indices v2", improveActionTableIndices),
// v219 -> v220
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
diff --git a/models/migrations/v220.go b/models/migrations/v220.go
new file mode 100644
index 0000000000..f5983582a3
--- /dev/null
+++ b/models/migrations/v220.go
@@ -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
+}
diff --git a/models/packages/container/search.go b/models/packages/container/search.go
index 972cac9528..a3409fe743 100644
--- a/models/packages/container/search.go
+++ b/models/packages/container/search.go
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages"
+ user_model "code.gitea.io/gitea/models/user"
container_module "code.gitea.io/gitea/modules/packages/container"
"xorm.io/builder"
@@ -210,6 +211,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack
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) {
var cond builder.Cond = builder.Eq{
"package_version.is_internal": true,
@@ -225,3 +227,37 @@ func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([
Where(cond).
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)
+}
diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go
index fbdc40f37f..31819ccca1 100644
--- a/models/packages/descriptor.go
+++ b/models/packages/descriptor.go
@@ -40,15 +40,16 @@ func (l PackagePropertyList) GetByName(name string) string {
// PackageDescriptor describes a package
type PackageDescriptor struct {
- Package *Package
- Owner *user_model.User
- Repository *repo_model.Repository
- Version *PackageVersion
- SemVer *version.Version
- Creator *user_model.User
- Properties PackagePropertyList
- Metadata interface{}
- Files []*PackageFileDescriptor
+ Package *Package
+ Owner *user_model.User
+ Repository *repo_model.Repository
+ Version *PackageVersion
+ SemVer *version.Version
+ Creator *user_model.User
+ PackageProperties PackagePropertyList
+ VersionProperties PackagePropertyList
+ Metadata interface{}
+ Files []*PackageFileDescriptor
}
// PackageFileDescriptor describes a package file
@@ -102,6 +103,10 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
return nil, err
}
}
+ pps, err := GetProperties(ctx, PropertyTypePackage, p.ID)
+ if err != nil {
+ return nil, err
+ }
pvps, err := GetProperties(ctx, PropertyTypeVersion, pv.ID)
if err != nil {
return nil, err
@@ -152,15 +157,16 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
}
return &PackageDescriptor{
- Package: p,
- Owner: o,
- Repository: repository,
- Version: pv,
- SemVer: semVer,
- Creator: creator,
- Properties: PackagePropertyList(pvps),
- Metadata: metadata,
- Files: pfds,
+ Package: p,
+ Owner: o,
+ Repository: repository,
+ Version: pv,
+ SemVer: semVer,
+ Creator: creator,
+ PackageProperties: PackagePropertyList(pps),
+ VersionProperties: PackagePropertyList(pvps),
+ Metadata: metadata,
+ Files: pfds,
}, nil
}
diff --git a/models/packages/package.go b/models/packages/package.go
index bdb535492b..97cfbc6cad 100644
--- a/models/packages/package.go
+++ b/models/packages/package.go
@@ -131,6 +131,12 @@ func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
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})
@@ -192,21 +198,20 @@ func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([]
Find(&ps)
}
-// DeletePackagesIfUnreferenced deletes a package if there are no associated versions
-func DeletePackagesIfUnreferenced(ctx context.Context) error {
+// 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"))
- _, err := db.GetEngine(ctx).
+ 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"))).
- Delete(&Package{})
-
- return err
+ Find(&ps)
}
// HasOwnerPackages tests if a user/org has packages
diff --git a/models/packages/package_property.go b/models/packages/package_property.go
index bf7dc346c6..fc10713801 100644
--- a/models/packages/package_property.go
+++ b/models/packages/package_property.go
@@ -21,9 +21,11 @@ const (
PropertyTypeVersion PropertyType = iota // 0
// PropertyTypeFile means the reference is a package file
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 {
ID int64 `xorm:"pk autoincr"`
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{})
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
+}
diff --git a/models/user/search.go b/models/user/search.go
index 76ff55ea26..f8e6c89f06 100644
--- a/models/user/search.go
+++ b/models/user/search.go
@@ -58,24 +58,7 @@ func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
cond = cond.And(builder.In("visibility", opts.Visible))
}
- if opts.Actor != nil {
- // 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))
- }
+ cond = cond.And(BuildCanSeeUserCondition(opts.Actor))
if opts.UID > 0 {
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)
+}
diff --git a/modules/packages/container/metadata.go b/modules/packages/container/metadata.go
index 087d38e5bd..4222cdb30a 100644
--- a/modules/packages/container/metadata.go
+++ b/modules/packages/container/metadata.go
@@ -16,6 +16,7 @@ import (
)
const (
+ PropertyRepository = "container.repository"
PropertyDigest = "container.digest"
PropertyMediaType = "container.mediatype"
PropertyManifestTagged = "container.manifest.tagged"
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index b5fdc739d7..bb9a42e33d 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -257,6 +257,7 @@ func ContainerRoutes() *web.Route {
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
r.Get("/token", container.Authenticate)
+ r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() {
r.Group("/{image}", func() {
r.Group("/blobs/uploads", func() {
diff --git a/routers/api/packages/composer/api.go b/routers/api/packages/composer/api.go
index 5e1cc293da..45bb7eae1c 100644
--- a/routers/api/packages/composer/api.go
+++ b/routers/api/packages/composer/api.go
@@ -88,7 +88,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
for _, pd := range pds {
packageType := ""
- for _, pvp := range pd.Properties {
+ for _, pvp := range pd.VersionProperties {
if pvp.Name == composer_module.TypeProperty {
packageType = pvp.Value
break
diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go
index b7c1f140dc..81cef39f1c 100644
--- a/routers/api/packages/composer/composer.go
+++ b/routers/api/packages/composer/composer.go
@@ -227,7 +227,7 @@ func UploadPackage(ctx *context.Context) {
SemverCompatible: true,
Creator: ctx.Doer,
Metadata: cp.Metadata,
- Properties: map[string]string{
+ VersionProperties: map[string]string{
composer_module.TypeProperty: cp.Type,
},
},
diff --git a/routers/api/packages/container/blob.go b/routers/api/packages/container/blob.go
index 8f6254f583..8a9cbd4a15 100644
--- a/routers/api/packages/container/blob.go
+++ b/routers/api/packages/container/blob.go
@@ -29,6 +29,7 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
contentStore := packages_module.NewContentStore()
err := db.WithTx(func(ctx context.Context) error {
+ created := true
p := &packages_model.Package{
OwnerID: pi.Owner.ID,
Type: packages_model.TypeContainer,
@@ -37,12 +38,21 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
}
var err error
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)
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{
PackageID: p.ID,
CreatorID: pi.Owner.ID,
diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go
index 2a564b3446..b961cd4afb 100644
--- a/routers/api/packages/container/container.go
+++ b/routers/api/packages/container/container.go
@@ -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)
func ReqContainerAccess(ctx *context.Context) {
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)
}
}
@@ -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#single-post
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go
index d899ac8ee2..319c9bcabc 100644
--- a/routers/api/packages/container/manifest.go
+++ b/routers/api/packages/container/manifest.go
@@ -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) {
+ created := true
p := &packages_model.Package{
OwnerID: mci.Owner.ID,
Type: packages_model.TypeContainer,
@@ -275,12 +276,21 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
}
var err error
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)
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
metadataJSON, err := json.Marshal(metadata)
diff --git a/routers/api/packages/npm/api.go b/routers/api/packages/npm/api.go
index 56c8977043..4b6b803971 100644
--- a/routers/api/packages/npm/api.go
+++ b/routers/api/packages/npm/api.go
@@ -25,7 +25,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
for _, pd := range pds {
versions[pd.SemVer.String()] = createPackageMetadataVersion(registryURL, pd)
- for _, pvp := range pd.Properties {
+ for _, pvp := range pd.VersionProperties {
if pvp.Name == npm_module.TagProperty {
distTags[pvp.Value] = pd.Version.Version
}
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index c22a124e74..3f7bc59856 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -24,6 +24,7 @@ import (
user_setting "code.gitea.io/gitea/routers/web/user/setting"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/org"
+ container_service "code.gitea.io/gitea/services/packages/container"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
)
@@ -88,6 +89,12 @@ func SettingsPost(ctx *context.Context) {
}
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
ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(form.Name)
log.Trace("Organization name changed: %s -> %s", org.Name, form.Name)
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index b07813e725..c9a7afe982 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -30,6 +30,7 @@ import (
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/agit"
"code.gitea.io/gitea/services/forms"
+ container_service "code.gitea.io/gitea/services/packages/container"
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
}
+ 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)
return nil
}
diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go
index 3e44f9aa1a..d23a481f27 100644
--- a/services/packages/container/cleanup.go
+++ b/services/packages/container/cleanup.go
@@ -6,10 +6,13 @@ package container
import (
"context"
+ "strings"
"time"
packages_model "code.gitea.io/gitea/models/packages"
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"
)
@@ -78,3 +81,25 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
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
+}
diff --git a/services/packages/packages.go b/services/packages/packages.go
index aa1796e8b3..975c5ddd35 100644
--- a/services/packages/packages.go
+++ b/services/packages/packages.go
@@ -34,10 +34,11 @@ type PackageInfo struct {
// PackageCreationInfo describes a package to create
type PackageCreationInfo struct {
PackageInfo
- SemverCompatible bool
- Creator *user_model.User
- Metadata interface{}
- Properties map[string]string
+ SemverCompatible bool
+ Creator *user_model.User
+ Metadata interface{}
+ PackageProperties map[string]string
+ VersionProperties map[string]string
}
// 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) {
- 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{
OwnerID: pvci.Owner.ID,
Type: pvci.PackageType,
@@ -121,18 +123,29 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
}
var err error
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)
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)
if err != nil {
return nil, false, err
}
- created := true
+ versionCreated := true
pv := &packages_model.PackageVersion{
PackageID: p.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 err == packages_model.ErrDuplicatePackageVersion {
- created = false
+ versionCreated = false
}
if err != packages_model.ErrDuplicatePackageVersion || !allowDuplicate {
log.Error("Error inserting package: %v", err)
@@ -150,8 +163,8 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
}
}
- if created {
- for name, value := range pvci.Properties {
+ if versionCreated {
+ for name, value := range pvci.VersionProperties {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, name, value); err != nil {
log.Error("Error setting package version property: %v", 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
@@ -350,9 +363,18 @@ func Cleanup(unused context.Context, olderThan time.Duration) error {
return err
}
- if err := packages_model.DeletePackagesIfUnreferenced(ctx); err != nil {
+ ps, err := packages_model.FindUnreferencedPackages(ctx)
+ if err != nil {
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)
if err != nil {