-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathvault-client.go
117 lines (96 loc) · 3.19 KB
/
vault-client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Copyright 2021 Outreach Corporation. All Rights Reserved.
//
// Description: This file is the entrypoint for the vault-client library.
package vault_client //nolint:revive // Why: This nolint is here just in case your project name contains any of [-_].
import (
"bytes"
"context"
"encoding/json" // Client is a Vault client
"fmt"
"io"
"net/http"
"path"
"github.com/getoutreach/gobox/pkg/log"
"github.com/getoutreach/gobox/pkg/trace"
"github.com/pkg/errors"
)
// Client is a Vault client
type Client struct {
opts *Options
hc *http.Client
}
// New creates a new Vault client. By default it is non-functional. Most likely
// it will be consumed like so:
//
// vault_client.New(vault_client.WithEnv)
func New(optFns ...Opts) *Client {
opts := &Options{}
for _, optFn := range optFns {
optFn(opts)
}
hc := (*http.DefaultClient)
if opts.am != nil {
// pass the options we created earlier to the AuthMethod
// so it can create it's own client.
opts.am.Options(opts)
hc.Transport = NewTransport(http.DefaultTransport, opts.am)
}
return &Client{opts, &hc}
}
// ErrorResponse is returned when an error occurs
type ErrorResponse struct {
// Errors is a list of errors that were encountered when Vault tried
// to process this request.
Errors []string `json:"errors"`
}
// doRequest sends a request
//
//nolint:funlen // Why: not that important to break out
func (c *Client) doRequest(ctx context.Context, method, endpoint string, body, resp interface{}) error {
uri := c.opts.Host + path.Join("/v1/", endpoint)
ctx = trace.StartCall(ctx, "vault.request", log.F{"vault.uri": uri, "vault.method": method})
defer trace.EndCall(ctx)
var bodyReader io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return errors.Wrap(err, "failed to serialize request into json")
}
bodyReader = bytes.NewReader(b)
}
req, err := http.NewRequestWithContext(ctx, method, uri, bodyReader)
if err != nil {
return errors.Wrap(err, "failed to create request")
}
r, err := c.hc.Do(req)
if err != nil {
return errors.Wrap(err, "failed to make request")
}
defer r.Body.Close()
// Useful debugging code
// buf := &bytes.Buffer{}
// r.Body = io.NopCloser(io.TeeReader(r.Body, buf))
// defer func(buf *bytes.Buffer) {
// fmt.Printf("\n\n!!!!!!! %s %d response: %s\n\n", endpoint, r.StatusCode, buf.String())
// }(buf)
// we're in error territory, read the entire body and try to parse for errors. If nothing is there, then just
// try to parse the response as normal json and trust the caller know's what it is doing
if !(r.StatusCode >= 200 && r.StatusCode < 400) {
b, err := io.ReadAll(r.Body)
if err != nil {
return errors.Wrap(err, "failed to read response")
}
var errResp ErrorResponse
if err := json.Unmarshal(b, &errResp); err == nil && len(errResp.Errors) >= 1 {
return fmt.Errorf("%v", errResp.Errors)
}
// set the body back to the original contents
// so that we can optimistically try to parse the non-errors response as JSON again
r.Body = io.NopCloser(bytes.NewReader(b))
}
if resp != nil {
// not an errorresponse, so optimistically try to parse it
return errors.Wrap(json.NewDecoder(r.Body).Decode(&resp), "failed to decode response")
}
return nil
}