github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/api/cloudcontroller/cloud_controller_connection.go (about)

     1  package cloudcontroller
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"encoding/json"
     8  	"io/ioutil"
     9  	"net"
    10  	"net/http"
    11  	"net/url"
    12  	"strings"
    13  	"time"
    14  
    15  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    16  )
    17  
    18  // CloudControllerConnection represents a connection to the Cloud Controller
    19  // server.
    20  type CloudControllerConnection struct {
    21  	HTTPClient *http.Client
    22  	UserAgent  string
    23  }
    24  
    25  // Config is for configuring a CloudControllerConnection.
    26  type Config struct {
    27  	DialTimeout       time.Duration
    28  	SkipSSLValidation bool
    29  }
    30  
    31  // NewConnection returns a new CloudControllerConnection with provided
    32  // configuration.
    33  func NewConnection(config Config) *CloudControllerConnection {
    34  	tr := &http.Transport{
    35  		TLSClientConfig: &tls.Config{
    36  			InsecureSkipVerify: config.SkipSSLValidation,
    37  		},
    38  		Proxy: http.ProxyFromEnvironment,
    39  		DialContext: (&net.Dialer{
    40  			KeepAlive: 30 * time.Second,
    41  			Timeout:   config.DialTimeout,
    42  		}).DialContext,
    43  	}
    44  
    45  	return &CloudControllerConnection{
    46  		HTTPClient: &http.Client{Transport: tr},
    47  	}
    48  }
    49  
    50  // Make performs the request and parses the response.
    51  func (connection *CloudControllerConnection) Make(request *Request, passedResponse *Response) error {
    52  	// In case this function is called from a retry, passedResponse may already
    53  	// be populated with a previous response. We reset in case there's an HTTP
    54  	// error and we don't repopulate it in populateResponse.
    55  	passedResponse.reset()
    56  
    57  	response, err := connection.HTTPClient.Do(request.Request)
    58  	if err != nil {
    59  		return connection.processRequestErrors(request.Request, err)
    60  	}
    61  
    62  	return connection.populateResponse(response, passedResponse)
    63  }
    64  
    65  func (*CloudControllerConnection) processRequestErrors(request *http.Request, err error) error {
    66  	switch e := err.(type) {
    67  	case *url.Error:
    68  		switch urlErr := e.Err.(type) {
    69  		case x509.UnknownAuthorityError:
    70  			return ccerror.UnverifiedServerError{
    71  				URL: request.URL.String(),
    72  			}
    73  		case x509.HostnameError:
    74  			return ccerror.SSLValidationHostnameError{
    75  				Message: urlErr.Error(),
    76  			}
    77  		default:
    78  			return ccerror.RequestError{Err: e}
    79  		}
    80  	default:
    81  		return err
    82  	}
    83  }
    84  
    85  func (connection *CloudControllerConnection) populateResponse(response *http.Response, passedResponse *Response) error {
    86  	passedResponse.HTTPResponse = response
    87  
    88  	// The cloud controller returns warnings with key "X-Cf-Warnings", and the
    89  	// value is a comma seperated string.
    90  	if rawWarnings := response.Header.Get("X-Cf-Warnings"); rawWarnings != "" {
    91  		passedResponse.Warnings = []string{}
    92  		for _, warning := range strings.Split(rawWarnings, ",") {
    93  			warningTrimmed := strings.Trim(warning, " ")
    94  			passedResponse.Warnings = append(passedResponse.Warnings, warningTrimmed)
    95  		}
    96  	}
    97  
    98  	if resourceLocationURL := response.Header.Get("Location"); resourceLocationURL != "" {
    99  		passedResponse.ResourceLocationURL = resourceLocationURL
   100  	}
   101  
   102  	err := connection.handleStatusCodes(response, passedResponse)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	// TODO: only unmarshal on 'application/json', skip otherwise
   108  	if passedResponse.Result != nil {
   109  		decoder := json.NewDecoder(bytes.NewBuffer(passedResponse.RawResponse))
   110  		decoder.UseNumber()
   111  		err = decoder.Decode(passedResponse.Result)
   112  		if err != nil {
   113  			return err
   114  		}
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  func (*CloudControllerConnection) handleStatusCodes(response *http.Response, passedResponse *Response) error {
   121  	if response.StatusCode == http.StatusNoContent {
   122  		passedResponse.RawResponse = []byte("{}")
   123  	} else {
   124  		rawBytes, err := ioutil.ReadAll(response.Body)
   125  		defer response.Body.Close()
   126  		if err != nil {
   127  			return err
   128  		}
   129  
   130  		passedResponse.RawResponse = rawBytes
   131  	}
   132  
   133  	if response.StatusCode >= 400 {
   134  		return ccerror.RawHTTPStatusError{
   135  			StatusCode:  response.StatusCode,
   136  			RawResponse: passedResponse.RawResponse,
   137  			RequestIDs:  response.Header["X-Vcap-Request-Id"],
   138  		}
   139  	}
   140  
   141  	return nil
   142  }