package resource import ( "errors" "net/http" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" grpcstatus "google.golang.org/grpc/status" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/util/scheduler" ) // Package-level errors. var ( ErrOptimisticLockingFailed = errors.New("optimistic locking failed") ErrNotImplementedYet = errors.New("not implemented yet") ) var ( ErrResourceAlreadyExists error = &apierrors.StatusError{ ErrStatus: metav1.Status{ Status: metav1.StatusFailure, Reason: metav1.StatusReasonAlreadyExists, Message: "the resource already exists", Code: http.StatusConflict, }, } ) func NewBadRequestError(msg string) *resourcepb.ErrorResult { return &resourcepb.ErrorResult{ Message: msg, Code: http.StatusBadRequest, Reason: string(metav1.StatusReasonBadRequest), } } func NewNotFoundError(key *resourcepb.ResourceKey) *resourcepb.ErrorResult { return &resourcepb.ErrorResult{ Code: http.StatusNotFound, Details: &resourcepb.ErrorDetails{ Group: key.Group, Kind: key.Resource, // yup, resource as kind same is true in apierrors.NewNotFound() Name: key.Name, }, } } func NewTooManyRequestsError(msg string) *resourcepb.ErrorResult { return &resourcepb.ErrorResult{ Message: msg, Code: http.StatusTooManyRequests, Reason: string(metav1.StatusReasonTooManyRequests), } } // Convert golang errors to status result errors that can be returned to a client func AsErrorResult(err error) *resourcepb.ErrorResult { if err == nil { return nil } var apistatus apierrors.APIStatus if errors.As(err, &apistatus) { s := apistatus.Status() res := &resourcepb.ErrorResult{ Message: s.Message, Reason: string(s.Reason), Code: s.Code, } if s.Details != nil { res.Details = &resourcepb.ErrorDetails{ Group: s.Details.Group, Kind: s.Details.Kind, Name: s.Details.Name, Uid: string(s.Details.UID), RetryAfterSeconds: s.Details.RetryAfterSeconds, } for _, c := range s.Details.Causes { res.Details.Causes = append(res.Details.Causes, &resourcepb.ErrorCause{ Reason: string(c.Type), Message: c.Message, Field: c.Field, }) } } return res } code := 500 st, ok := grpcstatus.FromError(err) if ok { code = runtime.HTTPStatusFromCode(st.Code()) } return &resourcepb.ErrorResult{ Message: err.Error(), Code: int32(code), } } func GetError(res *resourcepb.ErrorResult) error { if res == nil { return nil } status := &apierrors.StatusError{ErrStatus: metav1.Status{ Status: metav1.StatusFailure, Code: res.Code, Reason: metav1.StatusReason(res.Reason), Message: res.Message, }} if res.Details != nil { status.ErrStatus.Details = &metav1.StatusDetails{ Group: res.Details.Group, Kind: res.Details.Kind, Name: res.Details.Name, UID: types.UID(res.Details.Uid), RetryAfterSeconds: res.Details.RetryAfterSeconds, } for _, c := range res.Details.Causes { status.ErrStatus.Details.Causes = append(status.ErrStatus.Details.Causes, metav1.StatusCause{ Type: metav1.CauseType(c.Reason), Message: c.Message, Field: c.Field, }) } } return status } func HandleQueueError[T any](err error, makeResp func(*resourcepb.ErrorResult) *T) (*T, error) { if errors.Is(err, scheduler.ErrTenantQueueFull) { return makeResp(NewTooManyRequestsError("tenant queue is full, please try again later")), nil } return makeResp(AsErrorResult(err)), nil }