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
					
				| @ -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 New Issue
	
	Block a user