-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handler middlewares #1019
Merged
Merged
Handler middlewares #1019
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
b528ab5
Handler middlewares
marefr fb06a1e
setup context in middleware handler
marefr dc37cb5
Merge remote-tracking branch 'origin' into handler_middleware
marefr e980486
Merge remote-tracking branch 'origin' into handler_middleware
marefr 4962c23
Update backend/stream.go
marefr f2a6b93
updates after review
marefr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package backend | ||
|
||
import "context" | ||
|
||
// Handler interface for all handlers. | ||
type Handler interface { | ||
QueryDataHandler | ||
CheckHealthHandler | ||
CallResourceHandler | ||
CollectMetricsHandler | ||
StreamHandler | ||
AdmissionHandler | ||
ConversionHandler | ||
} | ||
|
||
var _ = Handler(&BaseHandler{}) | ||
|
||
// BaseHandler base handler provides a base implementation of Handler interface | ||
// passing the request down the chain to next Handler. | ||
// This allows handlers to avoid implementing the full Handler interface. | ||
type BaseHandler struct { | ||
next Handler | ||
} | ||
|
||
// NewBaseHandler creates a new BaseHandler. | ||
func NewBaseHandler(next Handler) BaseHandler { | ||
return BaseHandler{ | ||
next: next, | ||
} | ||
} | ||
|
||
func (m BaseHandler) QueryData(ctx context.Context, req *QueryDataRequest) (*QueryDataResponse, error) { | ||
return m.next.QueryData(ctx, req) | ||
} | ||
|
||
func (m BaseHandler) CallResource(ctx context.Context, req *CallResourceRequest, sender CallResourceResponseSender) error { | ||
return m.next.CallResource(ctx, req, sender) | ||
} | ||
|
||
func (m BaseHandler) CheckHealth(ctx context.Context, req *CheckHealthRequest) (*CheckHealthResult, error) { | ||
return m.next.CheckHealth(ctx, req) | ||
} | ||
|
||
func (m BaseHandler) CollectMetrics(ctx context.Context, req *CollectMetricsRequest) (*CollectMetricsResult, error) { | ||
return m.next.CollectMetrics(ctx, req) | ||
} | ||
|
||
func (m BaseHandler) SubscribeStream(ctx context.Context, req *SubscribeStreamRequest) (*SubscribeStreamResponse, error) { | ||
return m.next.SubscribeStream(ctx, req) | ||
} | ||
|
||
func (m BaseHandler) PublishStream(ctx context.Context, req *PublishStreamRequest) (*PublishStreamResponse, error) { | ||
return m.next.PublishStream(ctx, req) | ||
} | ||
|
||
func (m BaseHandler) RunStream(ctx context.Context, req *RunStreamRequest, sender *StreamSender) error { | ||
return m.next.RunStream(ctx, req, sender) | ||
} | ||
|
||
func (m BaseHandler) ValidateAdmission(ctx context.Context, req *AdmissionRequest) (*ValidationResponse, error) { | ||
return m.next.ValidateAdmission(ctx, req) | ||
} | ||
|
||
func (m *BaseHandler) MutateAdmission(ctx context.Context, req *AdmissionRequest) (*MutationResponse, error) { | ||
return m.next.MutateAdmission(ctx, req) | ||
} | ||
|
||
func (m *BaseHandler) ConvertObjects(ctx context.Context, req *ConversionRequest) (*ConversionResponse, error) { | ||
return m.next.ConvertObjects(ctx, req) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package backend | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"slices" | ||
) | ||
|
||
var ( | ||
errNilRequest = errors.New("req cannot be nil") | ||
errNilSender = errors.New("sender cannot be nil") | ||
) | ||
|
||
// HandlerMiddleware is an interface representing the ability to create a middleware | ||
// that implements the Handler interface. | ||
type HandlerMiddleware interface { | ||
// CreateHandlerMiddleware creates a new Handler by decorating next Handler. | ||
CreateHandlerMiddleware(next Handler) Handler | ||
} | ||
|
||
// The HandlerMiddlewareFunc type is an adapter to allow the use of ordinary | ||
// functions as HandlerMiddleware's. If f is a function with the appropriate | ||
// signature, HandlerMiddlewareFunc(f) is a HandlerMiddleware that calls f. | ||
type HandlerMiddlewareFunc func(next Handler) Handler | ||
|
||
// CreateHandlerMiddleware implements the HandlerMiddleware interface. | ||
func (fn HandlerMiddlewareFunc) CreateHandlerMiddleware(next Handler) Handler { | ||
return fn(next) | ||
} | ||
|
||
// MiddlewareHandler decorates a Handler with HandlerMiddleware's. | ||
type MiddlewareHandler struct { | ||
middlewares []HandlerMiddleware | ||
finalHandler Handler | ||
} | ||
|
||
// HandlerFromMiddlewares creates a new MiddlewareHandler implementing Handler that decorates finalHandler with middlewares. | ||
func HandlerFromMiddlewares(finalHandler Handler, middlewares ...HandlerMiddleware) (*MiddlewareHandler, error) { | ||
if finalHandler == nil { | ||
return nil, errors.New("finalHandler cannot be nil") | ||
} | ||
|
||
return &MiddlewareHandler{ | ||
middlewares: middlewares, | ||
finalHandler: finalHandler, | ||
}, nil | ||
} | ||
|
||
func (h *MiddlewareHandler) setupContext(ctx context.Context, pluginCtx PluginContext, endpoint Endpoint) context.Context { | ||
ctx = initErrorSource(ctx) | ||
ctx = WithEndpoint(ctx, endpoint) | ||
ctx = WithPluginContext(ctx, pluginCtx) | ||
ctx = WithGrafanaConfig(ctx, pluginCtx.GrafanaConfig) | ||
ctx = WithUser(ctx, pluginCtx.User) | ||
ctx = WithUserAgent(ctx, pluginCtx.UserAgent) | ||
marefr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return ctx | ||
} | ||
|
||
func (h *MiddlewareHandler) QueryData(ctx context.Context, req *QueryDataRequest) (*QueryDataResponse, error) { | ||
if req == nil { | ||
return nil, errNilRequest | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointQueryData) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.QueryData(ctx, req) | ||
} | ||
|
||
func (h MiddlewareHandler) CallResource(ctx context.Context, req *CallResourceRequest, sender CallResourceResponseSender) error { | ||
if req == nil { | ||
return errNilRequest | ||
} | ||
|
||
if sender == nil { | ||
return errNilSender | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointCallResource) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.CallResource(ctx, req, sender) | ||
} | ||
|
||
func (h MiddlewareHandler) CollectMetrics(ctx context.Context, req *CollectMetricsRequest) (*CollectMetricsResult, error) { | ||
if req == nil { | ||
return nil, errNilRequest | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointCollectMetrics) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.CollectMetrics(ctx, req) | ||
} | ||
|
||
func (h MiddlewareHandler) CheckHealth(ctx context.Context, req *CheckHealthRequest) (*CheckHealthResult, error) { | ||
if req == nil { | ||
return nil, errNilRequest | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointCheckHealth) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.CheckHealth(ctx, req) | ||
} | ||
|
||
func (h MiddlewareHandler) SubscribeStream(ctx context.Context, req *SubscribeStreamRequest) (*SubscribeStreamResponse, error) { | ||
if req == nil { | ||
return nil, errNilRequest | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointSubscribeStream) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.SubscribeStream(ctx, req) | ||
} | ||
|
||
func (h MiddlewareHandler) PublishStream(ctx context.Context, req *PublishStreamRequest) (*PublishStreamResponse, error) { | ||
if req == nil { | ||
return nil, errNilRequest | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointPublishStream) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.PublishStream(ctx, req) | ||
} | ||
|
||
func (h MiddlewareHandler) RunStream(ctx context.Context, req *RunStreamRequest, sender *StreamSender) error { | ||
if req == nil { | ||
return errNilRequest | ||
} | ||
|
||
if sender == nil { | ||
return errors.New("sender cannot be nil") | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointRunStream) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.RunStream(ctx, req, sender) | ||
} | ||
|
||
func (h MiddlewareHandler) ValidateAdmission(ctx context.Context, req *AdmissionRequest) (*ValidationResponse, error) { | ||
if req == nil { | ||
return nil, errNilRequest | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointValidateAdmission) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.ValidateAdmission(ctx, req) | ||
} | ||
|
||
func (h MiddlewareHandler) MutateAdmission(ctx context.Context, req *AdmissionRequest) (*MutationResponse, error) { | ||
if req == nil { | ||
return nil, errNilRequest | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointMutateAdmission) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.MutateAdmission(ctx, req) | ||
} | ||
|
||
func (h MiddlewareHandler) ConvertObjects(ctx context.Context, req *ConversionRequest) (*ConversionResponse, error) { | ||
if req == nil { | ||
return nil, errNilRequest | ||
} | ||
|
||
ctx = h.setupContext(ctx, req.PluginContext, EndpointConvertObjects) | ||
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler) | ||
return handler.ConvertObjects(ctx, req) | ||
} | ||
|
||
func handlerFromMiddlewares(middlewares []HandlerMiddleware, finalHandler Handler) Handler { | ||
if len(middlewares) == 0 { | ||
return finalHandler | ||
} | ||
|
||
clonedMws := slices.Clone(middlewares) | ||
slices.Reverse(clonedMws) | ||
next := finalHandler | ||
|
||
for _, m := range clonedMws { | ||
next = m.CreateHandlerMiddleware(next) | ||
} | ||
|
||
return next | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only now when looking at all this when I realize all we really need to pass through for a lot of these is
pluginCtx
since these are all exported fields that could be read as needed 🤦There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah 😄 What do you suggest, remove
WithGrafanaConfig
and usePluginConfigFromContext
inGrafanaConfigFromContext
? Or removeGrafanaConfigFromContext
altogether?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we remove
WithGrafanaConfig
it'll break Grafana core - and all the core plugins would need to be updated.Removing
GrafanaConfigFromContext
will smash a bunch of plugins in the wild.So I guess for now we do nothing, except cry 😢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah 😄 On the positive side, the current way allow tests to use
backend. WithGrafanaConfig
rather than creating a full context.