github.com/arunkumar7540/cli@v6.45.0+incompatible/api/cloudcontroller/ccv3/buildpack_test.go (about) 1 package ccv3_test 2 3 import ( 4 "code.cloudfoundry.org/cli/api/cloudcontroller" 5 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/ccv3fakes" 6 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 7 "code.cloudfoundry.org/cli/api/cloudcontroller/wrapper" 8 "code.cloudfoundry.org/cli/types" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "mime/multipart" 14 "net/http" 15 "strings" 16 17 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 18 . "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" 19 . "github.com/onsi/ginkgo" 20 . "github.com/onsi/gomega" 21 . "github.com/onsi/gomega/ghttp" 22 ) 23 24 var _ = Describe("Buildpacks", func() { 25 var client *Client 26 27 BeforeEach(func() { 28 client, _ = NewTestClient() 29 }) 30 31 Describe("GetBuildpacks", func() { 32 var ( 33 query Query 34 35 buildpacks []Buildpack 36 warnings Warnings 37 executeErr error 38 ) 39 40 JustBeforeEach(func() { 41 buildpacks, warnings, executeErr = client.GetBuildpacks(query) 42 }) 43 44 When("buildpacks exist", func() { 45 BeforeEach(func() { 46 response1 := fmt.Sprintf(`{ 47 "pagination": { 48 "next": { 49 "href": "%s/v3/buildpacks?names=some-buildpack-name&page=2&per_page=2" 50 } 51 }, 52 "resources": [ 53 { 54 "guid": "guid1", 55 "name": "ruby_buildpack", 56 "state": "AWAITING_UPLOAD", 57 "stack": "windows64", 58 "position": 1, 59 "enabled": true, 60 "locked": false 61 }, 62 { 63 "guid": "guid2", 64 "name": "staticfile_buildpack", 65 "state": "AWAITING_UPLOAD", 66 "stack": "cflinuxfs3", 67 "position": 2, 68 "enabled": false, 69 "locked": true 70 } 71 ] 72 }`, server.URL()) 73 response2 := `{ 74 "pagination": { 75 "next": null 76 }, 77 "resources": [ 78 { 79 "guid": "guid3", 80 "name": "go_buildpack", 81 "state": "AWAITING_UPLOAD", 82 "stack": "cflinuxfs2", 83 "position": 3, 84 "enabled": true, 85 "locked": false 86 } 87 ] 88 }` 89 90 server.AppendHandlers( 91 CombineHandlers( 92 VerifyRequest(http.MethodGet, "/v3/buildpacks", "names=some-buildpack-name"), 93 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 94 ), 95 ) 96 server.AppendHandlers( 97 CombineHandlers( 98 VerifyRequest(http.MethodGet, "/v3/buildpacks", "names=some-buildpack-name&page=2&per_page=2"), 99 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 100 ), 101 ) 102 103 query = Query{ 104 Key: NameFilter, 105 Values: []string{"some-buildpack-name"}, 106 } 107 }) 108 109 It("returns the queried buildpacks and all warnings", func() { 110 Expect(executeErr).NotTo(HaveOccurred()) 111 112 Expect(buildpacks).To(ConsistOf( 113 Buildpack{ 114 Name: "ruby_buildpack", 115 GUID: "guid1", 116 Position: types.NullInt{Value: 1, IsSet: true}, 117 Enabled: types.NullBool{Value: true, IsSet: true}, 118 Locked: types.NullBool{Value: false, IsSet: true}, 119 Stack: "windows64", 120 State: "AWAITING_UPLOAD", 121 }, 122 Buildpack{ 123 Name: "staticfile_buildpack", 124 GUID: "guid2", 125 Position: types.NullInt{Value: 2, IsSet: true}, 126 Enabled: types.NullBool{Value: false, IsSet: true}, 127 Locked: types.NullBool{Value: true, IsSet: true}, 128 Stack: "cflinuxfs3", 129 State: "AWAITING_UPLOAD", 130 }, 131 Buildpack{ 132 Name: "go_buildpack", 133 GUID: "guid3", 134 Position: types.NullInt{Value: 3, IsSet: true}, 135 Enabled: types.NullBool{Value: true, IsSet: true}, 136 Locked: types.NullBool{Value: false, IsSet: true}, 137 Stack: "cflinuxfs2", 138 State: "AWAITING_UPLOAD", 139 }, 140 )) 141 Expect(warnings).To(ConsistOf("this is a warning", "this is another warning")) 142 }) 143 }) 144 145 When("the cloud controller returns errors and warnings", func() { 146 BeforeEach(func() { 147 response := `{ 148 "errors": [ 149 { 150 "code": 10008, 151 "detail": "The request is semantically invalid: command presence", 152 "title": "CF-UnprocessableEntity" 153 }, 154 { 155 "code": 10010, 156 "detail": "buildpack not found", 157 "title": "CF-buildpackNotFound" 158 } 159 ] 160 }` 161 server.AppendHandlers( 162 CombineHandlers( 163 VerifyRequest(http.MethodGet, "/v3/buildpacks"), 164 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 165 ), 166 ) 167 }) 168 169 It("returns the error and all warnings", func() { 170 Expect(executeErr).To(MatchError(ccerror.MultiError{ 171 ResponseCode: http.StatusTeapot, 172 Errors: []ccerror.V3Error{ 173 { 174 Code: 10008, 175 Detail: "The request is semantically invalid: command presence", 176 Title: "CF-UnprocessableEntity", 177 }, 178 { 179 Code: 10010, 180 Detail: "buildpack not found", 181 Title: "CF-buildpackNotFound", 182 }, 183 }, 184 })) 185 Expect(warnings).To(ConsistOf("this is a warning")) 186 }) 187 }) 188 }) 189 190 Describe("CreateBuildpack", func() { 191 var ( 192 inputBuildpack Buildpack 193 194 bp Buildpack 195 warnings Warnings 196 executeErr error 197 ) 198 199 JustBeforeEach(func() { 200 bp, warnings, executeErr = client.CreateBuildpack(inputBuildpack) 201 }) 202 203 When("the buildpack is successfully created", func() { 204 BeforeEach(func() { 205 inputBuildpack = Buildpack{ 206 Name: "some-buildpack", 207 Stack: "some-stack", 208 } 209 response := `{ 210 "guid": "some-bp-guid", 211 "created_at": "2016-03-18T23:26:46Z", 212 "updated_at": "2016-10-17T20:00:42Z", 213 "name": "some-buildpack", 214 "state": "AWAITING_UPLOAD", 215 "filename": null, 216 "stack": "some-stack", 217 "position": 42, 218 "enabled": true, 219 "locked": false, 220 "links": { 221 "self": { 222 "href": "/v3/buildpacks/some-bp-guid" 223 }, 224 "upload": { 225 "href": "/v3/buildpacks/some-bp-guid/upload", 226 "method": "POST" 227 } 228 } 229 }` 230 231 expectedBody := map[string]interface{}{ 232 "name": "some-buildpack", 233 "stack": "some-stack", 234 } 235 server.AppendHandlers( 236 CombineHandlers( 237 VerifyRequest(http.MethodPost, "/v3/buildpacks"), 238 VerifyJSONRepresenting(expectedBody), 239 RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 240 ), 241 ) 242 }) 243 244 It("returns the created buildpack and warnings", func() { 245 Expect(executeErr).NotTo(HaveOccurred()) 246 Expect(warnings).To(ConsistOf("this is a warning")) 247 248 expectedBuildpack := Buildpack{ 249 GUID: "some-bp-guid", 250 Name: "some-buildpack", 251 Stack: "some-stack", 252 Enabled: types.NullBool{Value: true, IsSet: true}, 253 Filename: "", 254 Locked: types.NullBool{Value: false, IsSet: true}, 255 State: constant.BuildpackAwaitingUpload, 256 Position: types.NullInt{Value: 42, IsSet: true}, 257 Links: APILinks{ 258 "upload": APILink{ 259 Method: "POST", 260 HREF: "/v3/buildpacks/some-bp-guid/upload", 261 }, 262 "self": APILink{ 263 HREF: "/v3/buildpacks/some-bp-guid", 264 }, 265 }, 266 } 267 Expect(bp).To(Equal(expectedBuildpack)) 268 }) 269 }) 270 271 When("cc returns back an error or warnings", func() { 272 BeforeEach(func() { 273 inputBuildpack = Buildpack{} 274 response := ` { 275 "errors": [ 276 { 277 "code": 10008, 278 "detail": "The request is semantically invalid: command presence", 279 "title": "CF-UnprocessableEntity" 280 }, 281 { 282 "code": 10010, 283 "detail": "Buildpack not found", 284 "title": "CF-ResourceNotFound" 285 } 286 ] 287 }` 288 server.AppendHandlers( 289 CombineHandlers( 290 VerifyRequest(http.MethodPost, "/v3/buildpacks"), 291 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 292 ), 293 ) 294 }) 295 296 It("returns the error and all warnings", func() { 297 Expect(executeErr).To(MatchError(ccerror.MultiError{ 298 ResponseCode: http.StatusTeapot, 299 Errors: []ccerror.V3Error{ 300 { 301 Code: 10008, 302 Detail: "The request is semantically invalid: command presence", 303 Title: "CF-UnprocessableEntity", 304 }, 305 { 306 Code: 10010, 307 Detail: "Buildpack not found", 308 Title: "CF-ResourceNotFound", 309 }, 310 }, 311 })) 312 Expect(warnings).To(ConsistOf("this is a warning")) 313 }) 314 }) 315 }) 316 317 Describe("UploadBuildpack", func() { 318 var ( 319 jobURL JobURL 320 warnings Warnings 321 executeErr error 322 bpFile io.Reader 323 bpFilePath string 324 bpContent string 325 ) 326 327 BeforeEach(func() { 328 bpContent = "some-content" 329 bpFile = strings.NewReader(bpContent) 330 bpFilePath = "some/fake-buildpack.zip" 331 }) 332 333 JustBeforeEach(func() { 334 jobURL, warnings, executeErr = client.UploadBuildpack("some-buildpack-guid", bpFilePath, bpFile, int64(len(bpContent))) 335 }) 336 337 When("the upload is successful", func() { 338 BeforeEach(func() { 339 response := `{ 340 "metadata": { 341 "guid": "some-buildpack-guid", 342 "url": "/v3/buildpacks/buildpack-guid/upload" 343 }, 344 "entity": { 345 "guid": "some-buildpack-guid", 346 "status": "queued" 347 } 348 }` 349 350 verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) { 351 contentType := req.Header.Get("Content-Type") 352 Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+")) 353 354 defer req.Body.Close() 355 requestReader := multipart.NewReader(req.Body, contentType[30:]) 356 357 buildpackPart, err := requestReader.NextPart() 358 Expect(err).NotTo(HaveOccurred()) 359 360 Expect(buildpackPart.FormName()).To(Equal("bits")) 361 Expect(buildpackPart.FileName()).To(Equal("fake-buildpack.zip")) 362 363 defer buildpackPart.Close() 364 partContents, err := ioutil.ReadAll(buildpackPart) 365 Expect(err).ToNot(HaveOccurred()) 366 Expect(string(partContents)).To(Equal(bpContent)) 367 } 368 369 server.AppendHandlers( 370 CombineHandlers( 371 VerifyRequest(http.MethodPost, "/v3/buildpacks/some-buildpack-guid/upload"), 372 verifyHeaderAndBody, 373 RespondWith( 374 http.StatusAccepted, 375 response, 376 http.Header{ 377 "X-Cf-Warnings": {"this is a warning"}, 378 "Location": {"http://example.com/job-guid"}, 379 }, 380 ), 381 ), 382 ) 383 }) 384 385 It("returns the processing job URL and warnings", func() { 386 Expect(executeErr).ToNot(HaveOccurred()) 387 Expect(warnings).To(ConsistOf(Warnings{"this is a warning"})) 388 Expect(jobURL).To(Equal(JobURL("http://example.com/job-guid"))) 389 }) 390 }) 391 392 When("there is an error reading the buildpack", func() { 393 var ( 394 fakeReader *ccv3fakes.FakeReader 395 expectedErr error 396 ) 397 398 BeforeEach(func() { 399 expectedErr = errors.New("some read error") 400 fakeReader = new(ccv3fakes.FakeReader) 401 fakeReader.ReadReturns(0, expectedErr) 402 bpFile = fakeReader 403 404 server.AppendHandlers( 405 VerifyRequest(http.MethodPost, "/v3/buildpacks/some-buildpack-guid/upload"), 406 ) 407 }) 408 409 It("returns the error", func() { 410 Expect(executeErr).To(MatchError(expectedErr)) 411 }) 412 }) 413 414 When("the upload returns an error", func() { 415 BeforeEach(func() { 416 response := `{ 417 "errors": [{ 418 "detail": "The buildpack could not be found: some-buildpack-guid", 419 "title": "CF-ResourceNotFound", 420 "code": 10010 421 }] 422 }` 423 424 server.AppendHandlers( 425 CombineHandlers( 426 VerifyRequest(http.MethodPost, "/v3/buildpacks/some-buildpack-guid/upload"), 427 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 428 ), 429 ) 430 }) 431 432 It("returns the error and warnings", func() { 433 Expect(executeErr).To(MatchError( 434 ccerror.ResourceNotFoundError{ 435 Message: "The buildpack could not be found: some-buildpack-guid", 436 }, 437 )) 438 Expect(warnings).To(ConsistOf(Warnings{"this is a warning"})) 439 }) 440 }) 441 442 When("a retryable error occurs", func() { 443 BeforeEach(func() { 444 wrapper := &wrapper.CustomWrapper{ 445 CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error { 446 defer GinkgoRecover() // Since this will be running in a thread 447 448 if strings.HasSuffix(request.URL.String(), "/v3/buildpacks/some-buildpack-guid/upload") { 449 _, err := ioutil.ReadAll(request.Body) 450 Expect(err).ToNot(HaveOccurred()) 451 Expect(request.Body.Close()).ToNot(HaveOccurred()) 452 return request.ResetBody() 453 } 454 return connection.Make(request, response) 455 }, 456 } 457 458 client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}}) 459 }) 460 461 It("returns the PipeSeekError", func() { 462 Expect(executeErr).To(MatchError(ccerror.PipeSeekError{})) 463 }) 464 }) 465 466 When("an http error occurs mid-transfer", func() { 467 var expectedErr error 468 469 BeforeEach(func() { 470 expectedErr = errors.New("some read error") 471 472 wrapper := &wrapper.CustomWrapper{ 473 CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error { 474 defer GinkgoRecover() // Since this will be running in a thread 475 476 if strings.HasSuffix(request.URL.String(), "/v3/buildpacks/some-buildpack-guid/upload") { 477 defer request.Body.Close() 478 readBytes, err := ioutil.ReadAll(request.Body) 479 Expect(err).ToNot(HaveOccurred()) 480 Expect(len(readBytes)).To(BeNumerically(">", len(bpContent))) 481 return expectedErr 482 } 483 return connection.Make(request, response) 484 }, 485 } 486 487 client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}}) 488 }) 489 490 It("returns the http error", func() { 491 Expect(executeErr).To(MatchError(expectedErr)) 492 }) 493 }) 494 }) 495 496 Describe("UpdateBuildpack", func() { 497 var ( 498 inputBuildpack Buildpack 499 500 bp Buildpack 501 warnings Warnings 502 executeErr error 503 ) 504 505 JustBeforeEach(func() { 506 bp, warnings, executeErr = client.UpdateBuildpack(inputBuildpack) 507 }) 508 509 When("the buildpack is successfully created", func() { 510 BeforeEach(func() { 511 inputBuildpack = Buildpack{ 512 Name: "some-buildpack", 513 GUID: "some-bp-guid", 514 Stack: "some-stack", 515 Locked: types.NullBool{IsSet: true, Value: true}, 516 } 517 response := `{ 518 "guid": "some-bp-guid", 519 "created_at": "2016-03-18T23:26:46Z", 520 "updated_at": "2016-10-17T20:00:42Z", 521 "name": "some-buildpack", 522 "state": "AWAITING_UPLOAD", 523 "filename": null, 524 "stack": "some-stack", 525 "position": 42, 526 "enabled": true, 527 "locked": true, 528 "links": { 529 "self": { 530 "href": "/v3/buildpacks/some-bp-guid" 531 }, 532 "upload": { 533 "href": "/v3/buildpacks/some-bp-guid/upload", 534 "method": "POST" 535 } 536 } 537 }` 538 539 expectedBody := map[string]interface{}{ 540 "name": "some-buildpack", 541 "stack": "some-stack", 542 "locked": true, 543 } 544 server.AppendHandlers( 545 CombineHandlers( 546 VerifyRequest(http.MethodPatch, "/v3/buildpacks/some-bp-guid"), 547 VerifyJSONRepresenting(expectedBody), 548 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 549 ), 550 ) 551 }) 552 553 It("returns the created buildpack and warnings", func() { 554 Expect(executeErr).NotTo(HaveOccurred()) 555 Expect(warnings).To(ConsistOf("this is a warning")) 556 557 expectedBuildpack := Buildpack{ 558 GUID: "some-bp-guid", 559 Name: "some-buildpack", 560 Stack: "some-stack", 561 Enabled: types.NullBool{Value: true, IsSet: true}, 562 Filename: "", 563 Locked: types.NullBool{Value: true, IsSet: true}, 564 State: constant.BuildpackAwaitingUpload, 565 Position: types.NullInt{Value: 42, IsSet: true}, 566 Links: APILinks{ 567 "upload": APILink{ 568 Method: "POST", 569 HREF: "/v3/buildpacks/some-bp-guid/upload", 570 }, 571 "self": APILink{ 572 HREF: "/v3/buildpacks/some-bp-guid", 573 }, 574 }, 575 } 576 Expect(bp).To(Equal(expectedBuildpack)) 577 }) 578 }) 579 580 When("cc returns back an error or warnings", func() { 581 BeforeEach(func() { 582 inputBuildpack = Buildpack{ 583 GUID: "some-bp-guid", 584 } 585 response := ` { 586 "errors": [ 587 { 588 "code": 10008, 589 "detail": "The request is semantically invalid: command presence", 590 "title": "CF-UnprocessableEntity" 591 }, 592 { 593 "code": 10010, 594 "detail": "Buildpack not found", 595 "title": "CF-ResourceNotFound" 596 } 597 ] 598 }` 599 server.AppendHandlers( 600 CombineHandlers( 601 VerifyRequest(http.MethodPatch, "/v3/buildpacks/some-bp-guid"), 602 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 603 ), 604 ) 605 }) 606 607 It("returns the error and all warnings", func() { 608 Expect(executeErr).To(MatchError(ccerror.MultiError{ 609 ResponseCode: http.StatusTeapot, 610 Errors: []ccerror.V3Error{ 611 { 612 Code: 10008, 613 Detail: "The request is semantically invalid: command presence", 614 Title: "CF-UnprocessableEntity", 615 }, 616 { 617 Code: 10010, 618 Detail: "Buildpack not found", 619 Title: "CF-ResourceNotFound", 620 }, 621 }, 622 })) 623 Expect(warnings).To(ConsistOf("this is a warning")) 624 }) 625 }) 626 }) 627 628 Describe("DeleteBuildpacks", func() { 629 var ( 630 buildpackGUID = "some-guid" 631 632 jobURL JobURL 633 warnings Warnings 634 executeErr error 635 ) 636 637 JustBeforeEach(func() { 638 jobURL, warnings, executeErr = client.DeleteBuildpack(buildpackGUID) 639 }) 640 641 When("buildpacks exist", func() { 642 BeforeEach(func() { 643 server.AppendHandlers( 644 CombineHandlers( 645 VerifyRequest(http.MethodDelete, "/v3/buildpacks/"+buildpackGUID), 646 RespondWith(http.StatusAccepted, "{}", http.Header{"X-Cf-Warnings": {"this is a warning"}, "Location": {"some-job-url"}}), 647 ), 648 ) 649 }) 650 651 It("returns the delete job URL and all warnings", func() { 652 Expect(executeErr).NotTo(HaveOccurred()) 653 654 Expect(jobURL).To(Equal(JobURL("some-job-url"))) 655 Expect(warnings).To(ConsistOf("this is a warning")) 656 }) 657 }) 658 659 When("the cloud controller returns errors and warnings", func() { 660 BeforeEach(func() { 661 response := `{ 662 "errors": [ 663 { 664 "code": 10008, 665 "detail": "The request is semantically invalid: command presence", 666 "title": "CF-UnprocessableEntity" 667 }, 668 { 669 "code": 10010, 670 "detail": "buildpack not found", 671 "title": "CF-buildpackNotFound" 672 } 673 ] 674 }` 675 server.AppendHandlers( 676 CombineHandlers( 677 VerifyRequest(http.MethodDelete, "/v3/buildpacks/"+buildpackGUID), 678 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 679 ), 680 ) 681 }) 682 683 It("returns the error and all warnings", func() { 684 Expect(executeErr).To(MatchError(ccerror.MultiError{ 685 ResponseCode: http.StatusTeapot, 686 Errors: []ccerror.V3Error{ 687 { 688 Code: 10008, 689 Detail: "The request is semantically invalid: command presence", 690 Title: "CF-UnprocessableEntity", 691 }, 692 { 693 Code: 10010, 694 Detail: "buildpack not found", 695 Title: "CF-buildpackNotFound", 696 }, 697 }, 698 })) 699 Expect(warnings).To(ConsistOf("this is a warning")) 700 }) 701 }) 702 }) 703 })