github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/api/cloudcontroller/ccv3/droplet.go (about)

     1  package ccv3
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  
     8  	"code.cloudfoundry.org/cli/api/cloudcontroller"
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/uploads"
    13  )
    14  
    15  // Droplet represents a Cloud Controller droplet's metadata. A droplet is a set of
    16  // compiled bits for a given application.
    17  type Droplet struct {
    18  	//Buildpacks are the detected buildpacks from the staging process.
    19  	Buildpacks []DropletBuildpack `json:"buildpacks,omitempty"`
    20  	// CreatedAt is the timestamp that the Cloud Controller created the droplet.
    21  	CreatedAt string `json:"created_at"`
    22  	// GUID is the unique droplet identifier.
    23  	GUID string `json:"guid"`
    24  	// Image is the Docker image name.
    25  	Image string `json:"image"`
    26  	// Stack is the root filesystem to use with the buildpack.
    27  	Stack string `json:"stack,omitempty"`
    28  	// State is the current state of the droplet.
    29  	State constant.DropletState `json:"state"`
    30  }
    31  
    32  // DropletBuildpack is the name and output of a buildpack used to create a
    33  // droplet.
    34  type DropletBuildpack struct {
    35  	// Name is the buildpack name.
    36  	Name string `json:"name"`
    37  	//DetectOutput is the output during buildpack detect process.
    38  	DetectOutput string `json:"detect_output"`
    39  }
    40  
    41  type DropletCreateRequest struct {
    42  	Relationships Relationships `json:"relationships"`
    43  }
    44  
    45  // CreateDroplet creates a new droplet without a package for the app with
    46  // the given guid.
    47  func (client *Client) CreateDroplet(appGUID string) (Droplet, Warnings, error) {
    48  	requestBody := DropletCreateRequest{
    49  		Relationships: Relationships{
    50  			constant.RelationshipTypeApplication: Relationship{GUID: appGUID},
    51  		},
    52  	}
    53  
    54  	body, marshalErr := json.Marshal(requestBody)
    55  	if marshalErr != nil {
    56  		return Droplet{}, nil, marshalErr
    57  	}
    58  
    59  	request, createRequestErr := client.newHTTPRequest(requestOptions{
    60  		RequestName: internal.PostDropletRequest,
    61  		Body:        bytes.NewReader(body),
    62  	})
    63  	if createRequestErr != nil {
    64  		return Droplet{}, nil, createRequestErr
    65  	}
    66  
    67  	var responseDroplet Droplet
    68  	response := cloudcontroller.Response{
    69  		DecodeJSONResponseInto: &responseDroplet,
    70  	}
    71  	err := client.connection.Make(request, &response)
    72  
    73  	return responseDroplet, response.Warnings, err
    74  }
    75  
    76  // GetApplicationDropletCurrent returns the current droplet for a given
    77  // application.
    78  func (client *Client) GetApplicationDropletCurrent(appGUID string) (Droplet, Warnings, error) {
    79  	request, err := client.newHTTPRequest(requestOptions{
    80  		RequestName: internal.GetApplicationDropletCurrentRequest,
    81  		URIParams:   map[string]string{"app_guid": appGUID},
    82  	})
    83  	if err != nil {
    84  		return Droplet{}, nil, err
    85  	}
    86  
    87  	var responseDroplet Droplet
    88  	response := cloudcontroller.Response{
    89  		DecodeJSONResponseInto: &responseDroplet,
    90  	}
    91  	err = client.connection.Make(request, &response)
    92  	return responseDroplet, response.Warnings, err
    93  }
    94  
    95  // GetDroplet returns a droplet with the given GUID.
    96  func (client *Client) GetDroplet(dropletGUID string) (Droplet, Warnings, error) {
    97  	request, err := client.newHTTPRequest(requestOptions{
    98  		RequestName: internal.GetDropletRequest,
    99  		URIParams:   map[string]string{"droplet_guid": dropletGUID},
   100  	})
   101  	if err != nil {
   102  		return Droplet{}, nil, err
   103  	}
   104  
   105  	var responseDroplet Droplet
   106  	response := cloudcontroller.Response{
   107  		DecodeJSONResponseInto: &responseDroplet,
   108  	}
   109  	err = client.connection.Make(request, &response)
   110  
   111  	return responseDroplet, response.Warnings, err
   112  }
   113  
   114  // GetDroplets lists droplets with optional filters.
   115  func (client *Client) GetDroplets(query ...Query) ([]Droplet, Warnings, error) {
   116  	request, err := client.newHTTPRequest(requestOptions{
   117  		RequestName: internal.GetDropletsRequest,
   118  		Query:       query,
   119  	})
   120  	if err != nil {
   121  		return nil, nil, err
   122  	}
   123  
   124  	var responseDroplets []Droplet
   125  	warnings, err := client.paginate(request, Droplet{}, func(item interface{}) error {
   126  		if droplet, ok := item.(Droplet); ok {
   127  			responseDroplets = append(responseDroplets, droplet)
   128  		} else {
   129  			return ccerror.UnknownObjectInListError{
   130  				Expected:   Droplet{},
   131  				Unexpected: item,
   132  			}
   133  		}
   134  		return nil
   135  	})
   136  
   137  	return responseDroplets, warnings, err
   138  }
   139  
   140  // GetPackageDroplets returns the droplets that run the specified packages
   141  func (client *Client) GetPackageDroplets(packageGUID string, query ...Query) ([]Droplet, Warnings, error) {
   142  	request, err := client.newHTTPRequest(requestOptions{
   143  		RequestName: internal.GetPackageDropletsRequest,
   144  		URIParams:   map[string]string{"package_guid": packageGUID},
   145  		Query:       query,
   146  	})
   147  	if err != nil {
   148  		return nil, nil, err
   149  	}
   150  
   151  	var responseDroplets []Droplet
   152  	warnings, err := client.paginate(request, Droplet{}, func(item interface{}) error {
   153  		if droplet, ok := item.(Droplet); ok {
   154  			responseDroplets = append(responseDroplets, droplet)
   155  		} else {
   156  			return ccerror.UnknownObjectInListError{
   157  				Expected:   Droplet{},
   158  				Unexpected: item,
   159  			}
   160  		}
   161  		return nil
   162  	})
   163  
   164  	return responseDroplets, warnings, err
   165  }
   166  
   167  // UploadDropletBits asynchronously uploads bits from a .tgz file located at dropletPath to the
   168  // droplet with guid dropletGUID. It returns a job URL pointing to the asynchronous upload job.
   169  func (client *Client) UploadDropletBits(dropletGUID string, dropletPath string, droplet io.Reader, dropletLength int64) (JobURL, Warnings, error) {
   170  	contentLength, err := uploads.CalculateRequestSize(dropletLength, dropletPath, "bits")
   171  	if err != nil {
   172  		return "", nil, err
   173  	}
   174  
   175  	contentType, body, writeErrors := uploads.CreateMultipartBodyAndHeader(droplet, dropletPath, "bits")
   176  
   177  	request, err := client.newHTTPRequest(requestOptions{
   178  		RequestName: internal.PostDropletBitsRequest,
   179  		URIParams:   internal.Params{"droplet_guid": dropletGUID},
   180  		Body:        body,
   181  	})
   182  	if err != nil {
   183  		return "", nil, err
   184  	}
   185  
   186  	request.ContentLength = contentLength
   187  	request.Header.Set("Content-Type", contentType)
   188  
   189  	jobURL, warnings, err := client.uploadDropletAsynchronously(request, writeErrors)
   190  	if err != nil {
   191  		return "", warnings, err
   192  	}
   193  
   194  	return jobURL, warnings, nil
   195  }
   196  
   197  func (client *Client) uploadDropletAsynchronously(request *cloudcontroller.Request, writeErrors <-chan error) (JobURL, Warnings, error) {
   198  	var droplet Droplet
   199  	response := cloudcontroller.Response{
   200  		DecodeJSONResponseInto: &droplet,
   201  	}
   202  
   203  	httpErrors := make(chan error)
   204  
   205  	go func() {
   206  		defer close(httpErrors)
   207  
   208  		err := client.connection.Make(request, &response)
   209  		if err != nil {
   210  			httpErrors <- err
   211  		}
   212  	}()
   213  
   214  	// The following section makes the following assumptions:
   215  	// 1) If an error occurs during file reading, an EOF is sent to the request
   216  	// object. Thus ending the request transfer.
   217  	// 2) If an error occurs during request transfer, an EOF is sent to the pipe.
   218  	// Thus ending the writing routine.
   219  	var firstError error
   220  	var writeClosed, httpClosed bool
   221  
   222  	for {
   223  		select {
   224  		case writeErr, ok := <-writeErrors:
   225  			if !ok {
   226  				writeClosed = true
   227  				break // for select
   228  			}
   229  			if firstError == nil {
   230  				firstError = writeErr
   231  			}
   232  		case httpErr, ok := <-httpErrors:
   233  			if !ok {
   234  				httpClosed = true
   235  				break // for select
   236  			}
   237  			if firstError == nil {
   238  				firstError = httpErr
   239  			}
   240  		}
   241  
   242  		if writeClosed && httpClosed {
   243  			break // for for
   244  		}
   245  	}
   246  
   247  	return JobURL(response.ResourceLocationURL), response.Warnings, firstError
   248  }