open-vault/api/response.go
hghaf099 90c5b3c1c5
VAULT-1303 when a request to vault fails, show namespace if set (#12196)
* VAULT-1303 when a request to vault fails, show namespace if set

* Adding changelog

* Fix Changelog file name

* Set namespace in ResponseWriter headers if it is set

* Using consts.NamespaceHeaderName instead of the literal string
2021-07-30 12:32:05 -04:00

134 lines
3.7 KiB
Go

package api
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
)
// Response is a raw response that wraps an HTTP response.
type Response struct {
*http.Response
}
// DecodeJSON will decode the response body to a JSON structure. This
// will consume the response body, but will not close it. Close must
// still be called.
func (r *Response) DecodeJSON(out interface{}) error {
return jsonutil.DecodeJSONFromReader(r.Body, out)
}
// Error returns an error response if there is one. If there is an error,
// this will fully consume the response body, but will not close it. The
// body must still be closed manually.
func (r *Response) Error() error {
// 200 to 399 are okay status codes. 429 is the code for health status of
// standby nodes, otherwise, 429 is treated as quota limit reached.
if (r.StatusCode >= 200 && r.StatusCode < 400) || (r.StatusCode == 429 && r.Request.URL.Path == "/v1/sys/health") {
return nil
}
// We have an error. Let's copy the body into our own buffer first,
// so that if we can't decode JSON, we can at least copy it raw.
bodyBuf := &bytes.Buffer{}
if _, err := io.Copy(bodyBuf, r.Body); err != nil {
return err
}
r.Body.Close()
r.Body = ioutil.NopCloser(bodyBuf)
ns := r.Header.Get(consts.NamespaceHeaderName)
// Build up the error object
respErr := &ResponseError{
HTTPMethod: r.Request.Method,
URL: r.Request.URL.String(),
StatusCode: r.StatusCode,
NamespacePath: ns,
}
// Decode the error response if we can. Note that we wrap the bodyBuf
// in a bytes.Reader here so that the JSON decoder doesn't move the
// read pointer for the original buffer.
var resp ErrorResponse
if err := jsonutil.DecodeJSON(bodyBuf.Bytes(), &resp); err != nil {
// Store the fact that we couldn't decode the errors
respErr.RawError = true
respErr.Errors = []string{bodyBuf.String()}
} else {
// Store the decoded errors
respErr.Errors = resp.Errors
}
return respErr
}
// ErrorResponse is the raw structure of errors when they're returned by the
// HTTP API.
type ErrorResponse struct {
Errors []string
}
// ResponseError is the error returned when Vault responds with an error or
// non-success HTTP status code. If a request to Vault fails because of a
// network error a different error message will be returned. ResponseError gives
// access to the underlying errors and status code.
type ResponseError struct {
// HTTPMethod is the HTTP method for the request (PUT, GET, etc).
HTTPMethod string
// URL is the URL of the request.
URL string
// StatusCode is the HTTP status code.
StatusCode int
// RawError marks that the underlying error messages returned by Vault were
// not parsable. The Errors slice will contain the raw response body as the
// first and only error string if this value is set to true.
RawError bool
// Errors are the underlying errors returned by Vault.
Errors []string
// Namespace path to be reported to the client if it is set to anything other
// than root
NamespacePath string
}
// Error returns a human-readable error string for the response error.
func (r *ResponseError) Error() string {
errString := "Errors"
if r.RawError {
errString = "Raw Message"
}
var ns string
if r.NamespacePath != "" && r.NamespacePath != "root/" {
ns = "Namespace: " + r.NamespacePath + "\n"
}
var errBody bytes.Buffer
errBody.WriteString(fmt.Sprintf(
"Error making API request.\n\n"+
ns+
"URL: %s %s\n"+
"Code: %d. %s:\n\n",
r.HTTPMethod, r.URL, r.StatusCode, errString))
if r.RawError && len(r.Errors) == 1 {
errBody.WriteString(r.Errors[0])
} else {
for _, err := range r.Errors {
errBody.WriteString(fmt.Sprintf("* %s", err))
}
}
return errBody.String()
}