-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathclient.go
More file actions
182 lines (170 loc) · 6.38 KB
/
Copy pathclient.go
File metadata and controls
182 lines (170 loc) · 6.38 KB
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package cinc
import (
"context"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
// Client is a Chef/CINC Server API client. It is safe for concurrent use.
type Client struct {
baseURL *url.URL
baseURLStr string // cached baseURL.String() for the request hot path
org string
clientName string
key *rsa.PrivateKey
httpClient *http.Client
// transferClient sends the unsigned bookshelf transfers. It is httpClient
// with the Timeout swapped for opts.transferTimeout, sharing its
// transport and connection pool.
transferClient *http.Client
opts options
clock func() time.Time
// sleep waits for d, reporting false if ctx ended first. A field so tests
// can drive retry timing without waiting.
sleep func(ctx context.Context, d time.Duration) bool
// Services.
Nodes *NodesService
Roles *RolesService
Environments *EnvironmentsService
Clients *ClientsService
DataBags *DataBagsService
Search *SearchService
Cookbooks *CookbooksService
CookbookArtifacts *CookbookArtifactsService
Keys *KeysService
Groups *GroupsService
Status *StatusService
License *LicenseService
Policies *PoliciesService
PolicyGroups *PolicyGroupsService
Orgs *OrgsService
Users *UsersService
Containers *ContainersService
ACLs *ACLsService
RequiredRecipe *RequiredRecipeService
Associations *AssociationsService
Principals *PrincipalsService
Universe *UniverseService
Stats *StatsService
}
// NewClient builds a Client from cfg and optional Options.
func NewClient(cfg Config, opts ...Option) (*Client, error) {
if err := cfg.validate(); err != nil {
return nil, err
}
base, err := url.Parse(strings.TrimRight(cfg.ServerURL, "/"))
if err != nil || base.Host == "" {
return nil, fmt.Errorf("cinc: invalid ServerURL %q", cfg.ServerURL)
}
o := defaultOptions()
for _, opt := range opts {
opt(&o)
}
hc := o.httpClient
if o.skipTLSVerify || o.rootCAs != nil {
// Copy the caller's client and swap only the transport, so Jar,
// CheckRedirect and any other configuration survive. Building a fresh
// http.Client here would silently drop them.
clone := *hc
clone.Transport = cloneTransportTLS(hc.Transport, o.rootCAs, o.skipTLSVerify)
hc = &clone
}
// Transfers keep the caller's redirect policy (Go's default follows
// redirects, which S3 needs across regions): they carry no signature.
tc := *hc
tc.Timeout = o.transferTimeout
// Signed requests never follow a redirect. Go would forward the X-Ops-*
// headers to the new host, which could replay the signed request for the
// server's clock-skew window; and the signature covers the original path,
// so the new location would reject it anyway. doOnce reports the 3xx.
// Set on a copy, never on the caller's client.
signed := *hc
signed.CheckRedirect = refuseRedirect
c := &Client{
baseURL: base, baseURLStr: base.String(), org: cfg.Org, clientName: cfg.ClientName,
key: cfg.Key, httpClient: &signed, transferClient: &tc, opts: o, clock: time.Now, sleep: sleepCtx,
}
c.Nodes = &NodesService{client: c}
c.Roles = &RolesService{client: c}
c.Environments = &EnvironmentsService{client: c}
c.Clients = &ClientsService{client: c}
c.DataBags = &DataBagsService{client: c}
c.Search = &SearchService{client: c}
c.Cookbooks = &CookbooksService{client: c}
c.CookbookArtifacts = &CookbookArtifactsService{client: c}
c.Keys = &KeysService{client: c}
c.Groups = &GroupsService{client: c}
c.Status = &StatusService{client: c}
c.License = &LicenseService{client: c}
c.Policies = &PoliciesService{client: c}
c.PolicyGroups = &PolicyGroupsService{client: c}
c.Orgs = &OrgsService{client: c}
c.Users = &UsersService{client: c}
c.Containers = &ContainersService{client: c}
c.ACLs = &ACLsService{client: c}
c.RequiredRecipe = &RequiredRecipeService{client: c}
c.Associations = &AssociationsService{client: c}
c.Principals = &PrincipalsService{client: c}
c.Universe = &UniverseService{client: c}
c.Stats = &StatsService{client: c}
return c, nil
}
// refuseRedirect is the signed client's CheckRedirect: it hands the 3xx back
// to doOnce unfollowed.
func refuseRedirect(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }
// cloneTransportTLS returns a transport that mirrors base with its TLS
// verification changed: rootCAs, when set, replaces the roots servers are
// verified against, and skipVerify turns verification off. When base is a
// caller-supplied *http.Transport its tuning is preserved; otherwise (nil, as
// with the default client, or a non-Transport RoundTripper) http.DefaultTransport
// is cloned so HTTP/2, proxy support, and connection pooling are retained.
// Clone copies the TLS config too, so the caller's is never modified.
func cloneTransportTLS(base http.RoundTripper, rootCAs *x509.CertPool, skipVerify bool) *http.Transport {
tr, ok := base.(*http.Transport)
if !ok || tr == nil {
tr = http.DefaultTransport.(*http.Transport)
}
clone := tr.Clone()
if clone.TLSClientConfig == nil {
clone.TLSClientConfig = &tls.Config{}
}
if rootCAs != nil {
clone.TLSClientConfig.RootCAs = rootCAs
}
if skipVerify {
clone.TLSClientConfig.InsecureSkipVerify = true
}
return clone
}
// ServerURL returns the base server URL the client was built with
// (scheme://host[:port], any trailing slash trimmed). Pair it with Org and
// FormatServerURL for the combined https://host/organizations/<org> form.
func (c *Client) ServerURL() string { return c.baseURLStr }
// Org returns the organization the client's org-scoped requests go to.
func (c *Client) Org() string { return c.org }
// ClientName returns the client or user name requests are signed as.
func (c *Client) ClientName() string { return c.clientName }
// orgPath prefixes p with /organizations/<org>.
func (c *Client) orgPath(p string) string {
return "/organizations/" + esc(c.org) + "/" + strings.TrimLeft(p, "/")
}
// sleepCtx waits for d, reporting false if ctx ended first.
func sleepCtx(ctx context.Context, d time.Duration) bool {
t := time.NewTimer(d)
defer t.Stop()
select {
case <-t.C:
return true
case <-ctx.Done():
return false
}
}
// timestamp returns the current time as an ISO-8601 UTC string.
func (c *Client) timestamp() string {
return c.clock().UTC().Format("2006-01-02T15:04:05Z")
}