Initial client porting

apigen
Jeff Mitchell 6 years ago
parent b1f3050859
commit 170d3f0a1b

@ -0,0 +1,710 @@
package api
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"unicode"
"github.com/hashicorp/errwrap"
cleanhttp "github.com/hashicorp/go-cleanhttp"
retryablehttp "github.com/hashicorp/go-retryablehttp"
rootcerts "github.com/hashicorp/go-rootcerts"
"github.com/hashicorp/vault/sdk/helper/parseutil"
"golang.org/x/time/rate"
)
const EnvWatchtowerAddress = "WATCHTOWER_ADDR"
const EnvWatchtowerCACert = "WATCHTOWER_CACERT"
const EnvWatchtowerCAPath = "WATCHTOWER_CAPATH"
const EnvWatchtowerClientCert = "WATCHTOWER_CLIENT_CERT"
const EnvWatchtowerClientKey = "WATCHTOWER_CLIENT_KEY"
const EnvWatchtowerClientTimeout = "WATCHTOWER_CLIENT_TIMEOUT"
const EnvWatchtowerTLSInsecure = "WATCHTOWER_TLS_INSECURE"
const EnvWatchtowerTLSServerName = "WATCHTOWER_TLS_SERVER_NAME"
const EnvWatchtowerMaxRetries = "WATCHTOWER_MAX_RETRIES"
const EnvWatchtowerToken = "WATCHTOWER_TOKEN"
const EnvWatchtowerRateLimit = "WATCHTOWER_RATE_LIMIT"
const EnvWatchtowerSRVLookup = "WATCHTOWER_SRV_LOOKUP"
// Config is used to configure the creation of the client
type Config struct {
// Address is the address of the Watchtower controller. This should be a
// complete URL such as "http://watchtower.example.com". If you need a custom
// SSL cert or want to enable insecure mode, you need to specify a custom
// HttpClient.
Address string
// Token is the client token that reuslts from authentication and can be
// used to make calls into Watchtower
Token string
// HTTPClient is the HTTP client to use. Watchtower sets sane defaults for
// the http.Client and its associated http.Transport created in
// DefaultConfig. If you must modify Watchtower's defaults, it is
// suggested that you start with that client and modify as needed rather
// than start with an empty client (or http.DefaultClient).
HTTPClient *http.Client
// TLSConfig contains TLS configuration information. After modifying these
// values, ConfigureTLS should be called.
TLSConfig *TLSConfig
// Headers contains extra headers that will be added to any request
Headers http.Header
// MaxRetries controls the maximum number of times to retry when a 5xx
// error occurs. Set to 0 to disable retrying. Defaults to 2 (for a total
// of three tries).
MaxRetries int
// Timeout is for setting custom timeout parameter in the HTTPClient
Timeout time.Duration
// If there is an error when creating the configuration, this will be the
// error
Error error
// The Backoff function to use; a default is used if not provided
Backoff retryablehttp.Backoff
// The CheckRetry function to use; a default is used if not provided
CheckRetry retryablehttp.CheckRetry
// Limiter is the rate limiter used by the client.
// If this pointer is nil, then there will be no limit set.
// In contrast, if this pointer is set, even to an empty struct,
// then that limiter will be used. Note that an empty Limiter
// is equivalent blocking all events.
Limiter *rate.Limiter
// OutputCurlString causes the actual request to return an error of type
// *OutputStringError. Type asserting the error message will allow
// fetching a cURL-compatible string for the operation.
//
// Note: It is not thread-safe to set this and make concurrent requests
// with the same client. Cloning a client will not clone this value.
OutputCurlString bool
// SRVLookup enables the client to lookup the host through DNS SRV lookup
SRVLookup bool
}
// TLSConfig contains the parameters needed to configure TLS on the HTTP client
// used to communicate with Watchtower.
type TLSConfig struct {
// CACert is the path to a PEM-encoded CA cert file to use to verify the
// Vault server SSL certificate.
CACert string
// CAPath is the path to a directory of PEM-encoded CA cert files to verify
// the Vault server SSL certificate.
CAPath string
// ClientCert is the path to the certificate for Vault communication
ClientCert string
// ClientKey is the path to the private key for Vault communication
ClientKey string
// ServerName, if set, is used to set the SNI host when connecting via
// TLS.
ServerName string
// Insecure enables or disables SSL verification
Insecure bool
}
// DefaultConfig returns a default configuration for the client. It is
// safe to modify the return value of this function.
//
// The default Address is https://127.0.0.1:9200, but this can be overridden by
// setting the `WATCHTOWER_ADDR` environment variable.
//
// If an error is encountered, this will return nil.
func DefaultConfig() *Config {
config := &Config{
Address: "https://127.0.0.1:9200",
HTTPClient: cleanhttp.DefaultPooledClient(),
Timeout: time.Second * 60,
}
// We read the environment now; after DefaultClient returns we can override
// values from command line flags, which should take precedence.
if err := config.ReadEnvironment(); err != nil {
config.Error = err
return config
}
transport := config.HTTPClient.Transport.(*http.Transport)
transport.TLSHandshakeTimeout = 10 * time.Second
transport.TLSClientConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
}
config.Backoff = retryablehttp.LinearJitterBackoff
config.MaxRetries = 2
config.Headers = make(http.Header)
return config
}
// ConfigureTLS takes a set of TLS configurations and applies those to the the
// HTTP client.
func (c *Config) ConfigureTLS() error {
if c.HTTPClient == nil {
c.HTTPClient = DefaultConfig().HTTPClient
}
clientTLSConfig := c.HTTPClient.Transport.(*http.Transport).TLSClientConfig
var clientCert tls.Certificate
foundClientCert := false
switch {
case c.TLSConfig.ClientCert != "" && c.TLSConfig.ClientKey != "":
var err error
clientCert, err = tls.LoadX509KeyPair(c.TLSConfig.ClientCert, c.TLSConfig.ClientKey)
if err != nil {
return err
}
foundClientCert = true
case c.TLSConfig.ClientCert != "" || c.TLSConfig.ClientKey != "":
return fmt.Errorf("both client cert and client key must be provided")
}
if c.TLSConfig.CACert != "" || c.TLSConfig.CAPath != "" {
rootConfig := &rootcerts.Config{
CAFile: c.TLSConfig.CACert,
CAPath: c.TLSConfig.CAPath,
}
if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil {
return err
}
}
if c.TLSConfig.Insecure {
clientTLSConfig.InsecureSkipVerify = true
}
if foundClientCert {
// We use this function to ignore the server's preferential list of
// CAs, otherwise any CA used for the cert auth backend must be in the
// server's CA pool
clientTLSConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return &clientCert, nil
}
}
if c.TLSConfig.ServerName != "" {
clientTLSConfig.ServerName = c.TLSConfig.ServerName
}
return nil
}
// ReadEnvironment reads configuration information from the environment. If
// there is an error, no configuration value is updated.
func (c *Config) ReadEnvironment() error {
var envAddress string
var envCACert string
var envCAPath string
var envClientCert string
var envClientKey string
var envClientTimeout time.Duration
var envInsecure bool
var envServerName string
var envMaxRetries *uint64
var envSRVLookup *bool
var envToken string
var limit *rate.Limiter
var foundTLSConfig bool
// Parse the environment variables
if v := os.Getenv(EnvWatchtowerAddress); v != "" {
envAddress = v
}
if v := os.Getenv(EnvWatchtowerToken); v != "" {
envToken = v
}
if v := os.Getenv(EnvWatchtowerMaxRetries); v != "" {
maxRetries, err := strconv.ParseUint(v, 10, 32)
if err != nil {
return err
}
envMaxRetries = &maxRetries
}
if v := os.Getenv(EnvWatchtowerCACert); v != "" {
foundTLSConfig = true
envCACert = v
}
if v := os.Getenv(EnvWatchtowerCAPath); v != "" {
foundTLSConfig = true
envCAPath = v
}
if v := os.Getenv(EnvWatchtowerClientCert); v != "" {
foundTLSConfig = true
envClientCert = v
}
if v := os.Getenv(EnvWatchtowerClientKey); v != "" {
foundTLSConfig = true
envClientKey = v
}
if v := os.Getenv(EnvWatchtowerTLSInsecure); v != "" {
foundTLSConfig = true
var err error
envInsecure, err = strconv.ParseBool(v)
if err != nil {
return fmt.Errorf("could not parse WATCHTOWER_TLS_INSECURE")
}
}
if v := os.Getenv(EnvWatchtowerTLSServerName); v != "" {
foundTLSConfig = true
envServerName = v
}
if v := os.Getenv(EnvWatchtowerSRVLookup); v != "" {
var err error
lookup, err := strconv.ParseBool(v)
if err != nil {
return fmt.Errorf("could not parse %s", EnvWatchtowerSRVLookup)
}
envSRVLookup = new(bool)
*envSRVLookup = lookup
}
if v := os.Getenv(EnvWatchtowerRateLimit); v != "" {
rateLimit, burstLimit, err := parseRateLimit(v)
if err != nil {
return err
}
limit = rate.NewLimiter(rate.Limit(rateLimit), burstLimit)
}
if t := os.Getenv(EnvWatchtowerClientTimeout); t != "" {
clientTimeout, err := parseutil.ParseDurationSecond(t)
if err != nil {
return fmt.Errorf("could not parse %q", EnvWatchtowerClientTimeout)
}
envClientTimeout = clientTimeout
}
// Set the values on the config
{
if envToken != "" {
c.Token = envToken
}
if envAddress != "" {
c.Address = envAddress
}
if envMaxRetries != nil {
c.MaxRetries = int(*envMaxRetries)
}
if envClientTimeout != 0 {
c.Timeout = envClientTimeout
}
if envSRVLookup != nil {
c.SRVLookup = *envSRVLookup
}
if limit != nil {
c.Limiter = limit
}
// Configure the HTTP clients TLS configuration.
if foundTLSConfig {
c.TLSConfig = &TLSConfig{
CACert: envCACert,
CAPath: envCAPath,
ClientCert: envClientCert,
ClientKey: envClientKey,
ServerName: envServerName,
Insecure: envInsecure,
}
}
}
if foundTLSConfig {
return c.ConfigureTLS()
}
return nil
}
func parseRateLimit(val string) (rate float64, burst int, err error) {
_, err = fmt.Sscanf(val, "%f:%d", &rate, &burst)
if err != nil {
rate, err = strconv.ParseFloat(val, 64)
if err != nil {
err = fmt.Errorf("%v was provided but incorrectly formatted", EnvWatchtowerRateLimit)
}
burst = int(rate)
}
return rate, burst, err
}
// Client is the client to the Vault API. Create a client with NewClient.
type Client struct {
modifyLock sync.RWMutex
config *Config
}
// NewClient returns a new client for the given configuration.
//
// If the configuration is nil, Watchtower will use configuration from
// DefaultConfig(), which is the recommended starting configuration.
//
// If the environment variable `WATCHTOWER_TOKEN` is present, the token will be
// automatically added to the client. Otherwise, you must manually call
// `SetToken()`.
func NewClient(c *Config) (*Client, error) {
def := DefaultConfig()
if def == nil {
return nil, fmt.Errorf("could not create/read default configuration")
}
if def.Error != nil {
return nil, errwrap.Wrapf("error encountered setting up default configuration: {{err}}", def.Error)
}
if c == nil {
c = def
}
if c.HTTPClient == nil {
c.HTTPClient = def.HTTPClient
}
if c.HTTPClient.Transport == nil {
c.HTTPClient.Transport = def.HTTPClient.Transport
}
if c.HTTPClient.CheckRedirect == nil {
// Ensure redirects are not automatically followed
c.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
// Returning this value causes the Go net library to not close the
// response body and to nil out the error. Otherwise retry clients may
// try three times on every redirect because it sees an error from this
// function (to prevent redirects) passing through to it.
return http.ErrUseLastResponse
}
}
return &Client{
config: c,
}, nil
}
// Sets the address of Watchtower in the client. The format of address should
// be "<Scheme>://<Host>:<Port>". Setting this on a client will override the
// value of the WATCHTOWER_ADDR environment variable.
func (c *Client) SetAddress(addr string) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.Address = addr
}
// SetLimiter will set the rate limiter for this client. This method is
// thread-safe. rateLimit and burst are specified according to
// https://godoc.org/golang.org/x/time/rate#NewLimiter
func (c *Client) SetLimiter(rateLimit float64, burst int) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.Limiter = rate.NewLimiter(rate.Limit(rateLimit), burst)
}
// SetMaxRetries sets the number of retries that will be used in the case of
// certain errors
func (c *Client) SetMaxRetries(retries int) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.MaxRetries = retries
}
// SetCheckRetry sets the CheckRetry function to be used for future requests.
func (c *Client) SetCheckRetry(checkRetry retryablehttp.CheckRetry) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.CheckRetry = checkRetry
}
// SetClientTimeout sets the client request timeout
func (c *Client) SetClientTimeout(timeout time.Duration) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.Timeout = timeout
}
func (c *Client) SetOutputCurlString(curl bool) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.OutputCurlString = curl
}
// SetToken sets the token directly. This won't perform any auth
// verification, it simply sets the token properly for future requests.
func (c *Client) SetToken(token string) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.Token = token
}
// SetHeaders clears all previous headers and uses only the given
// ones going forward.
func (c *Client) SetHeaders(headers http.Header) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.Headers = headers
}
// SetBackoff sets the backoff function to be used for future requests.
func (c *Client) SetBackoff(backoff retryablehttp.Backoff) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.Backoff = backoff
}
// Clone creates a new client with the same configuration. Note that the same
// underlying http.Client is used; modifying the client from more than one
// goroutine at once may not be safe, so modify the client as needed and then
// clone.
func (c *Client) Clone() (*Client, error) {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
config := c.config
newConfig := &Config{
Address: config.Address,
Token: config.Token,
HTTPClient: config.HTTPClient,
Headers: make(http.Header),
MaxRetries: config.MaxRetries,
Timeout: config.Timeout,
Backoff: config.Backoff,
CheckRetry: config.CheckRetry,
Limiter: config.Limiter,
SRVLookup: config.SRVLookup,
}
if config.TLSConfig != nil {
newConfig.TLSConfig = new(TLSConfig)
*newConfig.TLSConfig = *config.TLSConfig
}
for k, v := range config.Headers {
vSlice := make([]string, 0, len(v))
for _, i := range v {
vSlice = append(vSlice, i)
}
newConfig.Headers[k] = vSlice
}
return NewClient(newConfig)
}
func copyHeaders(in http.Header) http.Header {
ret := make(http.Header)
for k, v := range in.headers {
for _, val := range v {
ret[k] = append(ret[k], val)
}
}
return ret
}
// GetReaderFuncForJSON returns a func compatible with retryablehttp.ReaderFunc
// after marshaling JSON
func getBufferForJSON(val interface{}) (*bytes.Buffer, error) {
b, err := json.Marshal(val)
if err != nil {
return nil, err
}
return bytes.NewBuffer(b), nil
}
// NewRequest creates a new raw request object to query the Watchtower controller
// configured for this client. This is an advanced method and generally
// doesn't need to be called externally.
func (c *Client) NewRequest(ctx context.Context, method, requestPath string, body interface{}) (*http.Request, error) {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
u, err := url.Parse(c.config.Address)
if err != nil {
return nil, err
}
if strings.HasPrefix(c.config.Address, "unix://") {
socket := strings.TrimPrefix(c.config.Address, "unix://")
transport := c.config.HTTPClient.Transport.(*http.Transport)
transport.DialContext = func(context.Context, string, string) (net.Conn, error) {
dialer := net.Dialer{}
return dialer.DialContext(ctx, "unix", socket)
}
// Since the address points to a unix domain socket, the scheme in the
// *URL would be set to `unix`. The *URL in the client is expected to
// be pointing to the protocol used in the application layer and not to
// the transport layer. Hence, setting the fields accordingly.
u.Scheme = "http"
u.Host = socket
u.Path = ""
}
var host = u.Host
// if SRV records exist (see
// https://tools.ietf.org/html/draft-andrews-http-srv-02), lookup the SRV
// record and take the highest match; this is not designed for
// high-availability, just discovery Internet Draft specifies that the SRV
// record is ignored if a port is given
if u.Port() == "" && c.config.SRVLookup {
_, addrs, err := net.LookupSRV("http", "tcp", u.Hostname())
if err != nil {
return nil, fmt.Errorf("error performing SRV lookup of http:tcp:%s : %w", u.Hostname(), err)
}
if len(addrs) > 0 {
host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port)
}
}
req := &http.Request{
Method: method,
URL: &url.URL{
User: u.User,
Scheme: u.Scheme,
Host: host,
Path: path.Join(u.Path, requestPath),
},
Host: u.Host,
}
req.Header = copyHeaders(c.config.Headers)
req.Header.Add("authorization", "bearer: "+c.config.Token)
if ctx != nil {
req = req.WithContext(ctx)
}
return req, nil
}
// RawRequestWithContext performs the raw request given. This request may be against
// a Vault server not configured with this client. This is an advanced operation
// that generally won't need to be called externally.
func (c *Client) Do(r *http.Request) (*http.Response, error) {
c.modifyLock.RLock()
limiter := c.config.Limiter
maxRetries := c.config.MaxRetries
checkRetry := c.config.CheckRetry
backoff := c.config.Backoff
httpClient := c.config.HTTPClient
timeout := c.config.Timeout
token := c.config.Token
outputCurlString := c.config.OutputCurlString
c.modifyLock.RUnlock()
ctx := r.Context()
if limiter != nil {
limiter.Wait(ctx)
}
// Sanity check the token before potentially erroring from the API
idx := strings.IndexFunc(token, func(c rune) bool {
return !unicode.IsPrint(c)
})
if idx != -1 {
return nil, fmt.Errorf("configured Watchtower token contains non-printable characters and cannot be used")
}
req, err := retryablehttp.FromRequest(r)
if err != nil {
return nil, fmt.Errorf("error converting request to retryable request: %w", err)
}
if req == nil {
return nil, fmt.Errorf("nil request created")
}
if outputCurlString {
LastOutputStringError = &OutputStringError{Request: req}
return nil, LastOutputStringError
}
if timeout != 0 {
// NOTE: this leaks a timer. But when we defer a cancel call here for
// the returned function we see errors in tests with contxt canceled.
// Although the request is done by the time we exit this function it is
// still causing something else to go wrong. Maybe it ends up being
// tied to the response somehow and reading the response body ends up
// checking it, or something. I don't know, but until we can chase this
// down, keep it not-canceled even though vet complains.
ctx, _ = context.WithTimeout(ctx, timeout)
}
req.Request = req.Request.WithContext(ctx)
if backoff == nil {
backoff = retryablehttp.LinearJitterBackoff
}
if checkRetry == nil {
checkRetry = retryablehttp.DefaultRetryPolicy
}
client := &retryablehttp.Client{
HTTPClient: httpClient,
RetryWaitMin: 1000 * time.Millisecond,
RetryWaitMax: 1500 * time.Millisecond,
RetryMax: maxRetries,
Backoff: backoff,
CheckRetry: checkRetry,
ErrorHandler: retryablehttp.PassthroughErrorHandler,
}
var result *http.Response
resp, err := client.Do(req)
if resp != nil {
result = &Response{Response: resp}
}
if err != nil {
if strings.Contains(err.Error(), "tls: oversized") {
err = errwrap.Wrapf(
"{{err}}\n\n"+
"This error usually means that the controller is running with TLS disabled\n"+
"but the client is configured to use TLS. Please either enable TLS\n"+
"on the server or run the client with -address set to an address\n"+
"that uses the http protocol:\n\n"+
" watchtower <command> -address http://<address>\n\n"+
"You can also set the WATCHTOWER_ADDR environment variable:\n\n\n"+
" WATCHTOWER_ADDR=http://<address> watchtower <command>\n\n"+
"where <address> is replaced by the actual address to the controller.",
err)
}
return result, err
}
return result, nil
}

@ -0,0 +1,71 @@
package api
import (
"fmt"
"strings"
retryablehttp "github.com/hashicorp/go-retryablehttp"
)
const (
ErrOutputStringRequest = "output a string, please"
)
var (
LastOutputStringError *OutputStringError
)
type OutputStringError struct {
*retryablehttp.Request
parsingError error
parsedCurlString string
}
func (d *OutputStringError) Error() string {
if d.parsedCurlString == "" {
d.parseRequest()
if d.parsingError != nil {
return d.parsingError.Error()
}
}
return ErrOutputStringRequest
}
func (d *OutputStringError) parseRequest() {
body, err := d.Request.BodyBytes()
if err != nil {
d.parsingError = err
return
}
// Build cURL string
d.parsedCurlString = "curl "
if d.Request.Method != "GET" {
d.parsedCurlString = fmt.Sprintf("%s-X %s ", d.parsedCurlString, d.Request.Method)
}
for k, v := range d.Request.Header {
for _, h := range v {
if strings.ToLower(k) == "authorization" {
h = `Bearer: <token>`
}
d.parsedCurlString = fmt.Sprintf("%s-H \"%s: %s\" ", d.parsedCurlString, k, h)
}
}
if len(body) > 0 {
// We need to escape single quotes since that's what we're using to
// quote the body
escapedBody := strings.Replace(string(body), "'", "'\"'\"'", -1)
d.parsedCurlString = fmt.Sprintf("%s-d '%s' ", d.parsedCurlString, escapedBody)
}
d.parsedCurlString = fmt.Sprintf("%s%s", d.parsedCurlString, d.Request.URL.String())
}
func (d *OutputStringError) CurlString() string {
if d.parsedCurlString == "" {
d.parseRequest()
}
return d.parsedCurlString
}

@ -0,0 +1,120 @@
package api
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"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.
if (r.StatusCode >= 200 && r.StatusCode < 400) || r.StatusCode == 429 {
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)
// Build up the error object
respErr := &ResponseError{
HTTPMethod: r.Request.Method,
URL: r.Request.URL.String(),
StatusCode: r.StatusCode,
}
// 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
}
// 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 errBody bytes.Buffer
errBody.WriteString(fmt.Sprintf(
"Error making API request.\n\n"+
"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()
}

@ -18,9 +18,12 @@ require (
github.com/grpc-ecosystem/grpc-gateway v1.14.4-rc.1
github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/go-alpnmux v0.0.0-20200323180452-dee08f00df54
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-hclog v0.12.2
github.com/hashicorp/go-kms-wrapping v0.5.8
github.com/hashicorp/go-multierror v1.0.0
github.com/hashicorp/go-retryablehttp v0.6.6
github.com/hashicorp/go-rootcerts v1.0.2
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/vault v1.2.1-0.20200310200732-a321b1217d5a
@ -44,6 +47,7 @@ require (
go.mongodb.org/mongo-driver v1.3.2 // indirect
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
golang.org/x/tools v0.0.0-20200421185700-66008de356c7 // indirect
google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d
google.golang.org/grpc v1.27.1

@ -599,6 +599,8 @@ github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.2 h1:bHM2aVXwBtBJWxHtkSrWuI4umABCUczs52eiUS9nSiw=
github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=

@ -26,14 +26,4 @@ const (
const EnvWatchtowerCLINoColor = `WATCHTOWER_CLI_NO_COLOR`
const EnvWatchtowerCLIFormat = `WATCHTOWER_CLI_FORMAT`
const EnvWatchtowerAddress = "WATCHTOWER_ADDR"
const EnvWatchtowerCACert = "WATCHTOWER_CACERT"
const EnvWatchtowerCAPath = "WATCHTOWER_CAPATH"
const EnvWatchtowerClientCert = "WATCHTOWER_CLIENT_CERT"
const EnvWatchtowerClientKey = "WATCHTOWER_CLIENT_KEY"
const EnvWatchtowerClientTimeout = "WATCHTOWER_CLIENT_TIMEOUT"
const EnvWatchtowerTLSInsecure = "WATCHTOWER_TLS_INSECURE"
const EnvWatchtowerTLSServerName = "WATCHTOWER_TLS_SERVER_NAME"
const EnvWatchtowerMaxRetries = "WATCHTOWER_MAX_RETRIES"
const EnvWatchtowerToken = "WATCHTOWER_TOKEN"
const EnvRateLimit = "WATCHTOWER_RATE_LIMIT"

Loading…
Cancel
Save