From b61f45e038f8f8e09450753c611e3f726d8e5f22 Mon Sep 17 00:00:00 2001 From: Nathan Lowe Date: Sun, 14 Jul 2019 23:44:41 -0400 Subject: [PATCH] Retention: Implement Evaluator: Keep N Most Recently Pulled Signed-off-by: Nathan Lowe --- .../policy/rule/lastpulled/evaluator.go | 71 +++++++++++++++ .../policy/rule/lastpulled/evaluator_test.go | 89 +++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 src/pkg/retention/policy/rule/lastpulled/evaluator.go create mode 100644 src/pkg/retention/policy/rule/lastpulled/evaluator_test.go diff --git a/src/pkg/retention/policy/rule/lastpulled/evaluator.go b/src/pkg/retention/policy/rule/lastpulled/evaluator.go new file mode 100644 index 000000000..93090ac91 --- /dev/null +++ b/src/pkg/retention/policy/rule/lastpulled/evaluator.go @@ -0,0 +1,71 @@ +// 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 lastpulled + +import ( + "sort" + + "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/rule" + "github.com/goharbor/harbor/src/pkg/retention/res" +) + +const ( + // TemplateID of the rule + TemplateID = "lastpulled" + + // ParameterN is the name of the metadata parameter for the N value + ParameterN = TemplateID + + // DefaultN is the default number of tags to retain + DefaultN = 10 +) + +type evaluator struct { + n int +} + +func (e *evaluator) Process(artifacts []*res.Candidate) ([]*res.Candidate, error) { + sort.Slice(artifacts, func(i, j int) bool { + return artifacts[i].PulledTime > artifacts[j].PulledTime + }) + + i := e.n + if i > len(artifacts) { + i = len(artifacts) + } + + return artifacts[:i], nil +} + +func (e *evaluator) Action() string { + return action.Retain +} + +// New constructs an evaluator with the given parameters +func New(params rule.Parameters) rule.Evaluator { + if params != nil { + if p, ok := params[ParameterN]; ok { + if v, ok := p.(int); ok && v >= 0 { + return &evaluator{n: v} + } + } + } + + log.Debugf("default parameter %d used for rule %s", DefaultN, TemplateID) + + return &evaluator{n: DefaultN} +} diff --git a/src/pkg/retention/policy/rule/lastpulled/evaluator_test.go b/src/pkg/retention/policy/rule/lastpulled/evaluator_test.go new file mode 100644 index 000000000..3d35d813c --- /dev/null +++ b/src/pkg/retention/policy/rule/lastpulled/evaluator_test.go @@ -0,0 +1,89 @@ +// 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 lastpulled + +import ( + "math/rand" + "strconv" + "testing" + + "github.com/goharbor/harbor/src/pkg/retention/policy/rule" + "github.com/goharbor/harbor/src/pkg/retention/res" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +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{ParameterN: 5}, expectedK: 5}, + {Name: "Default If Negative", args: map[string]rule.Parameter{ParameterN: -1}, expectedK: DefaultN}, + {Name: "Default If Not Set", args: map[string]rule.Parameter{}, expectedK: DefaultN}, + {Name: "Default If Wrong Type", args: map[string]rule.Parameter{ParameterN: "foo"}, expectedK: DefaultN}, + } + + for _, tt := range tests { + e.T().Run(tt.Name, func(t *testing.T) { + e := New(tt.args).(*evaluator) + + require.Equal(t, tt.expectedK, e.n) + }) + } +} + +func (e *EvaluatorTestSuite) TestProcess() { + data := []*res.Candidate{{PulledTime: 0}, {PulledTime: 1}, {PulledTime: 2}, {PulledTime: 3}, {PulledTime: 4}} + rand.Shuffle(len(data), func(i, j int) { + data[i], data[j] = data[j], data[i] + }) + + tests := []struct { + n int + expected int + minPullTime int64 + }{ + {n: 0, expected: 0, minPullTime: 0}, + {n: 1, expected: 1, minPullTime: 4}, + {n: 3, expected: 3, minPullTime: 2}, + {n: 5, expected: 5, minPullTime: 0}, + {n: 6, expected: 5, minPullTime: 0}, + } + + for _, tt := range tests { + e.T().Run(strconv.Itoa(tt.n), func(t *testing.T) { + ev := New(map[string]rule.Parameter{ParameterN: tt.n}) + + result, err := ev.Process(data) + + require.NoError(t, err) + require.Len(t, result, tt.expected) + + for _, v := range result { + require.False(e.T(), v.PulledTime < tt.minPullTime) + } + }) + } +} + +func TestEvaluatorSuite(t *testing.T) { + suite.Run(t, &EvaluatorTestSuite{}) +}