diff --git a/src/core/main.go b/src/core/main.go index 2c68141dd..30b055ba6 100644 --- a/src/core/main.go +++ b/src/core/main.go @@ -17,6 +17,7 @@ package main import ( "encoding/gob" "fmt" + "github.com/goharbor/harbor/src/pkg/retention" "os" "os/signal" "strconv" @@ -157,6 +158,12 @@ func main() { 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") proxy.Init() // go proxy.StartProxy() diff --git a/src/pkg/retention/boot.go b/src/pkg/retention/boot.go new file mode 100644 index 000000000..41dca8e1a --- /dev/null +++ b/src/pkg/retention/boot.go @@ -0,0 +1,23 @@ +// 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 + +// Init the retention components +func Init() error { + // New default retention client + DefaultClient = NewClient() + + return nil +} diff --git a/src/pkg/retention/client.go b/src/pkg/retention/client.go index 7ae675c83..198198e78 100644 --- a/src/pkg/retention/client.go +++ b/src/pkg/retention/client.go @@ -29,6 +29,9 @@ import ( "github.com/goharbor/harbor/src/pkg/retention/res" ) +// DefaultClient for the retention +var DefaultClient Client + // Client is designed to access core service to get required infos type Client interface { // Get the tag candidates under the repository @@ -63,8 +66,8 @@ type Client interface { SubmitTask(taskID int64, repository *res.Repository, meta *policy.LiteMeta) (string, error) } -// New basic client -func New(client ...*http.Client) Client { +// NewClient new a basic client +func NewClient(client ...*http.Client) Client { var c *http.Client if len(client) > 0 { c = client[0] diff --git a/src/pkg/retention/policy/action/performer.go b/src/pkg/retention/policy/action/performer.go index da23e14f2..b57cc8d65 100644 --- a/src/pkg/retention/policy/action/performer.go +++ b/src/pkg/retention/policy/action/performer.go @@ -14,7 +14,10 @@ package action -import "github.com/goharbor/harbor/src/pkg/retention/res" +import ( + "github.com/goharbor/harbor/src/pkg/retention" + "github.com/goharbor/harbor/src/pkg/retention/res" +) const ( // Retain artifacts @@ -43,17 +46,30 @@ type retainAction struct { } // Perform the action -func (ra *retainAction) Perform(candidates []*res.Candidate) ([]*res.Result, error) { - // TODO: REPLACE SAMPLE CODE WITH REAL IMPLEMENTATION - results := make([]*res.Result, 0) - +func (ra *retainAction) Perform(candidates []*res.Candidate) (results []*res.Result, err error) { + retained := make(map[string]bool) for _, c := range candidates { - results = append(results, &res.Result{ - Target: c, - }) + retained[c.Hash()] = true } - 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 := retention.DefaultClient.Delete(art); err != nil { + result.Error = err + } + + results = append(results, result) + } + } + } + + return } // NewRetainAction is factory method for RetainAction diff --git a/src/pkg/retention/policy/action/performer_test.go b/src/pkg/retention/policy/action/performer_test.go new file mode 100644 index 000000000..7bb14967d --- /dev/null +++ b/src/pkg/retention/policy/action/performer_test.go @@ -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" + "github.com/goharbor/harbor/src/pkg/retention/policy" + "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 retention.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 = retention.DefaultClient + retention.DefaultClient = &fakeRetentionClient{} +} + +// TearDownSuite ... +func (suite *TestPerformerSuite) TearDownSuite() { + retention.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(), "latest", 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 *policy.LiteMeta) (string, error) { + return "", errors.New("not implemented") +}