github.com/sleungcy-sap/cli@v7.1.0+incompatible/util/manifest/manifest_test.go (about) 1 package manifest_test 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "code.cloudfoundry.org/cli/types" 12 . "code.cloudfoundry.org/cli/util/manifest" 13 "github.com/cloudfoundry/bosh-cli/director/template" 14 . "github.com/onsi/ginkgo" 15 . "github.com/onsi/ginkgo/extensions/table" 16 . "github.com/onsi/gomega" 17 . "github.com/onsi/gomega/gstruct" 18 ) 19 20 var _ = Describe("Manifest", func() { 21 var manifest string 22 23 Describe("ReadAndInterpolateManifest", func() { 24 var ( 25 pathToManifest string 26 pathsToVarsFiles []string 27 vars []template.VarKV 28 apps []Application 29 executeErr error 30 ) 31 32 BeforeEach(func() { 33 tempFile, err := ioutil.TempFile("", "manifest-test-") 34 Expect(err).ToNot(HaveOccurred()) 35 Expect(tempFile.Close()).ToNot(HaveOccurred()) 36 pathToManifest = tempFile.Name() 37 vars = nil 38 39 pathsToVarsFiles = nil 40 }) 41 42 AfterEach(func() { 43 Expect(os.RemoveAll(pathToManifest)).ToNot(HaveOccurred()) 44 for _, path := range pathsToVarsFiles { 45 Expect(os.RemoveAll(path)).ToNot(HaveOccurred()) 46 } 47 }) 48 49 JustBeforeEach(func() { 50 apps, executeErr = ReadAndInterpolateManifest(pathToManifest, pathsToVarsFiles, vars) 51 }) 52 53 When("the manifest contains NO variables that need interpolation", func() { 54 BeforeEach(func() { 55 manifest = `--- 56 applications: 57 - name: app-1 58 buildpack: "some-buildpack" 59 command: "some-command" 60 health-check-http-endpoint: "\\some-endpoint" 61 health-check-type: "http" 62 instances: 10 63 disk_quota: 100M 64 docker: 65 image: "some-docker-image" 66 username: "some-docker-username" 67 memory: 200M 68 random-route: true 69 stack: "some-stack" 70 timeout: 120 71 - name: "app-2" 72 buildpack: default 73 disk_quota: 1G 74 instances: 0 75 memory: 2G 76 routes: 77 - route: foo.bar.com 78 - route: baz.qux.com 79 - route: blep.blah.com/boop 80 services: 81 - service_1 82 - service_2 83 - name: "app-3" 84 no-route: true 85 env: 86 env_1: 'foo' 87 env_2: 182837403930483038 88 env_3: true 89 env_4: 1.00001 90 - name: "app-4" 91 buildpack: null 92 command: null 93 - name: "app-5" 94 domain: "some-domain" 95 domains: 96 - domain_1 97 - domain_2 98 - name: "app-6" 99 host: "some-hostname" 100 hosts: 101 - hostname_1 102 - hostname_2 103 no-hostname: true 104 - name: "app-7" 105 routes: 106 - route: hello.com 107 - route: bleep.blah.com 108 random-route: true 109 ` 110 111 err := ioutil.WriteFile(pathToManifest, []byte(manifest), 0666) 112 Expect(err).ToNot(HaveOccurred()) 113 }) 114 115 When("the manifest does not contain deprecated fields", func() { 116 It("returns a merged set of applications", func() { 117 Expect(executeErr).ToNot(HaveOccurred()) 118 Expect(apps).To(HaveLen(7)) 119 120 Expect(apps[0]).To(Equal(Application{ 121 Name: "app-1", 122 Buildpack: types.FilteredString{ 123 IsSet: true, 124 Value: "some-buildpack", 125 }, 126 Command: types.FilteredString{ 127 IsSet: true, 128 Value: "some-command", 129 }, 130 HealthCheckHTTPEndpoint: `\some-endpoint`, 131 HealthCheckType: "http", 132 Instances: types.NullInt{ 133 Value: 10, 134 IsSet: true, 135 }, 136 DiskQuota: types.NullByteSizeInMb{ 137 Value: 100, 138 IsSet: true, 139 }, 140 DockerImage: "some-docker-image", 141 DockerUsername: "some-docker-username", 142 Memory: types.NullByteSizeInMb{ 143 Value: 200, 144 IsSet: true, 145 }, 146 RandomRoute: true, 147 StackName: "some-stack", 148 HealthCheckTimeout: 120, 149 })) 150 151 Expect(apps[1]).To(Equal(Application{ 152 Name: "app-2", 153 Buildpack: types.FilteredString{ 154 IsSet: true, 155 Value: "", 156 }, 157 DiskQuota: types.NullByteSizeInMb{ 158 Value: 1024, 159 IsSet: true, 160 }, 161 Instances: types.NullInt{ 162 IsSet: true, 163 Value: 0, 164 }, 165 Memory: types.NullByteSizeInMb{ 166 Value: 2048, 167 IsSet: true, 168 }, 169 Routes: []string{"foo.bar.com", "baz.qux.com", "blep.blah.com/boop"}, 170 Services: []string{"service_1", "service_2"}, 171 })) 172 173 Expect(apps[2]).To(Equal(Application{ 174 Name: "app-3", 175 EnvironmentVariables: map[string]string{ 176 "env_1": "foo", 177 "env_2": "182837403930483038", 178 "env_3": "true", 179 "env_4": "1.00001", 180 }, 181 NoRoute: true, 182 })) 183 184 Expect(apps[3]).To(Equal(Application{ 185 Name: "app-4", 186 Buildpack: types.FilteredString{ 187 IsSet: true, 188 Value: "", 189 }, 190 Command: types.FilteredString{ 191 IsSet: true, 192 Value: "", 193 }, 194 })) 195 196 Expect(apps[4].Name).To(Equal("app-5")) 197 Expect(apps[4].DeprecatedDomain).ToNot(BeNil()) 198 Expect(apps[4].DeprecatedDomains).ToNot(BeNil()) 199 200 Expect(apps[5].Name).To(Equal("app-6")) 201 Expect(apps[5].DeprecatedHost).ToNot(BeNil()) 202 Expect(apps[5].DeprecatedHosts).ToNot(BeNil()) 203 Expect(apps[5].DeprecatedNoHostname).ToNot(BeNil()) 204 205 Expect(apps[6]).To(Equal(Application{ 206 Name: "app-7", 207 Routes: []string{"hello.com", "bleep.blah.com"}, 208 RandomRoute: true, 209 })) 210 }) 211 }) 212 213 When("provided deprecated fields", func() { 214 When("global fields are provided", func() { 215 DescribeTable("raises a GlobalFieldsError", 216 func(manifestProperty string, numberOfValues int) { 217 tempManifest, err := ioutil.TempFile("", "manifest-test-") 218 Expect(err).ToNot(HaveOccurred()) 219 Expect(tempManifest.Close()).ToNot(HaveOccurred()) 220 manifestPath := tempManifest.Name() 221 defer os.RemoveAll(manifestPath) 222 223 if numberOfValues == 1 { 224 manifest = fmt.Sprintf("---\n%s: value", manifestProperty) 225 } else { 226 values := []string{"A", "B"} 227 manifest = fmt.Sprintf("---\n%s: [%s]", manifestProperty, strings.Join(values, ",")) 228 } 229 err = ioutil.WriteFile(manifestPath, []byte(manifest), 0666) 230 Expect(err).ToNot(HaveOccurred()) 231 232 _, err = ReadAndInterpolateManifest(manifestPath, pathsToVarsFiles, vars) 233 Expect(err).To(MatchError(GlobalFieldsError{Fields: []string{manifestProperty}})) 234 }, 235 236 Entry("global buildpack", "buildpack", 1), 237 Entry("global command", "command", 1), 238 Entry("global disk quota", "disk_quota", 1), 239 Entry("global docker", "docker", 1), 240 Entry("global domain", "domain", 1), 241 Entry("global domains", "domains", 2), 242 Entry("global environment variables", "env", 2), 243 Entry("global health check HTTP endpoint", "health-check-http-endpoint", 1), 244 Entry("global health check timeout", "timeout", 1), 245 Entry("global health check type", "health-check-type", 1), 246 Entry("global host", "host", 1), 247 Entry("global hosts", "hosts", 2), 248 Entry("global instances", "instances", 1), 249 Entry("global memory", "memory", 1), 250 Entry("global name", "name", 1), 251 Entry("global no hostname", "no-hostname", 1), 252 Entry("global no route", "no-route", 1), 253 Entry("global path", "path", 1), 254 Entry("global random-route", "random-route", 1), 255 Entry("global routes", "routes", 2), 256 Entry("global services", "services", 2), 257 Entry("global stack", "stack", 1), 258 ) 259 }) 260 }) 261 262 When("inheritance is provided", func() { 263 BeforeEach(func() { 264 manifest = `--- 265 inherit: "./some-inheritance-file" 266 applications: 267 - name: "app-1" 268 ` 269 270 err := ioutil.WriteFile(pathToManifest, []byte(manifest), 0666) 271 Expect(err).ToNot(HaveOccurred()) 272 }) 273 274 It("raises an InheritanceFieldError", func() { 275 Expect(executeErr).To(MatchError(InheritanceFieldError{})) 276 }) 277 }) 278 279 When("the manifest specified a single buildpack", func() { 280 BeforeEach(func() { 281 manifest = `--- 282 applications: 283 - name: app-1 284 buildpack: "some-buildpack" 285 memory: 200M 286 instances: 10 287 ` 288 err := ioutil.WriteFile(pathToManifest, []byte(manifest), 0666) 289 Expect(err).ToNot(HaveOccurred()) 290 }) 291 292 It("returns a merged set of applications", func() { 293 Expect(executeErr).ToNot(HaveOccurred()) 294 Expect(apps).To(HaveLen(1)) 295 296 Expect(apps[0]).To(MatchFields(IgnoreExtras, Fields{ 297 "Buildpack": Equal(types.FilteredString{ 298 IsSet: true, 299 Value: "some-buildpack", 300 }), 301 "Buildpacks": BeEmpty(), 302 })) 303 }) 304 }) 305 306 When("the manifest contains buildpacks (plural)", func() { 307 BeforeEach(func() { 308 manifest = `--- 309 applications: 310 - name: app-1 311 buildpacks: 312 - "some-buildpack-1" 313 - "some-buildpack-2" 314 memory: 200M 315 instances: 10 316 - name: app-2 317 buildpacks: 318 - "some-other-buildpack-1" 319 - "some-other-buildpack-2" 320 memory: 2048M 321 instances: 0` 322 err := ioutil.WriteFile(pathToManifest, []byte(manifest), 0666) 323 Expect(err).ToNot(HaveOccurred()) 324 }) 325 326 It("returns a merged set of applications", func() { 327 Expect(executeErr).ToNot(HaveOccurred()) 328 Expect(apps).To(HaveLen(2)) 329 330 Expect(apps[0]).To(MatchFields(IgnoreExtras, Fields{ 331 "Buildpack": Equal(types.FilteredString{ 332 IsSet: false, 333 Value: "", 334 }), 335 "Buildpacks": ConsistOf("some-buildpack-1", "some-buildpack-2"), 336 })) 337 338 Expect(apps[1]).To(MatchFields(IgnoreExtras, Fields{ 339 "Buildpack": Equal(types.FilteredString{ 340 IsSet: false, 341 Value: "", 342 }), 343 "Buildpacks": ConsistOf( 344 "some-other-buildpack-1", 345 "some-other-buildpack-2", 346 ), 347 })) 348 }) 349 }) 350 351 When("the manifest sets buildpacks to an empty array", func() { 352 BeforeEach(func() { 353 manifest = `--- 354 applications: 355 - name: app-1 356 buildpacks: [] 357 memory: 200M 358 instances: 10 359 ` 360 err := ioutil.WriteFile(pathToManifest, []byte(manifest), 0666) 361 Expect(err).ToNot(HaveOccurred()) 362 }) 363 364 It("returns a merged set of applications", func() { 365 Expect(executeErr).ToNot(HaveOccurred()) 366 Expect(apps).To(HaveLen(1)) 367 368 Expect(apps[0]).To(MatchFields(IgnoreExtras, Fields{ 369 "Buildpack": Equal(types.FilteredString{ 370 IsSet: false, 371 Value: "", 372 }), 373 "Buildpacks": Equal([]string{}), 374 })) 375 }) 376 }) 377 378 When("the manifest contains an empty buildpacks attribute", func() { 379 BeforeEach(func() { 380 manifest = `--- 381 applications: 382 - name: app-1 383 buildpacks: 384 ` 385 err := ioutil.WriteFile(pathToManifest, []byte(manifest), 0666) 386 Expect(err).ToNot(HaveOccurred()) 387 }) 388 389 It("raises an error", func() { 390 Expect(executeErr).ToNot(MatchError(new(EmptyBuildpacksError))) 391 }) 392 }) 393 }) 394 395 When("the manifest contains variables that need interpolation", func() { 396 BeforeEach(func() { 397 manifest = `--- 398 applications: 399 - name: ((var1)) 400 instances: ((var2)) 401 ` 402 err := ioutil.WriteFile(pathToManifest, []byte(manifest), 0666) 403 Expect(err).ToNot(HaveOccurred()) 404 }) 405 406 When("only vars files are provided", func() { 407 var ( 408 varsDir string 409 ) 410 411 BeforeEach(func() { 412 var err error 413 varsDir, err = ioutil.TempDir("", "vars-test") 414 Expect(err).ToNot(HaveOccurred()) 415 416 varsFilePath1 := filepath.Join(varsDir, "vars-1") 417 err = ioutil.WriteFile(varsFilePath1, []byte("var1: app-1"), 0666) 418 Expect(err).ToNot(HaveOccurred()) 419 420 varsFilePath2 := filepath.Join(varsDir, "vars-2") 421 err = ioutil.WriteFile(varsFilePath2, []byte("var2: 4"), 0666) 422 Expect(err).ToNot(HaveOccurred()) 423 424 pathsToVarsFiles = append(pathsToVarsFiles, varsFilePath1, varsFilePath2) 425 }) 426 427 AfterEach(func() { 428 Expect(os.RemoveAll(varsDir)).ToNot(HaveOccurred()) 429 }) 430 431 When("multiple values for the same variable(s) are provided", func() { 432 BeforeEach(func() { 433 varsFilePath1 := filepath.Join(varsDir, "vars-1") 434 err := ioutil.WriteFile(varsFilePath1, []byte("var1: garbageapp\nvar1: app-1\nvar2: 0"), 0666) 435 Expect(err).ToNot(HaveOccurred()) 436 437 varsFilePath2 := filepath.Join(varsDir, "vars-2") 438 err = ioutil.WriteFile(varsFilePath2, []byte("var2: 4"), 0666) 439 Expect(err).ToNot(HaveOccurred()) 440 441 pathsToVarsFiles = append(pathsToVarsFiles, varsFilePath1, varsFilePath2) 442 }) 443 444 It("interpolates the placeholder values", func() { 445 Expect(executeErr).ToNot(HaveOccurred()) 446 Expect(apps[0].Name).To(Equal("app-1")) 447 Expect(apps[0].Instances).To(Equal(types.NullInt{Value: 4, IsSet: true})) 448 }) 449 }) 450 451 When("the provided files exists and contain valid yaml", func() { 452 It("interpolates the placeholder values", func() { 453 Expect(executeErr).ToNot(HaveOccurred()) 454 Expect(apps[0].Name).To(Equal("app-1")) 455 Expect(apps[0].Instances).To(Equal(types.NullInt{Value: 4, IsSet: true})) 456 }) 457 }) 458 459 When("a variable in the manifest is not provided in the vars file", func() { 460 BeforeEach(func() { 461 varsFilePath := filepath.Join(varsDir, "vars-1") 462 err := ioutil.WriteFile(varsFilePath, []byte("notvar: foo"), 0666) 463 Expect(err).ToNot(HaveOccurred()) 464 465 pathsToVarsFiles = []string{varsFilePath} 466 }) 467 468 It("returns an error", func() { 469 Expect(executeErr.Error()).To(Equal("Expected to find variables: var1, var2")) 470 }) 471 }) 472 473 When("the provided file path does not exist", func() { 474 BeforeEach(func() { 475 pathsToVarsFiles = []string{"garbagepath"} 476 }) 477 478 It("returns an error", func() { 479 Expect(executeErr).To(HaveOccurred()) 480 Expect(os.IsNotExist(executeErr)).To(BeTrue()) 481 }) 482 }) 483 484 When("the provided file is not a valid yaml file", func() { 485 BeforeEach(func() { 486 varsFilePath := filepath.Join(varsDir, "vars-1") 487 err := ioutil.WriteFile(varsFilePath, []byte(": bad"), 0666) 488 Expect(err).ToNot(HaveOccurred()) 489 490 pathsToVarsFiles = []string{varsFilePath} 491 }) 492 493 It("returns an error", func() { 494 Expect(executeErr).To(HaveOccurred()) 495 Expect(executeErr).To(MatchError(InvalidYAMLError{ 496 Err: errors.New("yaml: did not find expected key"), 497 })) 498 }) 499 }) 500 }) 501 502 When("only vars are provided", func() { 503 BeforeEach(func() { 504 vars = []template.VarKV{ 505 {Name: "var1", Value: "app-1"}, 506 {Name: "var2", Value: 4}, 507 } 508 manifest = `--- 509 applications: 510 - name: ((var1)) 511 instances: ((var2)) 512 ` 513 }) 514 515 It("interpolates the placeholder values", func() { 516 Expect(executeErr).ToNot(HaveOccurred()) 517 Expect(apps[0].Name).To(Equal("app-1")) 518 Expect(apps[0].Instances).To(Equal(types.NullInt{Value: 4, IsSet: true})) 519 }) 520 }) 521 522 When("vars and vars files are provided", func() { 523 var varsFilePath string 524 BeforeEach(func() { 525 tmp, err := ioutil.TempFile("", "util-manifest-varsilfe") 526 Expect(err).NotTo(HaveOccurred()) 527 Expect(tmp.Close()).NotTo(HaveOccurred()) 528 529 varsFilePath = tmp.Name() 530 err = ioutil.WriteFile(varsFilePath, []byte("var1: app-1\nvar2: 12345"), 0666) 531 Expect(err).ToNot(HaveOccurred()) 532 533 pathsToVarsFiles = []string{varsFilePath} 534 vars = []template.VarKV{ 535 {Name: "var2", Value: 4}, 536 } 537 }) 538 539 AfterEach(func() { 540 Expect(os.RemoveAll(varsFilePath)).ToNot(HaveOccurred()) 541 }) 542 543 It("interpolates the placeholder values, prioritizing the vars flag", func() { 544 Expect(executeErr).ToNot(HaveOccurred()) 545 Expect(apps[0].Name).To(Equal("app-1")) 546 Expect(apps[0].Instances).To(Equal(types.NullInt{Value: 4, IsSet: true})) 547 }) 548 }) 549 }) 550 }) 551 552 Describe("WriteApplicationManifest", func() { 553 var ( 554 application Application 555 tmpDir string 556 filePath string 557 558 executeErr error 559 ) 560 561 BeforeEach(func() { 562 var err error 563 tmpDir, err = ioutil.TempDir("", "manifest-test-") 564 Expect(err).NotTo(HaveOccurred()) 565 filePath = filepath.Join(tmpDir, "manifest.yml") 566 }) 567 568 AfterEach(func() { 569 os.RemoveAll(tmpDir) 570 }) 571 572 JustBeforeEach(func() { 573 executeErr = WriteApplicationManifest(application, filePath) 574 }) 575 576 When("all app properties are provided", func() { 577 BeforeEach(func() { 578 application = Application{ 579 Name: "app-1", 580 Buildpack: types.FilteredString{ 581 IsSet: true, 582 Value: "some-buildpack", 583 }, 584 Buildpacks: []string{"buildpack1", "buildpack2"}, 585 Command: types.FilteredString{ 586 IsSet: true, 587 Value: "some-command", 588 }, 589 DockerImage: "some-docker-image", 590 DockerUsername: "some-docker-username", 591 DockerPassword: "", 592 EnvironmentVariables: map[string]string{ 593 "env_1": "foo", 594 "env_2": "182837403930483038", 595 "env_3": "true", 596 "env_4": "1.00001", 597 }, 598 HealthCheckHTTPEndpoint: `\some-endpoint`, 599 HealthCheckType: "http", 600 Instances: types.NullInt{ 601 Value: 10, 602 IsSet: true, 603 }, 604 DiskQuota: types.NullByteSizeInMb{ 605 Value: 1024, 606 IsSet: true, 607 }, 608 Memory: types.NullByteSizeInMb{ 609 Value: 200, 610 IsSet: true, 611 }, 612 NoRoute: true, 613 Routes: []string{"foo.bar.com", "baz.qux.com", "blep.blah.com/boop"}, 614 Services: []string{"service_1", "service_2"}, 615 StackName: "some-stack", 616 HealthCheckTimeout: 120, 617 } 618 }) 619 620 It("creates and writes the manifest to the specified filepath", func() { 621 Expect(executeErr).NotTo(HaveOccurred()) 622 manifestBytes, err := ioutil.ReadFile(filePath) 623 Expect(err).NotTo(HaveOccurred()) 624 Expect(string(manifestBytes)).To(Equal(`applications: 625 - name: app-1 626 buildpack: some-buildpack 627 buildpacks: 628 - buildpack1 629 - buildpack2 630 command: some-command 631 disk_quota: 1G 632 docker: 633 image: some-docker-image 634 username: some-docker-username 635 env: 636 env_1: foo 637 env_2: "182837403930483038" 638 env_3: "true" 639 env_4: "1.00001" 640 health-check-http-endpoint: \some-endpoint 641 health-check-type: http 642 instances: 10 643 memory: 200M 644 no-route: true 645 routes: 646 - route: foo.bar.com 647 - route: baz.qux.com 648 - route: blep.blah.com/boop 649 services: 650 - service_1 651 - service_2 652 stack: some-stack 653 timeout: 120 654 `)) 655 }) 656 }) 657 658 When("some properties are not provided", func() { 659 BeforeEach(func() { 660 application = Application{ 661 Name: "app-1", 662 } 663 }) 664 665 It("does not save them in manifest", func() { 666 Expect(executeErr).NotTo(HaveOccurred()) 667 manifestBytes, err := ioutil.ReadFile(filePath) 668 Expect(err).NotTo(HaveOccurred()) 669 Expect(string(manifestBytes)).To(Equal(`applications: 670 - name: app-1 671 `)) 672 }) 673 }) 674 675 When("the file is a relative path", func() { 676 var pwd string 677 678 BeforeEach(func() { 679 var err error 680 pwd, err = os.Getwd() 681 Expect(err).ToNot(HaveOccurred()) 682 683 filePath = "./manifest.yml" 684 Expect(os.Chdir(tmpDir)).To(Succeed()) 685 686 application = Application{ 687 Name: "app-1", 688 } 689 }) 690 691 AfterEach(func() { 692 Expect(os.Chdir(pwd)).To(Succeed()) 693 }) 694 695 It("writes the file in an expanded path", func() { 696 Expect(executeErr).ToNot(HaveOccurred()) 697 manifestBytes, err := ioutil.ReadFile(filepath.Join(tmpDir, "manifest.yml")) 698 Expect(err).ToNot(HaveOccurred()) 699 Expect(string(manifestBytes)).To(Equal(`applications: 700 - name: app-1 701 `)) 702 }) 703 }) 704 705 When("the file already exists", func() { 706 BeforeEach(func() { 707 err := ioutil.WriteFile(filePath, []byte(`{}`), 0644) 708 Expect(err).ToNot(HaveOccurred()) 709 application = Application{ 710 Name: "app-1", 711 } 712 }) 713 714 Context("writes the file", func() { 715 It("truncates and writes the manifest to specified filepath", func() { 716 Expect(executeErr).ToNot(HaveOccurred()) 717 manifestBytes, err := ioutil.ReadFile(filePath) 718 Expect(err).ToNot(HaveOccurred()) 719 Expect(string(manifestBytes)).To(Equal(`applications: 720 - name: app-1 721 `)) 722 }) 723 }) 724 }) 725 }) 726 })