Merge pull request #8349 from steven-zou/feature/tag_retention-performer

Feature/tag retention performer
This commit is contained in:
Steven Zou 2019-07-20 09:37:45 +08:00 committed by GitHub
commit 42a462de47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 315 additions and 90 deletions

View File

@ -17,6 +17,7 @@ package main
import ( import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"github.com/goharbor/harbor/src/pkg/retention"
"os" "os"
"os/signal" "os/signal"
"strconv" "strconv"
@ -157,6 +158,12 @@ func main() {
log.Infof("Because SYNC_REGISTRY set false , no need to sync registry \n") log.Infof("Because SYNC_REGISTRY set false , no need to sync registry \n")
} }
// Initialize retention
log.Info("Initialize retention")
if err := retention.Init(); err != nil {
log.Fatalf("Failed to initialize retention with error: %s", err)
}
log.Info("Init proxy") log.Info("Init proxy")
proxy.Init() proxy.Init()
// go proxy.StartProxy() // go proxy.StartProxy()

27
src/pkg/retention/boot.go Normal file
View File

@ -0,0 +1,27 @@
// 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 retention
import "github.com/goharbor/harbor/src/pkg/retention/dep"
// TODO: Move to api.Init()
// Init the retention components
func Init() error {
// New default retention client
dep.DefaultClient = dep.NewClient()
return nil
}

View File

@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package retention package dep
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
"net/http" "net/http"
"github.com/goharbor/harbor/src/common/http/modifier/auth" "github.com/goharbor/harbor/src/common/http/modifier/auth"
@ -25,10 +26,21 @@ import (
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/pkg/clients/core" "github.com/goharbor/harbor/src/pkg/clients/core"
"github.com/goharbor/harbor/src/pkg/retention/policy"
"github.com/goharbor/harbor/src/pkg/retention/res" "github.com/goharbor/harbor/src/pkg/retention/res"
) )
const (
// ParamRepo ...
ParamRepo = "repository"
// ParamMeta ...
ParamMeta = "liteMeta"
)
// TODO: Move to api.Base
// DefaultClient for the retention
var DefaultClient Client
// Client is designed to access core service to get required infos // Client is designed to access core service to get required infos
type Client interface { type Client interface {
// Get the tag candidates under the repository // Get the tag candidates under the repository
@ -55,16 +67,16 @@ type Client interface {
// Arguments: // Arguments:
// taskID : the ID of task // taskID : the ID of task
// repository *res.Repository : repository info // repository *res.Repository : repository info
// meta *policy.LiteMeta : policy lite metadata // meta *lwp.Metadata : policy lightweight metadata
// //
// Returns: // Returns:
// string : the job ID // string : the job ID
// error : common error if any errors occurred // error : common error if any errors occurred
SubmitTask(taskID int64, repository *res.Repository, meta *policy.LiteMeta) (string, error) SubmitTask(taskID int64, repository *res.Repository, meta *lwp.Metadata) (string, error)
} }
// New basic client // NewClient new a basic client
func New(client ...*http.Client) Client { func NewClient(client ...*http.Client) Client {
var c *http.Client var c *http.Client
if len(client) > 0 { if len(client) > 0 {
c = client[0] c = client[0]
@ -105,7 +117,7 @@ func (bc *basicClient) GetCandidates(repository *res.Repository) ([]*res.Candida
} }
candidates := make([]*res.Candidate, 0) candidates := make([]*res.Candidate, 0)
switch repository.Kind { switch repository.Kind {
case CandidateKindImage: case res.Image:
images, err := bc.coreClient.ListAllImages(repository.Namespace, repository.Name) images, err := bc.coreClient.ListAllImages(repository.Namespace, repository.Name)
if err != nil { if err != nil {
return nil, err return nil, err
@ -116,7 +128,7 @@ func (bc *basicClient) GetCandidates(repository *res.Repository) ([]*res.Candida
labels = append(labels, label.Name) labels = append(labels, label.Name)
} }
candidate := &res.Candidate{ candidate := &res.Candidate{
Kind: CandidateKindImage, Kind: res.Image,
Namespace: repository.Namespace, Namespace: repository.Namespace,
Repository: repository.Name, Repository: repository.Name,
Tag: image.Name, Tag: image.Name,
@ -128,7 +140,7 @@ func (bc *basicClient) GetCandidates(repository *res.Repository) ([]*res.Candida
} }
candidates = append(candidates, candidate) candidates = append(candidates, candidate)
} }
case CandidateKindChart: case res.Chart:
charts, err := bc.coreClient.ListAllCharts(repository.Namespace, repository.Name) charts, err := bc.coreClient.ListAllCharts(repository.Namespace, repository.Name)
if err != nil { if err != nil {
return nil, err return nil, err
@ -139,7 +151,7 @@ func (bc *basicClient) GetCandidates(repository *res.Repository) ([]*res.Candida
labels = append(labels, label.Name) labels = append(labels, label.Name)
} }
candidate := &res.Candidate{ candidate := &res.Candidate{
Kind: CandidateKindChart, Kind: res.Chart,
Namespace: repository.Namespace, Namespace: repository.Namespace,
Repository: repository.Name, Repository: repository.Name,
Tag: chart.Name, Tag: chart.Name,
@ -163,9 +175,9 @@ func (bc *basicClient) Delete(candidate *res.Candidate) error {
return errors.New("candidate is nil") return errors.New("candidate is nil")
} }
switch candidate.Kind { switch candidate.Kind {
case CandidateKindImage: case res.Image:
return bc.coreClient.DeleteImage(candidate.Namespace, candidate.Repository, candidate.Tag) return bc.coreClient.DeleteImage(candidate.Namespace, candidate.Repository, candidate.Tag)
case CandidateKindChart: case res.Chart:
return bc.coreClient.DeleteChart(candidate.Namespace, candidate.Repository, candidate.Tag) return bc.coreClient.DeleteChart(candidate.Namespace, candidate.Repository, candidate.Tag)
default: default:
return fmt.Errorf("unsupported candidate kind: %s", candidate.Kind) return fmt.Errorf("unsupported candidate kind: %s", candidate.Kind)
@ -173,7 +185,7 @@ func (bc *basicClient) Delete(candidate *res.Candidate) error {
} }
// SubmitTask to jobservice // SubmitTask to jobservice
func (bc *basicClient) SubmitTask(taskID int64, repository *res.Repository, meta *policy.LiteMeta) (string, error) { func (bc *basicClient) SubmitTask(taskID int64, repository *res.Repository, meta *lwp.Metadata) (string, error) {
j := &models.JobData{ j := &models.JobData{
Metadata: &models.JobMetadata{ Metadata: &models.JobMetadata{
JobKind: job.KindGeneric, JobKind: job.KindGeneric,

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package retention package dep
import ( import (
"testing" "testing"
@ -80,25 +80,25 @@ func (c *clientTestSuite) TestGetCandidates() {
// image repository // image repository
repository = &res.Repository{} repository = &res.Repository{}
repository.Kind = CandidateKindImage repository.Kind = res.Image
repository.Namespace = "library" repository.Namespace = "library"
repository.Name = "hello-world" repository.Name = "hello-world"
candidates, err = client.GetCandidates(repository) candidates, err = client.GetCandidates(repository)
require.Nil(c.T(), err) require.Nil(c.T(), err)
assert.Equal(c.T(), 1, len(candidates)) assert.Equal(c.T(), 1, len(candidates))
assert.Equal(c.T(), CandidateKindImage, candidates[0].Kind) assert.Equal(c.T(), res.Image, candidates[0].Kind)
assert.Equal(c.T(), "library", candidates[0].Namespace) assert.Equal(c.T(), "library", candidates[0].Namespace)
assert.Equal(c.T(), "hello-world", candidates[0].Repository) assert.Equal(c.T(), "hello-world", candidates[0].Repository)
assert.Equal(c.T(), "latest", candidates[0].Tag) assert.Equal(c.T(), "latest", candidates[0].Tag)
// chart repository // chart repository
repository.Kind = CandidateKindChart repository.Kind = res.Chart
repository.Namespace = "goharbor" repository.Namespace = "goharbor"
repository.Name = "harbor" repository.Name = "harbor"
candidates, err = client.GetCandidates(repository) candidates, err = client.GetCandidates(repository)
require.Nil(c.T(), err) require.Nil(c.T(), err)
assert.Equal(c.T(), 1, len(candidates)) assert.Equal(c.T(), 1, len(candidates))
assert.Equal(c.T(), CandidateKindChart, candidates[0].Kind) assert.Equal(c.T(), res.Chart, candidates[0].Kind)
assert.Equal(c.T(), "goharbor", candidates[0].Namespace) assert.Equal(c.T(), "goharbor", candidates[0].Namespace)
assert.Equal(c.T(), "1.0", candidates[0].Tag) assert.Equal(c.T(), "1.0", candidates[0].Tag)
} }
@ -114,12 +114,12 @@ func (c *clientTestSuite) TestDelete() {
// image // image
candidate = &res.Candidate{} candidate = &res.Candidate{}
candidate.Kind = CandidateKindImage candidate.Kind = res.Image
err = client.Delete(candidate) err = client.Delete(candidate)
require.Nil(c.T(), err) require.Nil(c.T(), err)
// chart // chart
candidate.Kind = CandidateKindChart candidate.Kind = res.Chart
err = client.Delete(candidate) err = client.Delete(candidate)
require.Nil(c.T(), err) require.Nil(c.T(), err)

View File

@ -18,22 +18,17 @@ import (
"encoding/json" "encoding/json"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/jobservice/logger" "github.com/goharbor/harbor/src/jobservice/logger"
"github.com/goharbor/harbor/src/pkg/retention/dep"
"github.com/goharbor/harbor/src/pkg/retention/policy" "github.com/goharbor/harbor/src/pkg/retention/policy"
"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
"github.com/goharbor/harbor/src/pkg/retention/res" "github.com/goharbor/harbor/src/pkg/retention/res"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
const (
// ParamRepo ...
ParamRepo = "repository"
// ParamMeta ...
ParamMeta = "liteMeta"
)
// Job of running retention process // Job of running retention process
type Job struct { type Job struct {
// client used to talk to core // client used to talk to core
client Client client dep.Client
} }
// MaxFails of the job // MaxFails of the job
@ -108,28 +103,28 @@ func logError(logger logger.Interface, err error) error {
} }
func getParamRepo(params job.Parameters) (*res.Repository, error) { func getParamRepo(params job.Parameters) (*res.Repository, error) {
v, ok := params[ParamRepo] v, ok := params[dep.ParamRepo]
if !ok { if !ok {
return nil, errors.Errorf("missing parameter: %s", ParamRepo) return nil, errors.Errorf("missing parameter: %s", dep.ParamRepo)
} }
repo, ok := v.(*res.Repository) repo, ok := v.(*res.Repository)
if !ok { if !ok {
return nil, errors.Errorf("invalid parameter: %s", ParamRepo) return nil, errors.Errorf("invalid parameter: %s", dep.ParamRepo)
} }
return repo, nil return repo, nil
} }
func getParamMeta(params job.Parameters) (*policy.LiteMeta, error) { func getParamMeta(params job.Parameters) (*lwp.Metadata, error) {
v, ok := params[ParamMeta] v, ok := params[dep.ParamMeta]
if !ok { if !ok {
return nil, errors.Errorf("missing parameter: %s", ParamMeta) return nil, errors.Errorf("missing parameter: %s", dep.ParamMeta)
} }
meta, ok := v.(*policy.LiteMeta) meta, ok := v.(*lwp.Metadata)
if !ok { if !ok {
return nil, errors.Errorf("invalid parameter: %s", ParamMeta) return nil, errors.Errorf("invalid parameter: %s", dep.ParamMeta)
} }
return meta, nil return meta, nil

View File

@ -17,10 +17,13 @@ package retention
import ( import (
"fmt" "fmt"
"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/project" "github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/repository" "github.com/goharbor/harbor/src/pkg/repository"
"github.com/goharbor/harbor/src/pkg/retention/dep"
"github.com/goharbor/harbor/src/pkg/retention/policy" "github.com/goharbor/harbor/src/pkg/retention/policy"
"github.com/goharbor/harbor/src/pkg/retention/res" "github.com/goharbor/harbor/src/pkg/retention/res"
"github.com/goharbor/harbor/src/pkg/retention/res/selectors" "github.com/goharbor/harbor/src/pkg/retention/res/selectors"
@ -44,7 +47,7 @@ type Launcher interface {
// NewLauncher returns an instance of Launcher // NewLauncher returns an instance of Launcher
func NewLauncher(projectMgr project.Manager, repositoryMgr repository.Manager, func NewLauncher(projectMgr project.Manager, repositoryMgr repository.Manager,
retentionMgr Manager, retentionClient Client) Launcher { retentionMgr Manager, retentionClient dep.Client) Launcher {
return &launcher{ return &launcher{
projectMgr: projectMgr, projectMgr: projectMgr,
repositoryMgr: repositoryMgr, repositoryMgr: repositoryMgr,
@ -55,14 +58,14 @@ func NewLauncher(projectMgr project.Manager, repositoryMgr repository.Manager,
type launcher struct { type launcher struct {
retentionMgr Manager retentionMgr Manager
retentionClient Client retentionClient dep.Client
projectMgr project.Manager projectMgr project.Manager
repositoryMgr repository.Manager repositoryMgr repository.Manager
} }
type jobData struct { type jobData struct {
repository *res.Repository repository *res.Repository
policy *policy.LiteMeta policy *lwp.Metadata
taskID int64 taskID int64
} }
@ -79,7 +82,7 @@ func (l *launcher) Launch(ply *policy.Metadata, executionID int64) (int64, error
return 0, launcherError(fmt.Errorf("the scope of policy is nil")) return 0, launcherError(fmt.Errorf("the scope of policy is nil"))
} }
repositoryRules := make(map[res.Repository]*policy.LiteMeta, 0) repositoryRules := make(map[res.Repository]*lwp.Metadata, 0)
level := scope.Level level := scope.Level
var projectCandidates []*res.Candidate var projectCandidates []*res.Candidate
var err error var err error
@ -135,17 +138,17 @@ func (l *launcher) Launch(ply *policy.Metadata, executionID int64) (int64, error
} }
for _, repositoryCandidate := range repositoryCandidates { for _, repositoryCandidate := range repositoryCandidates {
repository := res.Repository{ reposit := res.Repository{
Namespace: repositoryCandidate.Namespace, Namespace: repositoryCandidate.Namespace,
Name: repositoryCandidate.Repository, Name: repositoryCandidate.Repository,
Kind: repositoryCandidate.Kind, Kind: repositoryCandidate.Kind,
} }
if repositoryRules[repository] == nil { if repositoryRules[reposit] == nil {
repositoryRules[repository] = &policy.LiteMeta{ repositoryRules[reposit] = &lwp.Metadata{
Algorithm: ply.Algorithm, Algorithm: ply.Algorithm,
} }
} }
repositoryRules[repository].Rules = append(repositoryRules[repository].Rules, &rule) repositoryRules[reposit].Rules = append(repositoryRules[reposit].Rules, &rule)
} }
} }
// no tasks need to be submitted // no tasks need to be submitted
@ -154,8 +157,8 @@ func (l *launcher) Launch(ply *policy.Metadata, executionID int64) (int64, error
} }
// create task records // create task records
jobDatas := []*jobData{} jobDatas := make([]*jobData, 0)
for repository, policy := range repositoryRules { for repo, p := range repositoryRules {
taskID, err := l.retentionMgr.CreateTask(&Task{ taskID, err := l.retentionMgr.CreateTask(&Task{
ExecutionID: executionID, ExecutionID: executionID,
}) })
@ -163,8 +166,8 @@ func (l *launcher) Launch(ply *policy.Metadata, executionID int64) (int64, error
return 0, launcherError(err) return 0, launcherError(err)
} }
jobDatas = append(jobDatas, &jobData{ jobDatas = append(jobDatas, &jobData{
repository: &repository, repository: &repo,
policy: policy, policy: p,
taskID: taskID, taskID: taskID,
}) })
} }
@ -194,10 +197,10 @@ func getProjects(projectMgr project.Manager) ([]*res.Candidate, error) {
return nil, err return nil, err
} }
var candidates []*res.Candidate var candidates []*res.Candidate
for _, project := range projects { for _, pro := range projects {
candidates = append(candidates, &res.Candidate{ candidates = append(candidates, &res.Candidate{
NamespaceID: project.ProjectID, NamespaceID: pro.ProjectID,
Namespace: project.Name, Namespace: pro.Name,
}) })
} }
return candidates, nil return candidates, nil
@ -205,7 +208,7 @@ func getProjects(projectMgr project.Manager) ([]*res.Candidate, error) {
func getRepositories(projectMgr project.Manager, repositoryMgr repository.Manager, projectID int64) ([]*res.Candidate, error) { func getRepositories(projectMgr project.Manager, repositoryMgr repository.Manager, projectID int64) ([]*res.Candidate, error) {
var candidates []*res.Candidate var candidates []*res.Candidate
project, err := projectMgr.Get(projectID) pro, err := projectMgr.Get(projectID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -214,8 +217,8 @@ func getRepositories(projectMgr project.Manager, repositoryMgr repository.Manage
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, repository := range imageRepositories { for _, r := range imageRepositories {
namespace, repo := utils.ParseRepository(repository.Name) namespace, repo := utils.ParseRepository(r.Name)
candidates = append(candidates, &res.Candidate{ candidates = append(candidates, &res.Candidate{
Namespace: namespace, Namespace: namespace,
Repository: repo, Repository: repo,
@ -224,10 +227,10 @@ func getRepositories(projectMgr project.Manager, repositoryMgr repository.Manage
} }
// get chart repositories // get chart repositories
chartRepositories, err := repositoryMgr.ListChartRepositories(projectID) chartRepositories, err := repositoryMgr.ListChartRepositories(projectID)
for _, repository := range chartRepositories { for _, r := range chartRepositories {
candidates = append(candidates, &res.Candidate{ candidates = append(candidates, &res.Candidate{
Namespace: project.Name, Namespace: pro.Name,
Repository: repository.Name, Repository: r.Name,
Kind: "chart", Kind: "chart",
}) })
} }

View File

@ -19,6 +19,9 @@ import (
"strconv" "strconv"
"testing" "testing"
"github.com/goharbor/harbor/src/pkg/retention/dep"
"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
"github.com/goharbor/harbor/src/chartserver" "github.com/goharbor/harbor/src/chartserver"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/project" "github.com/goharbor/harbor/src/pkg/project"
@ -43,18 +46,18 @@ func (f *fakeProjectManager) List(...*models.ProjectQueryParam) ([]*models.Proje
func (f *fakeProjectManager) Get(idOrName interface{}) (*models.Project, error) { func (f *fakeProjectManager) Get(idOrName interface{}) (*models.Project, error) {
id, ok := idOrName.(int64) id, ok := idOrName.(int64)
if ok { if ok {
for _, project := range f.projects { for _, pro := range f.projects {
if project.ProjectID == id { if pro.ProjectID == id {
return project, nil return pro, nil
} }
} }
return nil, nil return nil, nil
} }
name, ok := idOrName.(string) name, ok := idOrName.(string)
if ok { if ok {
for _, project := range f.projects { for _, pro := range f.projects {
if project.Name == name { if pro.Name == name {
return project, nil return pro, nil
} }
} }
return nil, nil return nil, nil
@ -85,7 +88,7 @@ func (f *fakeClient) GetCandidates(repo *res.Repository) ([]*res.Candidate, erro
func (f *fakeClient) Delete(candidate *res.Candidate) error { func (f *fakeClient) Delete(candidate *res.Candidate) error {
return nil return nil
} }
func (f *fakeClient) SubmitTask(taskID int64, repository *res.Repository, meta *policy.LiteMeta) (string, error) { func (f *fakeClient) SubmitTask(taskID int64, repository *res.Repository, meta *lwp.Metadata) (string, error) {
f.id++ f.id++
return strconv.Itoa(f.id), nil return strconv.Itoa(f.id), nil
} }
@ -140,7 +143,7 @@ type launchTestSuite struct {
projectMgr project.Manager projectMgr project.Manager
repositoryMgr repository.Manager repositoryMgr repository.Manager
retentionMgr Manager retentionMgr Manager
retentionClient Client retentionClient dep.Client
} }
func (l *launchTestSuite) SetupTest() { func (l *launchTestSuite) SetupTest() {

View File

@ -14,7 +14,10 @@
package action package action
import "github.com/goharbor/harbor/src/pkg/retention/res" import (
"github.com/goharbor/harbor/src/pkg/retention/dep"
"github.com/goharbor/harbor/src/pkg/retention/res"
)
const ( const (
// Retain artifacts // Retain artifacts
@ -43,17 +46,30 @@ type retainAction struct {
} }
// Perform the action // Perform the action
func (ra *retainAction) Perform(candidates []*res.Candidate) ([]*res.Result, error) { func (ra *retainAction) Perform(candidates []*res.Candidate) (results []*res.Result, err error) {
// TODO: REPLACE SAMPLE CODE WITH REAL IMPLEMENTATION retained := make(map[string]bool)
results := make([]*res.Result, 0)
for _, c := range candidates { for _, c := range candidates {
results = append(results, &res.Result{ retained[c.Hash()] = true
Target: c,
})
} }
return results, nil // start to delete
if len(ra.all) > 0 {
for _, art := range ra.all {
if _, ok := retained[art.Hash()]; !ok {
result := &res.Result{
Target: art,
}
if err := dep.DefaultClient.Delete(art); err != nil {
result.Error = err
}
results = append(results, result)
}
}
}
return
} }
// NewRetainAction is factory method for RetainAction // NewRetainAction is factory method for RetainAction

View File

@ -0,0 +1,112 @@
// 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 action
import (
"github.com/goharbor/harbor/src/pkg/retention/dep"
"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
"github.com/goharbor/harbor/src/pkg/retention/res"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"testing"
"time"
)
// TestPerformerSuite tests the performer related function
type TestPerformerSuite struct {
suite.Suite
oldClient dep.Client
all []*res.Candidate
}
// TestPerformer is the entry of the TestPerformerSuite
func TestPerformer(t *testing.T) {
suite.Run(t, new(TestPerformerSuite))
}
// SetupSuite ...
func (suite *TestPerformerSuite) SetupSuite() {
suite.all = []*res.Candidate{
{
Namespace: "library",
Repository: "harbor",
Kind: "image",
Tag: "latest",
PushedTime: time.Now().Unix(),
Labels: []string{"L1", "L2"},
},
{
Namespace: "library",
Repository: "harbor",
Kind: "image",
Tag: "dev",
PushedTime: time.Now().Unix(),
Labels: []string{"L3"},
},
}
suite.oldClient = dep.DefaultClient
dep.DefaultClient = &fakeRetentionClient{}
}
// TearDownSuite ...
func (suite *TestPerformerSuite) TearDownSuite() {
dep.DefaultClient = suite.oldClient
}
// TestPerform tests Perform action
func (suite *TestPerformerSuite) TestPerform() {
p := &retainAction{
all: suite.all,
}
candidates := []*res.Candidate{
{
Namespace: "library",
Repository: "harbor",
Kind: "image",
Tag: "latest",
PushedTime: time.Now().Unix(),
Labels: []string{"L1", "L2"},
},
}
results, err := p.Perform(candidates)
require.NoError(suite.T(), err)
require.Equal(suite.T(), 1, len(results))
require.NotNil(suite.T(), results[0].Target)
assert.NoError(suite.T(), results[0].Error)
assert.Equal(suite.T(), "dev", results[0].Target.Tag)
}
type fakeRetentionClient struct{}
// GetCandidates ...
func (frc *fakeRetentionClient) GetCandidates(repo *res.Repository) ([]*res.Candidate, error) {
return nil, errors.New("not implemented")
}
// Delete ...
func (frc *fakeRetentionClient) Delete(candidate *res.Candidate) error {
return nil
}
// SubmitTask ...
func (frc *fakeRetentionClient) SubmitTask(taskID int64, repository *res.Repository, meta *lwp.Metadata) (string, error) {
return "", errors.New("not implemented")
}

View File

@ -15,6 +15,14 @@
package or package or
import ( import (
"errors"
"testing"
"time"
"github.com/goharbor/harbor/src/pkg/retention/dep"
"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
"github.com/goharbor/harbor/src/pkg/retention/policy/action" "github.com/goharbor/harbor/src/pkg/retention/policy/action"
"github.com/goharbor/harbor/src/pkg/retention/policy/alg" "github.com/goharbor/harbor/src/pkg/retention/policy/alg"
"github.com/goharbor/harbor/src/pkg/retention/policy/rule" "github.com/goharbor/harbor/src/pkg/retention/policy/rule"
@ -26,8 +34,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"testing"
"time"
) )
// ProcessorTestSuite is suite for testing processor // ProcessorTestSuite is suite for testing processor
@ -36,6 +42,8 @@ type ProcessorTestSuite struct {
p alg.Processor p alg.Processor
all []*res.Candidate all []*res.Candidate
oldClient dep.Client
} }
// TestProcessor is entrance for ProcessorTestSuite // TestProcessor is entrance for ProcessorTestSuite
@ -93,10 +101,15 @@ func (suite *ProcessorTestSuite) SetupSuite() {
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
suite.p = p suite.p = p
suite.oldClient = dep.DefaultClient
dep.DefaultClient = &fakeRetentionClient{}
} }
// TearDownSuite ... // TearDownSuite ...
func (suite *ProcessorTestSuite) TearDownSuite() {} func (suite *ProcessorTestSuite) TearDownSuite() {
dep.DefaultClient = suite.oldClient
}
// TestProcess tests process method // TestProcess tests process method
func (suite *ProcessorTestSuite) TestProcess() { func (suite *ProcessorTestSuite) TestProcess() {
@ -113,3 +126,20 @@ func (suite *ProcessorTestSuite) TestProcess() {
return true return true
}, "no errors in the returned result list") }, "no errors in the returned result list")
} }
type fakeRetentionClient struct{}
// GetCandidates ...
func (frc *fakeRetentionClient) GetCandidates(repo *res.Repository) ([]*res.Candidate, error) {
return nil, errors.New("not implemented")
}
// Delete ...
func (frc *fakeRetentionClient) Delete(candidate *res.Candidate) error {
return nil
}
// SubmitTask ...
func (frc *fakeRetentionClient) SubmitTask(taskID int64, repository *res.Repository, meta *lwp.Metadata) (string, error) {
return "", errors.New("not implemented")
}

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"github.com/goharbor/harbor/src/pkg/retention/policy/action" "github.com/goharbor/harbor/src/pkg/retention/policy/action"
"github.com/goharbor/harbor/src/pkg/retention/policy/alg" "github.com/goharbor/harbor/src/pkg/retention/policy/alg"
"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
"github.com/goharbor/harbor/src/pkg/retention/policy/rule" "github.com/goharbor/harbor/src/pkg/retention/policy/rule"
"github.com/goharbor/harbor/src/pkg/retention/res" "github.com/goharbor/harbor/src/pkg/retention/res"
"github.com/goharbor/harbor/src/pkg/retention/res/selectors" "github.com/goharbor/harbor/src/pkg/retention/res/selectors"
@ -29,12 +30,12 @@ type Builder interface {
// Builds runnable processor // Builds runnable processor
// //
// Arguments: // Arguments:
// policy *LiteMeta : the simple metadata of retention policy // policy *Metadata : the simple metadata of retention policy
// //
// Returns: // Returns:
// Processor : a processor implementation to process the candidates // Processor : a processor implementation to process the candidates
// error : common error object if any errors occurred // error : common error object if any errors occurred
Build(policy *LiteMeta) (alg.Processor, error) Build(policy *lwp.Metadata) (alg.Processor, error)
} }
// NewBuilder news a basic builder // NewBuilder news a basic builder
@ -50,7 +51,7 @@ type basicBuilder struct {
} }
// Build policy processor from the raw policy // Build policy processor from the raw policy
func (bb *basicBuilder) Build(policy *LiteMeta) (alg.Processor, error) { func (bb *basicBuilder) Build(policy *lwp.Metadata) (alg.Processor, error) {
if policy == nil { if policy == nil {
return nil, errors.New("nil policy to build processor") return nil, errors.New("nil policy to build processor")
} }

View File

@ -0,0 +1,29 @@
// 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 lwp = lightweight policy
package lwp
import "github.com/goharbor/harbor/src/pkg/retention/policy/rule"
// Metadata contains partial metadata of policy
// It's a lightweight version of policy.Metadata
type Metadata struct {
// Algorithm applied to the rules
// "OR" / "AND"
Algorithm string `json:"algorithm"`
// Rule collection
Rules []*rule.Metadata `json:"rules"`
}

View File

@ -70,13 +70,3 @@ type Scope struct {
// 0 for 'system', project ID for 'project' and repo ID for 'repository' // 0 for 'system', project ID for 'project' and repo ID for 'repository'
Reference int64 `json:"ref"` Reference int64 `json:"ref"`
} }
// LiteMeta contains partial metadata of policy
type LiteMeta struct {
// Algorithm applied to the rules
// "OR" / "AND"
Algorithm string `json:"algorithm"`
// Rule collection
Rules []*rule.Metadata `json:"rules"`
}