mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-27 21:12:42 +02:00
set the immutable status on getting/listting tag
Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
5cf0481b51
commit
596a6261ca
@ -433,6 +433,9 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
description: The latest pull time of the tag
|
description: The latest pull time of the tag
|
||||||
|
immutable:
|
||||||
|
type: boolean
|
||||||
|
description: The immutable status of the tag
|
||||||
ExtraAttrs:
|
ExtraAttrs:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
|
@ -18,6 +18,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/goharbor/harbor/src/api/artifact/abstractor"
|
"github.com/goharbor/harbor/src/api/artifact/abstractor"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/art"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/immutabletag/match"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
// registry image resolvers
|
// registry image resolvers
|
||||||
_ "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver/image"
|
_ "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver/image"
|
||||||
@ -76,20 +80,22 @@ type Controller interface {
|
|||||||
// NewController creates an instance of the default artifact controller
|
// NewController creates an instance of the default artifact controller
|
||||||
func NewController() Controller {
|
func NewController() Controller {
|
||||||
return &controller{
|
return &controller{
|
||||||
repoMgr: repository.Mgr,
|
repoMgr: repository.Mgr,
|
||||||
artMgr: artifact.Mgr,
|
artMgr: artifact.Mgr,
|
||||||
tagMgr: tag.Mgr,
|
tagMgr: tag.Mgr,
|
||||||
abstractor: abstractor.NewAbstractor(),
|
abstractor: abstractor.NewAbstractor(),
|
||||||
|
immutableMtr: rule.NewRuleMatcher(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO concurrency summary
|
// TODO concurrency summary
|
||||||
|
|
||||||
type controller struct {
|
type controller struct {
|
||||||
repoMgr repository.Manager
|
repoMgr repository.Manager
|
||||||
artMgr artifact.Manager
|
artMgr artifact.Manager
|
||||||
tagMgr tag.Manager
|
tagMgr tag.Manager
|
||||||
abstractor abstractor.Abstractor
|
abstractor abstractor.Abstractor
|
||||||
|
immutableMtr match.ImmutableTagMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) Ensure(ctx context.Context, repositoryID int64, digest string, tags ...string) (bool, int64, error) {
|
func (c *controller) Ensure(ctx context.Context, repositoryID int64, digest string, tags ...string) (bool, int64, error) {
|
||||||
@ -304,7 +310,7 @@ func (c *controller) ListTags(ctx context.Context, query *q.Query, option *TagOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) DeleteTag(ctx context.Context, tagID int64) error {
|
func (c *controller) DeleteTag(ctx context.Context, tagID int64) error {
|
||||||
// immutable checking is covered in middleware
|
// Immutable checking is covered in middleware
|
||||||
// TODO check signature
|
// TODO check signature
|
||||||
// TODO delete label
|
// TODO delete label
|
||||||
// TODO fire delete tag event
|
// TODO fire delete tag event
|
||||||
@ -374,8 +380,28 @@ func (c *controller) assembleTag(ctx context.Context, tag *tm.Tag, option *TagOp
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
if option.WithImmutableStatus {
|
if option.WithImmutableStatus {
|
||||||
// TODO populate immutable status
|
repo, err := c.repoMgr.Get(ctx, tag.RepositoryID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
} else {
|
||||||
|
t.Immutable = c.isImmutable(repo.ProjectID, repo.Name, tag.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO populate signature on tag level?
|
// TODO populate signature on tag level?
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check whether the tag is Immutable
|
||||||
|
func (c *controller) isImmutable(projectID int64, repo string, tag string) bool {
|
||||||
|
_, repoName := utils.ParseRepository(repo)
|
||||||
|
matched, err := c.immutableMtr.Match(projectID, art.Candidate{
|
||||||
|
Repository: repoName,
|
||||||
|
Tag: tag,
|
||||||
|
NamespaceID: projectID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/pkg/q"
|
"github.com/goharbor/harbor/src/pkg/q"
|
||||||
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
||||||
arttesting "github.com/goharbor/harbor/src/testing/pkg/artifact"
|
arttesting "github.com/goharbor/harbor/src/testing/pkg/artifact"
|
||||||
|
immutesting "github.com/goharbor/harbor/src/testing/pkg/immutabletag"
|
||||||
repotesting "github.com/goharbor/harbor/src/testing/pkg/repository"
|
repotesting "github.com/goharbor/harbor/src/testing/pkg/repository"
|
||||||
tagtesting "github.com/goharbor/harbor/src/testing/pkg/tag"
|
tagtesting "github.com/goharbor/harbor/src/testing/pkg/tag"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
@ -41,11 +42,12 @@ func (f *fakeAbstractor) Abstract(ctx context.Context, artifact *artifact.Artifa
|
|||||||
|
|
||||||
type controllerTestSuite struct {
|
type controllerTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
ctl *controller
|
ctl *controller
|
||||||
repoMgr *repotesting.FakeManager
|
repoMgr *repotesting.FakeManager
|
||||||
artMgr *arttesting.FakeManager
|
artMgr *arttesting.FakeManager
|
||||||
tagMgr *tagtesting.FakeManager
|
tagMgr *tagtesting.FakeManager
|
||||||
abstractor *fakeAbstractor
|
abstractor *fakeAbstractor
|
||||||
|
immutableMtr *immutesting.FakeMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controllerTestSuite) SetupTest() {
|
func (c *controllerTestSuite) SetupTest() {
|
||||||
@ -53,11 +55,13 @@ func (c *controllerTestSuite) SetupTest() {
|
|||||||
c.artMgr = &arttesting.FakeManager{}
|
c.artMgr = &arttesting.FakeManager{}
|
||||||
c.tagMgr = &tagtesting.FakeManager{}
|
c.tagMgr = &tagtesting.FakeManager{}
|
||||||
c.abstractor = &fakeAbstractor{}
|
c.abstractor = &fakeAbstractor{}
|
||||||
|
c.immutableMtr = &immutesting.FakeMatcher{}
|
||||||
c.ctl = &controller{
|
c.ctl = &controller{
|
||||||
repoMgr: c.repoMgr,
|
repoMgr: c.repoMgr,
|
||||||
artMgr: c.artMgr,
|
artMgr: c.artMgr,
|
||||||
tagMgr: c.tagMgr,
|
tagMgr: c.tagMgr,
|
||||||
abstractor: c.abstractor,
|
abstractor: c.abstractor,
|
||||||
|
immutableMtr: c.immutableMtr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,9 +78,16 @@ func (c *controllerTestSuite) TestAssembleTag() {
|
|||||||
WithImmutableStatus: true,
|
WithImmutableStatus: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.repoMgr.On("Get").Return(&models.RepoRecord{
|
||||||
|
ProjectID: 1,
|
||||||
|
Name: "hello-world",
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
c.immutableMtr.On("Match").Return(true, nil)
|
||||||
tag := c.ctl.assembleTag(nil, tg, option)
|
tag := c.ctl.assembleTag(nil, tg, option)
|
||||||
c.Require().NotNil(tag)
|
c.Require().NotNil(tag)
|
||||||
c.Equal(tag.ID, tg.ID)
|
c.Equal(tag.ID, tg.ID)
|
||||||
|
c.Equal(true, tag.Immutable)
|
||||||
// TODO check other fields of option
|
// TODO check other fields of option
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,8 +403,8 @@ func (c *controllerTestSuite) TestListTags() {
|
|||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
RepositoryID: 1,
|
RepositoryID: 1,
|
||||||
ArtifactID: 1,
|
|
||||||
Name: "latest",
|
Name: "latest",
|
||||||
|
ArtifactID: 1,
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
total, tags, err := c.ctl.ListTags(nil, nil, nil)
|
total, tags, err := c.ctl.ListTags(nil, nil, nil)
|
||||||
@ -401,6 +412,7 @@ func (c *controllerTestSuite) TestListTags() {
|
|||||||
c.Equal(int64(1), total)
|
c.Equal(int64(1), total)
|
||||||
c.Len(tags, 1)
|
c.Len(tags, 1)
|
||||||
c.tagMgr.AssertExpectations(c.T())
|
c.tagMgr.AssertExpectations(c.T())
|
||||||
|
c.Equal(tags[0].Immutable, false)
|
||||||
// TODO check other properties: label, etc
|
// TODO check other properties: label, etc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ func (a *Artifact) ToSwagger() *models.Artifact {
|
|||||||
PullTime: strfmt.DateTime(tag.PullTime),
|
PullTime: strfmt.DateTime(tag.PullTime),
|
||||||
PushTime: strfmt.DateTime(tag.PushTime),
|
PushTime: strfmt.DateTime(tag.PushTime),
|
||||||
RepositoryID: tag.RepositoryID,
|
RepositoryID: tag.RepositoryID,
|
||||||
|
Immutable: tag.Immutable,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for resource, links := range a.SubResourceLinks {
|
for resource, links := range a.SubResourceLinks {
|
||||||
@ -88,7 +89,8 @@ func (a *Artifact) ToSwagger() *models.Artifact {
|
|||||||
// Tag is the overall view of tag
|
// Tag is the overall view of tag
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
tag.Tag
|
tag.Tag
|
||||||
// TODO add other attrs: signature, label, immutable status, etc
|
Immutable bool
|
||||||
|
// TODO add other attrs: signature, label, etc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resource defines the specific resource of different artifacts: build history for image, values.yaml for chart, etc
|
// Resource defines the specific resource of different artifacts: build history for image, values.yaml for chart, etc
|
||||||
|
@ -825,7 +825,7 @@ func populateAuthor(detail *models.TagDetail) {
|
|||||||
// check whether the tag is immutable
|
// check whether the tag is immutable
|
||||||
func isImmutable(projectID int64, repo string, tag string) bool {
|
func isImmutable(projectID int64, repo string, tag string) bool {
|
||||||
_, repoName := utils.ParseRepository(repo)
|
_, repoName := utils.ParseRepository(repo)
|
||||||
matched, err := rule.NewRuleMatcher(projectID).Match(art.Candidate{
|
matched, err := rule.NewRuleMatcher().Match(projectID, art.Candidate{
|
||||||
Repository: repoName,
|
Repository: repoName,
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
NamespaceID: projectID,
|
NamespaceID: projectID,
|
||||||
|
@ -45,7 +45,7 @@ func (dmf *delmfInterceptor) HandleRequest(req *http.Request) (err error) {
|
|||||||
for _, af := range afs {
|
for _, af := range afs {
|
||||||
_, repoName := common_util.ParseRepository(dmf.mf.Repository)
|
_, repoName := common_util.ParseRepository(dmf.mf.Repository)
|
||||||
var matched bool
|
var matched bool
|
||||||
matched, err = rule.NewRuleMatcher(dmf.mf.ProjectID).Match(art.Candidate{
|
matched, err = rule.NewRuleMatcher().Match(dmf.mf.ProjectID, art.Candidate{
|
||||||
Repository: repoName,
|
Repository: repoName,
|
||||||
Tag: af.Tag,
|
Tag: af.Tag,
|
||||||
NamespaceID: dmf.mf.ProjectID,
|
NamespaceID: dmf.mf.ProjectID,
|
||||||
|
@ -29,7 +29,7 @@ func (pmf *pushmfInterceptor) HandleRequest(req *http.Request) (err error) {
|
|||||||
|
|
||||||
_, repoName := common_util.ParseRepository(pmf.mf.Repository)
|
_, repoName := common_util.ParseRepository(pmf.mf.Repository)
|
||||||
var matched bool
|
var matched bool
|
||||||
matched, err = rule.NewRuleMatcher(pmf.mf.ProjectID).Match(art.Candidate{
|
matched, err = rule.NewRuleMatcher().Match(pmf.mf.ProjectID, art.Candidate{
|
||||||
Repository: repoName,
|
Repository: repoName,
|
||||||
Tag: pmf.mf.Tag,
|
Tag: pmf.mf.Tag,
|
||||||
NamespaceID: pmf.mf.ProjectID,
|
NamespaceID: pmf.mf.ProjectID,
|
||||||
|
@ -7,5 +7,5 @@ import (
|
|||||||
// ImmutableTagMatcher ...
|
// ImmutableTagMatcher ...
|
||||||
type ImmutableTagMatcher interface {
|
type ImmutableTagMatcher interface {
|
||||||
// Match whether the candidate is in the immutable list
|
// Match whether the candidate is in the immutable list
|
||||||
Match(c art.Candidate) (bool, error)
|
Match(pid int64, c art.Candidate) (bool, error)
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,12 @@ import (
|
|||||||
|
|
||||||
// Matcher ...
|
// Matcher ...
|
||||||
type Matcher struct {
|
type Matcher struct {
|
||||||
pid int64
|
|
||||||
rules []model.Metadata
|
rules []model.Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match ...
|
// Match ...
|
||||||
func (rm *Matcher) Match(c art.Candidate) (bool, error) {
|
func (rm *Matcher) Match(pid int64, c art.Candidate) (bool, error) {
|
||||||
if err := rm.getImmutableRules(); err != nil {
|
if err := rm.getImmutableRules(pid); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +70,8 @@ func (rm *Matcher) Match(c art.Candidate) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rm *Matcher) getImmutableRules() error {
|
func (rm *Matcher) getImmutableRules(pid int64) error {
|
||||||
rules, err := immutabletag.ImmuCtr.ListImmutableRules(rm.pid)
|
rules, err := immutabletag.ImmuCtr.ListImmutableRules(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -81,8 +80,6 @@ func (rm *Matcher) getImmutableRules() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRuleMatcher ...
|
// NewRuleMatcher ...
|
||||||
func NewRuleMatcher(pid int64) match.ImmutableTagMatcher {
|
func NewRuleMatcher() match.ImmutableTagMatcher {
|
||||||
return &Matcher{
|
return &Matcher{}
|
||||||
pid: pid,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ func (s *MatchTestSuite) TestImmuMatch() {
|
|||||||
s.ruleID2 = id
|
s.ruleID2 = id
|
||||||
s.require.Nil(err)
|
s.require.Nil(err)
|
||||||
|
|
||||||
match := NewRuleMatcher(1)
|
match := NewRuleMatcher()
|
||||||
|
|
||||||
c1 := art.Candidate{
|
c1 := art.Candidate{
|
||||||
NamespaceID: 1,
|
NamespaceID: 1,
|
||||||
@ -93,7 +93,7 @@ func (s *MatchTestSuite) TestImmuMatch() {
|
|||||||
Repository: "redis",
|
Repository: "redis",
|
||||||
Tag: "release-1.10",
|
Tag: "release-1.10",
|
||||||
}
|
}
|
||||||
isMatch, err := match.Match(c1)
|
isMatch, err := match.Match(1, c1)
|
||||||
s.require.Equal(isMatch, true)
|
s.require.Equal(isMatch, true)
|
||||||
s.require.Nil(err)
|
s.require.Nil(err)
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ func (s *MatchTestSuite) TestImmuMatch() {
|
|||||||
Tag: "1.10",
|
Tag: "1.10",
|
||||||
Kind: art.Image,
|
Kind: art.Image,
|
||||||
}
|
}
|
||||||
isMatch, err = match.Match(c2)
|
isMatch, err = match.Match(1, c2)
|
||||||
s.require.Equal(isMatch, false)
|
s.require.Equal(isMatch, false)
|
||||||
s.require.Nil(err)
|
s.require.Nil(err)
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ func (s *MatchTestSuite) TestImmuMatch() {
|
|||||||
Tag: "9.4.8",
|
Tag: "9.4.8",
|
||||||
Kind: art.Image,
|
Kind: art.Image,
|
||||||
}
|
}
|
||||||
isMatch, err = match.Match(c3)
|
isMatch, err = match.Match(1, c3)
|
||||||
s.require.Equal(isMatch, true)
|
s.require.Equal(isMatch, true)
|
||||||
s.require.Nil(err)
|
s.require.Nil(err)
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ func (s *MatchTestSuite) TestImmuMatch() {
|
|||||||
Tag: "world",
|
Tag: "world",
|
||||||
Kind: art.Image,
|
Kind: art.Image,
|
||||||
}
|
}
|
||||||
isMatch, err = match.Match(c4)
|
isMatch, err = match.Match(1, c4)
|
||||||
s.require.Equal(isMatch, false)
|
s.require.Equal(isMatch, false)
|
||||||
s.require.Nil(err)
|
s.require.Nil(err)
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ func isImmutable(c *art.Candidate) bool {
|
|||||||
repo := c.Repository
|
repo := c.Repository
|
||||||
tag := c.Tag
|
tag := c.Tag
|
||||||
_, repoName := utils.ParseRepository(repo)
|
_, repoName := utils.ParseRepository(repo)
|
||||||
matched, err := rule.NewRuleMatcher(projectID).Match(art.Candidate{
|
matched, err := rule.NewRuleMatcher().Match(projectID, art.Candidate{
|
||||||
Repository: repoName,
|
Repository: repoName,
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
NamespaceID: projectID,
|
NamespaceID: projectID,
|
||||||
|
@ -84,7 +84,7 @@ func handleDelete(req *http.Request) error {
|
|||||||
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
var matched bool
|
var matched bool
|
||||||
matched, err = rule.NewRuleMatcher(mf.ProjectID).Match(art.Candidate{
|
matched, err = rule.NewRuleMatcher().Match(mf.ProjectID, art.Candidate{
|
||||||
Repository: repoName,
|
Repository: repoName,
|
||||||
Tag: tag.Name,
|
Tag: tag.Name,
|
||||||
NamespaceID: mf.ProjectID,
|
NamespaceID: mf.ProjectID,
|
||||||
|
@ -47,7 +47,7 @@ func handlePush(req *http.Request) error {
|
|||||||
|
|
||||||
_, repoName := common_util.ParseRepository(mf.Repository)
|
_, repoName := common_util.ParseRepository(mf.Repository)
|
||||||
var matched bool
|
var matched bool
|
||||||
matched, err := rule.NewRuleMatcher(mf.ProjectID).Match(art.Candidate{
|
matched, err := rule.NewRuleMatcher().Match(mf.ProjectID, art.Candidate{
|
||||||
Repository: repoName,
|
Repository: repoName,
|
||||||
Tag: mf.Tag,
|
Tag: mf.Tag,
|
||||||
NamespaceID: mf.ProjectID,
|
NamespaceID: mf.ProjectID,
|
||||||
|
17
src/testing/pkg/immutabletag/matcher.go
Normal file
17
src/testing/pkg/immutabletag/matcher.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package immutabletag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/pkg/art"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeMatcher ...
|
||||||
|
type FakeMatcher struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match ...
|
||||||
|
func (f *FakeMatcher) Match(pid int64, c art.Candidate) (bool, error) {
|
||||||
|
args := f.Called()
|
||||||
|
return args.Bool(0), args.Error(1)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user