mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-25 19:56:09 +01:00
add immutable match in the repository/tag delete api
Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
71bb8815bf
commit
424f11e697
@ -1089,6 +1089,8 @@ paths:
|
||||
description: Forbidden.
|
||||
'404':
|
||||
description: Repository not found.
|
||||
'412':
|
||||
description: Precondition Failed.
|
||||
put:
|
||||
summary: Update description of the repository.
|
||||
description: |
|
||||
|
@ -73,6 +73,7 @@ type TagDetail struct {
|
||||
Author string `json:"author"`
|
||||
Created time.Time `json:"created"`
|
||||
Config *TagCfg `json:"config"`
|
||||
Immutable bool `json:"immutable"`
|
||||
}
|
||||
|
||||
// TagCfg ...
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scan/api/scan"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
@ -45,6 +45,8 @@ import (
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
notifierEvt "github.com/goharbor/harbor/src/core/notifier/event"
|
||||
coreutils "github.com/goharbor/harbor/src/core/utils"
|
||||
"github.com/goharbor/harbor/src/pkg/art"
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
"github.com/goharbor/harbor/src/replication"
|
||||
"github.com/goharbor/harbor/src/replication/event"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
@ -283,11 +285,6 @@ func (ra *RepositoryAPI) Delete() {
|
||||
}
|
||||
|
||||
for _, t := range tags {
|
||||
image := fmt.Sprintf("%s:%s", repoName, t)
|
||||
if err = dao.DeleteLabelsOfResource(common.ResourceTypeImage, image); err != nil {
|
||||
ra.SendInternalServerError(fmt.Errorf("failed to delete labels of image %s: %v", image, err))
|
||||
return
|
||||
}
|
||||
if err = rc.DeleteTag(t); err != nil {
|
||||
if regErr, ok := err.(*commonhttp.Error); ok {
|
||||
if regErr.Code == http.StatusNotFound {
|
||||
@ -298,6 +295,11 @@ func (ra *RepositoryAPI) Delete() {
|
||||
return
|
||||
}
|
||||
log.Infof("delete tag: %s:%s", repoName, t)
|
||||
image := fmt.Sprintf("%s:%s", repoName, t)
|
||||
if err = dao.DeleteLabelsOfResource(common.ResourceTypeImage, image); err != nil {
|
||||
ra.SendInternalServerError(fmt.Errorf("failed to delete labels of image %s: %v", image, err))
|
||||
return
|
||||
}
|
||||
|
||||
go func(tag string) {
|
||||
e := &event.Event{
|
||||
@ -711,6 +713,9 @@ func assembleTag(c chan *models.TagResp, client *registry.Repository, projectID
|
||||
}
|
||||
}
|
||||
|
||||
// get immutable status
|
||||
item.Immutable = isImmutable(projectID, repository, tag)
|
||||
|
||||
c <- item
|
||||
}
|
||||
|
||||
@ -791,6 +796,21 @@ func populateAuthor(detail *models.TagDetail) {
|
||||
}
|
||||
}
|
||||
|
||||
// check whether the tag is immutable
|
||||
func isImmutable(projectID int64, repo string, tag string) bool {
|
||||
_, repoName := utils.ParseRepository(repo)
|
||||
matched, err := rule.NewRuleMatcher(projectID).Match(art.Candidate{
|
||||
Repository: repoName,
|
||||
Tag: tag,
|
||||
NamespaceID: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return false
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// GetManifests returns the manifest of a tag
|
||||
func (ra *RepositoryAPI) GetManifests() {
|
||||
repoName := ra.GetString(":splat")
|
||||
|
@ -35,4 +35,4 @@ var ChartMiddlewares = []string{CHART}
|
||||
var Middlewares = []string{READONLY, URL, MUITIPLEMANIFEST, LISTREPO, CONTENTTRUST, VULNERABLE, SIZEQUOTA, IMMUTABLE, COUNTQUOTA}
|
||||
|
||||
// MiddlewaresLocal ...
|
||||
var MiddlewaresLocal = []string{SIZEQUOTA, COUNTQUOTA}
|
||||
var MiddlewaresLocal = []string{SIZEQUOTA, IMMUTABLE, COUNTQUOTA}
|
||||
|
54
src/core/middlewares/immutable/builder.go
Normal file
54
src/core/middlewares/immutable/builder.go
Normal file
@ -0,0 +1,54 @@
|
||||
package immutable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor/immutable"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultBuilders = []interceptor.Builder{
|
||||
&manifestDeletionBuilder{},
|
||||
&manifestCreationBuilder{},
|
||||
}
|
||||
)
|
||||
|
||||
type manifestDeletionBuilder struct{}
|
||||
|
||||
func (*manifestDeletionBuilder) Build(req *http.Request) (interceptor.Interceptor, error) {
|
||||
if match, _, _ := util.MatchDeleteManifest(req); !match {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
info, ok := util.ManifestInfoFromContext(req.Context())
|
||||
if !ok {
|
||||
var err error
|
||||
info, err = util.ParseManifestInfoFromPath(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse manifest, error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return immutable.NewDeleteMFInteceptor(info), nil
|
||||
}
|
||||
|
||||
type manifestCreationBuilder struct{}
|
||||
|
||||
func (*manifestCreationBuilder) Build(req *http.Request) (interceptor.Interceptor, error) {
|
||||
if match, _, _ := util.MatchPushManifest(req); !match {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
info, ok := util.ManifestInfoFromContext(req.Context())
|
||||
if !ok {
|
||||
var err error
|
||||
info, err = util.ParseManifestInfoFromReq(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse manifest, error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return immutable.NewPushMFInteceptor(info), nil
|
||||
}
|
@ -16,78 +16,74 @@ package immutable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
common_util "github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
"github.com/goharbor/harbor/src/pkg/art"
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type immutableHandler struct {
|
||||
next http.Handler
|
||||
builders []interceptor.Builder
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// New ...
|
||||
func New(next http.Handler) http.Handler {
|
||||
func New(next http.Handler, builders ...interceptor.Builder) http.Handler {
|
||||
if len(builders) == 0 {
|
||||
builders = defaultBuilders
|
||||
}
|
||||
|
||||
return &immutableHandler{
|
||||
next: next,
|
||||
builders: builders,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP ...
|
||||
func (rh immutableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if match, _, _ := util.MatchPushManifest(req); !match {
|
||||
func (rh *immutableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
interceptor, err := rh.getInterceptor(req)
|
||||
if err != nil {
|
||||
log.Warningf("Error occurred when to handle request in immutable handler: %v", err)
|
||||
http.Error(rw, util.MarshalError("InternalError", fmt.Sprintf("Error occurred when to handle request in immutable handler: %v", err)),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if interceptor == nil {
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
info, ok := util.ManifestInfoFromContext(req.Context())
|
||||
if !ok {
|
||||
var err error
|
||||
info, err = util.ParseManifestInfoFromPath(req)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
|
||||
if err := interceptor.HandleRequest(req); err != nil {
|
||||
log.Warningf("Error occurred when to handle request in immutable handler: %v", err)
|
||||
if _, ok := err.(middlerware_err.ErrImmutable); ok {
|
||||
http.Error(rw, util.MarshalError("DENIED",
|
||||
fmt.Sprintf("The tag is immutable, cannot be overwrite: %v", err)), http.StatusPreconditionFailed)
|
||||
return
|
||||
}
|
||||
http.Error(rw, util.MarshalError("InternalError", fmt.Sprintf("Error occurred when to handle request in immutable handler: %v", err)),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
|
||||
interceptor.HandleResponse(rw, req)
|
||||
}
|
||||
|
||||
func (rh *immutableHandler) getInterceptor(req *http.Request) (interceptor.Interceptor, error) {
|
||||
for _, builder := range rh.builders {
|
||||
interceptor, err := builder.Build(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if interceptor != nil {
|
||||
return interceptor, nil
|
||||
}
|
||||
}
|
||||
|
||||
_, repoName := common_util.ParseRepository(info.Repository)
|
||||
matched, err := rule.NewRuleMatcher(info.ProjectID).Match(art.Candidate{
|
||||
Repository: repoName,
|
||||
Tag: info.Tag,
|
||||
NamespaceID: info.ProjectID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
if !matched {
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
artifactQuery := &models.ArtifactQuery{
|
||||
PID: info.ProjectID,
|
||||
Repo: info.Repository,
|
||||
Tag: info.Tag,
|
||||
}
|
||||
afs, err := dao.ListArtifacts(artifactQuery)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
if len(afs) == 0 {
|
||||
rh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
// rule matched and non-existent is a immutable tag
|
||||
http.Error(rw, util.MarshalError("DENIED",
|
||||
fmt.Sprintf("The tag:%s:%s is immutable, cannot be overwrite.", info.Repository, info.Tag)), http.StatusPreconditionFailed)
|
||||
return
|
||||
return nil, nil
|
||||
}
|
||||
|
65
src/core/middlewares/interceptor/immutable/deletemf.go
Normal file
65
src/core/middlewares/interceptor/immutable/deletemf.go
Normal file
@ -0,0 +1,65 @@
|
||||
package immutable
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
common_util "github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
|
||||
"github.com/goharbor/harbor/src/pkg/art"
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewDeleteMFInteceptor ....
|
||||
func NewDeleteMFInteceptor(mf *util.ManifestInfo) interceptor.Interceptor {
|
||||
return &delmfInterceptor{
|
||||
mf: mf,
|
||||
}
|
||||
}
|
||||
|
||||
type delmfInterceptor struct {
|
||||
mf *util.ManifestInfo
|
||||
}
|
||||
|
||||
// HandleRequest ...
|
||||
func (dmf *delmfInterceptor) HandleRequest(req *http.Request) (err error) {
|
||||
|
||||
artifactQuery := &models.ArtifactQuery{
|
||||
Digest: dmf.mf.Digest,
|
||||
}
|
||||
var afs []*models.Artifact
|
||||
afs, err = dao.ListArtifacts(artifactQuery)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if len(afs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, af := range afs {
|
||||
_, repoName := common_util.ParseRepository(dmf.mf.Repository)
|
||||
var matched bool
|
||||
matched, err = rule.NewRuleMatcher(dmf.mf.ProjectID).Match(art.Candidate{
|
||||
Repository: repoName,
|
||||
Tag: af.Tag,
|
||||
NamespaceID: dmf.mf.ProjectID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if matched {
|
||||
return middlerware_err.NewErrImmutable(repoName)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// HandleRequest ...
|
||||
func (dmf *delmfInterceptor) HandleResponse(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
65
src/core/middlewares/interceptor/immutable/pushmf.go
Normal file
65
src/core/middlewares/interceptor/immutable/pushmf.go
Normal file
@ -0,0 +1,65 @@
|
||||
package immutable
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
common_util "github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/interceptor"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
middlerware_err "github.com/goharbor/harbor/src/core/middlewares/util/error"
|
||||
"github.com/goharbor/harbor/src/pkg/art"
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewPushMFInteceptor ....
|
||||
func NewPushMFInteceptor(mf *util.ManifestInfo) interceptor.Interceptor {
|
||||
return &pushmfInterceptor{
|
||||
mf: mf,
|
||||
}
|
||||
}
|
||||
|
||||
type pushmfInterceptor struct {
|
||||
mf *util.ManifestInfo
|
||||
}
|
||||
|
||||
// HandleRequest ...
|
||||
func (pmf *pushmfInterceptor) HandleRequest(req *http.Request) (err error) {
|
||||
|
||||
_, repoName := common_util.ParseRepository(pmf.mf.Repository)
|
||||
var matched bool
|
||||
matched, err = rule.NewRuleMatcher(pmf.mf.ProjectID).Match(art.Candidate{
|
||||
Repository: repoName,
|
||||
Tag: pmf.mf.Tag,
|
||||
NamespaceID: pmf.mf.ProjectID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if !matched {
|
||||
return
|
||||
}
|
||||
|
||||
artifactQuery := &models.ArtifactQuery{
|
||||
PID: pmf.mf.ProjectID,
|
||||
Repo: pmf.mf.Repository,
|
||||
Tag: pmf.mf.Tag,
|
||||
}
|
||||
var afs []*models.Artifact
|
||||
afs, err = dao.ListArtifacts(artifactQuery)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if len(afs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return middlerware_err.NewErrImmutable(repoName)
|
||||
}
|
||||
|
||||
// HandleRequest ...
|
||||
func (pmf *pushmfInterceptor) HandleResponse(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
20
src/core/middlewares/util/error/immutable.go
Normal file
20
src/core/middlewares/util/error/immutable.go
Normal file
@ -0,0 +1,20 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrImmutable ...
|
||||
type ErrImmutable struct {
|
||||
repo string
|
||||
}
|
||||
|
||||
// Error ...
|
||||
func (ei ErrImmutable) Error() string {
|
||||
return fmt.Sprintf("Failed to process request, due to immutable. '%s'", ei.repo)
|
||||
}
|
||||
|
||||
// NewErrImmutable ...
|
||||
func NewErrImmutable(msg string) ErrImmutable {
|
||||
return ErrImmutable{repo: msg}
|
||||
}
|
Loading…
Reference in New Issue
Block a user