Skip to content

Commit 97a8c96

Browse files
6543KN4CK3Rlunnylafrikswxiaoguang
authoredJul 30, 2022
Add Docker /v2/_catalog endpoint (#20469) (#20556)
* Added properties for packages. * Fixed authenticate header format. * Added _catalog endpoint. * Check owner visibility. * Extracted condition. * Added test for _catalog. Co-authored-by: 6543 <[email protected]> Co-authored-by: KN4CK3R <[email protected]> Co-authored-by: Lunny Xiao <[email protected]> Co-authored-by: Lauris BH <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent d1e53bf commit 97a8c96

File tree

22 files changed

+374
-85
lines changed

22 files changed

+374
-85
lines changed
 

‎integrations/api_packages_container_test.go

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
func TestPackageContainer(t *testing.T) {
2828
defer prepareTestEnv(t)()
29+
2930
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
3031

3132
has := func(l packages_model.PackagePropertyList, name string) bool {
@@ -36,6 +37,15 @@ func TestPackageContainer(t *testing.T) {
3637
}
3738
return false
3839
}
40+
getAllByName := func(l packages_model.PackagePropertyList, name string) []string {
41+
values := make([]string, 0, len(l))
42+
for _, pp := range l {
43+
if pp.Name == name {
44+
values = append(values, pp.Value)
45+
}
46+
}
47+
return values
48+
}
3949

4050
images := []string{"test", "te/st"}
4151
tags := []string{"latest", "main"}
@@ -66,7 +76,7 @@ func TestPackageContainer(t *testing.T) {
6676
Token string `json:"token"`
6777
}
6878

69-
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token"`}
79+
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`}
7080

7181
t.Run("Anonymous", func(t *testing.T) {
7282
defer PrintCurrentTest(t)()
@@ -236,7 +246,8 @@ func TestPackageContainer(t *testing.T) {
236246
assert.Nil(t, pd.SemVer)
237247
assert.Equal(t, image, pd.Package.Name)
238248
assert.Equal(t, tag, pd.Version.Version)
239-
assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged))
249+
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
250+
assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
240251

241252
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
242253
metadata := pd.Metadata.(*container_module.Metadata)
@@ -330,7 +341,8 @@ func TestPackageContainer(t *testing.T) {
330341
assert.Nil(t, pd.SemVer)
331342
assert.Equal(t, image, pd.Package.Name)
332343
assert.Equal(t, untaggedManifestDigest, pd.Version.Version)
333-
assert.False(t, has(pd.Properties, container_module.PropertyManifestTagged))
344+
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
345+
assert.False(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
334346

335347
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
336348

@@ -362,18 +374,10 @@ func TestPackageContainer(t *testing.T) {
362374
assert.Nil(t, pd.SemVer)
363375
assert.Equal(t, image, pd.Package.Name)
364376
assert.Equal(t, multiTag, pd.Version.Version)
365-
assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged))
377+
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
378+
assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
366379

367-
getAllByName := func(l packages_model.PackagePropertyList, name string) []string {
368-
values := make([]string, 0, len(l))
369-
for _, pp := range l {
370-
if pp.Name == name {
371-
values = append(values, pp.Value)
372-
}
373-
}
374-
return values
375-
}
376-
assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.Properties, container_module.PropertyManifestReference))
380+
assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference))
377381

378382
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
379383
metadata := pd.Metadata.(*container_module.Metadata)
@@ -528,4 +532,56 @@ func TestPackageContainer(t *testing.T) {
528532
})
529533
})
530534
}
535+
536+
t.Run("OwnerNameChange", func(t *testing.T) {
537+
defer PrintCurrentTest(t)()
538+
539+
checkCatalog := func(owner string) func(t *testing.T) {
540+
return func(t *testing.T) {
541+
defer PrintCurrentTest(t)()
542+
543+
req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL))
544+
addTokenAuthHeader(req, userToken)
545+
resp := MakeRequest(t, req, http.StatusOK)
546+
547+
type RepositoryList struct {
548+
Repositories []string `json:"repositories"`
549+
}
550+
551+
repoList := &RepositoryList{}
552+
DecodeJSON(t, resp, &repoList)
553+
554+
assert.Len(t, repoList.Repositories, len(images))
555+
names := make([]string, 0, len(images))
556+
for _, image := range images {
557+
names = append(names, strings.ToLower(owner+"/"+image))
558+
}
559+
assert.ElementsMatch(t, names, repoList.Repositories)
560+
}
561+
}
562+
563+
t.Run(fmt.Sprintf("Catalog[%s]", user.LowerName), checkCatalog(user.LowerName))
564+
565+
session := loginUser(t, user.Name)
566+
567+
newOwnerName := "newUsername"
568+
569+
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
570+
"_csrf": GetCSRF(t, session, "/user/settings"),
571+
"name": newOwnerName,
572+
"email": "user2@example.com",
573+
"language": "en-US",
574+
})
575+
session.MakeRequest(t, req, http.StatusSeeOther)
576+
577+
t.Run(fmt.Sprintf("Catalog[%s]", newOwnerName), checkCatalog(newOwnerName))
578+
579+
req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
580+
"_csrf": GetCSRF(t, session, "/user/settings"),
581+
"name": user.Name,
582+
"email": "user2@example.com",
583+
"language": "en-US",
584+
})
585+
session.MakeRequest(t, req, http.StatusSeeOther)
586+
})
531587
}

‎integrations/api_packages_npm_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ func TestPackageNpm(t *testing.T) {
8585
assert.IsType(t, &npm.Metadata{}, pd.Metadata)
8686
assert.Equal(t, packageName, pd.Package.Name)
8787
assert.Equal(t, packageVersion, pd.Version.Version)
88-
assert.Len(t, pd.Properties, 1)
89-
assert.Equal(t, npm.TagProperty, pd.Properties[0].Name)
90-
assert.Equal(t, packageTag, pd.Properties[0].Value)
88+
assert.Len(t, pd.VersionProperties, 1)
89+
assert.Equal(t, npm.TagProperty, pd.VersionProperties[0].Name)
90+
assert.Equal(t, packageTag, pd.VersionProperties[0].Value)
9191

9292
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
9393
assert.NoError(t, err)

‎models/migrations/migrations.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,10 @@ var migrations = []Migration{
396396
NewMigration("Alter hook_task table TEXT fields to LONGTEXT", alterHookTaskTextFieldsToLongText),
397397
// v218 -> v219
398398
NewMigration("Improve Action table indices v2", improveActionTableIndices),
399+
// v219 -> v220
400+
NewMigration("Add sync_on_commit column to push_mirror table", addSyncOnCommitColForPushMirror),
401+
// v220 -> v221
402+
NewMigration("Add container repository property", addContainerRepositoryProperty),
399403
}
400404

401405
// GetCurrentDBVersion returns the current db version

‎models/migrations/v219.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"time"
9+
10+
"code.gitea.io/gitea/models/repo"
11+
"code.gitea.io/gitea/modules/timeutil"
12+
13+
"xorm.io/xorm"
14+
)
15+
16+
func addSyncOnCommitColForPushMirror(x *xorm.Engine) error {
17+
type PushMirror struct {
18+
ID int64 `xorm:"pk autoincr"`
19+
RepoID int64 `xorm:"INDEX"`
20+
Repo *repo.Repository `xorm:"-"`
21+
RemoteName string
22+
23+
SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"`
24+
Interval time.Duration
25+
CreatedUnix timeutil.TimeStamp `xorm:"created"`
26+
LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
27+
LastError string `xorm:"text"`
28+
}
29+
30+
return x.Sync2(new(PushMirror))
31+
}

‎models/migrations/v220.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
packages_model "code.gitea.io/gitea/models/packages"
9+
container_module "code.gitea.io/gitea/modules/packages/container"
10+
11+
"xorm.io/xorm"
12+
"xorm.io/xorm/schemas"
13+
)
14+
15+
func addContainerRepositoryProperty(x *xorm.Engine) error {
16+
switch x.Dialect().URI().DBType {
17+
case schemas.SQLITE:
18+
_, 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)
19+
if err != nil {
20+
return err
21+
}
22+
default:
23+
_, 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)
24+
if err != nil {
25+
return err
26+
}
27+
}
28+
return nil
29+
}

‎models/packages/container/search.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"code.gitea.io/gitea/models/db"
1414
"code.gitea.io/gitea/models/packages"
15+
user_model "code.gitea.io/gitea/models/user"
1516
container_module "code.gitea.io/gitea/modules/packages/container"
1617

1718
"xorm.io/builder"
@@ -210,6 +211,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack
210211
return pvs, count, err
211212
}
212213

214+
// SearchExpiredUploadedBlobs gets all uploaded blobs which are older than specified
213215
func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) {
214216
var cond builder.Cond = builder.Eq{
215217
"package_version.is_internal": true,
@@ -225,3 +227,37 @@ func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([
225227
Where(cond).
226228
Find(&pfs)
227229
}
230+
231+
// GetRepositories gets a sorted list of all repositories
232+
func GetRepositories(ctx context.Context, actor *user_model.User, n int, last string) ([]string, error) {
233+
var cond builder.Cond = builder.Eq{
234+
"package.type": packages.TypeContainer,
235+
"package_property.ref_type": packages.PropertyTypePackage,
236+
"package_property.name": container_module.PropertyRepository,
237+
}
238+
239+
cond = cond.And(builder.Exists(
240+
builder.
241+
Select("package_version.id").
242+
Where(builder.Eq{"package_version.is_internal": false}.And(builder.Expr("package.id = package_version.package_id"))).
243+
From("package_version"),
244+
))
245+
246+
if last != "" {
247+
cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)})
248+
}
249+
250+
cond = cond.And(user_model.BuildCanSeeUserCondition(actor))
251+
252+
sess := db.GetEngine(ctx).
253+
Table("package").
254+
Select("package_property.value").
255+
Join("INNER", "user", "`user`.id = package.owner_id").
256+
Join("INNER", "package_property", "package_property.ref_id = package.id").
257+
Where(cond).
258+
Asc("package_property.value").
259+
Limit(n)
260+
261+
repositories := make([]string, 0, n)
262+
return repositories, sess.Find(&repositories)
263+
}

‎models/packages/descriptor.go

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,16 @@ func (l PackagePropertyList) GetByName(name string) string {
4040

4141
// PackageDescriptor describes a package
4242
type PackageDescriptor struct {
43-
Package *Package
44-
Owner *user_model.User
45-
Repository *repo_model.Repository
46-
Version *PackageVersion
47-
SemVer *version.Version
48-
Creator *user_model.User
49-
Properties PackagePropertyList
50-
Metadata interface{}
51-
Files []*PackageFileDescriptor
43+
Package *Package
44+
Owner *user_model.User
45+
Repository *repo_model.Repository
46+
Version *PackageVersion
47+
SemVer *version.Version
48+
Creator *user_model.User
49+
PackageProperties PackagePropertyList
50+
VersionProperties PackagePropertyList
51+
Metadata interface{}
52+
Files []*PackageFileDescriptor
5253
}
5354

5455
// PackageFileDescriptor describes a package file
@@ -102,6 +103,10 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
102103
return nil, err
103104
}
104105
}
106+
pps, err := GetProperties(ctx, PropertyTypePackage, p.ID)
107+
if err != nil {
108+
return nil, err
109+
}
105110
pvps, err := GetProperties(ctx, PropertyTypeVersion, pv.ID)
106111
if err != nil {
107112
return nil, err
@@ -152,15 +157,16 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
152157
}
153158

154159
return &PackageDescriptor{
155-
Package: p,
156-
Owner: o,
157-
Repository: repository,
158-
Version: pv,
159-
SemVer: semVer,
160-
Creator: creator,
161-
Properties: PackagePropertyList(pvps),
162-
Metadata: metadata,
163-
Files: pfds,
160+
Package: p,
161+
Owner: o,
162+
Repository: repository,
163+
Version: pv,
164+
SemVer: semVer,
165+
Creator: creator,
166+
PackageProperties: PackagePropertyList(pps),
167+
VersionProperties: PackagePropertyList(pvps),
168+
Metadata: metadata,
169+
Files: pfds,
164170
}, nil
165171
}
166172

‎models/packages/package.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
131131
return p, nil
132132
}
133133

134+
// DeletePackageByID deletes a package by id
135+
func DeletePackageByID(ctx context.Context, packageID int64) error {
136+
_, err := db.GetEngine(ctx).ID(packageID).Delete(&Package{})
137+
return err
138+
}
139+
134140
// SetRepositoryLink sets the linked repository
135141
func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error {
136142
_, 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) ([]
192198
Find(&ps)
193199
}
194200

195-
// DeletePackagesIfUnreferenced deletes a package if there are no associated versions
196-
func DeletePackagesIfUnreferenced(ctx context.Context) error {
201+
// FindUnreferencedPackages gets all packages without associated versions
202+
func FindUnreferencedPackages(ctx context.Context) ([]*Package, error) {
197203
in := builder.
198204
Select("package.id").
199205
From("package").
200206
LeftJoin("package_version", "package_version.package_id = package.id").
201207
Where(builder.Expr("package_version.id IS NULL"))
202208

203-
_, err := db.GetEngine(ctx).
209+
ps := make([]*Package, 0, 10)
210+
return ps, db.GetEngine(ctx).
204211
// double select workaround for MySQL
205212
// https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition
206213
Where(builder.In("package.id", builder.Select("id").From(in, "temp"))).
207-
Delete(&Package{})
208-
209-
return err
214+
Find(&ps)
210215
}
211216

212217
// HasOwnerPackages tests if a user/org has packages

‎models/packages/package_property.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ const (
2121
PropertyTypeVersion PropertyType = iota // 0
2222
// PropertyTypeFile means the reference is a package file
2323
PropertyTypeFile // 1
24+
// PropertyTypePackage means the reference is a package
25+
PropertyTypePackage // 2
2426
)
2527

26-
// PackageProperty represents a property of a package version or file
28+
// PackageProperty represents a property of a package, version or file
2729
type PackageProperty struct {
2830
ID int64 `xorm:"pk autoincr"`
2931
RefType PropertyType `xorm:"INDEX NOT NULL"`
@@ -68,3 +70,9 @@ func DeletePropertyByID(ctx context.Context, propertyID int64) error {
6870
_, err := db.GetEngine(ctx).ID(propertyID).Delete(&PackageProperty{})
6971
return err
7072
}
73+
74+
// DeletePropertyByName deletes properties by name
75+
func DeletePropertyByName(ctx context.Context, refType PropertyType, refID int64, name string) error {
76+
_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{})
77+
return err
78+
}

‎models/user/search.go

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,31 +58,7 @@ func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
5858
cond = cond.And(builder.In("visibility", opts.Visible))
5959
}
6060

61-
if opts.Actor != nil {
62-
var exprCond builder.Cond = builder.Expr("org_user.org_id = `user`.id")
63-
64-
// If Admin - they see all users!
65-
if !opts.Actor.IsAdmin {
66-
// Force visibility for privacy
67-
var accessCond builder.Cond
68-
if !opts.Actor.IsRestricted {
69-
accessCond = builder.Or(
70-
builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
71-
builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
72-
} else {
73-
// restricted users only see orgs they are a member of
74-
accessCond = builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID})))
75-
}
76-
// Don't forget about self
77-
accessCond = accessCond.Or(builder.Eq{"id": opts.Actor.ID})
78-
cond = cond.And(accessCond)
79-
}
80-
81-
} else {
82-
// Force visibility for privacy
83-
// Not logged in - only public users
84-
cond = cond.And(builder.In("visibility", structs.VisibleTypePublic))
85-
}
61+
cond = cond.And(BuildCanSeeUserCondition(opts.Actor))
8662

8763
if opts.UID > 0 {
8864
cond = cond.And(builder.Eq{"id": opts.UID})
@@ -170,3 +146,26 @@ func IterateUser(f func(user *User) error) error {
170146
}
171147
}
172148
}
149+
150+
// BuildCanSeeUserCondition creates a condition which can be used to restrict results to users/orgs the actor can see
151+
func BuildCanSeeUserCondition(actor *User) builder.Cond {
152+
if actor != nil {
153+
// If Admin - they see all users!
154+
if !actor.IsAdmin {
155+
// Users can see an organization they are a member of
156+
cond := builder.In("`user`.id", builder.Select("org_id").From("org_user").Where(builder.Eq{"uid": actor.ID}))
157+
if !actor.IsRestricted {
158+
// Not-Restricted users can see public and limited users/organizations
159+
cond = cond.Or(builder.In("`user`.visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
160+
}
161+
// Don't forget about self
162+
return cond.Or(builder.Eq{"`user`.id": actor.ID})
163+
}
164+
165+
return nil
166+
}
167+
168+
// Force visibility for privacy
169+
// Not logged in - only public users
170+
return builder.In("`user`.visibility", structs.VisibleTypePublic)
171+
}

‎modules/packages/container/metadata.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
)
1717

1818
const (
19+
PropertyRepository = "container.repository"
1920
PropertyDigest = "container.digest"
2021
PropertyMediaType = "container.mediatype"
2122
PropertyManifestTagged = "container.manifest.tagged"

‎routers/api/packages/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ func ContainerRoutes() *web.Route {
257257

258258
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
259259
r.Get("/token", container.Authenticate)
260+
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
260261
r.Group("/{username}", func() {
261262
r.Group("/{image}", func() {
262263
r.Group("/blobs/uploads", func() {

‎routers/api/packages/composer/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
8888

8989
for _, pd := range pds {
9090
packageType := ""
91-
for _, pvp := range pd.Properties {
91+
for _, pvp := range pd.VersionProperties {
9292
if pvp.Name == composer_module.TypeProperty {
9393
packageType = pvp.Value
9494
break

‎routers/api/packages/composer/composer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func UploadPackage(ctx *context.Context) {
225225
SemverCompatible: true,
226226
Creator: ctx.Doer,
227227
Metadata: cp.Metadata,
228-
Properties: map[string]string{
228+
VersionProperties: map[string]string{
229229
composer_module.TypeProperty: cp.Type,
230230
},
231231
},

‎routers/api/packages/container/blob.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
2929
contentStore := packages_module.NewContentStore()
3030

3131
err := db.WithTx(func(ctx context.Context) error {
32+
created := true
3233
p := &packages_model.Package{
3334
OwnerID: pi.Owner.ID,
3435
Type: packages_model.TypeContainer,
@@ -37,12 +38,21 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
3738
}
3839
var err error
3940
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
40-
if err != packages_model.ErrDuplicatePackage {
41+
if err == packages_model.ErrDuplicatePackage {
42+
created = false
43+
} else {
4144
log.Error("Error inserting package: %v", err)
4245
return err
4346
}
4447
}
4548

49+
if created {
50+
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(pi.Owner.LowerName+"/"+pi.Name)); err != nil {
51+
log.Error("Error setting package property: %v", err)
52+
return err
53+
}
54+
}
55+
4656
pv := &packages_model.PackageVersion{
4757
PackageID: p.ID,
4858
CreatorID: pi.Owner.ID,

‎routers/api/packages/container/container.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
112112
// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost for anonymous access)
113113
func ReqContainerAccess(ctx *context.Context) {
114114
if ctx.Doer == nil {
115-
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token"`)
115+
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*"`)
116116
apiErrorDefined(ctx, errUnauthorized)
117117
}
118118
}
@@ -151,6 +151,39 @@ func Authenticate(ctx *context.Context) {
151151
})
152152
}
153153

154+
// https://docs.docker.com/registry/spec/api/#listing-repositories
155+
func GetRepositoryList(ctx *context.Context) {
156+
n := ctx.FormInt("n")
157+
if n <= 0 || n > 100 {
158+
n = 100
159+
}
160+
last := ctx.FormTrim("last")
161+
162+
repositories, err := container_model.GetRepositories(ctx, ctx.Doer, n, last)
163+
if err != nil {
164+
apiError(ctx, http.StatusInternalServerError, err)
165+
return
166+
}
167+
168+
type RepositoryList struct {
169+
Repositories []string `json:"repositories"`
170+
}
171+
172+
if len(repositories) == n {
173+
v := url.Values{}
174+
if n > 0 {
175+
v.Add("n", strconv.Itoa(n))
176+
}
177+
v.Add("last", repositories[len(repositories)-1])
178+
179+
ctx.Resp.Header().Set("Link", fmt.Sprintf(`</v2/_catalog?%s>; rel="next"`, v.Encode()))
180+
}
181+
182+
jsonResponse(ctx, http.StatusOK, RepositoryList{
183+
Repositories: repositories,
184+
})
185+
}
186+
154187
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#mounting-a-blob-from-another-repository
155188
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post
156189
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks

‎routers/api/packages/container/manifest.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H
267267
}
268268

269269
func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, metadata *container_module.Metadata) (*packages_model.PackageVersion, error) {
270+
created := true
270271
p := &packages_model.Package{
271272
OwnerID: mci.Owner.ID,
272273
Type: packages_model.TypeContainer,
@@ -275,12 +276,21 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
275276
}
276277
var err error
277278
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
278-
if err != packages_model.ErrDuplicatePackage {
279+
if err == packages_model.ErrDuplicatePackage {
280+
created = false
281+
} else {
279282
log.Error("Error inserting package: %v", err)
280283
return nil, err
281284
}
282285
}
283286

287+
if created {
288+
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(mci.Owner.LowerName+"/"+mci.Image)); err != nil {
289+
log.Error("Error setting package property: %v", err)
290+
return nil, err
291+
}
292+
}
293+
284294
metadata.IsTagged = mci.IsTagged
285295

286296
metadataJSON, err := json.Marshal(metadata)

‎routers/api/packages/npm/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
2525
for _, pd := range pds {
2626
versions[pd.SemVer.String()] = createPackageMetadataVersion(registryURL, pd)
2727

28-
for _, pvp := range pd.Properties {
28+
for _, pvp := range pd.VersionProperties {
2929
if pvp.Name == npm_module.TagProperty {
3030
distTags[pvp.Value] = pd.Version.Version
3131
}

‎routers/web/org/setting.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
user_setting "code.gitea.io/gitea/routers/web/user/setting"
2525
"code.gitea.io/gitea/services/forms"
2626
"code.gitea.io/gitea/services/org"
27+
container_service "code.gitea.io/gitea/services/packages/container"
2728
repo_service "code.gitea.io/gitea/services/repository"
2829
user_service "code.gitea.io/gitea/services/user"
2930
)
@@ -88,6 +89,12 @@ func SettingsPost(ctx *context.Context) {
8889
}
8990
return
9091
}
92+
93+
if err := container_service.UpdateRepositoryNames(ctx, org.AsUser(), form.Name); err != nil {
94+
ctx.ServerError("UpdateRepositoryNames", err)
95+
return
96+
}
97+
9198
// reset ctx.org.OrgLink with new name
9299
ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(form.Name)
93100
log.Trace("Organization name changed: %s -> %s", org.Name, form.Name)

‎routers/web/user/setting/profile.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"code.gitea.io/gitea/modules/web/middleware"
3131
"code.gitea.io/gitea/services/agit"
3232
"code.gitea.io/gitea/services/forms"
33+
container_service "code.gitea.io/gitea/services/packages/container"
3334
user_service "code.gitea.io/gitea/services/user"
3435
)
3536

@@ -90,6 +91,11 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s
9091
return err
9192
}
9293

94+
if err := container_service.UpdateRepositoryNames(ctx, user, newName); err != nil {
95+
ctx.ServerError("UpdateRepositoryNames", err)
96+
return err
97+
}
98+
9399
log.Trace("User name changed: %s -> %s", user.Name, newName)
94100
return nil
95101
}

‎services/packages/container/cleanup.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ package container
66

77
import (
88
"context"
9+
"strings"
910
"time"
1011

1112
packages_model "code.gitea.io/gitea/models/packages"
1213
container_model "code.gitea.io/gitea/models/packages/container"
14+
user_model "code.gitea.io/gitea/models/user"
15+
container_module "code.gitea.io/gitea/modules/packages/container"
1316
"code.gitea.io/gitea/modules/util"
1417
)
1518

@@ -78,3 +81,25 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
7881

7982
return nil
8083
}
84+
85+
// UpdateRepositoryNames updates the repository name property for all packages of the specific owner
86+
func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwnerName string) error {
87+
ps, err := packages_model.GetPackagesByType(ctx, owner.ID, packages_model.TypeContainer)
88+
if err != nil {
89+
return err
90+
}
91+
92+
newOwnerName = strings.ToLower(newOwnerName)
93+
94+
for _, p := range ps {
95+
if err := packages_model.DeletePropertyByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil {
96+
return err
97+
}
98+
99+
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, newOwnerName+"/"+p.LowerName); err != nil {
100+
return err
101+
}
102+
}
103+
104+
return nil
105+
}

‎services/packages/packages.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ type PackageInfo struct {
3232
// PackageCreationInfo describes a package to create
3333
type PackageCreationInfo struct {
3434
PackageInfo
35-
SemverCompatible bool
36-
Creator *user_model.User
37-
Metadata interface{}
38-
Properties map[string]string
35+
SemverCompatible bool
36+
Creator *user_model.User
37+
Metadata interface{}
38+
PackageProperties map[string]string
39+
VersionProperties map[string]string
3940
}
4041

4142
// PackageFileInfo describes a package file
@@ -108,8 +109,9 @@ func createPackageAndAddFile(pvci *PackageCreationInfo, pfci *PackageFileCreatio
108109
}
109110

110111
func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, allowDuplicate bool) (*packages_model.PackageVersion, bool, error) {
111-
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)
112+
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)
112113

114+
packageCreated := true
113115
p := &packages_model.Package{
114116
OwnerID: pvci.Owner.ID,
115117
Type: pvci.PackageType,
@@ -119,18 +121,29 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
119121
}
120122
var err error
121123
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
122-
if err != packages_model.ErrDuplicatePackage {
124+
if err == packages_model.ErrDuplicatePackage {
125+
packageCreated = false
126+
} else {
123127
log.Error("Error inserting package: %v", err)
124128
return nil, false, err
125129
}
126130
}
127131

132+
if packageCreated {
133+
for name, value := range pvci.PackageProperties {
134+
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, name, value); err != nil {
135+
log.Error("Error setting package property: %v", err)
136+
return nil, false, err
137+
}
138+
}
139+
}
140+
128141
metadataJSON, err := json.Marshal(pvci.Metadata)
129142
if err != nil {
130143
return nil, false, err
131144
}
132145

133-
created := true
146+
versionCreated := true
134147
pv := &packages_model.PackageVersion{
135148
PackageID: p.ID,
136149
CreatorID: pvci.Creator.ID,
@@ -140,24 +153,24 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
140153
}
141154
if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
142155
if err == packages_model.ErrDuplicatePackageVersion {
143-
created = false
156+
versionCreated = false
144157
}
145158
if err != packages_model.ErrDuplicatePackageVersion || !allowDuplicate {
146159
log.Error("Error inserting package: %v", err)
147160
return nil, false, err
148161
}
149162
}
150163

151-
if created {
152-
for name, value := range pvci.Properties {
164+
if versionCreated {
165+
for name, value := range pvci.VersionProperties {
153166
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, name, value); err != nil {
154167
log.Error("Error setting package version property: %v", err)
155168
return nil, false, err
156169
}
157170
}
158171
}
159172

160-
return pv, created, nil
173+
return pv, versionCreated, nil
161174
}
162175

163176
// AddFileToExistingPackage adds a file to an existing package. If the package does not exist, ErrPackageNotExist is returned
@@ -348,9 +361,18 @@ func Cleanup(unused context.Context, olderThan time.Duration) error {
348361
return err
349362
}
350363

351-
if err := packages_model.DeletePackagesIfUnreferenced(ctx); err != nil {
364+
ps, err := packages_model.FindUnreferencedPackages(ctx)
365+
if err != nil {
352366
return err
353367
}
368+
for _, p := range ps {
369+
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypePackage, p.ID); err != nil {
370+
return err
371+
}
372+
if err := packages_model.DeletePackageByID(ctx, p.ID); err != nil {
373+
return err
374+
}
375+
}
354376

355377
pbs, err := packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan)
356378
if err != nil {

0 commit comments

Comments
 (0)
Please sign in to comment.