github.com/swisscom/cloudfoundry-cli@v7.1.0+incompatible/api/cloudcontroller/ccv3/requester.go (about) 1 package ccv3 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "runtime" 9 10 "code.cloudfoundry.org/cli/api/cloudcontroller" 11 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal" 12 ) 13 14 //go:generate counterfeiter . Requester 15 16 type RequestParams struct { 17 RequestName string 18 URIParams internal.Params 19 Query []Query 20 RequestBody interface{} 21 RequestHeaders [][]string 22 ResponseBody interface{} 23 URL string 24 AppendToList func(item interface{}) error 25 } 26 27 type Requester interface { 28 InitializeConnection(settings TargetSettings) 29 30 InitializeRouter(resources map[string]string) 31 32 MakeListRequest(requestParams RequestParams) (IncludedResources, Warnings, error) 33 34 MakeRequest(requestParams RequestParams) (JobURL, Warnings, error) 35 36 MakeRequestReceiveRaw( 37 requestName string, 38 uriParams internal.Params, 39 responseBodyMimeType string, 40 ) ([]byte, Warnings, error) 41 42 MakeRequestSendRaw( 43 requestName string, 44 uriParams internal.Params, 45 requestBody []byte, 46 requestBodyMimeType string, 47 responseBody interface{}, 48 ) (string, Warnings, error) 49 50 MakeRequestUploadAsync( 51 requestName string, 52 uriParams internal.Params, 53 requestBodyMimeType string, 54 requestBody io.ReadSeeker, 55 dataLength int64, 56 responseBody interface{}, 57 writeErrors <-chan error, 58 ) (string, Warnings, error) 59 60 WrapConnection(wrapper ConnectionWrapper) 61 } 62 63 type RealRequester struct { 64 connection cloudcontroller.Connection 65 router *internal.Router 66 userAgent string 67 wrappers []ConnectionWrapper 68 } 69 70 func (requester *RealRequester) InitializeConnection(settings TargetSettings) { 71 requester.connection = cloudcontroller.NewConnection(cloudcontroller.Config{ 72 DialTimeout: settings.DialTimeout, 73 SkipSSLValidation: settings.SkipSSLValidation, 74 }) 75 76 for _, wrapper := range requester.wrappers { 77 requester.connection = wrapper.Wrap(requester.connection) 78 } 79 } 80 81 func (requester *RealRequester) InitializeRouter(resources map[string]string) { 82 requester.router = internal.NewRouter(internal.APIRoutes, resources) 83 } 84 85 func (requester *RealRequester) MakeListRequest(requestParams RequestParams) (IncludedResources, Warnings, error) { 86 request, err := requester.buildRequest(requestParams) 87 if err != nil { 88 return IncludedResources{}, nil, err 89 } 90 91 return requester.paginate(request, requestParams.ResponseBody, requestParams.AppendToList) 92 } 93 94 func (requester *RealRequester) MakeRequest(requestParams RequestParams) (JobURL, Warnings, error) { 95 request, err := requester.buildRequest(requestParams) 96 if err != nil { 97 return "", nil, err 98 } 99 100 response := cloudcontroller.Response{} 101 if requestParams.ResponseBody != nil { 102 response.DecodeJSONResponseInto = requestParams.ResponseBody 103 } 104 105 err = requester.connection.Make(request, &response) 106 107 return JobURL(response.ResourceLocationURL), response.Warnings, err 108 } 109 110 func (requester *RealRequester) MakeRequestReceiveRaw( 111 requestName string, 112 uriParams internal.Params, 113 responseBodyMimeType string, 114 ) ([]byte, Warnings, error) { 115 request, err := requester.newHTTPRequest(requestOptions{ 116 RequestName: requestName, 117 URIParams: uriParams, 118 }) 119 if err != nil { 120 return nil, nil, err 121 } 122 123 response := cloudcontroller.Response{} 124 125 request.Header.Set("Accept", responseBodyMimeType) 126 127 err = requester.connection.Make(request, &response) 128 129 return response.RawResponse, response.Warnings, err 130 } 131 132 func (requester *RealRequester) MakeRequestSendRaw( 133 requestName string, 134 uriParams internal.Params, 135 requestBody []byte, 136 requestBodyMimeType string, 137 responseBody interface{}, 138 ) (string, Warnings, error) { 139 request, err := requester.newHTTPRequest(requestOptions{ 140 RequestName: requestName, 141 URIParams: uriParams, 142 Body: bytes.NewReader(requestBody), 143 }) 144 if err != nil { 145 return "", nil, err 146 } 147 148 request.Header.Set("Content-type", requestBodyMimeType) 149 150 response := cloudcontroller.Response{ 151 DecodeJSONResponseInto: responseBody, 152 } 153 154 err = requester.connection.Make(request, &response) 155 156 return response.ResourceLocationURL, response.Warnings, err 157 } 158 159 func (requester *RealRequester) MakeRequestUploadAsync( 160 requestName string, 161 uriParams internal.Params, 162 requestBodyMimeType string, 163 requestBody io.ReadSeeker, 164 dataLength int64, 165 responseBody interface{}, 166 writeErrors <-chan error, 167 ) (string, Warnings, error) { 168 request, err := requester.newHTTPRequest(requestOptions{ 169 RequestName: requestName, 170 URIParams: uriParams, 171 Body: requestBody, 172 }) 173 if err != nil { 174 return "", nil, err 175 } 176 177 request.Header.Set("Content-Type", requestBodyMimeType) 178 request.ContentLength = dataLength 179 180 return requester.uploadAsynchronously(request, responseBody, writeErrors) 181 } 182 183 func NewRequester(config Config) *RealRequester { 184 userAgent := fmt.Sprintf( 185 "%s/%s (%s; %s %s)", 186 config.AppName, 187 config.AppVersion, 188 runtime.Version(), 189 runtime.GOARCH, 190 runtime.GOOS, 191 ) 192 193 return &RealRequester{ 194 userAgent: userAgent, 195 wrappers: append([]ConnectionWrapper{newErrorWrapper()}, config.Wrappers...), 196 } 197 } 198 199 func (requester *RealRequester) buildRequest(requestParams RequestParams) (*cloudcontroller.Request, error) { 200 options := requestOptions{ 201 RequestName: requestParams.RequestName, 202 URIParams: requestParams.URIParams, 203 Query: requestParams.Query, 204 URL: requestParams.URL, 205 } 206 207 if requestParams.RequestBody != nil { 208 body, err := json.Marshal(requestParams.RequestBody) 209 if err != nil { 210 return nil, err 211 } 212 213 options.Body = bytes.NewReader(body) 214 } 215 216 request, err := requester.newHTTPRequest(options) 217 if err != nil { 218 return nil, err 219 } 220 221 return request, err 222 } 223 224 func (requester *RealRequester) uploadAsynchronously(request *cloudcontroller.Request, responseBody interface{}, writeErrors <-chan error) (string, Warnings, error) { 225 response := cloudcontroller.Response{ 226 DecodeJSONResponseInto: responseBody, 227 } 228 229 httpErrors := make(chan error) 230 231 go func() { 232 defer close(httpErrors) 233 234 err := requester.connection.Make(request, &response) 235 if err != nil { 236 httpErrors <- err 237 } 238 }() 239 240 // The following section makes the following assumptions: 241 // 1) If an error occurs during file reading, an EOF is sent to the request 242 // object. Thus ending the request transfer. 243 // 2) If an error occurs during request transfer, an EOF is sent to the pipe. 244 // Thus ending the writing routine. 245 var firstError error 246 var writeClosed, httpClosed bool 247 248 for { 249 select { 250 case writeErr, ok := <-writeErrors: 251 if !ok { 252 writeClosed = true 253 break // for select 254 } 255 if firstError == nil { 256 firstError = writeErr 257 } 258 case httpErr, ok := <-httpErrors: 259 if !ok { 260 httpClosed = true 261 break // for select 262 } 263 if firstError == nil { 264 firstError = httpErr 265 } 266 } 267 268 if writeClosed && httpClosed { 269 break // for for 270 } 271 } 272 273 return response.ResourceLocationURL, response.Warnings, firstError 274 }