github.com/DaAlbrecht/cf-cli@v0.0.0-20231128151943-1fe19bb400b9/api/cloudcontroller/ccv3/requester.go (about)

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