harbor/src/controller/repository/controller.go
chlins a82f1ba63d feat(repository): introduce cache manager for repository (#16741)
Implement cache layer for resource repository and migrate repository.Mgr
to pkg.RepositoryMgr.

Signed-off-by: chlins <chenyuzh@vmware.com>
2022-05-12 10:14:35 +08:00

189 lines
6.0 KiB
Go

// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repository
import (
"context"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg"
art "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/repository"
"github.com/goharbor/harbor/src/pkg/repository/model"
)
var (
// Ctl is a global repository controller instance
Ctl = NewController()
)
// Controller defines the operations related with repositories
type Controller interface {
// Ensure the repository specified by the "name" exists, creates it if it doesn't exist.
// The "name" should contain the namespace part. The "created" will be set as true
// when the repository is created
Ensure(ctx context.Context, name string) (created bool, id int64, err error)
// Count returns the total count of repositories according to the query
Count(ctx context.Context, query *q.Query) (total int64, err error)
// List repositories according to the query
List(ctx context.Context, query *q.Query) (repositories []*model.RepoRecord, err error)
// Get the repository specified by ID
Get(ctx context.Context, id int64) (repository *model.RepoRecord, err error)
// GetByName gets the repository specified by name
GetByName(ctx context.Context, name string) (repository *model.RepoRecord, err error)
// Delete the repository specified by ID
Delete(ctx context.Context, id int64) (err error)
// Update the repository. Specify the properties or all properties will be updated
Update(ctx context.Context, repository *model.RepoRecord, properties ...string) (err error)
// AddPullCount increase pull count for the specified repository
AddPullCount(ctx context.Context, id int64, count uint64) error
}
// NewController creates an instance of the default repository controller
func NewController() Controller {
return &controller{
proMgr: pkg.ProjectMgr,
repoMgr: pkg.RepositoryMgr,
artMgr: pkg.ArtifactMgr,
artCtl: artifact.Ctl,
}
}
type controller struct {
proMgr project.Manager
repoMgr repository.Manager
artMgr art.Manager
artCtl artifact.Controller
}
func (c *controller) Ensure(ctx context.Context, name string) (bool, int64, error) {
// the repository already exists, return directly
repository, err := c.repoMgr.GetByName(ctx, name)
if err == nil {
return false, repository.RepositoryID, nil
}
// got other error when get the repository, return the error
if !errors.IsErr(err, errors.NotFoundCode) {
return false, 0, err
}
// the repository doesn't exist, create it first
projectName, _ := utils.ParseRepository(name)
project, err := c.proMgr.Get(ctx, projectName)
if err != nil {
return false, 0, err
}
var (
created bool
id int64
)
// use orm.WithTransaction here to avoid the issue:
// https://www.postgresql.org/message-id/002e01c04da9%24a8f95c20%2425efe6c1%40lasting.ro
if err = orm.WithTransaction(func(ctx context.Context) error {
id, err = c.repoMgr.Create(ctx, &model.RepoRecord{
ProjectID: project.ProjectID,
Name: name,
})
if err != nil {
return err
}
created = true
return nil
})(orm.SetTransactionOpNameToContext(ctx, "tx-repository-ensure")); err != nil {
// isn't conflict error, return directly
if !errors.IsConflictErr(err) {
return false, 0, err
}
// if got conflict error, try to get again
repository, err = c.repoMgr.GetByName(ctx, name)
if err != nil {
return false, 0, err
}
id = repository.RepositoryID
}
return created, id, nil
}
func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) {
return c.repoMgr.Count(ctx, query)
}
func (c *controller) List(ctx context.Context, query *q.Query) ([]*model.RepoRecord, error) {
return c.repoMgr.List(ctx, query)
}
func (c *controller) Get(ctx context.Context, id int64) (*model.RepoRecord, error) {
return c.repoMgr.Get(ctx, id)
}
func (c *controller) GetByName(ctx context.Context, name string) (*model.RepoRecord, error) {
return c.repoMgr.GetByName(ctx, name)
}
func (c *controller) Delete(ctx context.Context, id int64) error {
candidates, err := c.artCtl.List(ctx, &q.Query{
Keywords: map[string]interface{}{
"RepositoryID": id,
},
}, nil)
if err != nil {
return err
}
for len(candidates) > 0 {
artifacts := candidates
candidates = nil
for _, artifact := range artifacts {
parents, err := c.artMgr.ListReferences(ctx, &q.Query{
Keywords: map[string]interface{}{
"ChildID": artifact.ID,
},
})
if err != nil {
return err
}
if len(parents) > 0 {
// if the artifact is referenced by others, put it into the artifacts
// list again, and try to delete it in the next loop
log.Debugf("the artifact %d is referenced by others, put it into the backlog and try to delete it in the next loop",
artifact.ID)
candidates = append(candidates, artifact)
continue
}
if err = c.artCtl.Delete(ctx, artifact.ID); err != nil {
return err
}
log.Debugf("the artifact %d is deleted", artifact.ID)
}
}
return c.repoMgr.Delete(ctx, id)
}
func (c *controller) Update(ctx context.Context, repository *model.RepoRecord, properties ...string) error {
return c.repoMgr.Update(ctx, repository, properties...)
}
func (c *controller) AddPullCount(ctx context.Context, id int64, count uint64) error {
return c.repoMgr.AddPullCount(ctx, id, count)
}