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 }