mirror of
https://github.com/goharbor/harbor.git
synced 2024-10-06 01:07:55 +02:00
Merge pull request #8445 from steven-zou/fix/tag_retention
refactor index registering processes
This commit is contained in:
commit
4bf7f7b3e4
@ -3,14 +3,15 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/core/filter"
|
||||
"github.com/goharbor/harbor/src/core/promgr"
|
||||
"github.com/goharbor/harbor/src/pkg/retention"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/q"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// RetentionAPI ...
|
||||
@ -65,7 +66,7 @@ func (r *RetentionAPI) GetMetadatas() {
|
||||
]
|
||||
},
|
||||
{
|
||||
"rule_template": "latestK",
|
||||
"rule_template": "latestPushedK",
|
||||
"display_text": "the most recently pushed # images",
|
||||
"action": "retain",
|
||||
"params": [
|
||||
@ -77,7 +78,7 @@ func (r *RetentionAPI) GetMetadatas() {
|
||||
]
|
||||
},
|
||||
{
|
||||
"rule_template": "latestPulledK",
|
||||
"rule_template": "latestPulledN",
|
||||
"display_text": "the most recently pulled # images",
|
||||
"action": "retain",
|
||||
"params": [
|
||||
|
@ -147,7 +147,7 @@ func (r *DefaultAPIController) UpdateRetention(p *policy.Metadata) error {
|
||||
}
|
||||
}
|
||||
if needUn {
|
||||
err = r.scheduler.UnSchedule((p0.Trigger.References[policy.TriggerReferencesJobid].(int64)))
|
||||
err = r.scheduler.UnSchedule(p0.Trigger.References[policy.TriggerReferencesJobid].(int64))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ func (pj *Job) Run(ctx job.Context, params job.Parameters) error {
|
||||
|
||||
// Log stage: start
|
||||
repoPath := fmt.Sprintf("%s/%s", repo.Namespace, repo.Name)
|
||||
myLogger.Infof("Run retention process.\n Repository: %s \n Rule Algorithm: %s \n Dry Run: %f", repoPath, liteMeta.Algorithm, isDryRun)
|
||||
myLogger.Infof("Run retention process.\n Repository: %s \n Rule Algorithm: %s \n Dry Run: %v", repoPath, liteMeta.Algorithm, isDryRun)
|
||||
|
||||
// Stop check point 1:
|
||||
if isStopped(ctx) {
|
||||
@ -125,13 +125,13 @@ func logResults(logger logger.Interface, all []*res.Candidate, results []*res.Re
|
||||
op := func(art *res.Candidate) string {
|
||||
if e, exists := hash[art.Hash()]; exists {
|
||||
if e != nil {
|
||||
return "Err"
|
||||
return "ERR"
|
||||
}
|
||||
|
||||
return "X"
|
||||
return "DEL"
|
||||
}
|
||||
|
||||
return "√"
|
||||
return "RETAIN"
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
@ -158,7 +158,7 @@ func logResults(logger logger.Interface, all []*res.Candidate, results []*res.Re
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
|
||||
logger.Infof("%s", buf.String())
|
||||
logger.Infof("\n%s", buf.String())
|
||||
|
||||
// log all the concrete errors if have
|
||||
for _, r := range results {
|
||||
@ -214,15 +214,14 @@ func getParamRepo(params job.Parameters) (*res.Repository, error) {
|
||||
return nil, errors.Errorf("missing parameter: %s", ParamRepo)
|
||||
}
|
||||
|
||||
fmt.Printf("%T", v)
|
||||
repoMap, ok := v.(map[string]interface{})
|
||||
repoJSON, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("invalid parameter: %s", ParamRepo)
|
||||
}
|
||||
|
||||
repo := &res.Repository{}
|
||||
if err := repo.FromMap(repoMap); err != nil {
|
||||
return nil, fmt.Errorf("failed to convert map to repository: %v", err)
|
||||
if err := repo.FromJSON(repoJSON); err != nil {
|
||||
return nil, errors.Wrap(err, "parse repository from JSON")
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
@ -234,14 +233,14 @@ func getParamMeta(params job.Parameters) (*lwp.Metadata, error) {
|
||||
return nil, errors.Errorf("missing parameter: %s", ParamMeta)
|
||||
}
|
||||
|
||||
metaMap, ok := v.(map[string]interface{})
|
||||
metaJSON, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("invalid parameter: %s", ParamMeta)
|
||||
}
|
||||
|
||||
meta := &lwp.Metadata{}
|
||||
if err := meta.FromMap(metaMap); err != nil {
|
||||
return nil, fmt.Errorf("failed to convert map to metadata: %v", err)
|
||||
if err := meta.FromJSON(metaJSON); err != nil {
|
||||
return nil, errors.Wrap(err, "parse retention policy from JSON")
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
|
@ -16,7 +16,6 @@ package retention
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
@ -27,13 +26,10 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/retention/dep"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy"
|
||||
"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/or"
|
||||
"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/latestk"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestps"
|
||||
"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/doublestar"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/label"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -54,18 +50,6 @@ func TestJob(t *testing.T) {
|
||||
|
||||
// SetupSuite ...
|
||||
func (suite *JobTestSuite) SetupSuite() {
|
||||
alg.Register(alg.AlgorithmOR, or.New)
|
||||
selectors.Register(doublestar.Kind, []string{
|
||||
doublestar.Matches,
|
||||
doublestar.Excludes,
|
||||
doublestar.RepoMatches,
|
||||
doublestar.RepoExcludes,
|
||||
doublestar.NSMatches,
|
||||
doublestar.NSExcludes,
|
||||
}, doublestar.New)
|
||||
selectors.Register(label.Kind, []string{label.With, label.Without}, label.New)
|
||||
action.Register(action.Retain, action.NewRetainAction)
|
||||
|
||||
suite.oldClient = dep.DefaultClient
|
||||
dep.DefaultClient = &fakeRetentionClient{}
|
||||
}
|
||||
@ -83,9 +67,9 @@ func (suite *JobTestSuite) TestRunSuccess() {
|
||||
Name: "harbor",
|
||||
Kind: res.Image,
|
||||
}
|
||||
repoMap, err := toMap(repository)
|
||||
repoJSON, err := repository.ToJSON()
|
||||
require.Nil(suite.T(), err)
|
||||
params[ParamRepo] = repoMap
|
||||
params[ParamRepo] = repoJSON
|
||||
|
||||
scopeSelectors := make(map[string][]*rule.Selector)
|
||||
scopeSelectors["project"] = []*rule.Selector{{
|
||||
@ -95,7 +79,7 @@ func (suite *JobTestSuite) TestRunSuccess() {
|
||||
}}
|
||||
|
||||
ruleParams := make(rule.Parameters)
|
||||
ruleParams[latestk.ParameterK] = 10
|
||||
ruleParams[latestps.ParameterK] = 10
|
||||
|
||||
meta := &lwp.Metadata{
|
||||
Algorithm: policy.AlgorithmOR,
|
||||
@ -104,7 +88,7 @@ func (suite *JobTestSuite) TestRunSuccess() {
|
||||
ID: 1,
|
||||
Priority: 999,
|
||||
Action: action.Retain,
|
||||
Template: latestk.TemplateID,
|
||||
Template: latestps.TemplateID,
|
||||
Parameters: ruleParams,
|
||||
TagSelectors: []*rule.Selector{{
|
||||
Kind: label.Kind,
|
||||
@ -119,9 +103,9 @@ func (suite *JobTestSuite) TestRunSuccess() {
|
||||
},
|
||||
},
|
||||
}
|
||||
metaMap, err := toMap(meta)
|
||||
metaJSON, err := meta.ToJSON()
|
||||
require.Nil(suite.T(), err)
|
||||
params[ParamMeta] = metaMap
|
||||
params[ParamMeta] = metaJSON
|
||||
|
||||
j := &Job{}
|
||||
err = j.Validate(params)
|
||||
@ -239,15 +223,3 @@ func (c *fakeJobContext) GetLogger() logger.Interface {
|
||||
func (c *fakeJobContext) Tracker() job.Tracker {
|
||||
return nil
|
||||
}
|
||||
|
||||
func toMap(v interface{}) (map[string]interface{}, error) {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := map[string]interface{}{}
|
||||
if err = json.Unmarshal(data, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/index"
|
||||
|
||||
cjob "github.com/goharbor/harbor/src/common/job"
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
cmodels "github.com/goharbor/harbor/src/common/models"
|
||||
@ -31,7 +33,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/q"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -126,7 +127,7 @@ func (l *launcher) Launch(ply *policy.Metadata, executionID int64, isDryRun bool
|
||||
case "system":
|
||||
// filter projects according to the project selectors
|
||||
for _, projectSelector := range rule.ScopeSelectors["project"] {
|
||||
selector, err := selectors.Get(projectSelector.Kind, projectSelector.Decoration,
|
||||
selector, err := index.Get(projectSelector.Kind, projectSelector.Decoration,
|
||||
projectSelector.Pattern)
|
||||
if err != nil {
|
||||
return 0, launcherError(err)
|
||||
@ -153,7 +154,7 @@ func (l *launcher) Launch(ply *policy.Metadata, executionID int64, isDryRun bool
|
||||
}
|
||||
// filter repositories according to the repository selectors
|
||||
for _, repositorySelector := range rule.ScopeSelectors["repository"] {
|
||||
selector, err := selectors.Get(repositorySelector.Kind, repositorySelector.Decoration,
|
||||
selector, err := index.Get(repositorySelector.Kind, repositorySelector.Decoration,
|
||||
repositorySelector.Pattern)
|
||||
if err != nil {
|
||||
return 0, launcherError(err)
|
||||
@ -205,22 +206,38 @@ func (l *launcher) Launch(ply *policy.Metadata, executionID int64, isDryRun bool
|
||||
allFailed := true
|
||||
for _, jobData := range jobDatas {
|
||||
j := &models.JobData{
|
||||
Name: job.Retention,
|
||||
Metadata: &models.JobMetadata{
|
||||
JobKind: job.KindGeneric,
|
||||
},
|
||||
StatusHook: fmt.Sprintf("%s/service/notifications/jobs/retention/task/%d", l.internalCoreURL, jobData.taskID),
|
||||
Parameters: make(map[string]interface{}, 3),
|
||||
}
|
||||
j.Name = job.Retention
|
||||
j.Parameters = map[string]interface{}{
|
||||
ParamRepo: jobData.repository,
|
||||
ParamMeta: jobData.policy,
|
||||
ParamDryRun: isDryRun,
|
||||
|
||||
var (
|
||||
repoJSON, policyJSON string
|
||||
)
|
||||
// Set dry run
|
||||
j.Parameters[ParamDryRun] = isDryRun
|
||||
// Set repository
|
||||
if repoJSON, err = jobData.repository.ToJSON(); err == nil {
|
||||
j.Parameters[ParamRepo] = repoJSON
|
||||
// Set retention policy
|
||||
if policyJSON, err = jobData.policy.ToJSON(); err == nil {
|
||||
j.Parameters[ParamMeta] = policyJSON
|
||||
}
|
||||
}
|
||||
|
||||
var jobID string
|
||||
if err == nil {
|
||||
// Submit job
|
||||
jobID, err = l.jobserviceClient.SubmitJob(j)
|
||||
}
|
||||
|
||||
task := &Task{
|
||||
ID: jobData.taskID,
|
||||
}
|
||||
props := []string{"Status"}
|
||||
jobID, err := l.jobserviceClient.SubmitJob(j)
|
||||
if err != nil {
|
||||
log.Error(launcherError(fmt.Errorf("failed to submit task %d: %v", jobData.taskID, err)))
|
||||
task.Status = cmodels.JobError
|
||||
|
@ -187,7 +187,7 @@ func (d *DefaultManager) ListTasks(query ...*q.TaskQuery) ([]*Task, error) {
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
tasks := []*Task{}
|
||||
tasks := make([]*Task, 0)
|
||||
for _, t := range ts {
|
||||
tasks = append(tasks, &Task{
|
||||
ID: t.ID,
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/job"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||
|
@ -12,19 +12,26 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package action
|
||||
package index
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/action"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// index for keeping the mapping action and its performer
|
||||
var index sync.Map
|
||||
|
||||
func init() {
|
||||
// Register retain action
|
||||
Register(action.Retain, action.NewRetainAction)
|
||||
}
|
||||
|
||||
// Register the performer with the corresponding action
|
||||
func Register(action string, factory PerformerFactory) {
|
||||
func Register(action string, factory action.PerformerFactory) {
|
||||
if len(action) == 0 || factory == nil {
|
||||
// do nothing
|
||||
return
|
||||
@ -34,19 +41,19 @@ func Register(action string, factory PerformerFactory) {
|
||||
}
|
||||
|
||||
// Get performer with the provided action
|
||||
func Get(action string, params interface{}, isDryRun bool) (Performer, error) {
|
||||
if len(action) == 0 {
|
||||
func Get(act string, params interface{}, isDryRun bool) (action.Performer, error) {
|
||||
if len(act) == 0 {
|
||||
return nil, errors.New("empty action")
|
||||
}
|
||||
|
||||
v, ok := index.Load(action)
|
||||
v, ok := index.Load(act)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("action %s is not registered", action)
|
||||
return nil, errors.Errorf("action %s is not registered", act)
|
||||
}
|
||||
|
||||
factory, ok := v.(PerformerFactory)
|
||||
factory, ok := v.(action.PerformerFactory)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("invalid action performer registered for action %s", action)
|
||||
return nil, errors.Errorf("invalid action performer registered for action %s", act)
|
||||
}
|
||||
|
||||
return factory(params, isDryRun), nil
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package action
|
||||
package index
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/action"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@ -88,7 +89,7 @@ func (p *fakePerformer) Perform(candidates []*res.Candidate) (results []*res.Res
|
||||
return
|
||||
}
|
||||
|
||||
func newFakePerformer(params interface{}, isDryRun bool) Performer {
|
||||
func newFakePerformer(params interface{}, isDryRun bool) action.Performer {
|
||||
return &fakePerformer{
|
||||
parameters: params,
|
||||
isDryRun: isDryRun,
|
@ -21,7 +21,7 @@ import (
|
||||
|
||||
const (
|
||||
// Retain artifacts
|
||||
Retain = "Retain"
|
||||
Retain = "retain"
|
||||
)
|
||||
|
||||
// Performer performs the related actions targeting the candidates
|
||||
@ -92,8 +92,3 @@ func NewRetainAction(params interface{}, isDryRun bool) Performer {
|
||||
isDryRun: isDryRun,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register itself
|
||||
Register(Retain, NewRetainAction)
|
||||
}
|
||||
|
@ -12,11 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package alg
|
||||
package index
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"sync"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/alg"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/alg/or"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -27,21 +32,26 @@ const (
|
||||
// index for keeping the mapping between algorithm and its processor
|
||||
var index sync.Map
|
||||
|
||||
func init() {
|
||||
// Register or
|
||||
Register(AlgorithmOR, or.New)
|
||||
}
|
||||
|
||||
// Register processor with the algorithm
|
||||
func Register(algorithm string, processor Factory) {
|
||||
func Register(algorithm string, processor alg.Factory) {
|
||||
if len(algorithm) > 0 && processor != nil {
|
||||
index.Store(algorithm, processor)
|
||||
}
|
||||
}
|
||||
|
||||
// Get Processor
|
||||
func Get(algorithm string, params []*Parameter) (Processor, error) {
|
||||
func Get(algorithm string, params []*alg.Parameter) (alg.Processor, error) {
|
||||
v, ok := index.Load(algorithm)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no processor registered with algorithm: %s", algorithm)
|
||||
}
|
||||
|
||||
if fac, ok := v.(Factory); ok {
|
||||
if fac, ok := v.(alg.Factory); ok {
|
||||
return fac(params), nil
|
||||
}
|
||||
|
@ -15,13 +15,14 @@
|
||||
package or
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"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/rule"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/pkg/errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// processor to handle the rules with OR mapping ways
|
||||
@ -153,12 +154,10 @@ func (p *processor) Process(artifacts []*res.Candidate) ([]*res.Result, error) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(processed) > 0 {
|
||||
// Pass to the outside
|
||||
resChan <- &chanItem{
|
||||
action: evaluator.Action(),
|
||||
processed: processed,
|
||||
}
|
||||
// Pass to the outside
|
||||
resChan <- &chanItem{
|
||||
action: evaluator.Action(),
|
||||
processed: processed,
|
||||
}
|
||||
}(evaluator, selectors)
|
||||
}
|
||||
@ -204,10 +203,6 @@ func (p *processor) Process(artifacts []*res.Candidate) ([]*res.Result, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
alg.Register(alg.AlgorithmOR, New)
|
||||
}
|
||||
|
||||
type cHash map[string]*res.Candidate
|
||||
|
||||
func (ch cHash) toList() []*res.Candidate {
|
||||
|
@ -19,18 +19,21 @@ import (
|
||||
"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/rule/always"
|
||||
|
||||
"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/rule"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/lastx"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestk"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestps"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/doublestar"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/label"
|
||||
|
||||
"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/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -40,7 +43,6 @@ import (
|
||||
type ProcessorTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
p alg.Processor
|
||||
all []*res.Candidate
|
||||
|
||||
oldClient dep.Client
|
||||
@ -72,10 +74,21 @@ func (suite *ProcessorTestSuite) SetupSuite() {
|
||||
},
|
||||
}
|
||||
|
||||
params := make([]*alg.Parameter, 0)
|
||||
suite.oldClient = dep.DefaultClient
|
||||
dep.DefaultClient = &fakeRetentionClient{}
|
||||
}
|
||||
|
||||
// TearDownSuite ...
|
||||
func (suite *ProcessorTestSuite) TearDownSuite() {
|
||||
dep.DefaultClient = suite.oldClient
|
||||
}
|
||||
|
||||
// TestProcess tests process method
|
||||
func (suite *ProcessorTestSuite) TestProcess() {
|
||||
|
||||
perf := action.NewRetainAction(suite.all, false)
|
||||
|
||||
params := make([]*alg.Parameter, 0)
|
||||
lastxParams := make(map[string]rule.Parameter)
|
||||
lastxParams[lastx.ParameterX] = 10
|
||||
params = append(params, &alg.Parameter{
|
||||
@ -88,32 +101,18 @@ func (suite *ProcessorTestSuite) SetupSuite() {
|
||||
})
|
||||
|
||||
latestKParams := make(map[string]rule.Parameter)
|
||||
latestKParams[latestk.ParameterK] = 10
|
||||
latestKParams[latestps.ParameterK] = 10
|
||||
params = append(params, &alg.Parameter{
|
||||
Evaluator: latestk.New(latestKParams),
|
||||
Evaluator: latestps.New(latestKParams),
|
||||
Selectors: []res.Selector{
|
||||
label.New(label.With, "L3"),
|
||||
},
|
||||
Performer: perf,
|
||||
})
|
||||
|
||||
p, err := alg.Get(alg.AlgorithmOR, params)
|
||||
require.NoError(suite.T(), err)
|
||||
p := New(params)
|
||||
|
||||
suite.p = p
|
||||
|
||||
suite.oldClient = dep.DefaultClient
|
||||
dep.DefaultClient = &fakeRetentionClient{}
|
||||
}
|
||||
|
||||
// TearDownSuite ...
|
||||
func (suite *ProcessorTestSuite) TearDownSuite() {
|
||||
dep.DefaultClient = suite.oldClient
|
||||
}
|
||||
|
||||
// TestProcess tests process method
|
||||
func (suite *ProcessorTestSuite) TestProcess() {
|
||||
results, err := suite.p.Process(suite.all)
|
||||
results, err := p.Process(suite.all)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 1, len(results))
|
||||
assert.Condition(suite.T(), func() bool {
|
||||
@ -127,6 +126,43 @@ func (suite *ProcessorTestSuite) TestProcess() {
|
||||
}, "no errors in the returned result list")
|
||||
}
|
||||
|
||||
// TestProcess2 ...
|
||||
func (suite *ProcessorTestSuite) TestProcess2() {
|
||||
perf := action.NewRetainAction(suite.all, false)
|
||||
|
||||
params := make([]*alg.Parameter, 0)
|
||||
alwaysParams := make(map[string]rule.Parameter)
|
||||
params = append(params, &alg.Parameter{
|
||||
Evaluator: always.New(alwaysParams),
|
||||
Selectors: []res.Selector{
|
||||
doublestar.New(doublestar.Matches, "latest"),
|
||||
label.New(label.With, ""),
|
||||
},
|
||||
Performer: perf,
|
||||
})
|
||||
|
||||
p := New(params)
|
||||
|
||||
results, err := p.Process(suite.all)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 1, len(results))
|
||||
assert.Condition(suite.T(), func() bool {
|
||||
found := false
|
||||
for _, r := range results {
|
||||
if r.Error != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if r.Target.Tag == "dev" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}, "no errors in the returned result list")
|
||||
|
||||
}
|
||||
|
||||
type fakeRetentionClient struct{}
|
||||
|
||||
// GetCandidates ...
|
||||
|
@ -17,12 +17,17 @@ package policy
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/action"
|
||||
index4 "github.com/goharbor/harbor/src/pkg/retention/policy/action/index"
|
||||
|
||||
index3 "github.com/goharbor/harbor/src/pkg/retention/policy/alg/index"
|
||||
|
||||
index2 "github.com/goharbor/harbor/src/pkg/retention/res/selectors/index"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/index"
|
||||
|
||||
"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/res"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -61,19 +66,19 @@ func (bb *basicBuilder) Build(policy *lwp.Metadata, isDryRun bool) (alg.Processo
|
||||
params := make([]*alg.Parameter, 0)
|
||||
|
||||
for _, r := range policy.Rules {
|
||||
evaluator, err := rule.Get(r.Template, r.Parameters)
|
||||
evaluator, err := index.Get(r.Template, r.Parameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
perf, err := action.Get(r.Action, bb.allCandidates, isDryRun)
|
||||
perf, err := index4.Get(r.Action, bb.allCandidates, isDryRun)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get action performer by metadata")
|
||||
}
|
||||
|
||||
sl := make([]res.Selector, 0)
|
||||
for _, s := range r.TagSelectors {
|
||||
sel, err := selectors.Get(s.Kind, s.Decoration, s.Pattern)
|
||||
sel, err := index2.Get(s.Kind, s.Decoration, s.Pattern)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get selector by metadata")
|
||||
}
|
||||
@ -88,7 +93,7 @@ func (bb *basicBuilder) Build(policy *lwp.Metadata, isDryRun bool) (alg.Processo
|
||||
})
|
||||
}
|
||||
|
||||
p, err := alg.Get(policy.Algorithm, params)
|
||||
p, err := index3.Get(policy.Algorithm, params)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("get processor for algorithm: %s", policy.Algorithm))
|
||||
}
|
||||
|
@ -18,19 +18,23 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
index3 "github.com/goharbor/harbor/src/pkg/retention/policy/action/index"
|
||||
|
||||
index2 "github.com/goharbor/harbor/src/pkg/retention/policy/alg/index"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/index"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/dep"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/alg"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/alg/or"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/label"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/doublestar"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestk"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestps"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/action"
|
||||
|
||||
@ -83,8 +87,8 @@ func (suite *TestBuilderSuite) SetupSuite() {
|
||||
},
|
||||
}
|
||||
|
||||
alg.Register(alg.AlgorithmOR, or.New)
|
||||
selectors.Register(doublestar.Kind, []string{
|
||||
index2.Register(index2.AlgorithmOR, or.New)
|
||||
index.Register(doublestar.Kind, []string{
|
||||
doublestar.Matches,
|
||||
doublestar.Excludes,
|
||||
doublestar.RepoMatches,
|
||||
@ -92,8 +96,8 @@ func (suite *TestBuilderSuite) SetupSuite() {
|
||||
doublestar.NSMatches,
|
||||
doublestar.NSExcludes,
|
||||
}, doublestar.New)
|
||||
selectors.Register(label.Kind, []string{label.With, label.Without}, label.New)
|
||||
action.Register(action.Retain, action.NewRetainAction)
|
||||
index.Register(label.Kind, []string{label.With, label.Without}, label.New)
|
||||
index3.Register(action.Retain, action.NewRetainAction)
|
||||
|
||||
suite.oldClient = dep.DefaultClient
|
||||
dep.DefaultClient = &fakeRetentionClient{}
|
||||
@ -109,7 +113,7 @@ func (suite *TestBuilderSuite) TestBuild() {
|
||||
b := &basicBuilder{suite.all}
|
||||
|
||||
params := make(rule.Parameters)
|
||||
params[latestk.ParameterK] = 10
|
||||
params[latestps.ParameterK] = 10
|
||||
|
||||
scopeSelectors := make(map[string][]*rule.Selector, 1)
|
||||
scopeSelectors["repository"] = []*rule.Selector{{
|
||||
@ -124,14 +128,14 @@ func (suite *TestBuilderSuite) TestBuild() {
|
||||
ID: 1,
|
||||
Priority: 999,
|
||||
Action: action.Retain,
|
||||
Template: latestk.TemplateID,
|
||||
Template: latestps.TemplateID,
|
||||
Parameters: params,
|
||||
ScopeSelectors: scopeSelectors,
|
||||
TagSelectors: []*rule.Selector{
|
||||
{
|
||||
Kind: label.Kind,
|
||||
Decoration: label.With,
|
||||
Pattern: "L3",
|
||||
Kind: doublestar.Kind,
|
||||
Decoration: doublestar.Matches,
|
||||
Pattern: "latest",
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -141,19 +145,7 @@ func (suite *TestBuilderSuite) TestBuild() {
|
||||
require.NoError(suite.T(), err)
|
||||
require.NotNil(suite.T(), p)
|
||||
|
||||
artifacts := []*res.Candidate{
|
||||
{
|
||||
NamespaceID: 1,
|
||||
Namespace: "library",
|
||||
Repository: "harbor",
|
||||
Kind: "image",
|
||||
Tag: "dev",
|
||||
PushedTime: time.Now().Unix(),
|
||||
Labels: []string{"L3"},
|
||||
},
|
||||
}
|
||||
|
||||
results, err := p.Process(artifacts)
|
||||
results, err := p.Process(suite.all)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 1, len(results))
|
||||
assert.Condition(suite.T(), func() (success bool) {
|
||||
@ -161,7 +153,7 @@ func (suite *TestBuilderSuite) TestBuild() {
|
||||
success = art.Error == nil &&
|
||||
art.Target != nil &&
|
||||
art.Target.Repository == "harbor" &&
|
||||
art.Target.Tag == "latest"
|
||||
art.Target.Tag == "dev"
|
||||
|
||||
return
|
||||
})
|
||||
|
@ -18,6 +18,8 @@ package lwp
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||
)
|
||||
|
||||
@ -32,14 +34,21 @@ type Metadata struct {
|
||||
Rules []*rule.Metadata `json:"rules"`
|
||||
}
|
||||
|
||||
// FromMap constructs the metadata struct from map
|
||||
func (meta *Metadata) FromMap(m map[string]interface{}) error {
|
||||
mdata, err := json.Marshal(&m)
|
||||
// ToJSON marshals metadata to JSON string
|
||||
func (m *Metadata) ToJSON() (string, error) {
|
||||
jsonData, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", errors.Wrap(err, "marshal reporitory")
|
||||
}
|
||||
if err := json.Unmarshal(mdata, meta); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
return string(jsonData), nil
|
||||
}
|
||||
|
||||
// FromJSON constructs the metadata from json data
|
||||
func (m *Metadata) FromJSON(jsonData string) error {
|
||||
if len(jsonData) == 0 {
|
||||
return errors.New("empty json data to construct repository")
|
||||
}
|
||||
|
||||
return json.Unmarshal([]byte(jsonData), m)
|
||||
}
|
||||
|
@ -40,11 +40,3 @@ func (e *evaluator) Action() string {
|
||||
func New(_ rule.Parameters) rule.Evaluator {
|
||||
return &evaluator{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rule.Register(&rule.IndexMeta{
|
||||
TemplateID: TemplateID,
|
||||
Action: action.Retain,
|
||||
Parameters: []*rule.IndexedParam{},
|
||||
}, New)
|
||||
}
|
||||
|
@ -12,18 +12,33 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rule
|
||||
package index
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"sync"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestpl"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestk"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/always"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/lastx"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestps"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/action"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// index for keeping the mapping between template ID and evaluator
|
||||
var index sync.Map
|
||||
|
||||
// IndexMeta defines metadata for rule registration
|
||||
type IndexMeta struct {
|
||||
// Metadata defines metadata for rule registration
|
||||
type Metadata struct {
|
||||
TemplateID string `json:"rule_template"`
|
||||
|
||||
// Action of the rule performs
|
||||
@ -48,13 +63,78 @@ type IndexedParam struct {
|
||||
|
||||
// indexedItem is the item saved in the sync map
|
||||
type indexedItem struct {
|
||||
Meta *IndexMeta
|
||||
Meta *Metadata
|
||||
|
||||
Factory Factory
|
||||
Factory rule.Factory
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register latest pushed
|
||||
Register(&Metadata{
|
||||
TemplateID: latestps.TemplateID,
|
||||
Action: action.Retain,
|
||||
Parameters: []*IndexedParam{
|
||||
{
|
||||
Name: latestps.ParameterK,
|
||||
Type: "int",
|
||||
Unit: "count",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}, latestps.New)
|
||||
|
||||
// Register latest pulled
|
||||
Register(&Metadata{
|
||||
TemplateID: latestpl.TemplateID,
|
||||
Action: action.Retain,
|
||||
Parameters: []*IndexedParam{
|
||||
{
|
||||
Name: latestpl.ParameterN,
|
||||
Type: "int",
|
||||
Unit: "count",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}, latestpl.New)
|
||||
|
||||
// Register latest active
|
||||
Register(&Metadata{
|
||||
TemplateID: latestk.TemplateID,
|
||||
Action: action.Retain,
|
||||
Parameters: []*IndexedParam{
|
||||
{
|
||||
Name: latestk.ParameterK,
|
||||
Type: "int",
|
||||
Unit: "count",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}, latestk.New)
|
||||
|
||||
// Register lastx
|
||||
Register(&Metadata{
|
||||
TemplateID: lastx.TemplateID,
|
||||
Action: action.Retain,
|
||||
Parameters: []*IndexedParam{
|
||||
{
|
||||
Name: lastx.ParameterX,
|
||||
Type: "int",
|
||||
Unit: "days",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}, lastx.New)
|
||||
|
||||
// Register always
|
||||
Register(&Metadata{
|
||||
TemplateID: always.TemplateID,
|
||||
Action: action.Retain,
|
||||
Parameters: []*IndexedParam{},
|
||||
}, always.New)
|
||||
}
|
||||
|
||||
// Register the rule evaluator with the corresponding rule template
|
||||
func Register(meta *IndexMeta, factory Factory) {
|
||||
func Register(meta *Metadata, factory rule.Factory) {
|
||||
if meta == nil || factory == nil || len(meta.TemplateID) == 0 {
|
||||
// do nothing
|
||||
return
|
||||
@ -67,7 +147,7 @@ func Register(meta *IndexMeta, factory Factory) {
|
||||
}
|
||||
|
||||
// Get rule evaluator with the provided template ID
|
||||
func Get(templateID string, parameters Parameters) (Evaluator, error) {
|
||||
func Get(templateID string, parameters rule.Parameters) (rule.Evaluator, error) {
|
||||
if len(templateID) == 0 {
|
||||
return nil, errors.New("empty rule template ID")
|
||||
}
|
||||
@ -100,8 +180,8 @@ func Get(templateID string, parameters Parameters) (Evaluator, error) {
|
||||
}
|
||||
|
||||
// Index returns all the metadata of the registered rules
|
||||
func Index() []*IndexMeta {
|
||||
res := make([]*IndexMeta, 0)
|
||||
func Index() []*Metadata {
|
||||
res := make([]*Metadata, 0)
|
||||
|
||||
index.Range(func(k, v interface{}) bool {
|
||||
if item, ok := v.(*indexedItem); ok {
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rule
|
||||
package index
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -39,7 +40,7 @@ func TestIndexEntry(t *testing.T) {
|
||||
|
||||
// SetupSuite ...
|
||||
func (suite *IndexTestSuite) SetupSuite() {
|
||||
Register(&IndexMeta{
|
||||
Register(&Metadata{
|
||||
TemplateID: "fakeEvaluator",
|
||||
Action: "retain",
|
||||
Parameters: []*IndexedParam{
|
||||
@ -56,7 +57,7 @@ func (suite *IndexTestSuite) SetupSuite() {
|
||||
// TestRegister tests register
|
||||
func (suite *IndexTestSuite) TestGet() {
|
||||
|
||||
params := make(Parameters)
|
||||
params := make(rule.Parameters)
|
||||
params["fakeParam"] = 99
|
||||
evaluator, err := Get("fakeEvaluator", params)
|
||||
require.NoError(suite.T(), err)
|
||||
@ -71,11 +72,11 @@ func (suite *IndexTestSuite) TestGet() {
|
||||
Labels: []string{"L1", "L2"},
|
||||
}}
|
||||
|
||||
res, err := evaluator.Process(candidates)
|
||||
results, err := evaluator.Process(candidates)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 1, len(res))
|
||||
assert.Equal(suite.T(), 1, len(results))
|
||||
assert.Condition(suite.T(), func() bool {
|
||||
c := res[0]
|
||||
c := results[0]
|
||||
return c.Repository == "harbor" && c.Tag == "latest"
|
||||
})
|
||||
}
|
||||
@ -83,13 +84,17 @@ func (suite *IndexTestSuite) TestGet() {
|
||||
// TestIndex tests Index
|
||||
func (suite *IndexTestSuite) TestIndex() {
|
||||
metas := Index()
|
||||
require.Equal(suite.T(), 1, len(metas))
|
||||
require.Equal(suite.T(), 6, len(metas))
|
||||
assert.Condition(suite.T(), func() bool {
|
||||
m := metas[0]
|
||||
return m.TemplateID == "fakeEvaluator" &&
|
||||
m.Action == "retain" &&
|
||||
len(m.Parameters) > 0
|
||||
})
|
||||
for _, m := range metas {
|
||||
if m.TemplateID == "fakeEvaluator" &&
|
||||
m.Action == "retain" &&
|
||||
len(m.Parameters) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, "check fake evaluator in index")
|
||||
}
|
||||
|
||||
type fakeEvaluator struct {
|
||||
@ -107,7 +112,7 @@ func (e *fakeEvaluator) Action() string {
|
||||
}
|
||||
|
||||
// newFakeEvaluator is the factory of fakeEvaluator
|
||||
func newFakeEvaluator(parameters Parameters) Evaluator {
|
||||
func newFakeEvaluator(parameters rule.Parameters) rule.Evaluator {
|
||||
i := 10
|
||||
if v, ok := parameters["fakeParam"]; ok {
|
||||
i = v.(int)
|
@ -73,19 +73,3 @@ func New(params rule.Parameters) rule.Evaluator {
|
||||
x: DefaultX,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register itself
|
||||
rule.Register(&rule.IndexMeta{
|
||||
TemplateID: TemplateID,
|
||||
Action: action.Retain,
|
||||
Parameters: []*rule.IndexedParam{
|
||||
{
|
||||
Name: ParameterX,
|
||||
Type: "int",
|
||||
Unit: "days",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}, New)
|
||||
}
|
||||
|
@ -1,99 +0,0 @@
|
||||
// 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 latestk
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type EvaluatorTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
artifacts []*res.Candidate
|
||||
}
|
||||
|
||||
func (e *EvaluatorTestSuite) SetupSuite() {
|
||||
e.artifacts = []*res.Candidate{
|
||||
{PulledTime: 1, PushedTime: 2},
|
||||
{PulledTime: 3, PushedTime: 4},
|
||||
{PulledTime: 6, PushedTime: 5},
|
||||
{PulledTime: 8, PushedTime: 7},
|
||||
{PulledTime: 9, PushedTime: 9},
|
||||
{PulledTime: 10, PushedTime: 10},
|
||||
{PulledTime: 0, PushedTime: 11},
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EvaluatorTestSuite) TestProcess() {
|
||||
tests := []struct {
|
||||
k int
|
||||
expected int
|
||||
minActiveTime int64
|
||||
}{
|
||||
{k: 0, expected: 0},
|
||||
{k: 1, expected: 1, minActiveTime: 11},
|
||||
{k: 2, expected: 2, minActiveTime: 10},
|
||||
{k: 5, expected: 5, minActiveTime: 6},
|
||||
{k: 6, expected: 6, minActiveTime: 3},
|
||||
{k: 99, expected: len(e.artifacts)},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
e.T().Run(strconv.Itoa(tt.k), func(t *testing.T) {
|
||||
sut := &evaluator{k: tt.k}
|
||||
|
||||
result, err := sut.Process(e.artifacts)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, result, tt.expected)
|
||||
|
||||
for _, v := range result {
|
||||
assert.True(t, activeTime(v) >= tt.minActiveTime)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EvaluatorTestSuite) TestNew() {
|
||||
tests := []struct {
|
||||
name string
|
||||
params rule.Parameters
|
||||
expectedK int
|
||||
}{
|
||||
{name: "Valid", params: rule.Parameters{ParameterK: 5}, expectedK: 5},
|
||||
{name: "Default If Negative", params: rule.Parameters{ParameterK: -5}, expectedK: DefaultK},
|
||||
{name: "Default If Wrong Type", params: rule.Parameters{ParameterK: "5"}, expectedK: DefaultK},
|
||||
{name: "Default If Wrong Key", params: rule.Parameters{"n": 5}, expectedK: DefaultK},
|
||||
{name: "Default If Empty", params: rule.Parameters{}, expectedK: DefaultK},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
e.T().Run(tt.name, func(t *testing.T) {
|
||||
sut := New(tt.params).(*evaluator)
|
||||
|
||||
require.Equal(t, tt.expectedK, sut.k)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorSuite(t *testing.T) {
|
||||
suite.Run(t, &EvaluatorTestSuite{})
|
||||
}
|
@ -24,15 +24,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// TemplateID of latest k rule
|
||||
TemplateID = "latestK"
|
||||
// TemplateID of latest active k rule
|
||||
TemplateID = "latestActiveK"
|
||||
// ParameterK ...
|
||||
ParameterK = TemplateID
|
||||
// DefaultK defines the default K
|
||||
DefaultK = 10
|
||||
)
|
||||
|
||||
// evaluator for evaluating latest k tags
|
||||
// evaluator for evaluating latest active k images
|
||||
type evaluator struct {
|
||||
// latest k
|
||||
k int
|
||||
@ -40,9 +40,12 @@ type evaluator struct {
|
||||
|
||||
// Process the candidates based on the rule definition
|
||||
func (e *evaluator) Process(artifacts []*res.Candidate) ([]*res.Candidate, error) {
|
||||
// The updated proposal does not guarantee the order artifacts are provided, so we have to sort them first
|
||||
// Sort artifacts by their "active time"
|
||||
//
|
||||
// Active time is defined as the selection of c.PulledTime or c.PushedTime,
|
||||
// whichever is bigger, aka more recent.
|
||||
sort.Slice(artifacts, func(i, j int) bool {
|
||||
return artifacts[i].PushedTime < artifacts[j].PushedTime
|
||||
return activeTime(artifacts[i]) > activeTime(artifacts[j])
|
||||
})
|
||||
|
||||
i := e.k
|
||||
@ -77,18 +80,10 @@ func New(params rule.Parameters) rule.Evaluator {
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register itself
|
||||
rule.Register(&rule.IndexMeta{
|
||||
TemplateID: TemplateID,
|
||||
Action: action.Retain,
|
||||
Parameters: []*rule.IndexedParam{
|
||||
{
|
||||
Name: ParameterK,
|
||||
Type: "int",
|
||||
Unit: "count",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}, New)
|
||||
func activeTime(c *res.Candidate) int64 {
|
||||
if c.PulledTime > c.PushedTime {
|
||||
return c.PulledTime
|
||||
}
|
||||
|
||||
return c.PushedTime
|
||||
}
|
||||
|
@ -1,71 +1,99 @@
|
||||
// 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 latestk
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type EvaluatorTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
artifacts []*res.Candidate
|
||||
}
|
||||
|
||||
func (e *EvaluatorTestSuite) TestNew() {
|
||||
tests := []struct {
|
||||
Name string
|
||||
args rule.Parameters
|
||||
expectedK int
|
||||
}{
|
||||
{Name: "Valid", args: map[string]rule.Parameter{ParameterK: 5}, expectedK: 5},
|
||||
{Name: "Default If Negative", args: map[string]rule.Parameter{ParameterK: -1}, expectedK: DefaultK},
|
||||
{Name: "Default If Not Set", args: map[string]rule.Parameter{}, expectedK: DefaultK},
|
||||
{Name: "Default If Wrong Type", args: map[string]rule.Parameter{ParameterK: "foo"}, expectedK: DefaultK},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
e.T().Run(tt.Name, func(t *testing.T) {
|
||||
e := New(tt.args).(*evaluator)
|
||||
|
||||
require.Equal(t, tt.expectedK, e.k)
|
||||
})
|
||||
func (e *EvaluatorTestSuite) SetupSuite() {
|
||||
e.artifacts = []*res.Candidate{
|
||||
{PulledTime: 1, PushedTime: 2},
|
||||
{PulledTime: 3, PushedTime: 4},
|
||||
{PulledTime: 6, PushedTime: 5},
|
||||
{PulledTime: 8, PushedTime: 7},
|
||||
{PulledTime: 9, PushedTime: 9},
|
||||
{PulledTime: 10, PushedTime: 10},
|
||||
{PulledTime: 0, PushedTime: 11},
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EvaluatorTestSuite) TestProcess() {
|
||||
data := []*res.Candidate{{PushedTime: 0}, {PushedTime: 1}, {PushedTime: 2}, {PushedTime: 3}, {PushedTime: 4}}
|
||||
rand.Shuffle(len(data), func(i, j int) {
|
||||
data[i], data[j] = data[j], data[i]
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
k int
|
||||
expected int
|
||||
k int
|
||||
expected int
|
||||
minActiveTime int64
|
||||
}{
|
||||
{k: 0, expected: 0},
|
||||
{k: 1, expected: 1},
|
||||
{k: 3, expected: 3},
|
||||
{k: 5, expected: 5},
|
||||
{k: 6, expected: 5},
|
||||
{k: 1, expected: 1, minActiveTime: 11},
|
||||
{k: 2, expected: 2, minActiveTime: 10},
|
||||
{k: 5, expected: 5, minActiveTime: 6},
|
||||
{k: 6, expected: 6, minActiveTime: 3},
|
||||
{k: 99, expected: len(e.artifacts)},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
e.T().Run(strconv.Itoa(tt.k), func(t *testing.T) {
|
||||
e := New(map[string]rule.Parameter{ParameterK: tt.k})
|
||||
sut := &evaluator{k: tt.k}
|
||||
|
||||
result, err := e.Process(data)
|
||||
result, err := sut.Process(e.artifacts)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, result, tt.expected)
|
||||
|
||||
for _, v := range result {
|
||||
assert.True(t, activeTime(v) >= tt.minActiveTime)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluator(t *testing.T) {
|
||||
func (e *EvaluatorTestSuite) TestNew() {
|
||||
tests := []struct {
|
||||
name string
|
||||
params rule.Parameters
|
||||
expectedK int
|
||||
}{
|
||||
{name: "Valid", params: rule.Parameters{ParameterK: 5}, expectedK: 5},
|
||||
{name: "Default If Negative", params: rule.Parameters{ParameterK: -5}, expectedK: DefaultK},
|
||||
{name: "Default If Wrong Type", params: rule.Parameters{ParameterK: "5"}, expectedK: DefaultK},
|
||||
{name: "Default If Wrong Key", params: rule.Parameters{"n": 5}, expectedK: DefaultK},
|
||||
{name: "Default If Empty", params: rule.Parameters{}, expectedK: DefaultK},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
e.T().Run(tt.name, func(t *testing.T) {
|
||||
sut := New(tt.params).(*evaluator)
|
||||
|
||||
require.Equal(t, tt.expectedK, sut.k)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorSuite(t *testing.T) {
|
||||
suite.Run(t, &EvaluatorTestSuite{})
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lastpulled
|
||||
package latestpl
|
||||
|
||||
import (
|
||||
"sort"
|
||||
@ -25,7 +25,7 @@ import (
|
||||
|
||||
const (
|
||||
// TemplateID of the rule
|
||||
TemplateID = "lastpulled"
|
||||
TemplateID = "latestPulledN"
|
||||
|
||||
// ParameterN is the name of the metadata parameter for the N value
|
||||
ParameterN = TemplateID
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lastpulled
|
||||
package latestpl
|
||||
|
||||
import (
|
||||
"math/rand"
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package latestk
|
||||
package latestps
|
||||
|
||||
import (
|
||||
"sort"
|
||||
@ -24,15 +24,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// TemplateID of latest active k rule
|
||||
TemplateID = "latestActiveK"
|
||||
// TemplateID of latest k rule
|
||||
TemplateID = "latestPushedK"
|
||||
// ParameterK ...
|
||||
ParameterK = TemplateID
|
||||
// DefaultK defines the default K
|
||||
DefaultK = 10
|
||||
)
|
||||
|
||||
// evaluator for evaluating latest active k images
|
||||
// evaluator for evaluating latest k tags
|
||||
type evaluator struct {
|
||||
// latest k
|
||||
k int
|
||||
@ -40,12 +40,9 @@ type evaluator struct {
|
||||
|
||||
// Process the candidates based on the rule definition
|
||||
func (e *evaluator) Process(artifacts []*res.Candidate) ([]*res.Candidate, error) {
|
||||
// Sort artifacts by their "active time"
|
||||
//
|
||||
// Active time is defined as the selection of c.PulledTime or c.PushedTime,
|
||||
// whichever is bigger, aka more recent.
|
||||
// The updated proposal does not guarantee the order artifacts are provided, so we have to sort them first
|
||||
sort.Slice(artifacts, func(i, j int) bool {
|
||||
return activeTime(artifacts[i]) > activeTime(artifacts[j])
|
||||
return artifacts[i].PushedTime < artifacts[j].PushedTime
|
||||
})
|
||||
|
||||
i := e.k
|
||||
@ -79,27 +76,3 @@ func New(params rule.Parameters) rule.Evaluator {
|
||||
k: DefaultK,
|
||||
}
|
||||
}
|
||||
|
||||
func activeTime(c *res.Candidate) int64 {
|
||||
if c.PulledTime > c.PushedTime {
|
||||
return c.PulledTime
|
||||
}
|
||||
|
||||
return c.PushedTime
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register itself
|
||||
rule.Register(&rule.IndexMeta{
|
||||
TemplateID: TemplateID,
|
||||
Action: action.Retain,
|
||||
Parameters: []*rule.IndexedParam{
|
||||
{
|
||||
Name: ParameterK,
|
||||
Type: "int",
|
||||
Unit: "count",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}, New)
|
||||
}
|
71
src/pkg/retention/policy/rule/latestps/evaluator_test.go
Normal file
71
src/pkg/retention/policy/rule/latestps/evaluator_test.go
Normal file
@ -0,0 +1,71 @@
|
||||
package latestps
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type EvaluatorTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (e *EvaluatorTestSuite) TestNew() {
|
||||
tests := []struct {
|
||||
Name string
|
||||
args rule.Parameters
|
||||
expectedK int
|
||||
}{
|
||||
{Name: "Valid", args: map[string]rule.Parameter{ParameterK: 5}, expectedK: 5},
|
||||
{Name: "Default If Negative", args: map[string]rule.Parameter{ParameterK: -1}, expectedK: DefaultK},
|
||||
{Name: "Default If Not Set", args: map[string]rule.Parameter{}, expectedK: DefaultK},
|
||||
{Name: "Default If Wrong Type", args: map[string]rule.Parameter{ParameterK: "foo"}, expectedK: DefaultK},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
e.T().Run(tt.Name, func(t *testing.T) {
|
||||
e := New(tt.args).(*evaluator)
|
||||
|
||||
require.Equal(t, tt.expectedK, e.k)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EvaluatorTestSuite) TestProcess() {
|
||||
data := []*res.Candidate{{PushedTime: 0}, {PushedTime: 1}, {PushedTime: 2}, {PushedTime: 3}, {PushedTime: 4}}
|
||||
rand.Shuffle(len(data), func(i, j int) {
|
||||
data[i], data[j] = data[j], data[i]
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
k int
|
||||
expected int
|
||||
}{
|
||||
{k: 0, expected: 0},
|
||||
{k: 1, expected: 1},
|
||||
{k: 3, expected: 3},
|
||||
{k: 5, expected: 5},
|
||||
{k: 6, expected: 5},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
e.T().Run(strconv.Itoa(tt.k), func(t *testing.T) {
|
||||
e := New(map[string]rule.Parameter{ParameterK: tt.k})
|
||||
|
||||
result, err := e.Process(data)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, result, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluator(t *testing.T) {
|
||||
suite.Run(t, &EvaluatorTestSuite{})
|
||||
}
|
@ -18,6 +18,8 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -30,24 +32,31 @@ const (
|
||||
// Repository of candidate
|
||||
type Repository struct {
|
||||
// Namespace
|
||||
Namespace string
|
||||
Namespace string `json:"namespace"`
|
||||
// Repository name
|
||||
Name string
|
||||
Name string `json:"name"`
|
||||
// So far we need the kind of repository and retrieve candidates with different APIs
|
||||
// TODO: REMOVE IT IN THE FUTURE IF WE SUPPORT UNIFIED ARTIFACT MODEL
|
||||
Kind string
|
||||
Kind string `json:"kind"`
|
||||
}
|
||||
|
||||
// FromMap constructs the repository struct from map
|
||||
func (r *Repository) FromMap(m map[string]interface{}) error {
|
||||
mdata, err := json.Marshal(&m)
|
||||
// ToJSON marshals repository to JSON string
|
||||
func (r *Repository) ToJSON() (string, error) {
|
||||
jsonData, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", errors.Wrap(err, "marshal reporitory")
|
||||
}
|
||||
if err := json.Unmarshal(mdata, r); err != nil {
|
||||
return err
|
||||
|
||||
return string(jsonData), nil
|
||||
}
|
||||
|
||||
// FromJSON constructs the repository from json data
|
||||
func (r *Repository) FromJSON(jsonData string) error {
|
||||
if len(jsonData) == 0 {
|
||||
return errors.New("empty json data to construct repository")
|
||||
}
|
||||
return nil
|
||||
|
||||
return json.Unmarshal([]byte(jsonData), r)
|
||||
}
|
||||
|
||||
// Candidate for retention processor to match
|
||||
|
@ -12,11 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package selectors
|
||||
package index
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/label"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors/doublestar"
|
||||
"github.com/pkg/errors"
|
||||
@ -32,6 +34,9 @@ func init() {
|
||||
doublestar.NSMatches,
|
||||
doublestar.NSExcludes,
|
||||
}, doublestar.New)
|
||||
|
||||
// Register label selector
|
||||
Register(label.Kind, []string{label.With, label.Without}, label.New)
|
||||
}
|
||||
|
||||
// index for keeping the mapping between selector meta and its implementation
|
@ -15,9 +15,9 @@
|
||||
package label
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res/selectors"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/retention/res"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -51,7 +51,10 @@ func (s *selector) Select(artifacts []*res.Candidate) (selected []*res.Candidate
|
||||
|
||||
// New is factory method for list selector
|
||||
func New(decoration string, pattern string) res.Selector {
|
||||
labels := strings.Split(pattern, ",")
|
||||
labels := make([]string, 0)
|
||||
if len(pattern) > 0 {
|
||||
labels = append(labels, strings.Split(pattern, ",")...)
|
||||
}
|
||||
|
||||
return &selector{
|
||||
decoration: decoration,
|
||||
@ -81,8 +84,3 @@ func isMatched(patternLbls []string, resLbls []string, decoration string) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register doublestar selector
|
||||
selectors.Register(Kind, []string{With, Without}, New)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user