mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-01 04:21:36 +01:00
Merge pull request #8349 from steven-zou/feature/tag_retention-performer
Feature/tag retention performer
This commit is contained in:
commit
42a462de47
@ -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
27
src/pkg/retention/boot.go
Normal 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
|
||||||
|
}
|
@ -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,
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
@ -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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
|
112
src/pkg/retention/policy/action/performer_test.go
Normal file
112
src/pkg/retention/policy/action/performer_test.go
Normal 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")
|
||||||
|
}
|
@ -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")
|
||||||
|
}
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
29
src/pkg/retention/policy/lwp/models.go
Normal file
29
src/pkg/retention/policy/lwp/models.go
Normal 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"`
|
||||||
|
}
|
@ -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"`
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user