github.com/jghiloni/cli@v6.28.1-0.20170628223758-0ce05fe032a2+incompatible/cf/commands/application/push_test.go (about) 1 package application_test 2 3 import ( 4 "os" 5 "path/filepath" 6 "syscall" 7 8 "code.cloudfoundry.org/cli/cf" 9 "code.cloudfoundry.org/cli/cf/actors/actorsfakes" 10 "code.cloudfoundry.org/cli/cf/api/apifakes" 11 "code.cloudfoundry.org/cli/cf/api/applications/applicationsfakes" 12 "code.cloudfoundry.org/cli/cf/api/authentication/authenticationfakes" 13 "code.cloudfoundry.org/cli/cf/api/resources" 14 "code.cloudfoundry.org/cli/cf/api/stacks/stacksfakes" 15 "code.cloudfoundry.org/cli/cf/appfiles/appfilesfakes" 16 "code.cloudfoundry.org/cli/cf/commandregistry" 17 "code.cloudfoundry.org/cli/cf/commands/application" 18 "code.cloudfoundry.org/cli/cf/commands/application/applicationfakes" 19 "code.cloudfoundry.org/cli/cf/commands/service/servicefakes" 20 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 21 "code.cloudfoundry.org/cli/cf/errors" 22 "code.cloudfoundry.org/cli/cf/flags" 23 "code.cloudfoundry.org/cli/cf/manifest" 24 "code.cloudfoundry.org/cli/cf/manifest/manifestfakes" 25 "code.cloudfoundry.org/cli/cf/models" 26 "code.cloudfoundry.org/cli/cf/requirements" 27 "code.cloudfoundry.org/cli/cf/requirements/requirementsfakes" 28 "code.cloudfoundry.org/cli/cf/terminal" 29 "code.cloudfoundry.org/cli/cf/trace" 30 "code.cloudfoundry.org/cli/util/generic" 31 testconfig "code.cloudfoundry.org/cli/util/testhelpers/configuration" 32 testterm "code.cloudfoundry.org/cli/util/testhelpers/terminal" 33 "code.cloudfoundry.org/cli/util/words/generator/generatorfakes" 34 . "github.com/onsi/ginkgo" 35 . "github.com/onsi/gomega" 36 "github.com/onsi/gomega/gbytes" 37 ) 38 39 var _ = Describe("Push Command", func() { 40 var ( 41 cmd application.Push 42 ui *testterm.FakeUI 43 configRepo coreconfig.Repository 44 manifestRepo *manifestfakes.FakeRepository 45 starter *applicationfakes.FakeStarter 46 stopper *applicationfakes.FakeStopper 47 serviceBinder *servicefakes.OldFakeAppBinder 48 appRepo *applicationsfakes.FakeRepository 49 domainRepo *apifakes.FakeDomainRepository 50 routeRepo *apifakes.FakeRouteRepository 51 stackRepo *stacksfakes.FakeStackRepository 52 serviceRepo *apifakes.FakeServiceRepository 53 wordGenerator *generatorfakes.FakeWordGenerator 54 requirementsFactory *requirementsfakes.FakeFactory 55 authRepo *authenticationfakes.FakeRepository 56 actor *actorsfakes.FakePushActor 57 routeActor *actorsfakes.FakeRouteActor 58 appfiles *appfilesfakes.FakeAppFiles 59 zipper *appfilesfakes.FakeZipper 60 deps commandregistry.Dependency 61 flagContext flags.FlagContext 62 loginReq requirements.Passing 63 targetedSpaceReq requirements.Passing 64 usageReq requirements.Passing 65 minVersionReq requirements.Passing 66 OriginalCommandStart commandregistry.Command 67 OriginalCommandStop commandregistry.Command 68 OriginalCommandServiceBind commandregistry.Command 69 ) 70 71 BeforeEach(func() { 72 //save original command dependences and restore later 73 OriginalCommandStart = commandregistry.Commands.FindCommand("start") 74 OriginalCommandStop = commandregistry.Commands.FindCommand("stop") 75 OriginalCommandServiceBind = commandregistry.Commands.FindCommand("bind-service") 76 77 requirementsFactory = new(requirementsfakes.FakeFactory) 78 loginReq = requirements.Passing{Type: "login"} 79 requirementsFactory.NewLoginRequirementReturns(loginReq) 80 targetedSpaceReq = requirements.Passing{Type: "targeted space"} 81 requirementsFactory.NewTargetedSpaceRequirementReturns(targetedSpaceReq) 82 usageReq = requirements.Passing{Type: "usage"} 83 requirementsFactory.NewUsageRequirementReturns(usageReq) 84 minVersionReq = requirements.Passing{Type: "minVersionReq"} 85 requirementsFactory.NewMinAPIVersionRequirementReturns(minVersionReq) 86 87 ui = &testterm.FakeUI{} //new(terminalfakes.FakeUI) 88 configRepo = testconfig.NewRepositoryWithDefaults() 89 manifestRepo = new(manifestfakes.FakeRepository) 90 wordGenerator = new(generatorfakes.FakeWordGenerator) 91 wordGenerator.BabbleReturns("random-host") 92 actor = new(actorsfakes.FakePushActor) 93 routeActor = new(actorsfakes.FakeRouteActor) 94 zipper = new(appfilesfakes.FakeZipper) 95 appfiles = new(appfilesfakes.FakeAppFiles) 96 97 deps = commandregistry.Dependency{ 98 UI: ui, 99 Config: configRepo, 100 ManifestRepo: manifestRepo, 101 WordGenerator: wordGenerator, 102 PushActor: actor, 103 RouteActor: routeActor, 104 AppZipper: zipper, 105 AppFiles: appfiles, 106 } 107 108 appRepo = new(applicationsfakes.FakeRepository) 109 domainRepo = new(apifakes.FakeDomainRepository) 110 routeRepo = new(apifakes.FakeRouteRepository) 111 serviceRepo = new(apifakes.FakeServiceRepository) 112 stackRepo = new(stacksfakes.FakeStackRepository) 113 authRepo = new(authenticationfakes.FakeRepository) 114 deps.RepoLocator = deps.RepoLocator.SetApplicationRepository(appRepo) 115 deps.RepoLocator = deps.RepoLocator.SetDomainRepository(domainRepo) 116 deps.RepoLocator = deps.RepoLocator.SetRouteRepository(routeRepo) 117 deps.RepoLocator = deps.RepoLocator.SetServiceRepository(serviceRepo) 118 deps.RepoLocator = deps.RepoLocator.SetStackRepository(stackRepo) 119 deps.RepoLocator = deps.RepoLocator.SetAuthenticationRepository(authRepo) 120 121 //setup fake commands (counterfeiter) to correctly interact with commandregistry 122 starter = new(applicationfakes.FakeStarter) 123 starter.SetDependencyStub = func(_ commandregistry.Dependency, _ bool) commandregistry.Command { 124 return starter 125 } 126 starter.MetaDataReturns(commandregistry.CommandMetadata{Name: "start"}) 127 commandregistry.Register(starter) 128 129 stopper = new(applicationfakes.FakeStopper) 130 stopper.SetDependencyStub = func(_ commandregistry.Dependency, _ bool) commandregistry.Command { 131 return stopper 132 } 133 stopper.MetaDataReturns(commandregistry.CommandMetadata{Name: "stop"}) 134 commandregistry.Register(stopper) 135 136 //inject fake commands dependencies into registry 137 serviceBinder = new(servicefakes.OldFakeAppBinder) 138 commandregistry.Register(serviceBinder) 139 140 cmd = application.Push{} 141 cmd.SetDependency(deps, false) 142 flagContext = flags.NewFlagContext(cmd.MetaData().Flags) 143 }) 144 145 AfterEach(func() { 146 commandregistry.Register(OriginalCommandStart) 147 commandregistry.Register(OriginalCommandStop) 148 commandregistry.Register(OriginalCommandServiceBind) 149 }) 150 151 Describe("Requirements", func() { 152 var reqs []requirements.Requirement 153 154 BeforeEach(func() { 155 err := flagContext.Parse("app-name") 156 Expect(err).NotTo(HaveOccurred()) 157 158 reqs, err = cmd.Requirements(requirementsFactory, flagContext) 159 Expect(err).NotTo(HaveOccurred()) 160 }) 161 162 It("checks that the user is logged in", func() { 163 Expect(requirementsFactory.NewLoginRequirementCallCount()).To(Equal(1)) 164 Expect(reqs).To(ContainElement(loginReq)) 165 }) 166 167 It("checks that the space is targeted", func() { 168 Expect(requirementsFactory.NewTargetedSpaceRequirementCallCount()).To(Equal(1)) 169 Expect(reqs).To(ContainElement(targetedSpaceReq)) 170 }) 171 172 It("checks the number of args", func() { 173 Expect(requirementsFactory.NewUsageRequirementCallCount()).To(Equal(1)) 174 Expect(reqs).To(ContainElement(usageReq)) 175 }) 176 177 Context("when --route-path is passed in", func() { 178 BeforeEach(func() { 179 err := flagContext.Parse("app-name", "--route-path", "the-path") 180 Expect(err).NotTo(HaveOccurred()) 181 182 reqs, err = cmd.Requirements(requirementsFactory, flagContext) 183 Expect(err).NotTo(HaveOccurred()) 184 }) 185 186 It("returns a minAPIVersionRequirement", func() { 187 Expect(requirementsFactory.NewMinAPIVersionRequirementCallCount()).To(Equal(1)) 188 189 option, version := requirementsFactory.NewMinAPIVersionRequirementArgsForCall(0) 190 Expect(option).To(Equal("Option '--route-path'")) 191 Expect(version).To(Equal(cf.RoutePathMinimumAPIVersion)) 192 193 Expect(reqs).To(ContainElement(minVersionReq)) 194 }) 195 }) 196 197 Context("when --app-ports is passed in", func() { 198 BeforeEach(func() { 199 err := flagContext.Parse("app-name", "--app-ports", "the-app-port") 200 Expect(err).NotTo(HaveOccurred()) 201 202 reqs, err = cmd.Requirements(requirementsFactory, flagContext) 203 Expect(err).NotTo(HaveOccurred()) 204 }) 205 206 It("returns a minAPIVersionRequirement", func() { 207 Expect(requirementsFactory.NewMinAPIVersionRequirementCallCount()).To(Equal(1)) 208 209 option, version := requirementsFactory.NewMinAPIVersionRequirementArgsForCall(0) 210 Expect(option).To(Equal("Option '--app-ports'")) 211 Expect(version).To(Equal(cf.MultipleAppPortsMinimumAPIVersion)) 212 213 Expect(reqs).To(ContainElement(minVersionReq)) 214 }) 215 }) 216 }) 217 218 Describe("Execute", func() { 219 var ( 220 executeErr error 221 args []string 222 uiWithContents terminal.UI 223 input *gbytes.Buffer 224 output *gbytes.Buffer 225 ) 226 227 BeforeEach(func() { 228 input = gbytes.NewBuffer() 229 output = gbytes.NewBuffer() 230 uiWithContents = terminal.NewUI(input, output, terminal.NewTeePrinter(output), trace.NewWriterPrinter(output, false)) 231 232 domainRepo.FirstOrDefaultStub = func(orgGUID string, name *string) (models.DomainFields, error) { 233 if name == nil { 234 var foundDomain *models.DomainFields 235 domainRepo.ListDomainsForOrg(orgGUID, func(domain models.DomainFields) bool { 236 foundDomain = &domain 237 return !domain.Shared 238 }) 239 240 if foundDomain == nil { 241 return models.DomainFields{}, errors.New("Could not find a default domain") 242 } 243 244 return *foundDomain, nil 245 } 246 247 return domainRepo.FindByNameInOrg(*name, orgGUID) 248 } 249 250 domainRepo.ListDomainsForOrgStub = func(orgGUID string, cb func(models.DomainFields) bool) error { 251 cb(models.DomainFields{ 252 Name: "foo.cf-app.com", 253 GUID: "foo-domain-guid", 254 Shared: true, 255 }) 256 return nil 257 } 258 259 actor.ProcessPathStub = func(dirOrZipFile string, cb func(string) error) error { 260 return cb(dirOrZipFile) 261 } 262 263 actor.ValidateAppParamsReturns(nil) 264 265 appfiles.AppFilesInDirReturns( 266 []models.AppFileFields{ 267 { 268 Path: "some-path", 269 }, 270 }, 271 nil, 272 ) 273 274 zipper.ZipReturns(nil) 275 zipper.GetZipSizeReturns(9001, nil) 276 }) 277 278 AfterEach(func() { 279 output.Close() 280 }) 281 282 JustBeforeEach(func() { 283 cmd.SetDependency(deps, false) 284 285 err := flagContext.Parse(args...) 286 Expect(err).NotTo(HaveOccurred()) 287 288 executeErr = cmd.Execute(flagContext) 289 }) 290 291 Context("when pushing a new app", func() { 292 BeforeEach(func() { 293 m := &manifest.Manifest{ 294 Path: "manifest.yml", 295 Data: generic.NewMap(map[interface{}]interface{}{ 296 "applications": []interface{}{ 297 generic.NewMap(map[interface{}]interface{}{ 298 "name": "manifest-app-name", 299 "memory": "128MB", 300 "instances": 1, 301 "host": "manifest-host", 302 "stack": "custom-stack", 303 "timeout": 360, 304 "buildpack": "some-buildpack", 305 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 306 "path": filepath.Clean("some/path/from/manifest"), 307 "env": generic.NewMap(map[interface{}]interface{}{ 308 "FOO": "baz", 309 "PATH": "/u/apps/my-app/bin", 310 }), 311 }), 312 }, 313 }), 314 } 315 manifestRepo.ReadManifestReturns(m, nil) 316 317 appRepo.ReadReturns(models.Application{}, errors.NewModelNotFoundError("App", "the-app")) 318 appRepo.CreateStub = func(params models.AppParams) (models.Application, error) { 319 a := models.Application{} 320 a.GUID = *params.Name + "-guid" 321 a.Name = *params.Name 322 a.State = "stopped" 323 324 return a, nil 325 } 326 327 args = []string{"app-name"} 328 }) 329 330 Context("validating a manifest", func() { 331 BeforeEach(func() { 332 actor.ValidateAppParamsReturns([]error{ 333 errors.New("error1"), 334 errors.New("error2"), 335 }) 336 }) 337 338 It("returns an properly formatted error", func() { 339 Expect(executeErr).To(HaveOccurred()) 340 Expect(executeErr.Error()).To(MatchRegexp("Invalid application configuration:\nerror1\nerror2")) 341 }) 342 }) 343 344 Context("when given a bad path", func() { 345 BeforeEach(func() { 346 actor.ProcessPathStub = func(dirOrZipFile string, f func(string) error) error { 347 return errors.New("process-path-error") 348 } 349 }) 350 351 JustBeforeEach(func() { 352 err := flagContext.Parse("app-name", "-p", "badpath") 353 Expect(err).NotTo(HaveOccurred()) 354 355 executeErr = cmd.Execute(flagContext) 356 }) 357 358 It("fails with bad path error", func() { 359 Expect(executeErr).To(HaveOccurred()) 360 361 Expect(executeErr.Error()).To(ContainSubstring("Error processing app files: process-path-error")) 362 }) 363 }) 364 365 Context("when the default route for the app already exists", func() { 366 var route models.Route 367 BeforeEach(func() { 368 route = models.Route{ 369 GUID: "my-route-guid", 370 Host: "app-name", 371 Domain: models.DomainFields{ 372 Name: "foo.cf-app.com", 373 GUID: "foo-domain-guid", 374 Shared: true, 375 }, 376 } 377 routeActor.FindOrCreateRouteReturns( 378 route, 379 nil, 380 ) 381 }) 382 383 Context("when binding the app", func() { 384 BeforeEach(func() { 385 deps.UI = uiWithContents 386 }) 387 388 It("binds to existing routes", func() { 389 Expect(executeErr).NotTo(HaveOccurred()) 390 391 Expect(routeActor.BindRouteCallCount()).To(Equal(1)) 392 boundApp, boundRoute := routeActor.BindRouteArgsForCall(0) 393 Expect(boundApp.GUID).To(Equal("app-name-guid")) 394 Expect(boundRoute).To(Equal(route)) 395 }) 396 }) 397 398 Context("when pushing the app", func() { 399 BeforeEach(func() { 400 actor.GatherFilesReturns([]resources.AppFileResource{}, false, errors.New("failed to get file mode")) 401 }) 402 403 It("notifies users about the error actor.GatherFiles() returns", func() { 404 Expect(executeErr).To(HaveOccurred()) 405 406 Expect(executeErr.Error()).To(ContainSubstring("failed to get file mode")) 407 }) 408 }) 409 410 Context("when the CC returns 504 Gateway timeout", func() { 411 BeforeEach(func() { 412 var callCount int 413 actor.GatherFilesStub = func(localFiles []models.AppFileFields, appDir string, uploadDir string, useCache bool) ([]resources.AppFileResource, bool, error) { 414 callCount += 1 415 if callCount == 1 { 416 return []resources.AppFileResource{}, false, errors.NewHTTPError(504, "", "") 417 } else { 418 return []resources.AppFileResource{}, false, nil 419 } 420 } 421 }) 422 423 It("retries without the cache", func() { 424 Expect(executeErr).ToNot(HaveOccurred()) 425 426 Expect(actor.GatherFilesCallCount()).To(Equal(2)) 427 428 localFiles, appDir, uploadDir, useCache := actor.GatherFilesArgsForCall(0) 429 Expect(useCache).To(Equal(true)) 430 431 localFilesRetry, appDirRetry, uploadDirRetry, useCacheRetry := actor.GatherFilesArgsForCall(1) 432 Expect(localFilesRetry).To(Equal(localFiles)) 433 Expect(appDirRetry).To(Equal(appDir)) 434 Expect(uploadDirRetry).To(Equal(uploadDir)) 435 Expect(useCacheRetry).To(Equal(false)) 436 437 Expect(ui.Outputs()).To(ContainElement( 438 "Resource matching API timed out; pushing all app files.")) 439 }) 440 }) 441 }) 442 443 Context("when the default route for the app does not exist", func() { 444 BeforeEach(func() { 445 routeRepo.FindReturns(models.Route{}, errors.NewModelNotFoundError("Org", "couldn't find it")) 446 }) 447 448 It("refreshes the auth token (so fresh)", func() { 449 Expect(executeErr).NotTo(HaveOccurred()) 450 451 Expect(authRepo.RefreshAuthTokenCallCount()).To(Equal(1)) 452 }) 453 454 Context("when refreshing the auth token fails", func() { 455 BeforeEach(func() { 456 authRepo.RefreshAuthTokenReturns("", errors.New("I accidentally the UAA")) 457 }) 458 459 It("it returns an error", func() { 460 Expect(executeErr).To(HaveOccurred()) 461 462 Expect(executeErr.Error()).To(Equal("I accidentally the UAA")) 463 }) 464 }) 465 466 Context("when multiple domains are specified in manifest", func() { 467 var ( 468 route1 models.Route 469 route2 models.Route 470 route3 models.Route 471 route4 models.Route 472 ) 473 474 BeforeEach(func() { 475 deps.UI = uiWithContents 476 domainRepo.FindByNameInOrgStub = func(name string, owningOrgGUID string) (models.DomainFields, error) { 477 return map[string]models.DomainFields{ 478 "example1.com": {Name: "example1.com", GUID: "example-domain-guid"}, 479 "example2.com": {Name: "example2.com", GUID: "example-domain-guid"}, 480 }[name], nil 481 } 482 483 m := &manifest.Manifest{ 484 Path: "manifest.yml", 485 Data: generic.NewMap(map[interface{}]interface{}{ 486 "applications": []interface{}{ 487 generic.NewMap(map[interface{}]interface{}{ 488 "name": "manifest-app-name", 489 "memory": "128MB", 490 "instances": 1, 491 "host": "manifest-host", 492 "hosts": []interface{}{"host2"}, 493 "domains": []interface{}{"example1.com", "example2.com"}, 494 "stack": "custom-stack", 495 "timeout": 360, 496 "buildpack": "some-buildpack", 497 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 498 "path": filepath.Clean("some/path/from/manifest"), 499 "env": generic.NewMap(map[interface{}]interface{}{ 500 "FOO": "baz", 501 "PATH": "/u/apps/my-app/bin", 502 }), 503 }), 504 }, 505 }), 506 } 507 manifestRepo.ReadManifestReturns(m, nil) 508 routeRepo.CreateStub = func(host string, domain models.DomainFields, _ string, _ int, _ bool) (models.Route, error) { 509 return models.Route{ 510 GUID: "my-route-guid", 511 Host: host, 512 Domain: domain, 513 }, nil 514 } 515 args = []string{} 516 517 route1 = models.Route{ 518 GUID: "route1-guid", 519 } 520 route2 = models.Route{ 521 GUID: "route2-guid", 522 } 523 route3 = models.Route{ 524 GUID: "route3-guid", 525 } 526 route4 = models.Route{ 527 GUID: "route4-guid", 528 } 529 530 callCount := 0 531 routeActor.FindOrCreateRouteStub = func(hostname string, domain models.DomainFields, path string, _ int, useRandomPort bool) (models.Route, error) { 532 callCount = callCount + 1 533 switch callCount { 534 case 1: 535 Expect(hostname).To(Equal("host2")) 536 Expect(domain.Name).To(Equal("example1.com")) 537 Expect(path).To(BeEmpty()) 538 Expect(useRandomPort).To(BeFalse()) 539 return route1, nil 540 case 2: 541 Expect(hostname).To(Equal("manifest-host")) 542 Expect(domain.Name).To(Equal("example1.com")) 543 Expect(path).To(BeEmpty()) 544 Expect(useRandomPort).To(BeFalse()) 545 return route2, nil 546 case 3: 547 Expect(hostname).To(Equal("host2")) 548 Expect(domain.Name).To(Equal("example2.com")) 549 Expect(path).To(BeEmpty()) 550 Expect(useRandomPort).To(BeFalse()) 551 return route3, nil 552 case 4: 553 Expect(hostname).To(Equal("manifest-host")) 554 Expect(domain.Name).To(Equal("example2.com")) 555 Expect(path).To(BeEmpty()) 556 Expect(useRandomPort).To(BeFalse()) 557 return route4, nil 558 default: 559 Fail("should have only been called 4 times") 560 } 561 panic("should never have gotten this far") 562 } 563 }) 564 565 It("creates a route for each domain", func() { 566 Expect(executeErr).NotTo(HaveOccurred()) 567 568 Expect(routeActor.BindRouteCallCount()).To(Equal(4)) 569 app, route := routeActor.BindRouteArgsForCall(0) 570 Expect(app.Name).To(Equal("manifest-app-name")) 571 Expect(route).To(Equal(route1)) 572 573 app, route = routeActor.BindRouteArgsForCall(1) 574 Expect(app.Name).To(Equal("manifest-app-name")) 575 Expect(route).To(Equal(route2)) 576 577 app, route = routeActor.BindRouteArgsForCall(2) 578 Expect(app.Name).To(Equal("manifest-app-name")) 579 Expect(route).To(Equal(route3)) 580 581 app, route = routeActor.BindRouteArgsForCall(3) 582 Expect(app.Name).To(Equal("manifest-app-name")) 583 Expect(route).To(Equal(route4)) 584 }) 585 586 Context("when overriding the manifest with flags", func() { 587 BeforeEach(func() { 588 args = []string{"-d", "example1.com"} 589 route1 = models.Route{ 590 GUID: "route1-guid", 591 } 592 route2 = models.Route{ 593 GUID: "route2-guid", 594 } 595 596 callCount := 0 597 routeActor.FindOrCreateRouteStub = func(hostname string, domain models.DomainFields, path string, _ int, useRandomPort bool) (models.Route, error) { 598 callCount = callCount + 1 599 switch callCount { 600 case 1: 601 Expect(hostname).To(Equal("host2")) 602 Expect(domain.Name).To(Equal("example1.com")) 603 Expect(path).To(BeEmpty()) 604 Expect(useRandomPort).To(BeFalse()) 605 return route1, nil 606 case 2: 607 Expect(hostname).To(Equal("manifest-host")) 608 Expect(domain.Name).To(Equal("example1.com")) 609 Expect(path).To(BeEmpty()) 610 Expect(useRandomPort).To(BeFalse()) 611 return route2, nil 612 default: 613 Fail("should have only been called 2 times") 614 } 615 panic("should never have gotten this far") 616 } 617 }) 618 619 It("`-d` from argument will override the domains", func() { 620 Expect(executeErr).NotTo(HaveOccurred()) 621 622 Expect(routeActor.BindRouteCallCount()).To(Equal(2)) 623 app, route := routeActor.BindRouteArgsForCall(0) 624 Expect(app.Name).To(Equal("manifest-app-name")) 625 Expect(route).To(Equal(route1)) 626 627 app, route = routeActor.BindRouteArgsForCall(1) 628 Expect(app.Name).To(Equal("manifest-app-name")) 629 Expect(route).To(Equal(route2)) 630 }) 631 }) 632 }) 633 634 Context("when pushing an app", func() { 635 BeforeEach(func() { 636 deps.UI = uiWithContents 637 routeRepo.CreateStub = func(host string, domain models.DomainFields, _ string, _ int, _ bool) (models.Route, error) { 638 return models.Route{ 639 GUID: "my-route-guid", 640 Host: host, 641 Domain: domain, 642 }, nil 643 } 644 args = []string{"-t", "111", "app-name"} 645 }) 646 647 It("doesn't error", func() { 648 Expect(executeErr).NotTo(HaveOccurred()) 649 650 totalOutput := terminal.Decolorize(string(output.Contents())) 651 652 Expect(totalOutput).NotTo(ContainSubstring("FAILED")) 653 654 params := appRepo.CreateArgsForCall(0) 655 Expect(*params.Name).To(Equal("app-name")) 656 Expect(*params.SpaceGUID).To(Equal("my-space-guid")) 657 658 Expect(actor.UploadAppCallCount()).To(Equal(1)) 659 appGUID, _, _ := actor.UploadAppArgsForCall(0) 660 Expect(appGUID).To(Equal("app-name-guid")) 661 662 Expect(totalOutput).To(ContainSubstring("Creating app app-name in org my-org / space my-space as my-user...\nOK")) 663 Expect(totalOutput).To(ContainSubstring("Uploading app-name...\nOK")) 664 665 Expect(stopper.ApplicationStopCallCount()).To(Equal(0)) 666 667 app, orgName, spaceName := starter.ApplicationStartArgsForCall(0) 668 Expect(app.GUID).To(Equal(appGUID)) 669 Expect(app.Name).To(Equal("app-name")) 670 Expect(orgName).To(Equal(configRepo.OrganizationFields().Name)) 671 Expect(spaceName).To(Equal(configRepo.SpaceFields().Name)) 672 Expect(starter.SetStartTimeoutInSecondsArgsForCall(0)).To(Equal(111)) 673 }) 674 }) 675 676 Context("when there are special characters in the app name", func() { 677 Context("when the app name is specified via manifest file", func() { 678 BeforeEach(func() { 679 m := &manifest.Manifest{ 680 Path: "manifest.yml", 681 Data: generic.NewMap(map[interface{}]interface{}{ 682 "applications": []interface{}{ 683 generic.NewMap(map[interface{}]interface{}{ 684 "name": "manifest!app-nam#", 685 "memory": "128MB", 686 "instances": 1, 687 "stack": "custom-stack", 688 "timeout": 360, 689 "buildpack": "some-buildpack", 690 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 691 "path": filepath.Clean("some/path/from/manifest"), 692 "env": generic.NewMap(map[interface{}]interface{}{ 693 "FOO": "baz", 694 "PATH": "/u/apps/my-app/bin", 695 }), 696 }), 697 }, 698 }), 699 } 700 manifestRepo.ReadManifestReturns(m, nil) 701 702 args = []string{} 703 }) 704 705 It("strips special characters when creating a default route", func() { 706 Expect(executeErr).NotTo(HaveOccurred()) 707 708 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 709 host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 710 Expect(host).To(Equal("manifestapp-nam")) 711 }) 712 }) 713 714 Context("when the app name is specified via flag", func() { 715 BeforeEach(func() { 716 manifestRepo.ReadManifestReturns(manifest.NewEmptyManifest(), nil) 717 args = []string{"app@#name"} 718 }) 719 720 It("strips special characters when creating a default route", func() { 721 Expect(executeErr).NotTo(HaveOccurred()) 722 723 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 724 host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 725 Expect(host).To(Equal("appname")) 726 }) 727 }) 728 }) 729 730 Context("when flags are provided", func() { 731 BeforeEach(func() { 732 m := &manifest.Manifest{ 733 Path: "manifest.yml", 734 Data: generic.NewMap(map[interface{}]interface{}{ 735 "applications": []interface{}{ 736 generic.NewMap(map[interface{}]interface{}{ 737 "name": "manifest!app-nam#", 738 "memory": "128MB", 739 "instances": 1, 740 "host": "host-name", 741 "domain": "domain-name", 742 "disk_quota": "1G", 743 "stack": "custom-stack", 744 "timeout": 360, 745 "health-check-type": "none", 746 "app-ports": []interface{}{3000}, 747 "buildpack": "some-buildpack", 748 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 749 "path": filepath.Clean("some/path/from/manifest"), 750 "env": generic.NewMap(map[interface{}]interface{}{ 751 "FOO": "baz", 752 "PATH": "/u/apps/my-app/bin", 753 }), 754 }), 755 }, 756 }), 757 } 758 manifestRepo.ReadManifestReturns(m, nil) 759 760 args = []string{ 761 "-c", "unicorn -c config/unicorn.rb -D", 762 "-d", "bar.cf-app.com", 763 "-n", "my-hostname", 764 "--route-path", "my-route-path", 765 "-k", "4G", 766 "-i", "3", 767 "-m", "2G", 768 "-b", "https://github.com/heroku/heroku-buildpack-play.git", 769 "-s", "customLinux", 770 "-t", "1", 771 "-u", "port", 772 "--app-ports", "8080,9000", 773 "--no-start", 774 "app-name", 775 } 776 }) 777 778 It("sets the app params from the flags", func() { 779 Expect(executeErr).NotTo(HaveOccurred()) 780 781 Expect(appRepo.CreateCallCount()).To(Equal(1)) 782 appParam := appRepo.CreateArgsForCall(0) 783 Expect(*appParam.Command).To(Equal("unicorn -c config/unicorn.rb -D")) 784 Expect(appParam.Domains).To(Equal([]string{"bar.cf-app.com"})) 785 Expect(appParam.Hosts).To(Equal([]string{"my-hostname"})) 786 Expect(*appParam.RoutePath).To(Equal("my-route-path")) 787 Expect(*appParam.Name).To(Equal("app-name")) 788 Expect(*appParam.InstanceCount).To(Equal(3)) 789 Expect(*appParam.DiskQuota).To(Equal(int64(4096))) 790 Expect(*appParam.Memory).To(Equal(int64(2048))) 791 Expect(*appParam.StackName).To(Equal("customLinux")) 792 Expect(*appParam.HealthCheckTimeout).To(Equal(1)) 793 Expect(*appParam.HealthCheckType).To(Equal("port")) 794 Expect(*appParam.BuildpackURL).To(Equal("https://github.com/heroku/heroku-buildpack-play.git")) 795 Expect(*appParam.AppPorts).To(Equal([]int{8080, 9000})) 796 Expect(*appParam.HealthCheckTimeout).To(Equal(1)) 797 }) 798 }) 799 800 Context("when an invalid app port is porvided", func() { 801 BeforeEach(func() { 802 args = []string{"--app-ports", "8080abc", "app-name"} 803 }) 804 805 It("returns an error", func() { 806 Expect(executeErr).To(HaveOccurred()) 807 808 Expect(executeErr.Error()).To(ContainSubstring("Invalid app port: 8080abc")) 809 Expect(executeErr.Error()).To(ContainSubstring("App port must be a number")) 810 }) 811 }) 812 813 Context("when pushing a docker image with --docker-image", func() { 814 BeforeEach(func() { 815 deps.UI = uiWithContents 816 args = []string{"testApp", "--docker-image", "sample/dockerImage"} 817 }) 818 819 It("sets diego to true", func() { 820 Expect(executeErr).NotTo(HaveOccurred()) 821 822 Expect(appRepo.CreateCallCount()).To(Equal(1)) 823 params := appRepo.CreateArgsForCall(0) 824 Expect(*params.Diego).To(BeTrue()) 825 }) 826 827 It("sets docker_image", func() { 828 Expect(executeErr).NotTo(HaveOccurred()) 829 830 params := appRepo.CreateArgsForCall(0) 831 Expect(*params.DockerImage).To(Equal("sample/dockerImage")) 832 }) 833 834 It("does not upload appbits", func() { 835 Expect(executeErr).NotTo(HaveOccurred()) 836 837 Expect(actor.UploadAppCallCount()).To(Equal(0)) 838 Expect(terminal.Decolorize(string(output.Contents()))).NotTo(ContainSubstring("Uploading testApp")) 839 }) 840 841 Context("when using -o alias", func() { 842 BeforeEach(func() { 843 args = []string{"testApp", "-o", "sample/dockerImage"} 844 }) 845 846 It("sets docker_image", func() { 847 Expect(executeErr).NotTo(HaveOccurred()) 848 849 params := appRepo.CreateArgsForCall(0) 850 Expect(*params.DockerImage).To(Equal("sample/dockerImage")) 851 }) 852 }) 853 854 Context("when using --docker-username", func() { 855 BeforeEach(func() { 856 args = append(args, "--docker-username", "some-docker-username") 857 }) 858 859 Context("when CF_DOCKER_PASSWORD is set", func() { 860 BeforeEach(func() { 861 err := os.Setenv("CF_DOCKER_PASSWORD", "some-docker-pass") 862 Expect(err).ToNot(HaveOccurred()) 863 }) 864 865 AfterEach(func() { 866 err := os.Unsetenv("CF_DOCKER_PASSWORD") 867 Expect(err).ToNot(HaveOccurred()) 868 }) 869 870 It("it passes the credentials to create call", func() { 871 Expect(executeErr).NotTo(HaveOccurred()) 872 873 Expect(output).To(gbytes.Say("Using docker repository password from environment variable CF_DOCKER_PASSWORD.")) 874 875 Expect(appRepo.CreateCallCount()).To(Equal(1)) 876 params := appRepo.CreateArgsForCall(0) 877 Expect(*params.DockerUsername).To(Equal("some-docker-username")) 878 Expect(*params.DockerPassword).To(Equal("some-docker-pass")) 879 }) 880 }) 881 882 Context("when CF_DOCKER_PASSWORD is not set", func() { 883 BeforeEach(func() { 884 Skip("these [mostly] worked prior to the revert in 1d94b2df98b") 885 }) 886 887 Context("when the user gets prompted for the docker password", func() { 888 Context("when the user inputs the password on the first attempt", func() { 889 BeforeEach(func() { 890 _, err := input.Write([]byte("some-docker-pass\n")) 891 Expect(err).NotTo(HaveOccurred()) 892 }) 893 894 It("it passes the credentials to create call", func() { 895 Expect(executeErr).NotTo(HaveOccurred()) 896 897 Expect(output).To(gbytes.Say("Environment variable CF_DOCKER_PASSWORD not set.")) 898 Expect(output).To(gbytes.Say("Docker password")) 899 Expect(output).ToNot(gbytes.Say("Docker password")) // Only prompt once 900 901 Expect(appRepo.CreateCallCount()).To(Equal(1)) 902 params := appRepo.CreateArgsForCall(0) 903 Expect(*params.DockerUsername).To(Equal("some-docker-username")) 904 Expect(*params.DockerPassword).To(Equal("some-docker-pass")) 905 }) 906 }) 907 908 Context("when the user fails to input the password 3 times", func() { 909 BeforeEach(func() { 910 _, err := input.Write([]byte("\n\n\n")) 911 Expect(err).NotTo(HaveOccurred()) 912 }) 913 914 It("returns an error", func() { 915 Expect(executeErr).To(MatchError("Please provide a password")) 916 917 Expect(output).To(gbytes.Say("Docker password")) 918 Expect(output).To(gbytes.Say("Docker password")) 919 Expect(output).To(gbytes.Say("Docker password")) 920 Expect(output).ToNot(gbytes.Say("Docker password")) 921 922 Expect(appRepo.CreateCallCount()).To(Equal(0)) 923 }) 924 }) 925 }) 926 }) 927 }) 928 }) 929 930 Context("when pushing with --docker-username and not --docker-image", func() { 931 BeforeEach(func() { 932 deps.UI = uiWithContents 933 args = []string{"testApp", "--docker-username", "some-docker-username"} 934 }) 935 936 It("it returns an error", func() { 937 Expect(executeErr).To(MatchError("'--docker-username' requires '--docker-image' to be specified")) 938 }) 939 }) 940 941 Context("when health-check-type '-u' or '--health-check-type' is set", func() { 942 Context("when the value is not 'http', 'none', 'port', or 'process'", func() { 943 BeforeEach(func() { 944 args = []string{"app-name", "-u", "bad-value"} 945 }) 946 947 It("returns an error", func() { 948 Expect(executeErr).To(HaveOccurred()) 949 950 Expect(executeErr.Error()).To(ContainSubstring("Error: Invalid health-check-type param: bad-value")) 951 }) 952 }) 953 954 Context("when the value is 'http'", func() { 955 BeforeEach(func() { 956 args = []string{"app-name", "--health-check-type", "http"} 957 }) 958 959 It("does not show error", func() { 960 Expect(executeErr).NotTo(HaveOccurred()) 961 }) 962 963 It("sets the HTTP health check endpoint to /", func() { 964 params := appRepo.CreateArgsForCall(0) 965 Expect(*params.HealthCheckHTTPEndpoint).To(Equal("/")) 966 }) 967 }) 968 969 Context("when the value is 'none'", func() { 970 BeforeEach(func() { 971 args = []string{"app-name", "--health-check-type", "none"} 972 }) 973 974 It("does not show error", func() { 975 Expect(executeErr).NotTo(HaveOccurred()) 976 }) 977 }) 978 979 Context("when the value is 'port'", func() { 980 BeforeEach(func() { 981 args = []string{"app-name", "--health-check-type", "port"} 982 }) 983 984 It("does not show error", func() { 985 Expect(executeErr).NotTo(HaveOccurred()) 986 }) 987 }) 988 989 Context("when the value is 'process'", func() { 990 BeforeEach(func() { 991 args = []string{"app-name", "--health-check-type", "process"} 992 }) 993 994 It("does not show error", func() { 995 Expect(executeErr).NotTo(HaveOccurred()) 996 }) 997 }) 998 }) 999 1000 Context("with random-route option set", func() { 1001 var manifestApp generic.Map 1002 1003 BeforeEach(func() { 1004 manifestApp = generic.NewMap(map[interface{}]interface{}{ 1005 "name": "manifest-app-name", 1006 "memory": "128MB", 1007 "instances": 1, 1008 "domain": "manifest-example.com", 1009 "stack": "custom-stack", 1010 "timeout": 360, 1011 "buildpack": "some-buildpack", 1012 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1013 "path": filepath.Clean("some/path/from/manifest"), 1014 "env": generic.NewMap(map[interface{}]interface{}{ 1015 "FOO": "baz", 1016 "PATH": "/u/apps/my-app/bin", 1017 }), 1018 }) 1019 m := &manifest.Manifest{ 1020 Path: "manifest.yml", 1021 Data: generic.NewMap(map[interface{}]interface{}{ 1022 "applications": []interface{}{manifestApp}, 1023 }), 1024 } 1025 manifestRepo.ReadManifestReturns(m, nil) 1026 }) 1027 1028 Context("for http routes", func() { 1029 Context("when random-route is set as a flag", func() { 1030 BeforeEach(func() { 1031 args = []string{"--random-route", "app-name"} 1032 }) 1033 1034 It("provides a random hostname", func() { 1035 Expect(executeErr).NotTo(HaveOccurred()) 1036 1037 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1038 host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 1039 Expect(host).To(Equal("app-name-random-host")) 1040 }) 1041 }) 1042 1043 Context("when random-route is set in the manifest", func() { 1044 BeforeEach(func() { 1045 manifestApp.Set("random-route", true) 1046 args = []string{"app-name"} 1047 }) 1048 1049 It("provides a random hostname", func() { 1050 Expect(executeErr).NotTo(HaveOccurred()) 1051 1052 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1053 host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 1054 Expect(host).To(Equal("app-name-random-host")) 1055 }) 1056 }) 1057 }) 1058 1059 Context("for tcp routes", func() { 1060 var expectedDomain models.DomainFields 1061 1062 BeforeEach(func() { 1063 deps.UI = uiWithContents 1064 1065 expectedDomain = models.DomainFields{ 1066 GUID: "some-guid", 1067 Name: "some-name", 1068 OwningOrganizationGUID: "some-organization-guid", 1069 RouterGroupGUID: "some-router-group-guid", 1070 RouterGroupType: "tcp", 1071 Shared: true, 1072 } 1073 1074 domainRepo.FindByNameInOrgReturns( 1075 expectedDomain, 1076 nil, 1077 ) 1078 1079 route := models.Route{ 1080 Domain: expectedDomain, 1081 Port: 7777, 1082 } 1083 routeRepo.CreateReturns(route, nil) 1084 }) 1085 1086 Context("when random-route passed as a flag", func() { 1087 BeforeEach(func() { 1088 args = []string{"--random-route", "app-name"} 1089 }) 1090 1091 It("provides a random port and hostname", func() { 1092 Expect(executeErr).NotTo(HaveOccurred()) 1093 1094 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1095 _, _, _, _, randomPort := routeActor.FindOrCreateRouteArgsForCall(0) 1096 Expect(randomPort).To(BeTrue()) 1097 }) 1098 }) 1099 1100 Context("when random-route set in the manifest", func() { 1101 BeforeEach(func() { 1102 manifestApp.Set("random-route", true) 1103 args = []string{"app-name"} 1104 }) 1105 1106 It("provides a random port and hostname when set in the manifest", func() { 1107 Expect(executeErr).NotTo(HaveOccurred()) 1108 1109 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1110 _, _, _, _, randomPort := routeActor.FindOrCreateRouteArgsForCall(0) 1111 Expect(randomPort).To(BeTrue()) 1112 }) 1113 }) 1114 }) 1115 }) 1116 1117 Context("when path to an app is set", func() { 1118 var expectedLocalFiles []models.AppFileFields 1119 1120 BeforeEach(func() { 1121 expectedLocalFiles = []models.AppFileFields{ 1122 { 1123 Path: "the-path", 1124 }, 1125 { 1126 Path: "the-other-path", 1127 }, 1128 } 1129 appfiles.AppFilesInDirReturns(expectedLocalFiles, nil) 1130 args = []string{"-p", "../some/path-to/an-app/file.zip", "app-with-path"} 1131 }) 1132 1133 It("includes the app files in dir", func() { 1134 Expect(executeErr).NotTo(HaveOccurred()) 1135 1136 actualLocalFiles, _, _, _ := actor.GatherFilesArgsForCall(0) 1137 Expect(actualLocalFiles).To(Equal(expectedLocalFiles)) 1138 }) 1139 }) 1140 1141 Context("when there are no app files to process", func() { 1142 BeforeEach(func() { 1143 deps.UI = uiWithContents 1144 appfiles.AppFilesInDirReturns([]models.AppFileFields{}, nil) 1145 args = []string{"-p", "../some/path-to/an-app/file.zip", "app-with-path"} 1146 }) 1147 1148 It("errors", func() { 1149 Expect(executeErr).To(HaveOccurred()) 1150 Expect(executeErr.Error()).To(ContainSubstring("No app files found in '../some/path-to/an-app/file.zip'")) 1151 }) 1152 }) 1153 1154 Context("when there is an error getting app files", func() { 1155 BeforeEach(func() { 1156 deps.UI = uiWithContents 1157 appfiles.AppFilesInDirReturns([]models.AppFileFields{}, errors.New("some error")) 1158 args = []string{"-p", "../some/path-to/an-app/file.zip", "app-with-path"} 1159 }) 1160 1161 It("prints a message", func() { 1162 Expect(executeErr).To(HaveOccurred()) 1163 Expect(executeErr.Error()).To(ContainSubstring("Error processing app files in '../some/path-to/an-app/file.zip': some error")) 1164 }) 1165 }) 1166 1167 Context("when an app path is specified with the -p flag", func() { 1168 BeforeEach(func() { 1169 args = []string{"-p", "../some/path-to/an-app/file.zip", "app-with-path"} 1170 }) 1171 1172 It("pushes the contents of the app directory or zip file specified", func() { 1173 Expect(executeErr).NotTo(HaveOccurred()) 1174 1175 _, appDir, _, _ := actor.GatherFilesArgsForCall(0) 1176 Expect(appDir).To(Equal("../some/path-to/an-app/file.zip")) 1177 }) 1178 }) 1179 1180 Context("when no flags are specified", func() { 1181 BeforeEach(func() { 1182 m := &manifest.Manifest{ 1183 Path: "manifest.yml", 1184 Data: generic.NewMap(map[interface{}]interface{}{ 1185 "applications": []interface{}{ 1186 generic.NewMap(map[interface{}]interface{}{ 1187 "name": "manifest-app-name", 1188 "memory": "128MB", 1189 "instances": 1, 1190 "host": "manifest-host", 1191 "stack": "custom-stack", 1192 "timeout": 360, 1193 "buildpack": "some-buildpack", 1194 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1195 "env": generic.NewMap(map[interface{}]interface{}{ 1196 "FOO": "baz", 1197 "PATH": "/u/apps/my-app/bin", 1198 }), 1199 }), 1200 }, 1201 }), 1202 } 1203 manifestRepo.ReadManifestReturns(m, nil) 1204 args = []string{"app-with-default-path"} 1205 }) 1206 1207 It("pushes the contents of the current working directory by default", func() { 1208 Expect(executeErr).NotTo(HaveOccurred()) 1209 1210 dir, _ := os.Getwd() 1211 _, appDir, _, _ := actor.GatherFilesArgsForCall(0) 1212 Expect(appDir).To(Equal(dir)) 1213 }) 1214 }) 1215 1216 Context("when given a bad manifest", func() { 1217 BeforeEach(func() { 1218 manifestRepo.ReadManifestReturns(manifest.NewEmptyManifest(), errors.New("read manifest error")) 1219 args = []string{"-f", "bad/manifest/path"} 1220 }) 1221 1222 It("errors", func() { 1223 Expect(executeErr).To(HaveOccurred()) 1224 Expect(executeErr.Error()).To(ContainSubstring("read manifest error")) 1225 }) 1226 }) 1227 1228 Context("when the current directory does not contain a manifest", func() { 1229 BeforeEach(func() { 1230 deps.UI = uiWithContents 1231 manifestRepo.ReadManifestReturns(manifest.NewEmptyManifest(), syscall.ENOENT) 1232 args = []string{"--no-route", "app-name"} 1233 }) 1234 1235 It("does not fail", func() { 1236 Expect(executeErr).NotTo(HaveOccurred()) 1237 fullOutput := terminal.Decolorize(string(output.Contents())) 1238 Expect(fullOutput).To(ContainSubstring("Creating app app-name in org my-org / space my-space as my-user...\nOK")) 1239 Expect(fullOutput).To(ContainSubstring("Uploading app-name...\nOK")) 1240 }) 1241 }) 1242 1243 Context("when the current directory does contain a manifest", func() { 1244 BeforeEach(func() { 1245 deps.UI = uiWithContents 1246 m := &manifest.Manifest{ 1247 Path: "manifest.yml", 1248 Data: generic.NewMap(map[interface{}]interface{}{ 1249 "applications": []interface{}{ 1250 generic.NewMap(map[interface{}]interface{}{ 1251 "name": "manifest-app-name", 1252 "memory": "128MB", 1253 "instances": 1, 1254 "host": "manifest-host", 1255 "domain": "manifest-example.com", 1256 "stack": "custom-stack", 1257 "timeout": 360, 1258 "buildpack": "some-buildpack", 1259 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1260 "path": filepath.Clean("some/path/from/manifest"), 1261 "env": generic.NewMap(map[interface{}]interface{}{ 1262 "FOO": "baz", 1263 "PATH": "/u/apps/my-app/bin", 1264 }), 1265 }), 1266 }, 1267 }), 1268 } 1269 manifestRepo.ReadManifestReturns(m, nil) 1270 args = []string{"-p", "some/relative/path"} 1271 }) 1272 1273 It("uses the manifest in the current directory by default", func() { 1274 Expect(executeErr).NotTo(HaveOccurred()) 1275 1276 Expect(terminal.Decolorize(string(output.Contents()))).To(ContainSubstring("Using manifest file manifest.yml")) 1277 1278 cwd, _ := os.Getwd() 1279 Expect(manifestRepo.ReadManifestArgsForCall(0)).To(Equal(cwd)) 1280 }) 1281 }) 1282 1283 Context("when the 'no-manifest'flag is passed", func() { 1284 BeforeEach(func() { 1285 args = []string{"--no-route", "--no-manifest", "app-name"} 1286 }) 1287 1288 It("does not use a manifest", func() { 1289 Expect(executeErr).NotTo(HaveOccurred()) 1290 1291 fullOutput := terminal.Decolorize(string(output.Contents())) 1292 Expect(fullOutput).NotTo(ContainSubstring("FAILED")) 1293 Expect(fullOutput).NotTo(ContainSubstring("hacker-manifesto")) 1294 1295 Expect(manifestRepo.ReadManifestCallCount()).To(BeZero()) 1296 params := appRepo.CreateArgsForCall(0) 1297 Expect(*params.Name).To(Equal("app-name")) 1298 }) 1299 }) 1300 1301 Context("when the manifest has errors", func() { 1302 BeforeEach(func() { 1303 manifestRepo.ReadManifestReturns( 1304 &manifest.Manifest{ 1305 Path: "/some-path/", 1306 }, 1307 errors.New("buildpack should not be null"), 1308 ) 1309 1310 args = []string{} 1311 }) 1312 1313 It("fails when parsing the manifest has errors", func() { 1314 Expect(executeErr).To(HaveOccurred()) 1315 Expect(executeErr.Error()).To(ContainSubstring("Error reading manifest file:\nbuildpack should not be null")) 1316 }) 1317 }) 1318 1319 Context("when the no-route option is set", func() { 1320 Context("when provided the --no-route-flag", func() { 1321 BeforeEach(func() { 1322 domainRepo.FindByNameInOrgReturns(models.DomainFields{ 1323 Name: "bar.cf-app.com", 1324 GUID: "bar-domain-guid", 1325 }, nil) 1326 1327 args = []string{"--no-route", "app-name"} 1328 }) 1329 1330 It("does not create a route", func() { 1331 Expect(executeErr).NotTo(HaveOccurred()) 1332 params := appRepo.CreateArgsForCall(0) 1333 Expect(*params.Name).To(Equal("app-name")) 1334 Expect(routeRepo.CreateCallCount()).To(BeZero()) 1335 }) 1336 }) 1337 1338 Context("when no-route is set in the manifest", func() { 1339 BeforeEach(func() { 1340 deps.UI = uiWithContents 1341 workerManifest := &manifest.Manifest{ 1342 Path: "manifest.yml", 1343 Data: generic.NewMap(map[interface{}]interface{}{ 1344 "applications": []interface{}{ 1345 generic.NewMap(map[interface{}]interface{}{ 1346 "name": "manifest-app-name", 1347 "memory": "128MB", 1348 "instances": 1, 1349 "host": "manifest-host", 1350 "domain": "manifest-example.com", 1351 "stack": "custom-stack", 1352 "timeout": 360, 1353 "buildpack": "some-buildpack", 1354 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1355 "path": filepath.Clean("some/path/from/manifest"), 1356 "env": generic.NewMap(map[interface{}]interface{}{ 1357 "FOO": "baz", 1358 "PATH": "/u/apps/my-app/bin", 1359 }), 1360 }), 1361 }, 1362 }), 1363 } 1364 workerManifest.Data.Get("applications").([]interface{})[0].(generic.Map).Set("no-route", true) 1365 manifestRepo.ReadManifestReturns(workerManifest, nil) 1366 1367 args = []string{"app-name"} 1368 }) 1369 1370 It("Does not create a route", func() { 1371 Expect(executeErr).NotTo(HaveOccurred()) 1372 Expect(terminal.Decolorize(string(output.Contents()))).To(ContainSubstring("App app-name is a worker, skipping route creation")) 1373 Expect(routeRepo.BindCallCount()).To(BeZero()) 1374 }) 1375 }) 1376 }) 1377 1378 Context("when provided the --no-hostname flag", func() { 1379 BeforeEach(func() { 1380 domainRepo.ListDomainsForOrgStub = func(orgGUID string, cb func(models.DomainFields) bool) error { 1381 cb(models.DomainFields{ 1382 Name: "bar.cf-app.com", 1383 GUID: "bar-domain-guid", 1384 Shared: true, 1385 }) 1386 1387 return nil 1388 } 1389 routeRepo.FindReturns(models.Route{}, errors.NewModelNotFoundError("Org", "uh oh")) 1390 1391 args = []string{"--no-hostname", "app-name"} 1392 }) 1393 1394 It("maps the root domain route to the app", func() { 1395 Expect(executeErr).NotTo(HaveOccurred()) 1396 1397 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1398 _, domain, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 1399 Expect(domain.GUID).To(Equal("bar-domain-guid")) 1400 }) 1401 1402 Context("when using 'routes' in the manifest", func() { 1403 BeforeEach(func() { 1404 m := &manifest.Manifest{ 1405 Data: generic.NewMap(map[interface{}]interface{}{ 1406 "applications": []interface{}{ 1407 generic.NewMap(map[interface{}]interface{}{ 1408 "name": "app1", 1409 "routes": []interface{}{ 1410 map[interface{}]interface{}{"route": "app2route1.example.com"}, 1411 }, 1412 }), 1413 }, 1414 }), 1415 } 1416 manifestRepo.ReadManifestReturns(m, nil) 1417 }) 1418 1419 It("returns an error", func() { 1420 Expect(executeErr).To(HaveOccurred()) 1421 Expect(executeErr).To(MatchError("Option '--no-hostname' cannot be used with an app manifest containing the 'routes' attribute")) 1422 }) 1423 }) 1424 }) 1425 1426 Context("with an invalid memory limit", func() { 1427 BeforeEach(func() { 1428 args = []string{"-m", "abcM", "app-name"} 1429 }) 1430 1431 It("fails", func() { 1432 Expect(executeErr).To(HaveOccurred()) 1433 Expect(executeErr.Error()).To(ContainSubstring("Invalid memory limit: abcM")) 1434 }) 1435 }) 1436 1437 Context("when a manifest has many apps", func() { 1438 BeforeEach(func() { 1439 deps.UI = uiWithContents 1440 m := &manifest.Manifest{ 1441 Data: generic.NewMap(map[interface{}]interface{}{ 1442 "applications": []interface{}{ 1443 generic.NewMap(map[interface{}]interface{}{ 1444 "name": "app1", 1445 "services": []interface{}{"app1-service", "global-service"}, 1446 "env": generic.NewMap(map[interface{}]interface{}{ 1447 "SOMETHING": "definitely-something", 1448 }), 1449 }), 1450 generic.NewMap(map[interface{}]interface{}{ 1451 "name": "app2", 1452 "services": []interface{}{"app2-service", "global-service"}, 1453 "env": generic.NewMap(map[interface{}]interface{}{ 1454 "SOMETHING": "nothing", 1455 }), 1456 }), 1457 }, 1458 }), 1459 } 1460 manifestRepo.ReadManifestReturns(m, nil) 1461 args = []string{} 1462 }) 1463 1464 It("pushes each app", func() { 1465 Expect(executeErr).NotTo(HaveOccurred()) 1466 1467 totalOutput := terminal.Decolorize(string(output.Contents())) 1468 Expect(totalOutput).To(ContainSubstring("Creating app app1")) 1469 Expect(totalOutput).To(ContainSubstring("Creating app app2")) 1470 Expect(appRepo.CreateCallCount()).To(Equal(2)) 1471 1472 firstApp := appRepo.CreateArgsForCall(0) 1473 secondApp := appRepo.CreateArgsForCall(1) 1474 Expect(*firstApp.Name).To(Equal("app1")) 1475 Expect(*secondApp.Name).To(Equal("app2")) 1476 1477 envVars := *firstApp.EnvironmentVars 1478 Expect(envVars["SOMETHING"]).To(Equal("definitely-something")) 1479 1480 envVars = *secondApp.EnvironmentVars 1481 Expect(envVars["SOMETHING"]).To(Equal("nothing")) 1482 }) 1483 1484 Context("when a single app is given as an arg", func() { 1485 BeforeEach(func() { 1486 args = []string{"app2"} 1487 }) 1488 1489 It("pushes that single app", func() { 1490 Expect(executeErr).NotTo(HaveOccurred()) 1491 1492 totalOutput := terminal.Decolorize(string(output.Contents())) 1493 Expect(totalOutput).To(ContainSubstring("Creating app app2")) 1494 Expect(totalOutput).ToNot(ContainSubstring("Creating app app1")) 1495 Expect(appRepo.CreateCallCount()).To(Equal(1)) 1496 params := appRepo.CreateArgsForCall(0) 1497 Expect(*params.Name).To(Equal("app2")) 1498 }) 1499 }) 1500 1501 Context("when the given app is not in the manifest", func() { 1502 BeforeEach(func() { 1503 args = []string{"non-existant-app"} 1504 }) 1505 1506 It("fails", func() { 1507 Expect(executeErr).To(HaveOccurred()) 1508 Expect(appRepo.CreateCallCount()).To(BeZero()) 1509 }) 1510 }) 1511 }) 1512 }) 1513 }) 1514 1515 Context("re-pushing an existing app", func() { 1516 var existingApp models.Application 1517 1518 BeforeEach(func() { 1519 deps.UI = uiWithContents 1520 existingApp = models.Application{ 1521 ApplicationFields: models.ApplicationFields{ 1522 Name: "existing-app", 1523 GUID: "existing-app-guid", 1524 Command: "unicorn -c config/unicorn.rb -D", 1525 EnvironmentVars: map[string]interface{}{ 1526 "crazy": "pants", 1527 "FOO": "NotYoBaz", 1528 "foo": "manchu", 1529 }, 1530 }, 1531 } 1532 manifestRepo.ReadManifestReturns(manifest.NewEmptyManifest(), nil) 1533 appRepo.ReadReturns(existingApp, nil) 1534 appRepo.UpdateReturns(existingApp, nil) 1535 args = []string{"existing-app"} 1536 }) 1537 1538 It("stops the app, achieving a full-downtime deploy!", func() { 1539 Expect(executeErr).NotTo(HaveOccurred()) 1540 1541 app, orgName, spaceName := stopper.ApplicationStopArgsForCall(0) 1542 Expect(app.GUID).To(Equal(existingApp.GUID)) 1543 Expect(app.Name).To(Equal("existing-app")) 1544 Expect(orgName).To(Equal(configRepo.OrganizationFields().Name)) 1545 Expect(spaceName).To(Equal(configRepo.SpaceFields().Name)) 1546 1547 Expect(actor.UploadAppCallCount()).To(Equal(1)) 1548 appGUID, _, _ := actor.UploadAppArgsForCall(0) 1549 Expect(appGUID).To(Equal(existingApp.GUID)) 1550 }) 1551 1552 It("re-uploads the app", func() { 1553 Expect(executeErr).NotTo(HaveOccurred()) 1554 1555 totalOutputs := terminal.Decolorize(string(output.Contents())) 1556 Expect(totalOutputs).To(ContainSubstring("Uploading existing-app...\nOK")) 1557 }) 1558 1559 Context("when the -b flag is provided as 'default'", func() { 1560 BeforeEach(func() { 1561 args = []string{"-b", "default", "existing-app"} 1562 }) 1563 1564 It("resets the app's buildpack", func() { 1565 Expect(executeErr).NotTo(HaveOccurred()) 1566 1567 Expect(appRepo.UpdateCallCount()).To(Equal(1)) 1568 _, params := appRepo.UpdateArgsForCall(0) 1569 Expect(*params.BuildpackURL).To(Equal("")) 1570 }) 1571 }) 1572 1573 Context("when the -c flag is provided as 'default'", func() { 1574 BeforeEach(func() { 1575 args = []string{"-c", "default", "existing-app"} 1576 }) 1577 1578 It("resets the app's command", func() { 1579 Expect(executeErr).NotTo(HaveOccurred()) 1580 _, params := appRepo.UpdateArgsForCall(0) 1581 Expect(*params.Command).To(Equal("")) 1582 }) 1583 }) 1584 1585 Context("when the -b flag is provided as 'null'", func() { 1586 BeforeEach(func() { 1587 args = []string{"-b", "null", "existing-app"} 1588 }) 1589 1590 It("resets the app's buildpack", func() { 1591 Expect(executeErr).NotTo(HaveOccurred()) 1592 _, params := appRepo.UpdateArgsForCall(0) 1593 Expect(*params.BuildpackURL).To(Equal("")) 1594 }) 1595 }) 1596 1597 Context("when the -c flag is provided as 'null'", func() { 1598 BeforeEach(func() { 1599 args = []string{"-c", "null", "existing-app"} 1600 }) 1601 1602 It("resets the app's command", func() { 1603 Expect(executeErr).NotTo(HaveOccurred()) 1604 _, params := appRepo.UpdateArgsForCall(0) 1605 Expect(*params.Command).To(Equal("")) 1606 }) 1607 }) 1608 1609 Context("when the manifest provided env variables", func() { 1610 BeforeEach(func() { 1611 m := &manifest.Manifest{ 1612 Path: "manifest.yml", 1613 Data: generic.NewMap(map[interface{}]interface{}{ 1614 "applications": []interface{}{ 1615 generic.NewMap(map[interface{}]interface{}{ 1616 "name": "manifest-app-name", 1617 "memory": "128MB", 1618 "instances": 1, 1619 "host": "manifest-host", 1620 "domain": "manifest-example.com", 1621 "stack": "custom-stack", 1622 "timeout": 360, 1623 "buildpack": "some-buildpack", 1624 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1625 "path": filepath.Clean("some/path/from/manifest"), 1626 "env": generic.NewMap(map[interface{}]interface{}{ 1627 "FOO": "baz", 1628 "PATH": "/u/apps/my-app/bin", 1629 }), 1630 }), 1631 }, 1632 }), 1633 } 1634 manifestRepo.ReadManifestReturns(m, nil) 1635 1636 args = []string{"existing-app"} 1637 }) 1638 1639 It("merges env vars from the manifest with those from the server", func() { 1640 Expect(executeErr).NotTo(HaveOccurred()) 1641 1642 _, params := appRepo.UpdateArgsForCall(0) 1643 updatedAppEnvVars := *params.EnvironmentVars 1644 Expect(updatedAppEnvVars["crazy"]).To(Equal("pants")) 1645 Expect(updatedAppEnvVars["FOO"]).To(Equal("baz")) 1646 Expect(updatedAppEnvVars["foo"]).To(Equal("manchu")) 1647 Expect(updatedAppEnvVars["PATH"]).To(Equal("/u/apps/my-app/bin")) 1648 }) 1649 }) 1650 1651 Context("when the app is already stopped", func() { 1652 BeforeEach(func() { 1653 existingApp.State = "stopped" 1654 appRepo.ReadReturns(existingApp, nil) 1655 appRepo.UpdateReturns(existingApp, nil) 1656 args = []string{"existing-app"} 1657 }) 1658 1659 It("does not stop the app", func() { 1660 Expect(executeErr).NotTo(HaveOccurred()) 1661 Expect(stopper.ApplicationStopCallCount()).To(Equal(0)) 1662 }) 1663 }) 1664 1665 Context("when the application is pushed with updated parameters", func() { 1666 BeforeEach(func() { 1667 existingRoute := models.RouteSummary{ 1668 Host: "existing-app", 1669 } 1670 existingApp.Routes = []models.RouteSummary{existingRoute} 1671 appRepo.ReadReturns(existingApp, nil) 1672 appRepo.UpdateReturns(existingApp, nil) 1673 1674 stackRepo.FindByNameReturns(models.Stack{ 1675 Name: "differentStack", 1676 GUID: "differentStack-guid", 1677 }, nil) 1678 1679 args = []string{ 1680 "-c", "different start command", 1681 "-i", "10", 1682 "-m", "1G", 1683 "-b", "https://github.com/heroku/heroku-buildpack-different.git", 1684 "-s", "differentStack", 1685 "existing-app", 1686 } 1687 }) 1688 1689 It("updates the app", func() { 1690 Expect(executeErr).NotTo(HaveOccurred()) 1691 1692 appGUID, params := appRepo.UpdateArgsForCall(0) 1693 Expect(appGUID).To(Equal(existingApp.GUID)) 1694 Expect(*params.Command).To(Equal("different start command")) 1695 Expect(*params.InstanceCount).To(Equal(10)) 1696 Expect(*params.Memory).To(Equal(int64(1024))) 1697 Expect(*params.BuildpackURL).To(Equal("https://github.com/heroku/heroku-buildpack-different.git")) 1698 Expect(*params.StackGUID).To(Equal("differentStack-guid")) 1699 }) 1700 }) 1701 1702 Context("when the app has a route bound", func() { 1703 BeforeEach(func() { 1704 domain := models.DomainFields{ 1705 Name: "example.com", 1706 GUID: "domain-guid", 1707 Shared: true, 1708 } 1709 1710 existingApp.Routes = []models.RouteSummary{{ 1711 GUID: "existing-route-guid", 1712 Host: "existing-app", 1713 Domain: domain, 1714 }} 1715 1716 appRepo.ReadReturns(existingApp, nil) 1717 appRepo.UpdateReturns(existingApp, nil) 1718 }) 1719 1720 Context("and no route-related flags are given", func() { 1721 Context("and there is no route in the manifest", func() { 1722 It("does not add a route to the app", func() { 1723 Expect(executeErr).NotTo(HaveOccurred()) 1724 1725 appGUID, _, _ := actor.UploadAppArgsForCall(0) 1726 Expect(appGUID).To(Equal("existing-app-guid")) 1727 Expect(domainRepo.FindByNameInOrgCallCount()).To(BeZero()) 1728 Expect(routeRepo.FindCallCount()).To(BeZero()) 1729 Expect(routeRepo.CreateCallCount()).To(BeZero()) 1730 }) 1731 }) 1732 }) 1733 1734 Context("when --no-route flag is given", func() { 1735 BeforeEach(func() { 1736 args = []string{"--no-route", "existing-app"} 1737 }) 1738 1739 It("removes existing routes that the app is bound to", func() { 1740 Expect(executeErr).NotTo(HaveOccurred()) 1741 1742 appGUID, _, _ := actor.UploadAppArgsForCall(0) 1743 Expect(appGUID).To(Equal("existing-app-guid")) 1744 1745 Expect(routeActor.UnbindAllCallCount()).To(Equal(1)) 1746 app := routeActor.UnbindAllArgsForCall(0) 1747 Expect(app.GUID).To(Equal(appGUID)) 1748 1749 Expect(routeActor.FindOrCreateRouteCallCount()).To(BeZero()) 1750 }) 1751 }) 1752 1753 Context("when the --no-hostname flag is given", func() { 1754 BeforeEach(func() { 1755 routeRepo.FindReturns(models.Route{}, errors.NewModelNotFoundError("Org", "existing-app.example.com")) 1756 args = []string{"--no-hostname", "existing-app"} 1757 }) 1758 1759 It("binds the root domain route to an app with a pre-existing route", func() { 1760 Expect(executeErr).NotTo(HaveOccurred()) 1761 1762 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1763 hostname, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 1764 Expect(hostname).To(BeEmpty()) 1765 }) 1766 }) 1767 }) 1768 1769 Context("service instances", func() { 1770 BeforeEach(func() { 1771 appRepo.CreateStub = func(params models.AppParams) (models.Application, error) { 1772 a := models.Application{} 1773 a.Name = *params.Name 1774 a.GUID = *params.Name + "-guid" 1775 1776 return a, nil 1777 } 1778 1779 serviceRepo.FindInstanceByNameStub = func(name string) (models.ServiceInstance, error) { 1780 return models.ServiceInstance{ 1781 ServiceInstanceFields: models.ServiceInstanceFields{Name: name}, 1782 }, nil 1783 } 1784 1785 m := &manifest.Manifest{ 1786 Data: generic.NewMap(map[interface{}]interface{}{ 1787 "applications": []interface{}{ 1788 generic.NewMap(map[interface{}]interface{}{ 1789 "name": "app1", 1790 "services": []interface{}{"app1-service", "global-service"}, 1791 "env": generic.NewMap(map[interface{}]interface{}{ 1792 "SOMETHING": "definitely-something", 1793 }), 1794 }), 1795 generic.NewMap(map[interface{}]interface{}{ 1796 "name": "app2", 1797 "services": []interface{}{"app2-service", "global-service"}, 1798 "env": generic.NewMap(map[interface{}]interface{}{ 1799 "SOMETHING": "nothing", 1800 }), 1801 }), 1802 }, 1803 }), 1804 } 1805 manifestRepo.ReadManifestReturns(m, nil) 1806 1807 args = []string{} 1808 }) 1809 1810 Context("when the service is not bound", func() { 1811 BeforeEach(func() { 1812 appRepo.ReadReturns(models.Application{}, errors.NewModelNotFoundError("App", "the-app")) 1813 }) 1814 1815 It("binds service instances to the app", func() { 1816 Expect(executeErr).NotTo(HaveOccurred()) 1817 1818 Expect(len(serviceBinder.AppsToBind)).To(Equal(4)) 1819 Expect(serviceBinder.AppsToBind[0].Name).To(Equal("app1")) 1820 Expect(serviceBinder.AppsToBind[1].Name).To(Equal("app1")) 1821 Expect(serviceBinder.InstancesToBindTo[0].Name).To(Equal("app1-service")) 1822 Expect(serviceBinder.InstancesToBindTo[1].Name).To(Equal("global-service")) 1823 1824 Expect(serviceBinder.AppsToBind[2].Name).To(Equal("app2")) 1825 Expect(serviceBinder.AppsToBind[3].Name).To(Equal("app2")) 1826 Expect(serviceBinder.InstancesToBindTo[2].Name).To(Equal("app2-service")) 1827 Expect(serviceBinder.InstancesToBindTo[3].Name).To(Equal("global-service")) 1828 1829 totalOutputs := terminal.Decolorize(string(output.Contents())) 1830 Expect(totalOutputs).To(ContainSubstring("Creating app app1 in org my-org / space my-space as my-user...\nOK")) 1831 Expect(totalOutputs).To(ContainSubstring("Binding service app1-service to app app1 in org my-org / space my-space as my-user...\nOK")) 1832 Expect(totalOutputs).To(ContainSubstring("Binding service global-service to app app1 in org my-org / space my-space as my-user...\nOK")) 1833 Expect(totalOutputs).To(ContainSubstring("Creating app app2 in org my-org / space my-space as my-user...\nOK")) 1834 Expect(totalOutputs).To(ContainSubstring("Binding service app2-service to app app2 in org my-org / space my-space as my-user...\nOK")) 1835 Expect(totalOutputs).To(ContainSubstring("Binding service global-service to app app2 in org my-org / space my-space as my-user...\nOK")) 1836 }) 1837 }) 1838 1839 Context("when the app is already bound to the service", func() { 1840 BeforeEach(func() { 1841 appRepo.ReadReturns(models.Application{ 1842 ApplicationFields: models.ApplicationFields{Name: "app-name"}, 1843 }, nil) 1844 serviceBinder.BindApplicationReturns.Error = errors.NewHTTPError(500, errors.ServiceBindingAppServiceTaken, "it don't work") 1845 }) 1846 1847 It("gracefully continues", func() { 1848 Expect(executeErr).NotTo(HaveOccurred()) 1849 Expect(len(serviceBinder.AppsToBind)).To(Equal(4)) 1850 }) 1851 }) 1852 1853 Context("when the service instance can't be found", func() { 1854 BeforeEach(func() { 1855 serviceRepo.FindInstanceByNameReturns(models.ServiceInstance{}, errors.New("Error finding instance")) 1856 }) 1857 1858 It("fails with an error", func() { 1859 Expect(executeErr).To(HaveOccurred()) 1860 Expect(executeErr.Error()).To(ContainSubstring("Could not find service app1-service to bind to existing-app")) 1861 }) 1862 }) 1863 }) 1864 1865 Context("checking for bad flags", func() { 1866 BeforeEach(func() { 1867 appRepo.ReadReturns(models.Application{}, errors.NewModelNotFoundError("App", "the-app")) 1868 args = []string{"-t", "FooeyTimeout", "app-name"} 1869 }) 1870 1871 It("fails when a non-numeric start timeout is given", func() { 1872 Expect(executeErr).To(HaveOccurred()) 1873 Expect(executeErr.Error()).To(ContainSubstring("Invalid timeout param: FooeyTimeout")) 1874 }) 1875 }) 1876 1877 Context("displaying information about files being uploaded", func() { 1878 BeforeEach(func() { 1879 appfiles.CountFilesReturns(11) 1880 zipper.ZipReturns(nil) 1881 zipper.GetZipSizeReturns(6100000, nil) 1882 actor.GatherFilesReturns([]resources.AppFileResource{{Path: "path/to/app"}, {Path: "bar"}}, true, nil) 1883 args = []string{"appName"} 1884 }) 1885 1886 It("displays the information", func() { 1887 Expect(executeErr).NotTo(HaveOccurred()) 1888 1889 curDir, err := os.Getwd() 1890 Expect(err).NotTo(HaveOccurred()) 1891 1892 totalOutputs := terminal.Decolorize(string(output.Contents())) 1893 Expect(totalOutputs).To(ContainSubstring("Uploading app files from: " + curDir)) 1894 Expect(totalOutputs).To(ContainSubstring("Uploading 5.8M, 11 files\nOK")) 1895 }) 1896 }) 1897 1898 Context("when the app can't be uploaded", func() { 1899 BeforeEach(func() { 1900 actor.UploadAppReturns(errors.New("Boom!")) 1901 args = []string{"app"} 1902 }) 1903 1904 It("fails when the app can't be uploaded", func() { 1905 Expect(executeErr).To(HaveOccurred()) 1906 Expect(executeErr.Error()).To(ContainSubstring("Error uploading application")) 1907 }) 1908 }) 1909 1910 Context("when no name and no manifest is given", func() { 1911 BeforeEach(func() { 1912 manifestRepo.ReadManifestReturns(manifest.NewEmptyManifest(), errors.New("No such manifest")) 1913 args = []string{} 1914 }) 1915 1916 It("fails", func() { 1917 Expect(executeErr).To(HaveOccurred()) 1918 Expect(executeErr.Error()).To(ContainSubstring("Incorrect Usage. The push command requires an app name. The app name can be supplied as an argument or with a manifest.yml file.")) 1919 }) 1920 }) 1921 }) 1922 1923 Context("when routes are specified in the manifest", func() { 1924 Context("and the manifest has more than one app", func() { 1925 BeforeEach(func() { 1926 m := &manifest.Manifest{ 1927 Path: "manifest.yml", 1928 Data: generic.NewMap(map[interface{}]interface{}{ 1929 "applications": []interface{}{ 1930 generic.NewMap(map[interface{}]interface{}{ 1931 "routes": []interface{}{ 1932 map[interface{}]interface{}{"route": "app1route1.example.com/path"}, 1933 map[interface{}]interface{}{"route": "app1route2.example.com:8008"}, 1934 }, 1935 "name": "manifest-app-name-1", 1936 }), 1937 generic.NewMap(map[interface{}]interface{}{ 1938 "name": "manifest-app-name-2", 1939 "routes": []interface{}{ 1940 map[interface{}]interface{}{"route": "app2route1.example.com"}, 1941 }, 1942 }), 1943 }, 1944 }), 1945 } 1946 manifestRepo.ReadManifestReturns(m, nil) 1947 1948 appRepo.ReadStub = func(appName string) (models.Application, error) { 1949 return models.Application{ 1950 ApplicationFields: models.ApplicationFields{ 1951 Name: appName, 1952 GUID: appName + "-guid", 1953 }, 1954 }, nil 1955 } 1956 1957 appRepo.UpdateStub = func(appGUID string, appParams models.AppParams) (models.Application, error) { 1958 return models.Application{ 1959 ApplicationFields: models.ApplicationFields{ 1960 GUID: appGUID, 1961 }, 1962 }, nil 1963 } 1964 }) 1965 1966 Context("and there are no flags", func() { 1967 BeforeEach(func() { 1968 args = []string{} 1969 }) 1970 1971 It("maps the routes to the specified apps", func() { 1972 noHostBool := false 1973 emptyAppParams := models.AppParams{ 1974 NoHostname: &noHostBool, 1975 } 1976 1977 Expect(executeErr).ToNot(HaveOccurred()) 1978 1979 Expect(actor.MapManifestRouteCallCount()).To(Equal(3)) 1980 1981 route, app, appParams := actor.MapManifestRouteArgsForCall(0) 1982 Expect(route).To(Equal("app1route1.example.com/path")) 1983 Expect(app.ApplicationFields.GUID).To(Equal("manifest-app-name-1-guid")) 1984 Expect(appParams).To(Equal(emptyAppParams)) 1985 1986 route, app, appParams = actor.MapManifestRouteArgsForCall(1) 1987 Expect(route).To(Equal("app1route2.example.com:8008")) 1988 Expect(app.ApplicationFields.GUID).To(Equal("manifest-app-name-1-guid")) 1989 Expect(appParams).To(Equal(emptyAppParams)) 1990 1991 route, app, appParams = actor.MapManifestRouteArgsForCall(2) 1992 Expect(route).To(Equal("app2route1.example.com")) 1993 Expect(app.ApplicationFields.GUID).To(Equal("manifest-app-name-2-guid")) 1994 Expect(appParams).To(Equal(emptyAppParams)) 1995 }) 1996 }) 1997 1998 Context("and flags other than -f are present", func() { 1999 BeforeEach(func() { 2000 args = []string{"-n", "hostname-flag"} 2001 }) 2002 2003 It("should return an error", func() { 2004 Expect(executeErr).To(HaveOccurred()) 2005 Expect(executeErr.Error()).To(Equal("Incorrect Usage. Command line flags (except -f) cannot be applied when pushing multiple apps from a manifest file.")) 2006 }) 2007 }) 2008 }) 2009 2010 Context("and the manifest has only one app", func() { 2011 BeforeEach(func() { 2012 m := &manifest.Manifest{ 2013 Path: "manifest.yml", 2014 Data: generic.NewMap(map[interface{}]interface{}{ 2015 "applications": []interface{}{ 2016 generic.NewMap(map[interface{}]interface{}{ 2017 "routes": []interface{}{ 2018 map[interface{}]interface{}{"route": "app1route1.example.com/path"}, 2019 }, 2020 "name": "manifest-app-name-1", 2021 }), 2022 }, 2023 }), 2024 } 2025 manifestRepo.ReadManifestReturns(m, nil) 2026 2027 appRepo.ReadStub = func(appName string) (models.Application, error) { 2028 return models.Application{ 2029 ApplicationFields: models.ApplicationFields{ 2030 Name: appName, 2031 GUID: appName + "-guid", 2032 }, 2033 }, nil 2034 } 2035 2036 appRepo.UpdateStub = func(appGUID string, appParams models.AppParams) (models.Application, error) { 2037 return models.Application{ 2038 ApplicationFields: models.ApplicationFields{ 2039 GUID: appGUID, 2040 }, 2041 }, nil 2042 } 2043 }) 2044 2045 Context("and flags are present", func() { 2046 BeforeEach(func() { 2047 args = []string{"-n", "flag-value"} 2048 }) 2049 2050 It("maps the routes to the apps", func() { 2051 noHostBool := false 2052 appParamsFromContext := models.AppParams{ 2053 Hosts: []string{"flag-value"}, 2054 NoHostname: &noHostBool, 2055 } 2056 2057 Expect(executeErr).ToNot(HaveOccurred()) 2058 2059 Expect(actor.MapManifestRouteCallCount()).To(Equal(1)) 2060 2061 route, app, appParams := actor.MapManifestRouteArgsForCall(0) 2062 Expect(route).To(Equal("app1route1.example.com/path")) 2063 Expect(app.ApplicationFields.GUID).To(Equal("manifest-app-name-1-guid")) 2064 Expect(appParams).To(Equal(appParamsFromContext)) 2065 }) 2066 }) 2067 }) 2068 }) 2069 }) 2070 })