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