-
Notifications
You must be signed in to change notification settings - Fork 0
/
cloud_controller_connection.go
137 lines (117 loc) · 3.57 KB
/
cloud_controller_connection.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package cloudcontroller
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"time"
"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
)
// CloudControllerConnection represents a connection to the Cloud Controller
// server.
type CloudControllerConnection struct {
HTTPClient *http.Client
UserAgent string
}
// Config is for configuring a CloudControllerConnection.
type Config struct {
DialTimeout time.Duration
SkipSSLValidation bool
}
// NewConnection returns a new CloudControllerConnection with provided
// configuration.
func NewConnection(config Config) *CloudControllerConnection {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: config.SkipSSLValidation,
},
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
KeepAlive: 30 * time.Second,
Timeout: config.DialTimeout,
}).DialContext,
}
return &CloudControllerConnection{
HTTPClient: &http.Client{Transport: tr},
}
}
// Make performs the request and parses the response.
func (connection *CloudControllerConnection) Make(request *Request, passedResponse *Response) error {
// In case this function is called from a retry, passedResponse may already
// be populated with a previous response. We reset in case there's an HTTP
// error and we don't repopulate it in populateResponse.
passedResponse.reset()
response, err := connection.HTTPClient.Do(request.Request)
if err != nil {
return connection.processRequestErrors(request.Request, err)
}
return connection.populateResponse(response, passedResponse)
}
func (*CloudControllerConnection) processRequestErrors(request *http.Request, err error) error {
switch e := err.(type) {
case *url.Error:
switch urlErr := e.Err.(type) {
case x509.UnknownAuthorityError:
return ccerror.UnverifiedServerError{
URL: request.URL.String(),
}
case x509.HostnameError:
return ccerror.SSLValidationHostnameError{
Message: urlErr.Error(),
}
default:
return ccerror.RequestError{Err: e}
}
default:
return err
}
}
func (connection *CloudControllerConnection) populateResponse(response *http.Response, passedResponse *Response) error {
passedResponse.HTTPResponse = response
// The cloud controller returns warnings with key "X-Cf-Warnings", and the
// value is a comma seperated string.
if rawWarnings := response.Header.Get("X-Cf-Warnings"); rawWarnings != "" {
passedResponse.Warnings = []string{}
for _, warning := range strings.Split(rawWarnings, ",") {
warningTrimmed := strings.Trim(warning, " ")
passedResponse.Warnings = append(passedResponse.Warnings, warningTrimmed)
}
}
if resourceLocationURL := response.Header.Get("Location"); resourceLocationURL != "" {
passedResponse.ResourceLocationURL = resourceLocationURL
}
rawBytes, err := ioutil.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
return err
}
passedResponse.RawResponse = rawBytes
err = connection.handleStatusCodes(response, passedResponse)
if err != nil {
return err
}
if passedResponse.Result != nil {
decoder := json.NewDecoder(bytes.NewBuffer(passedResponse.RawResponse))
decoder.UseNumber()
err = decoder.Decode(passedResponse.Result)
if err != nil {
return err
}
}
return nil
}
func (*CloudControllerConnection) handleStatusCodes(response *http.Response, passedResponse *Response) error {
if response.StatusCode >= 400 {
return ccerror.RawHTTPStatusError{
StatusCode: response.StatusCode,
RawResponse: passedResponse.RawResponse,
RequestIDs: response.Header["X-Vcap-Request-Id"],
}
}
return nil
}