github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/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  	specificPage := false
    99  	for _, query := range requestParams.Query {
   100  		if query.Key == Page {
   101  			specificPage = true
   102  			break
   103  		}
   104  	}
   105  
   106  	return requester.paginate(request, requestParams.ResponseBody, requestParams.AppendToList, specificPage)
   107  }
   108  
   109  func (requester *RealRequester) MakeRequest(requestParams RequestParams) (JobURL, Warnings, error) {
   110  	request, err := requester.buildRequest(requestParams)
   111  	if err != nil {
   112  		return "", nil, err
   113  	}
   114  
   115  	response := cloudcontroller.Response{}
   116  	if requestParams.ResponseBody != nil {
   117  		response.DecodeJSONResponseInto = requestParams.ResponseBody
   118  	}
   119  
   120  	err = requester.connection.Make(request, &response)
   121  
   122  	return JobURL(response.ResourceLocationURL), response.Warnings, err
   123  }
   124  
   125  func (requester *RealRequester) MakeRequestReceiveRaw(
   126  	requestName string,
   127  	uriParams internal.Params,
   128  	responseBodyMimeType string,
   129  ) ([]byte, Warnings, error) {
   130  	request, err := requester.newHTTPRequest(requestOptions{
   131  		RequestName: requestName,
   132  		URIParams:   uriParams,
   133  	})
   134  	if err != nil {
   135  		return nil, nil, err
   136  	}
   137  
   138  	response := cloudcontroller.Response{}
   139  
   140  	request.Header.Set("Accept", responseBodyMimeType)
   141  
   142  	err = requester.connection.Make(request, &response)
   143  
   144  	return response.RawResponse, response.Warnings, err
   145  }
   146  
   147  func (requester *RealRequester) MakeRequestSendReceiveRaw(
   148  	Method string,
   149  	URL string,
   150  	headers http.Header,
   151  	requestBody []byte,
   152  ) ([]byte, *http.Response, error) {
   153  	request, err := requester.newHTTPRequest(requestOptions{
   154  		URL:    URL,
   155  		Method: Method,
   156  		Body:   bytes.NewReader(requestBody),
   157  		Header: headers,
   158  	})
   159  	if err != nil {
   160  		return nil, nil, err
   161  	}
   162  
   163  	response := cloudcontroller.Response{}
   164  
   165  	err = requester.connection.Make(request, &response)
   166  
   167  	return response.RawResponse, response.HTTPResponse, err
   168  }
   169  
   170  func (requester *RealRequester) MakeRequestSendRaw(
   171  	requestName string,
   172  	uriParams internal.Params,
   173  	requestBody []byte,
   174  	requestBodyMimeType string,
   175  	responseBody interface{},
   176  ) (string, Warnings, error) {
   177  	request, err := requester.newHTTPRequest(requestOptions{
   178  		RequestName: requestName,
   179  		URIParams:   uriParams,
   180  		Body:        bytes.NewReader(requestBody),
   181  	})
   182  	if err != nil {
   183  		return "", nil, err
   184  	}
   185  
   186  	request.Header.Set("Content-type", requestBodyMimeType)
   187  
   188  	response := cloudcontroller.Response{
   189  		DecodeJSONResponseInto: responseBody,
   190  	}
   191  
   192  	err = requester.connection.Make(request, &response)
   193  
   194  	return response.ResourceLocationURL, response.Warnings, err
   195  }
   196  
   197  func (requester *RealRequester) MakeRequestUploadAsync(
   198  	requestName string,
   199  	uriParams internal.Params,
   200  	requestBodyMimeType string,
   201  	requestBody io.ReadSeeker,
   202  	dataLength int64,
   203  	responseBody interface{},
   204  	writeErrors <-chan error,
   205  ) (string, Warnings, error) {
   206  	request, err := requester.newHTTPRequest(requestOptions{
   207  		RequestName: requestName,
   208  		URIParams:   uriParams,
   209  		Body:        requestBody,
   210  	})
   211  	if err != nil {
   212  		return "", nil, err
   213  	}
   214  
   215  	request.Header.Set("Content-Type", requestBodyMimeType)
   216  	request.ContentLength = dataLength
   217  
   218  	return requester.uploadAsynchronously(request, responseBody, writeErrors)
   219  }
   220  
   221  func NewRequester(config Config) *RealRequester {
   222  	userAgent := fmt.Sprintf(
   223  		"%s/%s (%s; %s %s)",
   224  		config.AppName,
   225  		config.AppVersion,
   226  		runtime.Version(),
   227  		runtime.GOARCH,
   228  		runtime.GOOS,
   229  	)
   230  
   231  	return &RealRequester{
   232  		userAgent: userAgent,
   233  		wrappers:  append([]ConnectionWrapper{newErrorWrapper()}, config.Wrappers...),
   234  	}
   235  }
   236  
   237  func (requester *RealRequester) buildRequest(requestParams RequestParams) (*cloudcontroller.Request, error) {
   238  	options := requestOptions{
   239  		RequestName: requestParams.RequestName,
   240  		URIParams:   requestParams.URIParams,
   241  		Query:       requestParams.Query,
   242  		URL:         requestParams.URL,
   243  	}
   244  
   245  	if requestParams.RequestBody != nil {
   246  		body, err := json.Marshal(requestParams.RequestBody)
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  
   251  		options.Body = bytes.NewReader(body)
   252  	}
   253  
   254  	request, err := requester.newHTTPRequest(options)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  
   259  	return request, err
   260  }
   261  
   262  func (requester *RealRequester) uploadAsynchronously(request *cloudcontroller.Request, responseBody interface{}, writeErrors <-chan error) (string, Warnings, error) {
   263  	response := cloudcontroller.Response{
   264  		DecodeJSONResponseInto: responseBody,
   265  	}
   266  
   267  	httpErrors := make(chan error)
   268  
   269  	go func() {
   270  		defer close(httpErrors)
   271  
   272  		err := requester.connection.Make(request, &response)
   273  		if err != nil {
   274  			httpErrors <- err
   275  		}
   276  	}()
   277  
   278  	// The following section makes the following assumptions:
   279  	// 1) If an error occurs during file reading, an EOF is sent to the request
   280  	// object. Thus ending the request transfer.
   281  	// 2) If an error occurs during request transfer, an EOF is sent to the pipe.
   282  	// Thus ending the writing routine.
   283  	var firstError error
   284  	var writeClosed, httpClosed bool
   285  
   286  	for {
   287  		select {
   288  		case writeErr, ok := <-writeErrors:
   289  			if !ok {
   290  				writeClosed = true
   291  				break // for select
   292  			}
   293  			if firstError == nil {
   294  				firstError = writeErr
   295  			}
   296  		case httpErr, ok := <-httpErrors:
   297  			if !ok {
   298  				httpClosed = true
   299  				break // for select
   300  			}
   301  			if firstError == nil {
   302  				firstError = httpErr
   303  			}
   304  		}
   305  
   306  		if writeClosed && httpClosed {
   307  			break // for for
   308  		}
   309  	}
   310  
   311  	return response.ResourceLocationURL, response.Warnings, firstError
   312  }