add notification middleware (#11072)

the notification is for send out the event after DB transaction complete.
It's safe to send hook as this middleware is after transaction in the response path.

Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2020-03-14 22:34:36 +08:00 committed by GitHub
parent ec31a87884
commit 9cc6e88a65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 153 additions and 0 deletions

View File

@ -16,6 +16,7 @@ package middlewares
import (
"github.com/goharbor/harbor/src/server/middleware/csrf"
"github.com/goharbor/harbor/src/server/middleware/notification"
"github.com/goharbor/harbor/src/server/middleware/readonly"
"net/http"
"path"
@ -76,6 +77,8 @@ func MiddleWares() []beego.MiddleWare {
requestid.Middleware(),
readonly.Middleware(readonlySkippers...),
orm.Middleware(legacyAPISkipper),
// notification must ahead of transaction ensure the DB transaction execution complete
notification.Middleware(),
transaction.Middleware(legacyAPISkipper, fetchBlobAPISkipper),
}
}

View File

@ -1,12 +1,15 @@
package notification
import (
"container/list"
"context"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/notification/hook"
"github.com/goharbor/harbor/src/pkg/notification/job"
jobMgr "github.com/goharbor/harbor/src/pkg/notification/job/manager"
"github.com/goharbor/harbor/src/pkg/notification/policy"
"github.com/goharbor/harbor/src/pkg/notification/policy/manager"
n_event "github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/notifier/model"
)
@ -61,3 +64,33 @@ func initSupportedNotifyType(notifyTypes ...string) {
SupportedNotifyTypes[notifyType] = struct{}{}
}
}
type eventKey struct{}
// EventCtx ...
type EventCtx struct {
Events *list.List
MustNotify bool
}
// NewContext returns new context with event
func NewContext(ctx context.Context, ec *EventCtx) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, eventKey{}, ec)
}
// AddEvent add events into request context, the event will be sent by the notification middleware eventually.
func AddEvent(ctx context.Context, m n_event.Metadata, notify ...bool) {
e, ok := ctx.Value(eventKey{}).(*EventCtx)
if !ok {
log.Debug("request has not event list, cannot add event into context")
return
}
if len(notify) != 0 {
e.MustNotify = notify[0]
}
e.Events.PushBack(m)
return
}

View File

@ -0,0 +1,52 @@
// 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 notification
import (
"container/list"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/server/middleware"
"net/http"
"github.com/goharbor/harbor/src/internal"
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
)
// publishEvent publishes the events in the context, it ensures publish happens after transaction success.
func publishEvent(es *list.List) {
if es == nil {
return
}
for e := es.Front(); e != nil; e = e.Next() {
evt.BuildAndPublish(e.Value.(evt.Metadata))
}
return
}
// Middleware sends the notification after transaction success
func Middleware(skippers ...middleware.Skipper) func(http.Handler) http.Handler {
return middleware.New(func(w http.ResponseWriter, r *http.Request, next http.Handler) {
res := internal.NewResponseRecorder(w)
eveCtx := &notification.EventCtx{
Events: list.New(),
MustNotify: false,
}
ctx := notification.NewContext(r.Context(), eveCtx)
next.ServeHTTP(res, r.WithContext(ctx))
if res.Success() || eveCtx.MustNotify {
publishEvent(eveCtx.Events)
}
}, skippers...)
}

View File

@ -0,0 +1,65 @@
package notification
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/api/event"
pkg_art "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/stretchr/testify/suite"
"net/http"
"net/http/httptest"
"testing"
)
type NotificatoinMiddlewareTestSuite struct {
suite.Suite
}
func (suite *NotificatoinMiddlewareTestSuite) TestMiddleware() {
next := func() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusAccepted)
notification.AddEvent(r.Context(), &event.DeleteArtifactEventMetadata{
Ctx: context.Background(),
Artifact: &pkg_art.Artifact{
ProjectID: 1,
RepositoryID: 2,
RepositoryName: "library/hello-world",
},
Tags: []string{"latest"},
})
})
}
path := fmt.Sprintf("/v2/library/photon/manifests/latest")
req := httptest.NewRequest(http.MethodPatch, path, nil)
res := httptest.NewRecorder()
Middleware()(next()).ServeHTTP(res, req)
suite.Equal(http.StatusAccepted, res.Code)
}
func (suite *NotificatoinMiddlewareTestSuite) TestMiddlewareMustNotify() {
next := func() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
notification.AddEvent(r.Context(), &event.DeleteArtifactEventMetadata{
Ctx: context.Background(),
Artifact: &pkg_art.Artifact{
ProjectID: 1,
RepositoryID: 2,
RepositoryName: "library/hello-world",
},
Tags: []string{"latest"},
}, true)
})
}
path := fmt.Sprintf("/v2/library/photon/manifests/latest")
req := httptest.NewRequest(http.MethodPatch, path, nil)
res := httptest.NewRecorder()
Middleware()(next()).ServeHTTP(res, req)
suite.Equal(http.StatusInternalServerError, res.Code)
}
func TestNotificatoinMiddlewareTestSuite(t *testing.T) {
suite.Run(t, &NotificatoinMiddlewareTestSuite{})
}