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