mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Errors: Update errutil to be compatible with k8s errors (#87605)
This commit is contained in:
parent
60e7a4e746
commit
6d10797812
@ -21,7 +21,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/expr/mathexp"
|
"github.com/grafana/grafana/pkg/expr/mathexp"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/util/errutil"
|
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -105,14 +104,14 @@ func (r *queryREST) Connect(ctx context.Context, name string, opts runtime.Objec
|
|||||||
Message: "datasource not found",
|
Message: "datasource not found",
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
responder.Error(convertToK8sError(err))
|
responder.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actually run the query
|
// Actually run the query
|
||||||
rsp, err := b.execute(ctx, req)
|
rsp, err := b.execute(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responder.Error(convertToK8sError(err))
|
responder.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,20 +121,6 @@ func (r *queryREST) Connect(ctx context.Context, name string, opts runtime.Objec
|
|||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Would be really nice if errutil was directly k8s compatible :(
|
|
||||||
func convertToK8sError(err error) error {
|
|
||||||
var gErr errutil.Error
|
|
||||||
if errors.As(err, &gErr) {
|
|
||||||
return &errorsK8s.StatusError{ErrStatus: metav1.Status{
|
|
||||||
Status: metav1.StatusFailure,
|
|
||||||
Code: int32(gErr.Reason.Status().HTTPStatus()),
|
|
||||||
Reason: metav1.StatusReason(gErr.Reason.Status()), // almost true
|
|
||||||
Message: gErr.PublicMessage,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *QueryAPIBuilder) execute(ctx context.Context, req parsedRequestInfo) (qdr *backend.QueryDataResponse, err error) {
|
func (b *QueryAPIBuilder) execute(ctx context.Context, req parsedRequestInfo) (qdr *backend.QueryDataResponse, err error) {
|
||||||
switch len(req.Requests) {
|
switch len(req.Requests) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -144,7 +144,8 @@ func TestIntegrationSimpleQuery(t *testing.T) {
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"status": "Failure",
|
"status": "Failure",
|
||||||
"message": "did not execute expression [Y] due to a failure to of the dependent expression or query [X]",
|
"message": "did not execute expression [Y] due to a failure to of the dependent expression or query [X]",
|
||||||
"reason": "Bad request",
|
"reason": "BadRequest",
|
||||||
|
"details": { "uid": "sse.dependencyError" },
|
||||||
"code": 400
|
"code": 400
|
||||||
}`, string(body))
|
}`, string(body))
|
||||||
// require.JSONEq(t, `{
|
// require.JSONEq(t, `{
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
@ -23,14 +22,6 @@ type ErrorOptions struct {
|
|||||||
logger log.Logger
|
logger log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type k8sError struct {
|
|
||||||
metav1.Status `json:",inline"`
|
|
||||||
|
|
||||||
// Internal values that do not have a clean home in the standard Status object
|
|
||||||
MessageID string `json:"messageId"`
|
|
||||||
Extra map[string]any `json:"extra,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes an error to the provided [http.ResponseWriter] with the
|
// Write writes an error to the provided [http.ResponseWriter] with the
|
||||||
// appropriate HTTP status and JSON payload from [errutil.Error].
|
// appropriate HTTP status and JSON payload from [errutil.Error].
|
||||||
// Write also logs the provided error to either the "request-errors"
|
// Write also logs the provided error to either the "request-errors"
|
||||||
@ -63,37 +54,9 @@ func Write(ctx context.Context, err error, w http.ResponseWriter, opts ...func(E
|
|||||||
// When running in k8s, this will return a v1 status
|
// When running in k8s, this will return a v1 status
|
||||||
// Typically, k8s handlers should directly support error negotiation, however
|
// Typically, k8s handlers should directly support error negotiation, however
|
||||||
// when implementing handlers directly this will maintain compatibility with client-go
|
// when implementing handlers directly this will maintain compatibility with client-go
|
||||||
info, ok := request.RequestInfoFrom(ctx)
|
_, ok := request.RequestInfoFrom(ctx)
|
||||||
if ok {
|
if ok {
|
||||||
status := &k8sError{
|
rsp = gErr.Status()
|
||||||
Status: metav1.Status{
|
|
||||||
Status: metav1.StatusFailure,
|
|
||||||
Code: int32(pub.StatusCode),
|
|
||||||
Message: pub.Message,
|
|
||||||
Details: &metav1.StatusDetails{
|
|
||||||
Name: info.Name,
|
|
||||||
Group: info.APIGroup,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Add the internal values into
|
|
||||||
MessageID: pub.MessageID,
|
|
||||||
Extra: pub.Extra,
|
|
||||||
}
|
|
||||||
switch pub.StatusCode {
|
|
||||||
case 400:
|
|
||||||
status.Reason = metav1.StatusReasonBadRequest
|
|
||||||
case 401:
|
|
||||||
status.Reason = metav1.StatusReasonUnauthorized
|
|
||||||
case 403:
|
|
||||||
status.Reason = metav1.StatusReasonForbidden
|
|
||||||
case 404:
|
|
||||||
status.Reason = metav1.StatusReasonNotFound
|
|
||||||
case 500: // many reasons things could map here
|
|
||||||
status.Reason = metav1.StatusReasonInternalError
|
|
||||||
case 504:
|
|
||||||
status.Reason = metav1.StatusReasonTimeout
|
|
||||||
}
|
|
||||||
rsp = status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(rsp)
|
err = json.NewEncoder(w).Encode(rsp)
|
||||||
|
@ -27,9 +27,8 @@ func TestWrite(t *testing.T) {
|
|||||||
"status": "Failure",
|
"status": "Failure",
|
||||||
"reason": "Timeout",
|
"reason": "Timeout",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"messageId": "test.thisIsExpected",
|
|
||||||
"message": "Timeout",
|
"message": "Timeout",
|
||||||
"details": { "group": "TestGroup" },
|
"details": { "uid": "test.thisIsExpected" },
|
||||||
"code": 504
|
"code": 504
|
||||||
}`, recorder.Body.String())
|
}`, recorder.Body.String())
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package errutil
|
package errutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
errorsK8s "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Base represents the static information about a specific error.
|
// Base represents the static information about a specific error.
|
||||||
@ -294,6 +299,9 @@ func (b Base) Is(err error) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow errorutil errors to be returned as informative k8s errors
|
||||||
|
var _ = errorsK8s.APIStatus(&Error{})
|
||||||
|
|
||||||
// Error is the error type for errors within Grafana, extending
|
// Error is the error type for errors within Grafana, extending
|
||||||
// the Go error type with Grafana specific metadata to reduce
|
// the Go error type with Grafana specific metadata to reduce
|
||||||
// boilerplate error handling for status codes and internationalization
|
// boilerplate error handling for status codes and internationalization
|
||||||
@ -365,6 +373,34 @@ func (e Error) Error() string {
|
|||||||
return fmt.Sprintf("[%s] %s", e.MessageID, e.LogMessage)
|
return fmt.Sprintf("[%s] %s", e.MessageID, e.LogMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When the error is rendered by an apiserver, this format is used
|
||||||
|
func (e Error) Status() metav1.Status {
|
||||||
|
public := e.Public()
|
||||||
|
s := metav1.Status{
|
||||||
|
Status: metav1.StatusFailure,
|
||||||
|
Code: int32(public.StatusCode),
|
||||||
|
Reason: metav1.StatusReason(e.Reason.Status()), // almost true
|
||||||
|
Message: public.Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shove the extra data into details
|
||||||
|
if public.Extra != nil || public.MessageID != "" {
|
||||||
|
s.Details = &metav1.StatusDetails{
|
||||||
|
UID: types.UID(public.MessageID),
|
||||||
|
}
|
||||||
|
for k, v := range public.Extra {
|
||||||
|
v, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
s.Details.Causes = append(s.Details.Causes, metav1.StatusCause{
|
||||||
|
Field: k,
|
||||||
|
Message: string(v),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// Unwrap is used by errors.As to iterate over the sequence of
|
// Unwrap is used by errors.As to iterate over the sequence of
|
||||||
// underlying errors until a matching type is found.
|
// underlying errors until a matching type is found.
|
||||||
func (e Error) Unwrap() error {
|
func (e Error) Unwrap() error {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package errutil
|
package errutil
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// StatusUnknown implies an error that should be updated to contain
|
// StatusUnknown implies an error that should be updated to contain
|
||||||
@ -11,15 +15,15 @@ const (
|
|||||||
// client's authentication, either because it has not been provided
|
// client's authentication, either because it has not been provided
|
||||||
// or is invalid for the operation.
|
// or is invalid for the operation.
|
||||||
// HTTP status code 401.
|
// HTTP status code 401.
|
||||||
StatusUnauthorized CoreStatus = "Unauthorized"
|
StatusUnauthorized CoreStatus = CoreStatus(metav1.StatusReasonUnauthorized)
|
||||||
// StatusForbidden means that the server refuses to perform the
|
// StatusForbidden means that the server refuses to perform the
|
||||||
// requested action for the authenticated uer.
|
// requested action for the authenticated uer.
|
||||||
// HTTP status code 403.
|
// HTTP status code 403.
|
||||||
StatusForbidden CoreStatus = "Forbidden"
|
StatusForbidden CoreStatus = CoreStatus(metav1.StatusReasonForbidden)
|
||||||
// StatusNotFound means that the server does not have any
|
// StatusNotFound means that the server does not have any
|
||||||
// corresponding document to return to the request.
|
// corresponding document to return to the request.
|
||||||
// HTTP status code 404.
|
// HTTP status code 404.
|
||||||
StatusNotFound CoreStatus = "Not found"
|
StatusNotFound CoreStatus = CoreStatus(metav1.StatusReasonNotFound)
|
||||||
// StatusUnprocessableEntity means that the server understands the request,
|
// StatusUnprocessableEntity means that the server understands the request,
|
||||||
// the content type and the syntax but it was unable to process the
|
// the content type and the syntax but it was unable to process the
|
||||||
// contained instructions.
|
// contained instructions.
|
||||||
@ -28,15 +32,15 @@ const (
|
|||||||
// StatusConflict means that the server cannot fulfill the request
|
// StatusConflict means that the server cannot fulfill the request
|
||||||
// there is a conflict in the current state of a resource
|
// there is a conflict in the current state of a resource
|
||||||
// HTTP status code 409.
|
// HTTP status code 409.
|
||||||
StatusConflict CoreStatus = "Conflict"
|
StatusConflict CoreStatus = CoreStatus(metav1.StatusReasonConflict)
|
||||||
// StatusTooManyRequests means that the client is rate limited
|
// StatusTooManyRequests means that the client is rate limited
|
||||||
// by the server and should back-off before trying again.
|
// by the server and should back-off before trying again.
|
||||||
// HTTP status code 429.
|
// HTTP status code 429.
|
||||||
StatusTooManyRequests CoreStatus = "Too many requests"
|
StatusTooManyRequests CoreStatus = CoreStatus(metav1.StatusReasonTooManyRequests)
|
||||||
// StatusBadRequest means that the server was unable to parse the
|
// StatusBadRequest means that the server was unable to parse the
|
||||||
// parameters or payload for the request.
|
// parameters or payload for the request.
|
||||||
// HTTP status code 400.
|
// HTTP status code 400.
|
||||||
StatusBadRequest CoreStatus = "Bad request"
|
StatusBadRequest CoreStatus = CoreStatus(metav1.StatusReasonBadRequest)
|
||||||
// StatusClientClosedRequest means that a client closes the connection
|
// StatusClientClosedRequest means that a client closes the connection
|
||||||
// while the server is processing the request.
|
// while the server is processing the request.
|
||||||
//
|
//
|
||||||
@ -52,11 +56,11 @@ const (
|
|||||||
// StatusInternal means that the server acknowledges that there's
|
// StatusInternal means that the server acknowledges that there's
|
||||||
// an error, but that there is nothing the client can do to fix it.
|
// an error, but that there is nothing the client can do to fix it.
|
||||||
// HTTP status code 500.
|
// HTTP status code 500.
|
||||||
StatusInternal CoreStatus = "Internal server error"
|
StatusInternal CoreStatus = CoreStatus(metav1.StatusReasonInternalError)
|
||||||
// StatusTimeout means that the server did not complete the request
|
// StatusTimeout means that the server did not complete the request
|
||||||
// within the required time and aborted the action.
|
// within the required time and aborted the action.
|
||||||
// HTTP status code 504.
|
// HTTP status code 504.
|
||||||
StatusTimeout CoreStatus = "Timeout"
|
StatusTimeout CoreStatus = CoreStatus(metav1.StatusReasonTimeout)
|
||||||
// StatusNotImplemented means that the server does not support the
|
// StatusNotImplemented means that the server does not support the
|
||||||
// requested action. Typically used during development of new
|
// requested action. Typically used during development of new
|
||||||
// features.
|
// features.
|
||||||
@ -83,7 +87,7 @@ type StatusReason interface {
|
|||||||
Status() CoreStatus
|
Status() CoreStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
type CoreStatus string
|
type CoreStatus metav1.StatusReason
|
||||||
|
|
||||||
// Status implements the StatusReason interface.
|
// Status implements the StatusReason interface.
|
||||||
func (s CoreStatus) Status() CoreStatus {
|
func (s CoreStatus) Status() CoreStatus {
|
||||||
|
Loading…
Reference in New Issue
Block a user