mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-18 14:47:38 +01:00
add get manifest information middleware in new v2 hanlder
To add a new middleware to resolve the manifest information from request path, then pass it into the request context for later use. Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
c4dd6c077e
commit
bffbc4009b
74
src/server/middleware/manifestinfo/manifest_info.go
Normal file
74
src/server/middleware/manifestinfo/manifest_info.go
Normal file
@ -0,0 +1,74 @@
|
||||
package manifestinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||
project2 "github.com/goharbor/harbor/src/pkg/project"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
reg_err "github.com/goharbor/harbor/src/server/registry/error"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
manifestURLRe = regexp.MustCompile(`^/v2/((?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)+)manifests/([\w][\w.:-]{0,127})`)
|
||||
)
|
||||
|
||||
// Middleware gets the manifest information from request and inject it into the context
|
||||
func Middleware() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
mf, err := parseManifestInfoFromPath(req)
|
||||
if err != nil {
|
||||
reg_err.Handle(rw, req, err)
|
||||
return
|
||||
}
|
||||
*req = *(req.WithContext(middleware.NewManifestInfoContext(req.Context(), mf)))
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// parseManifestInfoFromPath parse manifest from request path
|
||||
func parseManifestInfoFromPath(req *http.Request) (*middleware.ManifestInfo, error) {
|
||||
match, repository, reference := MatchManifestURL(req)
|
||||
if !match {
|
||||
return nil, fmt.Errorf("not match url %s for manifest", req.URL.Path)
|
||||
}
|
||||
|
||||
projectName, _ := utils.ParseRepository(repository)
|
||||
project, err := project2.Mgr.Get(projectName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get project %s, error: %v", projectName, err)
|
||||
}
|
||||
if project == nil {
|
||||
return nil, ierror.NotFoundError(nil).WithMessage("project %s not found", projectName)
|
||||
}
|
||||
|
||||
info := &middleware.ManifestInfo{
|
||||
ProjectID: project.ProjectID,
|
||||
Repository: repository,
|
||||
}
|
||||
|
||||
dgt, err := digest.Parse(reference)
|
||||
if err != nil {
|
||||
info.Tag = reference
|
||||
} else {
|
||||
info.Digest = dgt.String()
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// MatchManifestURL ...
|
||||
func MatchManifestURL(req *http.Request) (bool, string, string) {
|
||||
s := manifestURLRe.FindStringSubmatch(req.URL.Path)
|
||||
if len(s) == 3 {
|
||||
s[1] = strings.TrimSuffix(s[1], "/")
|
||||
return true, s[1], s[2]
|
||||
}
|
||||
return false, "", ""
|
||||
}
|
104
src/server/middleware/manifestinfo/manifest_info_test.go
Normal file
104
src/server/middleware/manifestinfo/manifest_info_test.go
Normal file
@ -0,0 +1,104 @@
|
||||
package manifestinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/utils/test"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type mfinfoTestSuite struct {
|
||||
suite.Suite
|
||||
require *require.Assertions
|
||||
assert *assert.Assertions
|
||||
}
|
||||
|
||||
func (t *mfinfoTestSuite) SetupSuite() {
|
||||
t.require = require.New(t.T())
|
||||
t.assert = assert.New(t.T())
|
||||
test.InitDatabaseFromEnv()
|
||||
}
|
||||
|
||||
func (t *mfinfoTestSuite) TestParseManifestInfoFromPath() {
|
||||
mustRequest := func(method, url string) *http.Request {
|
||||
req, _ := http.NewRequest(method, url, nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type args struct {
|
||||
req *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *middleware.ManifestInfo
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"ok for digest",
|
||||
args{mustRequest(http.MethodDelete, "/v2/library/photon/manifests/sha256:3e17b60ab9d92d953fb8ebefa25624c0d23fb95f78dde5572285d10158044059")},
|
||||
&middleware.ManifestInfo{
|
||||
ProjectID: 1,
|
||||
Repository: "library/photon",
|
||||
Digest: "sha256:3e17b60ab9d92d953fb8ebefa25624c0d23fb95f78dde5572285d10158044059",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"ok for tag",
|
||||
args{mustRequest(http.MethodDelete, "/v2/library/photon/manifests/latest")},
|
||||
&middleware.ManifestInfo{
|
||||
ProjectID: 1,
|
||||
Repository: "library/photon",
|
||||
Tag: "latest",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"project not found",
|
||||
args{mustRequest(http.MethodDelete, "/v2/notfound/photon/manifests/latest")},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"url not match",
|
||||
args{mustRequest(http.MethodDelete, "/v2/library/photon/manifest/latest")},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func() {
|
||||
got, err := parseManifestInfoFromPath(tt.args.req)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf(err, fmt.Sprintf("ParseManifestInfoFromPath() error = %v, wantErr %v", err, tt.wantErr))
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf(err, fmt.Sprintf("ParseManifestInfoFromPath() = %v, want %v", got, tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *mfinfoTestSuite) TestResolveManifest() {
|
||||
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodDelete, "/v2/library/hello-world/manifests/latest", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
Middleware()(next).ServeHTTP(rec, req)
|
||||
t.assert.Equal(rec.Code, http.StatusOK)
|
||||
|
||||
mf, ok := middleware.ManifestInfoFromContext(req.Context())
|
||||
t.assert.True(ok)
|
||||
t.assert.Equal(mf.Tag, "latest")
|
||||
t.assert.Equal(mf.ProjectID, int64(1))
|
||||
}
|
31
src/server/middleware/util.go
Normal file
31
src/server/middleware/util.go
Normal file
@ -0,0 +1,31 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
// manifestInfoKey the context key for manifest info
|
||||
manifestInfoKey = contextKey("ManifestInfo")
|
||||
)
|
||||
|
||||
// ManifestInfo ...
|
||||
type ManifestInfo struct {
|
||||
ProjectID int64
|
||||
Repository string
|
||||
Tag string
|
||||
Digest string
|
||||
}
|
||||
|
||||
// NewManifestInfoContext returns context with manifest info
|
||||
func NewManifestInfoContext(ctx context.Context, info *ManifestInfo) context.Context {
|
||||
return context.WithValue(ctx, manifestInfoKey, info)
|
||||
}
|
||||
|
||||
// ManifestInfoFromContext returns manifest info from context
|
||||
func ManifestInfoFromContext(ctx context.Context) (*ManifestInfo, bool) {
|
||||
info, ok := ctx.Value(manifestInfoKey).(*ManifestInfo)
|
||||
return info, ok
|
||||
}
|
Loading…
Reference in New Issue
Block a user