Skip to content

Commit 5adb06f

Browse files
Broke out rest client functionality into separate package
New package is found at github.com/myENA/restclient. This should be a transparent change to users of radosgwadmin package.
1 parent 3e52418 commit 5adb06f

File tree

7 files changed

+81
-249
lines changed

7 files changed

+81
-249
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
buildtmp
1414
vendor/
1515
proxy/proxy
16+
*.iml

adminapi.go

+29-233
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,22 @@
11
package radosgwadmin
22

33
import (
4-
"bytes"
54
"context"
6-
"crypto/tls"
7-
"crypto/x509"
8-
"encoding/json"
9-
"fmt"
10-
"io"
11-
"io/ioutil"
12-
"net"
135
"net/http"
146
"net/url"
15-
"reflect"
16-
"regexp"
177
"strings"
188
"time"
199

20-
"github.com/google/go-querystring/query"
10+
"github.com/myENA/restclient"
2111
"github.com/smartystreets/go-aws-auth"
22-
"gopkg.in/go-playground/validator.v9"
2312
)
2413

2514
var tz *time.Location
2615

2716
var falsch = false
2817

29-
var validate *validator.Validate
30-
31-
var altMatch = regexp.MustCompile(`eq=([^=\|]+)`)
32-
3318
func init() {
3419
tz, _ = time.LoadLocation("Local") // Defaults to local
35-
validate = validator.New()
3620
}
3721

3822
// SetTimeZone - override time zone. Not thread-safe, do
@@ -51,14 +35,9 @@ var FalseRef = &falsch
5135

5236
// AdminAPI - admin api struct
5337
type AdminAPI struct {
54-
c *http.Client
55-
u *url.URL
56-
creds *awsauth.Credentials
57-
rawValidatorErrors bool
58-
}
59-
60-
type customDecoder interface {
61-
decode(data io.Reader) error
38+
*restclient.Client
39+
u *url.URL
40+
creds *awsauth.Credentials
6241
}
6342

6443
// NewAdminAPI - AdminAPI factory method.
@@ -71,49 +50,12 @@ func NewAdminAPI(cfg *Config) (*AdminAPI, error) {
7150
if err != nil {
7251
return nil, err
7352
}
74-
// Lifted from http package DefaultTransort.
75-
t := &http.Transport{
76-
Proxy: http.ProxyFromEnvironment,
77-
DialContext: (&net.Dialer{
78-
Timeout: 30 * time.Second,
79-
KeepAlive: 30 * time.Second,
80-
DualStack: true,
81-
}).DialContext,
82-
MaxIdleConns: 100,
83-
IdleConnTimeout: 90 * time.Second,
84-
TLSHandshakeTimeout: 10 * time.Second,
85-
ExpectContinueTimeout: 1 * time.Second,
86-
}
87-
88-
tlsc := new(tls.Config)
89-
tlsc.InsecureSkipVerify = cfg.InsecureSkipVerify
90-
91-
var cacerts []byte
92-
if len(cfg.CACertBundle) > 0 {
93-
cacerts = cfg.CACertBundle
94-
} else if cfg.CACertBundlePath != "" {
95-
cacerts, err = ioutil.ReadFile(cfg.CACertBundlePath)
96-
if err != nil {
97-
return nil, fmt.Errorf("Cannot open ca cert bundle %s: %s", cfg.CACertBundlePath, err)
98-
}
99-
}
10053

101-
if len(cacerts) > 0 {
102-
bundle := x509.NewCertPool()
103-
ok := bundle.AppendCertsFromPEM(cacerts)
104-
if !ok {
105-
return nil, fmt.Errorf("Invalid cert bundle")
106-
}
107-
tlsc.RootCAs = bundle
108-
tlsc.BuildNameToCertificate()
109-
}
110-
111-
t.TLSClientConfig = tlsc
112-
113-
aa.c = &http.Client{
114-
Timeout: time.Duration(cfg.ClientTimeout),
115-
Transport: t,
54+
aa.Client, err = restclient.NewClient(&(cfg.ClientConfig), nil)
55+
if err != nil {
56+
return nil, err
11657
}
58+
aa.Client.FixupCallback = aa.fixupCallback
11759

11860
aa.creds = &awsauth.Credentials{
11961
AccessKeyID: cfg.AccessKeyID,
@@ -134,143 +76,20 @@ func NewAdminAPI(cfg *Config) (*AdminAPI, error) {
13476
}
13577

13678
func (aa *AdminAPI) get(ctx context.Context, path string, queryStruct interface{}, responseBody interface{}) error {
137-
return aa.req(ctx, "GET", path, queryStruct, nil, responseBody)
79+
return aa.Client.Get(ctx, aa.u, path, queryStruct, responseBody)
13880
}
13981

14082
func (aa *AdminAPI) delete(ctx context.Context, path string, queryStruct interface{}, responseBody interface{}) error {
141-
return aa.req(ctx, "DELETE", path, queryStruct, nil, responseBody)
83+
return aa.Client.Delete(ctx, aa.u, path, queryStruct, responseBody)
14284
}
14385

14486
func (aa *AdminAPI) post(ctx context.Context, path string, queryStruct, requestBody interface{}, responseBody interface{}) error {
14587

146-
return aa.req(ctx, "POST", path, queryStruct, requestBody, responseBody)
88+
return aa.Client.Post(ctx, aa.u, path, queryStruct, requestBody, responseBody)
14789
}
14890

14991
func (aa *AdminAPI) put(ctx context.Context, path string, queryStruct, requestBody interface{}, responseBody interface{}) error {
150-
return aa.req(ctx, "PUT", path, queryStruct, requestBody, responseBody)
151-
}
152-
153-
func isNil(i interface{}) bool {
154-
if i == nil {
155-
return true
156-
}
157-
v := reflect.ValueOf(i)
158-
switch v.Kind() {
159-
case reflect.Ptr:
160-
return v.IsNil()
161-
162-
default:
163-
panic("Invalid interface type: " + v.Kind().String())
164-
}
165-
}
166-
167-
func (aa *AdminAPI) req(ctx context.Context, verb, path string, queryStruct, requestBody, responseBody interface{}) error {
168-
path = strings.TrimLeft(path, "/")
169-
url := aa.u.String() + "/" + path
170-
if !isNil(queryStruct) {
171-
err := aa.validate(queryStruct)
172-
if err != nil {
173-
return err
174-
}
175-
v, err := query.Values(queryStruct)
176-
if err != nil {
177-
return err
178-
}
179-
180-
qs := v.Encode()
181-
182-
if qs != "" {
183-
if strings.Contains(url, "?") {
184-
url = url + "&" + qs
185-
} else {
186-
url = url + "?" + qs
187-
}
188-
}
189-
}
190-
191-
var bodyReader io.Reader
192-
if !isNil(requestBody) {
193-
err := aa.validate(requestBody)
194-
if err != nil {
195-
return err
196-
}
197-
bjson, err := json.Marshal(requestBody)
198-
if err != nil {
199-
return err
200-
}
201-
bodyReader = bytes.NewReader(bjson)
202-
}
203-
req, err := http.NewRequest(verb, url, bodyReader)
204-
if err != nil {
205-
return err
206-
}
207-
208-
req = req.WithContext(ctx)
209-
req.URL.Query().Set("format", "json")
210-
211-
_ = awsauth.SignS3(req, *aa.creds)
212-
213-
// This is to appease AWS signature algorithm. spaces must
214-
// be %20, go defaults to +
215-
req.URL.RawQuery = strings.Replace(req.URL.RawQuery, "+", "%20", -1)
216-
217-
resp, err := aa.c.Do(req)
218-
if err != nil {
219-
return err
220-
}
221-
222-
defer func() {
223-
_ = resp.Body.Close()
224-
}()
225-
if resp.StatusCode != 200 {
226-
body, _ := ioutil.ReadAll(resp.Body)
227-
return fmt.Errorf("Invalid status code %d : %s : body: %s", resp.StatusCode, resp.Status, string(body))
228-
}
229-
if isNil(responseBody) {
230-
return nil
231-
}
232-
233-
if cd, ok := responseBody.(customDecoder); ok {
234-
return cd.decode(resp.Body)
235-
}
236-
237-
return json.NewDecoder(resp.Body).Decode(responseBody)
238-
}
239-
240-
// make sense of the validator error types
241-
func (aa *AdminAPI) validate(i interface{}) error {
242-
err := validate.Struct(i)
243-
if err != nil {
244-
if aa.rawValidatorErrors {
245-
return err
246-
}
247-
if verr, ok := err.(validator.ValidationErrors); ok {
248-
var errs []string
249-
for _, ferr := range verr {
250-
if ferr.ActualTag() == "required" {
251-
errs = append(errs,
252-
fmt.Sprintf("Required field %s is missing or empty",
253-
ferr.StructField(),
254-
),
255-
)
256-
} else if matches := altMatch.FindAllStringSubmatch(ferr.ActualTag(), -1); len(matches) > 0 {
257-
valids := make([]string, len(matches))
258-
for i := 0; i < len(matches); i++ {
259-
valids[i] = "\"" + matches[i][1] + "\""
260-
}
261-
errs = append(errs,
262-
fmt.Sprintf("Field '%s' invalid value: '%s', valid values are: %s",
263-
ferr.StructNamespace(),
264-
ferr.Value(), // for now all are string - revise this if other types are needed
265-
strings.Join(valids, ",")),
266-
)
267-
}
268-
}
269-
270-
return fmt.Errorf("Validation error: %s", strings.Join(errs, " ; "))
271-
}
272-
}
273-
return err
92+
return aa.Put(ctx, aa.u, path, queryStruct, requestBody, responseBody)
27493
}
27594

27695
// Config - this configures an AdminAPI.
@@ -279,47 +98,24 @@ func (aa *AdminAPI) validate(i interface{}) error {
27998
// Specify CACertBundle if you want embed the cacert bundle in PEM format.
28099
// Specify one or the other. If both are specified, CACertBundle is honored.
281100
type Config struct {
282-
ClientTimeout Duration
283-
ServerURL string
284-
AdminPath string
285-
CACertBundlePath string
286-
CACertBundle []byte
287-
InsecureSkipVerify bool
288-
ZoneName string
289-
AccessKeyID string
290-
SecretAccessKey string
291-
SecurityToken string
292-
Expiration time.Time
293-
RawValidatorErrors bool // If true, then no attempt to interpret validator errors will be made.
294-
}
101+
restclient.ClientConfig
102+
ServerURL string
103+
AdminPath string
104+
CACertBundlePath string
105+
ZoneName string
106+
AccessKeyID string
107+
SecretAccessKey string
108+
SecurityToken string
109+
Expiration time.Time
110+
}
111+
112+
func (aa *AdminAPI) fixupCallback(req *http.Request) error {
113+
req.URL.Query().Set("format", "json")
295114

296-
// Duration - this allows us to use a text representation of a duration and
297-
// have it parse correctly. The go standard library time.Duration does not
298-
// implement the TextUnmarshaller interface, so we have to do this workaround
299-
// in order for json.Unmarshal or external parsers like toml.Decode to work
300-
// with human friendly input.
301-
type Duration time.Duration
115+
_ = awsauth.SignS3(req, *aa.creds)
302116

303-
// UnmarshalText - this implements the TextUnmarshaler interface
304-
func (d *Duration) UnmarshalText(text []byte) error {
305-
if len(text) == 0 {
306-
return nil
307-
}
308-
dur, err := time.ParseDuration(string(text))
309-
if err != nil {
310-
return err
311-
}
312-
*d = Duration(dur)
117+
// This is to appease AWS signature algorithm. spaces must
118+
// be %20, go defaults to +
119+
req.URL.RawQuery = strings.Replace(req.URL.RawQuery, "+", "%20", -1)
313120
return nil
314121
}
315-
316-
// MarshalText - this implements TextMarshaler
317-
func (d Duration) MarshalText() ([]byte, error) {
318-
return []byte(time.Duration(d).String()), nil
319-
}
320-
321-
// HTTPClient return the underlying http.Client. You can use this to fine tune
322-
// the http.Transport settings, for example.
323-
func (aa *AdminAPI) HTTPClient() *http.Client {
324-
return aa.c
325-
}

adminapi_test.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"gopkg.in/go-playground/validator.v9"
1818
)
1919

20+
var validate *validator.Validate
21+
2022
type ModelsSuite struct {
2123
suite.Suite
2224
dbags map[string][]byte
@@ -61,6 +63,7 @@ func (ms *ModelsSuite) SetupSuite() {
6163
}
6264

6365
ms.aa = &AdminAPI{}
66+
validate = validator.New()
6467

6568
}
6669

@@ -102,10 +105,6 @@ func (ms *ModelsSuite) Test01Validators() {
102105
_, ok := err.(validator.ValidationErrors)
103106
ms.True(ok, "not coerce to ValidationErrors")
104107

105-
err = ms.aa.validate(ucr)
106-
ms.Error(err, "did not fail internal validation")
107-
ms.T().Logf("error returned was: %s\n", err)
108-
109108
}
110109

111110
func (ms *ModelsSuite) Test02Usage() {
@@ -133,7 +132,7 @@ func (ms *ModelsSuite) Test03Bucket() {
133132

134133
bucketindjson := ms.dbags["bucketindex"]
135134
bir := &BucketIndexResponse{}
136-
err = bir.decode(bytes.NewReader(bucketindjson))
135+
err = bir.Decode(bytes.NewReader(bucketindjson))
137136
ms.NoError(err, "Error unmarshaling bucket index json")
138137
ms.Equal(bir.NewObjects[0], "key.json", "first element of NewObjects not as expected")
139138
ms.Equal(len(bir.NewObjects), 3, "length of NewObjects not 3")
@@ -142,7 +141,7 @@ func (ms *ModelsSuite) Test03Bucket() {
142141

143142
bucketindjsonNoFix := ms.dbags["bucketindex_nofix"]
144143
bir = &BucketIndexResponse{}
145-
err = bir.decode(bytes.NewReader(bucketindjsonNoFix))
144+
err = bir.Decode(bytes.NewReader(bucketindjsonNoFix))
146145
ms.NoError(err, "Error, could not read bucketindex_nofix")
147146

148147
bucketpoljson := ms.dbags["bucketpolicy"]

bucket.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ type BucketIndexResponse struct {
5959
} `json:"headers"`
6060
}
6161

62-
// Implements the customDecoder interface
63-
func (bir *BucketIndexResponse) decode(data io.Reader) error {
62+
// Decode - Implements the restapi.CustomDecoder interface
63+
func (bir *BucketIndexResponse) Decode(data io.Reader) error {
6464
// rgw does some weird shit with this response.
6565
// has an array followed by a json object, no delimters.
6666
dec := json.NewDecoder(data)

0 commit comments

Comments
 (0)