github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+incompatible/api/cloudcontroller/ccv2/buildpack.go (about) 1 package ccv2 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "mime/multipart" 8 "path/filepath" 9 10 "code.cloudfoundry.org/cli/api/cloudcontroller" 11 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 12 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv2/internal" 13 "code.cloudfoundry.org/cli/types" 14 ) 15 16 // Buildpack represents a Cloud Controller Buildpack. 17 type Buildpack struct { 18 Locked types.NullBool 19 Enabled types.NullBool 20 GUID string 21 Name string 22 Position types.NullInt 23 Stack string 24 } 25 26 func (buildpack Buildpack) MarshalJSON() ([]byte, error) { 27 ccBuildpack := struct { 28 Locked *bool `json:"locked,omitempty"` 29 Enabled *bool `json:"enabled,omitempty"` 30 Name string `json:"name"` 31 Position *int `json:"position,omitempty"` 32 Stack string `json:"stack,omitempty"` 33 }{ 34 Name: buildpack.Name, 35 Stack: buildpack.Stack, 36 } 37 38 if buildpack.Position.IsSet { 39 ccBuildpack.Position = &buildpack.Position.Value 40 } 41 if buildpack.Enabled.IsSet { 42 ccBuildpack.Enabled = &buildpack.Enabled.Value 43 } 44 if buildpack.Locked.IsSet { 45 ccBuildpack.Locked = &buildpack.Locked.Value 46 } 47 48 return json.Marshal(ccBuildpack) 49 } 50 51 func (buildpack *Buildpack) UnmarshalJSON(data []byte) error { 52 var alias struct { 53 Metadata internal.Metadata `json:"metadata"` 54 Entity struct { 55 Locked types.NullBool `json:"locked"` 56 Enabled types.NullBool `json:"enabled"` 57 Name string `json:"name"` 58 Position types.NullInt `json:"position"` 59 Stack string `json:"stack"` 60 } `json:"entity"` 61 } 62 63 err := json.Unmarshal(data, &alias) 64 if err != nil { 65 return err 66 } 67 68 buildpack.Locked = alias.Entity.Locked 69 buildpack.Enabled = alias.Entity.Enabled 70 buildpack.GUID = alias.Metadata.GUID 71 buildpack.Name = alias.Entity.Name 72 buildpack.Position = alias.Entity.Position 73 buildpack.Stack = alias.Entity.Stack 74 return nil 75 } 76 77 // CreateBuildpack creates a new buildpack. 78 func (client *Client) CreateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) { 79 body, err := json.Marshal(buildpack) 80 if err != nil { 81 return Buildpack{}, nil, err 82 } 83 84 request, err := client.newHTTPRequest(requestOptions{ 85 RequestName: internal.PostBuildpackRequest, 86 Body: bytes.NewReader(body), 87 }) 88 if err != nil { 89 return Buildpack{}, nil, err 90 } 91 92 var createdBuildpack Buildpack 93 response := cloudcontroller.Response{ 94 DecodeJSONResponseInto: &createdBuildpack, 95 } 96 97 err = client.connection.Make(request, &response) 98 return createdBuildpack, response.Warnings, err 99 } 100 101 // GetBuildpacks searches for a buildpack with the given name and returns it if it exists. 102 func (client *Client) GetBuildpacks(filters ...Filter) ([]Buildpack, Warnings, error) { 103 request, err := client.newHTTPRequest(requestOptions{ 104 RequestName: internal.GetBuildpacksRequest, 105 Query: ConvertFilterParameters(filters), 106 }) 107 108 if err != nil { 109 return nil, nil, err 110 } 111 112 var buildpacks []Buildpack 113 warnings, err := client.paginate(request, Buildpack{}, func(item interface{}) error { 114 if buildpack, ok := item.(Buildpack); ok { 115 buildpacks = append(buildpacks, buildpack) 116 } else { 117 return ccerror.UnknownObjectInListError{ 118 Expected: Buildpack{}, 119 Unexpected: item, 120 } 121 } 122 return nil 123 }) 124 125 return buildpacks, warnings, err 126 } 127 128 // UpdateBuildpack updates the buildpack with the provided GUID and returns the 129 // updated buildpack. Note: Stack cannot be updated without uploading a new 130 // buildpack. 131 func (client *Client) UpdateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) { 132 body, err := json.Marshal(buildpack) 133 if err != nil { 134 return Buildpack{}, nil, err 135 } 136 137 request, err := client.newHTTPRequest(requestOptions{ 138 RequestName: internal.PutBuildpackRequest, 139 URIParams: Params{"buildpack_guid": buildpack.GUID}, 140 Body: bytes.NewReader(body), 141 }) 142 if err != nil { 143 return Buildpack{}, nil, err 144 } 145 146 var updatedBuildpack Buildpack 147 response := cloudcontroller.Response{ 148 DecodeJSONResponseInto: &updatedBuildpack, 149 } 150 151 err = client.connection.Make(request, &response) 152 if err != nil { 153 return Buildpack{}, response.Warnings, err 154 } 155 156 return updatedBuildpack, response.Warnings, nil 157 } 158 159 // UploadBuildpack uploads the contents of a buildpack zip to the server. 160 func (client *Client) UploadBuildpack(buildpackGUID string, buildpackPath string, buildpack io.Reader, buildpackLength int64) (Warnings, error) { 161 162 contentLength, err := client.calculateBuildpackRequestSize(buildpackLength, buildpackPath) 163 if err != nil { 164 return nil, err 165 } 166 167 contentType, body, writeErrors := client.createMultipartBodyAndHeaderForBuildpack(buildpack, buildpackPath) 168 169 request, err := client.newHTTPRequest(requestOptions{ 170 RequestName: internal.PutBuildpackBitsRequest, 171 URIParams: Params{"buildpack_guid": buildpackGUID}, 172 Body: body, 173 }) 174 175 if err != nil { 176 return nil, err 177 } 178 179 request.Header.Set("Content-Type", contentType) 180 request.ContentLength = contentLength 181 182 _, warnings, err := client.uploadBuildpackAsynchronously(request, writeErrors) 183 if err != nil { 184 return warnings, err 185 } 186 return warnings, nil 187 188 } 189 190 func (*Client) calculateBuildpackRequestSize(buildpackSize int64, bpPath string) (int64, error) { 191 body := &bytes.Buffer{} 192 form := multipart.NewWriter(body) 193 194 bpFileName := filepath.Base(bpPath) 195 196 _, err := form.CreateFormFile("buildpack", bpFileName) 197 if err != nil { 198 return 0, err 199 } 200 201 err = form.Close() 202 if err != nil { 203 return 0, err 204 } 205 206 return int64(body.Len()) + buildpackSize, nil 207 } 208 209 func (*Client) createMultipartBodyAndHeaderForBuildpack(buildpack io.Reader, bpPath string) (string, io.ReadSeeker, <-chan error) { 210 writerOutput, writerInput := cloudcontroller.NewPipeBomb() 211 212 form := multipart.NewWriter(writerInput) 213 214 writeErrors := make(chan error) 215 216 go func() { 217 defer close(writeErrors) 218 defer writerInput.Close() 219 220 bpFileName := filepath.Base(bpPath) 221 writer, err := form.CreateFormFile("buildpack", bpFileName) 222 if err != nil { 223 writeErrors <- err 224 return 225 } 226 227 _, err = io.Copy(writer, buildpack) 228 if err != nil { 229 writeErrors <- err 230 return 231 } 232 233 err = form.Close() 234 if err != nil { 235 writeErrors <- err 236 } 237 }() 238 239 return form.FormDataContentType(), writerOutput, writeErrors 240 } 241 242 func (client *Client) uploadBuildpackAsynchronously(request *cloudcontroller.Request, writeErrors <-chan error) (Buildpack, Warnings, error) { 243 244 var buildpack Buildpack 245 response := cloudcontroller.Response{ 246 DecodeJSONResponseInto: &buildpack, 247 } 248 249 httpErrors := make(chan error) 250 251 go func() { 252 defer close(httpErrors) 253 254 err := client.connection.Make(request, &response) 255 if err != nil { 256 httpErrors <- err 257 } 258 }() 259 260 // The following section makes the following assumptions: 261 // 1) If an error occurs during file reading, an EOF is sent to the request 262 // object. Thus ending the request transfer. 263 // 2) If an error occurs during request transfer, an EOF is sent to the pipe. 264 // Thus ending the writing routine. 265 var firstError error 266 var writeClosed, httpClosed bool 267 268 for { 269 select { 270 case writeErr, ok := <-writeErrors: 271 if !ok { 272 writeClosed = true 273 break // for select 274 } 275 if firstError == nil { 276 firstError = writeErr 277 } 278 case httpErr, ok := <-httpErrors: 279 if !ok { 280 httpClosed = true 281 break // for select 282 } 283 if firstError == nil { 284 firstError = httpErr 285 } 286 } 287 288 if writeClosed && httpClosed { 289 break // for for 290 } 291 } 292 return buildpack, response.Warnings, firstError 293 }