|
|
|
package util
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/k3s-io/k3s/pkg/generated/clientset/versioned/scheme"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
|
|
|
)
|
|
|
|
|
|
|
|
var ErrAPINotReady = errors.New("apiserver not ready")
|
|
|
|
var ErrAPIDisabled = errors.New("apiserver disabled")
|
|
|
|
var ErrCoreNotReady = errors.New("runtime core not ready")
|
|
|
|
|
|
|
|
// SendErrorWithID sends and logs a random error ID so that logs can be correlated
|
|
|
|
// between the REST API (which does not provide any detailed error output, to avoid
|
|
|
|
// information disclosure) and the server logs.
|
|
|
|
func SendErrorWithID(err error, component string, resp http.ResponseWriter, req *http.Request, status ...int) {
|
|
|
|
errID, _ := rand.Int(rand.Reader, big.NewInt(99999))
|
|
|
|
logrus.Errorf("%s error ID %05d: %v", component, errID, err)
|
|
|
|
SendError(fmt.Errorf("%s error ID %05d", component, errID), resp, req, status...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendError sends a properly formatted error response
|
|
|
|
func SendError(err error, resp http.ResponseWriter, req *http.Request, status ...int) {
|
|
|
|
var code int
|
|
|
|
if len(status) == 1 {
|
|
|
|
code = status[0]
|
|
|
|
}
|
|
|
|
if code == 0 || code == http.StatusOK {
|
|
|
|
code = http.StatusInternalServerError
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't log "apiserver not ready" or "apiserver disabled" errors, they are frequent during startup
|
|
|
|
if !errors.Is(err, ErrAPINotReady) && !errors.Is(err, ErrAPIDisabled) {
|
|
|
|
logrus.Errorf("Sending HTTP %d response to %s: %v", code, req.RemoteAddr, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var serr *apierrors.StatusError
|
|
|
|
switch code {
|
|
|
|
case http.StatusBadRequest:
|
|
|
|
serr = apierrors.NewBadRequest(err.Error())
|
|
|
|
case http.StatusUnauthorized:
|
|
|
|
serr = apierrors.NewUnauthorized(err.Error())
|
|
|
|
case http.StatusForbidden:
|
|
|
|
serr = newForbidden(err)
|
|
|
|
case http.StatusInternalServerError:
|
|
|
|
serr = apierrors.NewInternalError(err)
|
|
|
|
case http.StatusBadGateway:
|
|
|
|
serr = newBadGateway(err)
|
|
|
|
case http.StatusServiceUnavailable:
|
|
|
|
serr = apierrors.NewServiceUnavailable(err.Error())
|
|
|
|
default:
|
|
|
|
serr = apierrors.NewGenericServerResponse(code, req.Method, schema.GroupResource{}, req.URL.Path, err.Error(), 0, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
responsewriters.ErrorNegotiated(serr, scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newForbidden(err error) *apierrors.StatusError {
|
|
|
|
return &apierrors.StatusError{
|
|
|
|
ErrStatus: metav1.Status{
|
|
|
|
Status: metav1.StatusFailure,
|
|
|
|
Code: http.StatusForbidden,
|
|
|
|
Reason: metav1.StatusReasonForbidden,
|
|
|
|
Message: err.Error(),
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newBadGateway(err error) *apierrors.StatusError {
|
|
|
|
return &apierrors.StatusError{
|
|
|
|
ErrStatus: metav1.Status{
|
|
|
|
Status: metav1.StatusFailure,
|
|
|
|
Code: http.StatusBadGateway,
|
|
|
|
Reason: metav1.StatusReasonInternalError,
|
|
|
|
Message: err.Error(),
|
|
|
|
}}
|
|
|
|
}
|