github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+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/commandregistry/commandregistryfakes" 18 "code.cloudfoundry.org/cli/cf/commands/application" 19 "code.cloudfoundry.org/cli/cf/commands/application/applicationfakes" 20 "code.cloudfoundry.org/cli/cf/commands/service/servicefakes" 21 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 22 "code.cloudfoundry.org/cli/cf/errors" 23 "code.cloudfoundry.org/cli/cf/flags" 24 "code.cloudfoundry.org/cli/cf/manifest" 25 "code.cloudfoundry.org/cli/cf/manifest/manifestfakes" 26 "code.cloudfoundry.org/cli/cf/models" 27 "code.cloudfoundry.org/cli/cf/requirements" 28 "code.cloudfoundry.org/cli/cf/requirements/requirementsfakes" 29 "code.cloudfoundry.org/cli/cf/terminal" 30 "code.cloudfoundry.org/cli/cf/trace" 31 "code.cloudfoundry.org/cli/util/generic" 32 testconfig "code.cloudfoundry.org/cli/util/testhelpers/configuration" 33 testterm "code.cloudfoundry.org/cli/util/testhelpers/terminal" 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 *commandregistryfakes.FakeRandomWordGenerator 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(commandregistryfakes.FakeRandomWordGenerator) 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 var oldDockerPassword string 861 862 BeforeEach(func() { 863 oldDockerPassword = os.Getenv("CF_DOCKER_PASSWORD") 864 Expect(os.Setenv("CF_DOCKER_PASSWORD", "some-docker-pass")).ToNot(HaveOccurred()) 865 }) 866 867 AfterEach(func() { 868 Expect(os.Setenv("CF_DOCKER_PASSWORD", oldDockerPassword)).ToNot(HaveOccurred()) 869 }) 870 871 It("it passes the credentials to create call", func() { 872 Expect(executeErr).NotTo(HaveOccurred()) 873 874 Expect(output).To(gbytes.Say("Using docker repository password from environment variable CF_DOCKER_PASSWORD.")) 875 876 Expect(appRepo.CreateCallCount()).To(Equal(1)) 877 params := appRepo.CreateArgsForCall(0) 878 Expect(*params.DockerUsername).To(Equal("some-docker-username")) 879 Expect(*params.DockerPassword).To(Equal("some-docker-pass")) 880 }) 881 }) 882 883 Context("when CF_DOCKER_PASSWORD is not set", func() { 884 BeforeEach(func() { 885 Skip("these [mostly] worked prior to the revert in 1d94b2df98b") 886 }) 887 888 Context("when the user gets prompted for the docker password", func() { 889 Context("when the user inputs the password on the first attempt", func() { 890 BeforeEach(func() { 891 _, err := input.Write([]byte("some-docker-pass\n")) 892 Expect(err).NotTo(HaveOccurred()) 893 }) 894 895 It("it passes the credentials to create call", func() { 896 Expect(executeErr).NotTo(HaveOccurred()) 897 898 Expect(output).To(gbytes.Say("Environment variable CF_DOCKER_PASSWORD not set.")) 899 Expect(output).To(gbytes.Say("Docker password")) 900 Expect(output).ToNot(gbytes.Say("Docker password")) // Only prompt once 901 902 Expect(appRepo.CreateCallCount()).To(Equal(1)) 903 params := appRepo.CreateArgsForCall(0) 904 Expect(*params.DockerUsername).To(Equal("some-docker-username")) 905 Expect(*params.DockerPassword).To(Equal("some-docker-pass")) 906 }) 907 }) 908 909 Context("when the user fails to input the password 3 times", func() { 910 BeforeEach(func() { 911 _, err := input.Write([]byte("\n\n\n")) 912 Expect(err).NotTo(HaveOccurred()) 913 }) 914 915 It("returns an error", func() { 916 Expect(executeErr).To(MatchError("Please provide a password")) 917 918 Expect(output).To(gbytes.Say("Docker password")) 919 Expect(output).To(gbytes.Say("Docker password")) 920 Expect(output).To(gbytes.Say("Docker password")) 921 Expect(output).ToNot(gbytes.Say("Docker password")) 922 923 Expect(appRepo.CreateCallCount()).To(Equal(0)) 924 }) 925 }) 926 }) 927 }) 928 }) 929 }) 930 931 Context("when the docker password is not set in the env", func() { 932 var oldDockerPassword string 933 934 BeforeEach(func() { 935 m := &manifest.Manifest{ 936 Path: "manifest.yml", 937 Data: generic.NewMap(map[interface{}]interface{}{ 938 "applications": []interface{}{ 939 map[interface{}]interface{}{ 940 "docker": map[interface{}]interface{}{ 941 "username": "user", 942 }, 943 }, 944 }, 945 }), 946 } 947 manifestRepo.ReadManifestReturns(m, nil) 948 949 oldDockerPassword = os.Getenv("CF_DOCKER_PASSWORD") 950 Expect(os.Unsetenv("CF_DOCKER_PASSWORD")).To(Succeed()) 951 }) 952 953 AfterEach(func() { 954 Expect(os.Setenv("CF_DOCKER_PASSWORD", oldDockerPassword)).To(Succeed()) 955 }) 956 957 Context("when docker username is provided via the manifest only", func() { 958 BeforeEach(func() { 959 deps.UI = uiWithContents 960 args = []string{"testApp", "--docker-image", "some-docker-image"} 961 }) 962 963 It("returns an error", func() { 964 Expect(executeErr).To(MatchError("No Docker password was provided. Please provide the password by setting the CF_DOCKER_PASSWORD environment variable.")) 965 }) 966 }) 967 }) 968 969 Context("when the docker username is provided via the command line and the docker image is not provided", func() { 970 var oldDockerPassword string 971 972 BeforeEach(func() { 973 oldDockerPassword = os.Getenv("CF_DOCKER_PASSWORD") 974 Expect(os.Setenv("CF_DOCKER_PASSWORD", "docker-pass")).To(Succeed()) 975 976 deps.UI = uiWithContents 977 args = []string{"testApp", "--docker-username", "user"} 978 }) 979 980 AfterEach(func() { 981 Expect(os.Setenv("CF_DOCKER_PASSWORD", oldDockerPassword)).To(Succeed()) 982 }) 983 984 It("returns an error", func() { 985 Expect(executeErr).To(MatchError("'--docker-username' requires '--docker-image' to be specified")) 986 }) 987 }) 988 989 Context("when docker username is provided via the manifest but docker image is not provided", func() { 990 BeforeEach(func() { 991 m := &manifest.Manifest{ 992 Path: "manifest.yml", 993 Data: generic.NewMap(map[interface{}]interface{}{ 994 "applications": []interface{}{ 995 map[interface{}]interface{}{ 996 "docker": map[interface{}]interface{}{ 997 "username": "user", 998 }, 999 }, 1000 }, 1001 }), 1002 } 1003 manifestRepo.ReadManifestReturns(m, nil) 1004 1005 deps.UI = uiWithContents 1006 args = []string{"testApp"} 1007 }) 1008 1009 It("returns an error", func() { 1010 Expect(executeErr).To(MatchError("'--docker-username' requires '--docker-image' to be specified")) 1011 }) 1012 }) 1013 1014 Context("when health-check-type '-u' or '--health-check-type' is set", func() { 1015 Context("when the value is not 'http', 'none', 'port', or 'process'", func() { 1016 BeforeEach(func() { 1017 args = []string{"app-name", "-u", "bad-value"} 1018 }) 1019 1020 It("returns an error", func() { 1021 Expect(executeErr).To(HaveOccurred()) 1022 1023 Expect(executeErr.Error()).To(ContainSubstring("Error: Invalid health-check-type param: bad-value")) 1024 }) 1025 }) 1026 1027 Context("when the value is 'http'", func() { 1028 BeforeEach(func() { 1029 args = []string{"app-name", "--health-check-type", "http"} 1030 }) 1031 1032 It("does not show error", func() { 1033 Expect(executeErr).NotTo(HaveOccurred()) 1034 }) 1035 1036 It("sets the HTTP health check endpoint to /", func() { 1037 params := appRepo.CreateArgsForCall(0) 1038 Expect(*params.HealthCheckHTTPEndpoint).To(Equal("/")) 1039 }) 1040 }) 1041 1042 Context("when the value is 'none'", func() { 1043 BeforeEach(func() { 1044 args = []string{"app-name", "--health-check-type", "none"} 1045 }) 1046 1047 It("does not show error", func() { 1048 Expect(executeErr).NotTo(HaveOccurred()) 1049 }) 1050 }) 1051 1052 Context("when the value is 'port'", func() { 1053 BeforeEach(func() { 1054 args = []string{"app-name", "--health-check-type", "port"} 1055 }) 1056 1057 It("does not show error", func() { 1058 Expect(executeErr).NotTo(HaveOccurred()) 1059 }) 1060 }) 1061 1062 Context("when the value is 'process'", func() { 1063 BeforeEach(func() { 1064 args = []string{"app-name", "--health-check-type", "process"} 1065 }) 1066 1067 It("does not show error", func() { 1068 Expect(executeErr).NotTo(HaveOccurred()) 1069 }) 1070 }) 1071 }) 1072 1073 Context("with random-route option set", func() { 1074 var manifestApp generic.Map 1075 1076 BeforeEach(func() { 1077 manifestApp = generic.NewMap(map[interface{}]interface{}{ 1078 "name": "manifest-app-name", 1079 "memory": "128MB", 1080 "instances": 1, 1081 "domain": "manifest-example.com", 1082 "stack": "custom-stack", 1083 "timeout": 360, 1084 "buildpack": "some-buildpack", 1085 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1086 "path": filepath.Clean("some/path/from/manifest"), 1087 "env": generic.NewMap(map[interface{}]interface{}{ 1088 "FOO": "baz", 1089 "PATH": "/u/apps/my-app/bin", 1090 }), 1091 }) 1092 m := &manifest.Manifest{ 1093 Path: "manifest.yml", 1094 Data: generic.NewMap(map[interface{}]interface{}{ 1095 "applications": []interface{}{manifestApp}, 1096 }), 1097 } 1098 manifestRepo.ReadManifestReturns(m, nil) 1099 }) 1100 1101 Context("for http routes", func() { 1102 Context("when random-route is set as a flag", func() { 1103 BeforeEach(func() { 1104 args = []string{"--random-route", "app-name"} 1105 }) 1106 1107 It("provides a random hostname", func() { 1108 Expect(executeErr).NotTo(HaveOccurred()) 1109 1110 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1111 host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 1112 Expect(host).To(Equal("app-name-random-host")) 1113 }) 1114 }) 1115 1116 Context("when random-route is set in the manifest", func() { 1117 BeforeEach(func() { 1118 manifestApp.Set("random-route", true) 1119 args = []string{"app-name"} 1120 }) 1121 1122 It("provides a random hostname", func() { 1123 Expect(executeErr).NotTo(HaveOccurred()) 1124 1125 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1126 host, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 1127 Expect(host).To(Equal("app-name-random-host")) 1128 }) 1129 }) 1130 }) 1131 1132 Context("for tcp routes", func() { 1133 var expectedDomain models.DomainFields 1134 1135 BeforeEach(func() { 1136 deps.UI = uiWithContents 1137 1138 expectedDomain = models.DomainFields{ 1139 GUID: "some-guid", 1140 Name: "some-name", 1141 OwningOrganizationGUID: "some-organization-guid", 1142 RouterGroupGUID: "some-router-group-guid", 1143 RouterGroupType: "tcp", 1144 Shared: true, 1145 } 1146 1147 domainRepo.FindByNameInOrgReturns( 1148 expectedDomain, 1149 nil, 1150 ) 1151 1152 route := models.Route{ 1153 Domain: expectedDomain, 1154 Port: 7777, 1155 } 1156 routeRepo.CreateReturns(route, nil) 1157 }) 1158 1159 Context("when random-route passed as a flag", func() { 1160 BeforeEach(func() { 1161 args = []string{"--random-route", "app-name"} 1162 }) 1163 1164 It("provides a random port and hostname", func() { 1165 Expect(executeErr).NotTo(HaveOccurred()) 1166 1167 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1168 _, _, _, _, randomPort := routeActor.FindOrCreateRouteArgsForCall(0) 1169 Expect(randomPort).To(BeTrue()) 1170 }) 1171 }) 1172 1173 Context("when random-route set in the manifest", func() { 1174 BeforeEach(func() { 1175 manifestApp.Set("random-route", true) 1176 args = []string{"app-name"} 1177 }) 1178 1179 It("provides a random port and hostname when set in the manifest", func() { 1180 Expect(executeErr).NotTo(HaveOccurred()) 1181 1182 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1183 _, _, _, _, randomPort := routeActor.FindOrCreateRouteArgsForCall(0) 1184 Expect(randomPort).To(BeTrue()) 1185 }) 1186 }) 1187 }) 1188 }) 1189 1190 Context("when path to an app is set", func() { 1191 var expectedLocalFiles []models.AppFileFields 1192 1193 BeforeEach(func() { 1194 expectedLocalFiles = []models.AppFileFields{ 1195 { 1196 Path: "the-path", 1197 }, 1198 { 1199 Path: "the-other-path", 1200 }, 1201 } 1202 appfiles.AppFilesInDirReturns(expectedLocalFiles, nil) 1203 args = []string{"-p", "../some/path-to/an-app/file.zip", "app-with-path"} 1204 }) 1205 1206 It("includes the app files in dir", func() { 1207 Expect(executeErr).NotTo(HaveOccurred()) 1208 1209 actualLocalFiles, _, _, _ := actor.GatherFilesArgsForCall(0) 1210 Expect(actualLocalFiles).To(Equal(expectedLocalFiles)) 1211 }) 1212 }) 1213 1214 Context("when there are no app files to process", func() { 1215 BeforeEach(func() { 1216 deps.UI = uiWithContents 1217 appfiles.AppFilesInDirReturns([]models.AppFileFields{}, nil) 1218 args = []string{"-p", "../some/path-to/an-app/file.zip", "app-with-path"} 1219 }) 1220 1221 It("errors", func() { 1222 Expect(executeErr).To(HaveOccurred()) 1223 Expect(executeErr.Error()).To(ContainSubstring("No app files found in '../some/path-to/an-app/file.zip'")) 1224 }) 1225 }) 1226 1227 Context("when there is an error getting app files", func() { 1228 BeforeEach(func() { 1229 deps.UI = uiWithContents 1230 appfiles.AppFilesInDirReturns([]models.AppFileFields{}, errors.New("some error")) 1231 args = []string{"-p", "../some/path-to/an-app/file.zip", "app-with-path"} 1232 }) 1233 1234 It("prints a message", func() { 1235 Expect(executeErr).To(HaveOccurred()) 1236 Expect(executeErr.Error()).To(ContainSubstring("Error processing app files in '../some/path-to/an-app/file.zip': some error")) 1237 }) 1238 }) 1239 1240 Context("when an app path is specified with the -p flag", func() { 1241 BeforeEach(func() { 1242 args = []string{"-p", "../some/path-to/an-app/file.zip", "app-with-path"} 1243 }) 1244 1245 It("pushes the contents of the app directory or zip file specified", func() { 1246 Expect(executeErr).NotTo(HaveOccurred()) 1247 1248 _, appDir, _, _ := actor.GatherFilesArgsForCall(0) 1249 Expect(appDir).To(Equal("../some/path-to/an-app/file.zip")) 1250 }) 1251 }) 1252 1253 Context("when no flags are specified", func() { 1254 BeforeEach(func() { 1255 m := &manifest.Manifest{ 1256 Path: "manifest.yml", 1257 Data: generic.NewMap(map[interface{}]interface{}{ 1258 "applications": []interface{}{ 1259 generic.NewMap(map[interface{}]interface{}{ 1260 "name": "manifest-app-name", 1261 "memory": "128MB", 1262 "instances": 1, 1263 "host": "manifest-host", 1264 "stack": "custom-stack", 1265 "timeout": 360, 1266 "buildpack": "some-buildpack", 1267 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1268 "env": generic.NewMap(map[interface{}]interface{}{ 1269 "FOO": "baz", 1270 "PATH": "/u/apps/my-app/bin", 1271 }), 1272 }), 1273 }, 1274 }), 1275 } 1276 manifestRepo.ReadManifestReturns(m, nil) 1277 args = []string{"app-with-default-path"} 1278 }) 1279 1280 It("pushes the contents of the current working directory by default", func() { 1281 Expect(executeErr).NotTo(HaveOccurred()) 1282 1283 dir, _ := os.Getwd() 1284 _, appDir, _, _ := actor.GatherFilesArgsForCall(0) 1285 Expect(appDir).To(Equal(dir)) 1286 }) 1287 }) 1288 1289 Context("when given a bad manifest", func() { 1290 BeforeEach(func() { 1291 manifestRepo.ReadManifestReturns(manifest.NewEmptyManifest(), errors.New("read manifest error")) 1292 args = []string{"-f", "bad/manifest/path"} 1293 }) 1294 1295 It("errors", func() { 1296 Expect(executeErr).To(HaveOccurred()) 1297 Expect(executeErr.Error()).To(ContainSubstring("read manifest error")) 1298 }) 1299 }) 1300 1301 Context("when the current directory does not contain a manifest", func() { 1302 BeforeEach(func() { 1303 deps.UI = uiWithContents 1304 manifestRepo.ReadManifestReturns(manifest.NewEmptyManifest(), syscall.ENOENT) 1305 args = []string{"--no-route", "app-name"} 1306 }) 1307 1308 It("does not fail", func() { 1309 Expect(executeErr).NotTo(HaveOccurred()) 1310 fullOutput := terminal.Decolorize(string(output.Contents())) 1311 Expect(fullOutput).To(ContainSubstring("Creating app app-name in org my-org / space my-space as my-user...\nOK")) 1312 Expect(fullOutput).To(ContainSubstring("Uploading app-name...\nOK")) 1313 }) 1314 }) 1315 1316 Context("when the current directory does contain a manifest", func() { 1317 BeforeEach(func() { 1318 deps.UI = uiWithContents 1319 m := &manifest.Manifest{ 1320 Path: "manifest.yml", 1321 Data: generic.NewMap(map[interface{}]interface{}{ 1322 "applications": []interface{}{ 1323 generic.NewMap(map[interface{}]interface{}{ 1324 "name": "manifest-app-name", 1325 "memory": "128MB", 1326 "instances": 1, 1327 "host": "manifest-host", 1328 "domain": "manifest-example.com", 1329 "stack": "custom-stack", 1330 "timeout": 360, 1331 "buildpack": "some-buildpack", 1332 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1333 "path": filepath.Clean("some/path/from/manifest"), 1334 "env": generic.NewMap(map[interface{}]interface{}{ 1335 "FOO": "baz", 1336 "PATH": "/u/apps/my-app/bin", 1337 }), 1338 }), 1339 }, 1340 }), 1341 } 1342 manifestRepo.ReadManifestReturns(m, nil) 1343 args = []string{"-p", "some/relative/path"} 1344 }) 1345 1346 It("uses the manifest in the current directory by default", func() { 1347 Expect(executeErr).NotTo(HaveOccurred()) 1348 1349 Expect(terminal.Decolorize(string(output.Contents()))).To(ContainSubstring("Using manifest file manifest.yml")) 1350 1351 cwd, _ := os.Getwd() 1352 Expect(manifestRepo.ReadManifestArgsForCall(0)).To(Equal(cwd)) 1353 }) 1354 }) 1355 1356 Context("when the 'no-manifest'flag is passed", func() { 1357 BeforeEach(func() { 1358 args = []string{"--no-route", "--no-manifest", "app-name"} 1359 }) 1360 1361 It("does not use a manifest", func() { 1362 Expect(executeErr).NotTo(HaveOccurred()) 1363 1364 fullOutput := terminal.Decolorize(string(output.Contents())) 1365 Expect(fullOutput).NotTo(ContainSubstring("FAILED")) 1366 Expect(fullOutput).NotTo(ContainSubstring("hacker-manifesto")) 1367 1368 Expect(manifestRepo.ReadManifestCallCount()).To(BeZero()) 1369 params := appRepo.CreateArgsForCall(0) 1370 Expect(*params.Name).To(Equal("app-name")) 1371 }) 1372 }) 1373 1374 Context("when the manifest has errors", func() { 1375 BeforeEach(func() { 1376 manifestRepo.ReadManifestReturns( 1377 &manifest.Manifest{ 1378 Path: "/some-path/", 1379 }, 1380 errors.New("buildpack should not be null"), 1381 ) 1382 1383 args = []string{} 1384 }) 1385 1386 It("fails when parsing the manifest has errors", func() { 1387 Expect(executeErr).To(HaveOccurred()) 1388 Expect(executeErr.Error()).To(ContainSubstring("Error reading manifest file:\nbuildpack should not be null")) 1389 }) 1390 }) 1391 1392 Context("when the no-route option is set", func() { 1393 Context("when provided the --no-route-flag", func() { 1394 BeforeEach(func() { 1395 domainRepo.FindByNameInOrgReturns(models.DomainFields{ 1396 Name: "bar.cf-app.com", 1397 GUID: "bar-domain-guid", 1398 }, nil) 1399 1400 args = []string{"--no-route", "app-name"} 1401 }) 1402 1403 It("does not create a route", func() { 1404 Expect(executeErr).NotTo(HaveOccurred()) 1405 params := appRepo.CreateArgsForCall(0) 1406 Expect(*params.Name).To(Equal("app-name")) 1407 Expect(routeRepo.CreateCallCount()).To(BeZero()) 1408 }) 1409 }) 1410 1411 Context("when no-route is set in the manifest", func() { 1412 BeforeEach(func() { 1413 deps.UI = uiWithContents 1414 workerManifest := &manifest.Manifest{ 1415 Path: "manifest.yml", 1416 Data: generic.NewMap(map[interface{}]interface{}{ 1417 "applications": []interface{}{ 1418 generic.NewMap(map[interface{}]interface{}{ 1419 "name": "manifest-app-name", 1420 "memory": "128MB", 1421 "instances": 1, 1422 "host": "manifest-host", 1423 "domain": "manifest-example.com", 1424 "stack": "custom-stack", 1425 "timeout": 360, 1426 "buildpack": "some-buildpack", 1427 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1428 "path": filepath.Clean("some/path/from/manifest"), 1429 "env": generic.NewMap(map[interface{}]interface{}{ 1430 "FOO": "baz", 1431 "PATH": "/u/apps/my-app/bin", 1432 }), 1433 }), 1434 }, 1435 }), 1436 } 1437 workerManifest.Data.Get("applications").([]interface{})[0].(generic.Map).Set("no-route", true) 1438 manifestRepo.ReadManifestReturns(workerManifest, nil) 1439 1440 args = []string{"app-name"} 1441 }) 1442 1443 It("Does not create a route", func() { 1444 Expect(executeErr).NotTo(HaveOccurred()) 1445 Expect(terminal.Decolorize(string(output.Contents()))).To(ContainSubstring("App app-name is a worker, skipping route creation")) 1446 Expect(routeRepo.BindCallCount()).To(BeZero()) 1447 }) 1448 }) 1449 }) 1450 1451 Context("when provided the --no-hostname flag", func() { 1452 BeforeEach(func() { 1453 domainRepo.ListDomainsForOrgStub = func(orgGUID string, cb func(models.DomainFields) bool) error { 1454 cb(models.DomainFields{ 1455 Name: "bar.cf-app.com", 1456 GUID: "bar-domain-guid", 1457 Shared: true, 1458 }) 1459 1460 return nil 1461 } 1462 routeRepo.FindReturns(models.Route{}, errors.NewModelNotFoundError("Org", "uh oh")) 1463 1464 args = []string{"--no-hostname", "app-name"} 1465 }) 1466 1467 It("maps the root domain route to the app", func() { 1468 Expect(executeErr).NotTo(HaveOccurred()) 1469 1470 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1471 _, domain, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 1472 Expect(domain.GUID).To(Equal("bar-domain-guid")) 1473 }) 1474 1475 Context("when using 'routes' in the manifest", func() { 1476 BeforeEach(func() { 1477 m := &manifest.Manifest{ 1478 Data: generic.NewMap(map[interface{}]interface{}{ 1479 "applications": []interface{}{ 1480 generic.NewMap(map[interface{}]interface{}{ 1481 "name": "app1", 1482 "routes": []interface{}{ 1483 map[interface{}]interface{}{"route": "app2route1.example.com"}, 1484 }, 1485 }), 1486 }, 1487 }), 1488 } 1489 manifestRepo.ReadManifestReturns(m, nil) 1490 }) 1491 1492 It("returns an error", func() { 1493 Expect(executeErr).To(HaveOccurred()) 1494 Expect(executeErr).To(MatchError("Option '--no-hostname' cannot be used with an app manifest containing the 'routes' attribute")) 1495 }) 1496 }) 1497 }) 1498 1499 Context("with an invalid memory limit", func() { 1500 BeforeEach(func() { 1501 args = []string{"-m", "abcM", "app-name"} 1502 }) 1503 1504 It("fails", func() { 1505 Expect(executeErr).To(HaveOccurred()) 1506 Expect(executeErr.Error()).To(ContainSubstring("Invalid memory limit: abcM")) 1507 }) 1508 }) 1509 1510 Context("when a manifest has many apps", func() { 1511 BeforeEach(func() { 1512 deps.UI = uiWithContents 1513 m := &manifest.Manifest{ 1514 Data: generic.NewMap(map[interface{}]interface{}{ 1515 "applications": []interface{}{ 1516 generic.NewMap(map[interface{}]interface{}{ 1517 "name": "app1", 1518 "services": []interface{}{"app1-service", "global-service"}, 1519 "env": generic.NewMap(map[interface{}]interface{}{ 1520 "SOMETHING": "definitely-something", 1521 }), 1522 }), 1523 generic.NewMap(map[interface{}]interface{}{ 1524 "name": "app2", 1525 "services": []interface{}{"app2-service", "global-service"}, 1526 "env": generic.NewMap(map[interface{}]interface{}{ 1527 "SOMETHING": "nothing", 1528 }), 1529 }), 1530 }, 1531 }), 1532 } 1533 manifestRepo.ReadManifestReturns(m, nil) 1534 args = []string{} 1535 }) 1536 1537 It("pushes each app", func() { 1538 Expect(executeErr).NotTo(HaveOccurred()) 1539 1540 totalOutput := terminal.Decolorize(string(output.Contents())) 1541 Expect(totalOutput).To(ContainSubstring("Creating app app1")) 1542 Expect(totalOutput).To(ContainSubstring("Creating app app2")) 1543 Expect(appRepo.CreateCallCount()).To(Equal(2)) 1544 1545 firstApp := appRepo.CreateArgsForCall(0) 1546 secondApp := appRepo.CreateArgsForCall(1) 1547 Expect(*firstApp.Name).To(Equal("app1")) 1548 Expect(*secondApp.Name).To(Equal("app2")) 1549 1550 envVars := *firstApp.EnvironmentVars 1551 Expect(envVars["SOMETHING"]).To(Equal("definitely-something")) 1552 1553 envVars = *secondApp.EnvironmentVars 1554 Expect(envVars["SOMETHING"]).To(Equal("nothing")) 1555 }) 1556 1557 Context("when a single app is given as an arg", func() { 1558 BeforeEach(func() { 1559 args = []string{"app2"} 1560 }) 1561 1562 It("pushes that single app", func() { 1563 Expect(executeErr).NotTo(HaveOccurred()) 1564 1565 totalOutput := terminal.Decolorize(string(output.Contents())) 1566 Expect(totalOutput).To(ContainSubstring("Creating app app2")) 1567 Expect(totalOutput).ToNot(ContainSubstring("Creating app app1")) 1568 Expect(appRepo.CreateCallCount()).To(Equal(1)) 1569 params := appRepo.CreateArgsForCall(0) 1570 Expect(*params.Name).To(Equal("app2")) 1571 }) 1572 }) 1573 1574 Context("when the given app is not in the manifest", func() { 1575 BeforeEach(func() { 1576 args = []string{"non-existant-app"} 1577 }) 1578 1579 It("fails", func() { 1580 Expect(executeErr).To(HaveOccurred()) 1581 Expect(appRepo.CreateCallCount()).To(BeZero()) 1582 }) 1583 }) 1584 }) 1585 }) 1586 }) 1587 1588 Context("re-pushing an existing app", func() { 1589 var existingApp models.Application 1590 1591 BeforeEach(func() { 1592 deps.UI = uiWithContents 1593 existingApp = models.Application{ 1594 ApplicationFields: models.ApplicationFields{ 1595 Name: "existing-app", 1596 GUID: "existing-app-guid", 1597 Command: "unicorn -c config/unicorn.rb -D", 1598 EnvironmentVars: map[string]interface{}{ 1599 "crazy": "pants", 1600 "FOO": "NotYoBaz", 1601 "foo": "manchu", 1602 }, 1603 }, 1604 } 1605 manifestRepo.ReadManifestReturns(manifest.NewEmptyManifest(), nil) 1606 appRepo.ReadReturns(existingApp, nil) 1607 appRepo.UpdateReturns(existingApp, nil) 1608 args = []string{"existing-app"} 1609 }) 1610 1611 It("stops the app, achieving a full-downtime deploy!", func() { 1612 Expect(executeErr).NotTo(HaveOccurred()) 1613 1614 app, orgName, spaceName := stopper.ApplicationStopArgsForCall(0) 1615 Expect(app.GUID).To(Equal(existingApp.GUID)) 1616 Expect(app.Name).To(Equal("existing-app")) 1617 Expect(orgName).To(Equal(configRepo.OrganizationFields().Name)) 1618 Expect(spaceName).To(Equal(configRepo.SpaceFields().Name)) 1619 1620 Expect(actor.UploadAppCallCount()).To(Equal(1)) 1621 appGUID, _, _ := actor.UploadAppArgsForCall(0) 1622 Expect(appGUID).To(Equal(existingApp.GUID)) 1623 }) 1624 1625 It("re-uploads the app", func() { 1626 Expect(executeErr).NotTo(HaveOccurred()) 1627 1628 totalOutputs := terminal.Decolorize(string(output.Contents())) 1629 Expect(totalOutputs).To(ContainSubstring("Uploading existing-app...\nOK")) 1630 }) 1631 1632 Context("when the -b flag is provided as 'default'", func() { 1633 BeforeEach(func() { 1634 args = []string{"-b", "default", "existing-app"} 1635 }) 1636 1637 It("resets the app's buildpack", func() { 1638 Expect(executeErr).NotTo(HaveOccurred()) 1639 1640 Expect(appRepo.UpdateCallCount()).To(Equal(1)) 1641 _, params := appRepo.UpdateArgsForCall(0) 1642 Expect(*params.BuildpackURL).To(Equal("")) 1643 }) 1644 }) 1645 1646 Context("when the -c flag is provided as 'default'", func() { 1647 BeforeEach(func() { 1648 args = []string{"-c", "default", "existing-app"} 1649 }) 1650 1651 It("resets the app's command", func() { 1652 Expect(executeErr).NotTo(HaveOccurred()) 1653 _, params := appRepo.UpdateArgsForCall(0) 1654 Expect(*params.Command).To(Equal("")) 1655 }) 1656 }) 1657 1658 Context("when the -b flag is provided as 'null'", func() { 1659 BeforeEach(func() { 1660 args = []string{"-b", "null", "existing-app"} 1661 }) 1662 1663 It("resets the app's buildpack", func() { 1664 Expect(executeErr).NotTo(HaveOccurred()) 1665 _, params := appRepo.UpdateArgsForCall(0) 1666 Expect(*params.BuildpackURL).To(Equal("")) 1667 }) 1668 }) 1669 1670 Context("when the -c flag is provided as 'null'", func() { 1671 BeforeEach(func() { 1672 args = []string{"-c", "null", "existing-app"} 1673 }) 1674 1675 It("resets the app's command", func() { 1676 Expect(executeErr).NotTo(HaveOccurred()) 1677 _, params := appRepo.UpdateArgsForCall(0) 1678 Expect(*params.Command).To(Equal("")) 1679 }) 1680 }) 1681 1682 Context("when the manifest provided env variables", func() { 1683 BeforeEach(func() { 1684 m := &manifest.Manifest{ 1685 Path: "manifest.yml", 1686 Data: generic.NewMap(map[interface{}]interface{}{ 1687 "applications": []interface{}{ 1688 generic.NewMap(map[interface{}]interface{}{ 1689 "name": "manifest-app-name", 1690 "memory": "128MB", 1691 "instances": 1, 1692 "host": "manifest-host", 1693 "domain": "manifest-example.com", 1694 "stack": "custom-stack", 1695 "timeout": 360, 1696 "buildpack": "some-buildpack", 1697 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1698 "path": filepath.Clean("some/path/from/manifest"), 1699 "env": generic.NewMap(map[interface{}]interface{}{ 1700 "FOO": "baz", 1701 "PATH": "/u/apps/my-app/bin", 1702 }), 1703 }), 1704 }, 1705 }), 1706 } 1707 manifestRepo.ReadManifestReturns(m, nil) 1708 1709 args = []string{"existing-app"} 1710 }) 1711 1712 It("merges env vars from the manifest with those from the server", func() { 1713 Expect(executeErr).NotTo(HaveOccurred()) 1714 1715 _, params := appRepo.UpdateArgsForCall(0) 1716 updatedAppEnvVars := *params.EnvironmentVars 1717 Expect(updatedAppEnvVars["crazy"]).To(Equal("pants")) 1718 Expect(updatedAppEnvVars["FOO"]).To(Equal("baz")) 1719 Expect(updatedAppEnvVars["foo"]).To(Equal("manchu")) 1720 Expect(updatedAppEnvVars["PATH"]).To(Equal("/u/apps/my-app/bin")) 1721 }) 1722 }) 1723 1724 Context("when the app is already stopped", func() { 1725 BeforeEach(func() { 1726 existingApp.State = "stopped" 1727 appRepo.ReadReturns(existingApp, nil) 1728 appRepo.UpdateReturns(existingApp, nil) 1729 args = []string{"existing-app"} 1730 }) 1731 1732 It("does not stop the app", func() { 1733 Expect(executeErr).NotTo(HaveOccurred()) 1734 Expect(stopper.ApplicationStopCallCount()).To(Equal(0)) 1735 }) 1736 }) 1737 1738 Context("when the application is pushed with updated parameters", func() { 1739 BeforeEach(func() { 1740 existingRoute := models.RouteSummary{ 1741 Host: "existing-app", 1742 } 1743 existingApp.Routes = []models.RouteSummary{existingRoute} 1744 appRepo.ReadReturns(existingApp, nil) 1745 appRepo.UpdateReturns(existingApp, nil) 1746 1747 stackRepo.FindByNameReturns(models.Stack{ 1748 Name: "differentStack", 1749 GUID: "differentStack-guid", 1750 }, nil) 1751 1752 args = []string{ 1753 "-c", "different start command", 1754 "-i", "10", 1755 "-m", "1G", 1756 "-b", "https://github.com/heroku/heroku-buildpack-different.git", 1757 "-s", "differentStack", 1758 "existing-app", 1759 } 1760 }) 1761 1762 It("updates the app", func() { 1763 Expect(executeErr).NotTo(HaveOccurred()) 1764 1765 appGUID, params := appRepo.UpdateArgsForCall(0) 1766 Expect(appGUID).To(Equal(existingApp.GUID)) 1767 Expect(*params.Command).To(Equal("different start command")) 1768 Expect(*params.InstanceCount).To(Equal(10)) 1769 Expect(*params.Memory).To(Equal(int64(1024))) 1770 Expect(*params.BuildpackURL).To(Equal("https://github.com/heroku/heroku-buildpack-different.git")) 1771 Expect(*params.StackGUID).To(Equal("differentStack-guid")) 1772 }) 1773 }) 1774 1775 Context("when the app has a route bound", func() { 1776 BeforeEach(func() { 1777 domain := models.DomainFields{ 1778 Name: "example.com", 1779 GUID: "domain-guid", 1780 Shared: true, 1781 } 1782 1783 existingApp.Routes = []models.RouteSummary{{ 1784 GUID: "existing-route-guid", 1785 Host: "existing-app", 1786 Domain: domain, 1787 }} 1788 1789 appRepo.ReadReturns(existingApp, nil) 1790 appRepo.UpdateReturns(existingApp, nil) 1791 }) 1792 1793 Context("and no route-related flags are given", func() { 1794 Context("and there is no route in the manifest", func() { 1795 It("does not add a route to the app", func() { 1796 Expect(executeErr).NotTo(HaveOccurred()) 1797 1798 appGUID, _, _ := actor.UploadAppArgsForCall(0) 1799 Expect(appGUID).To(Equal("existing-app-guid")) 1800 Expect(domainRepo.FindByNameInOrgCallCount()).To(BeZero()) 1801 Expect(routeRepo.FindCallCount()).To(BeZero()) 1802 Expect(routeRepo.CreateCallCount()).To(BeZero()) 1803 }) 1804 }) 1805 }) 1806 1807 Context("when --no-route flag is given", func() { 1808 BeforeEach(func() { 1809 args = []string{"--no-route", "existing-app"} 1810 }) 1811 1812 It("removes existing routes that the app is bound to", func() { 1813 Expect(executeErr).NotTo(HaveOccurred()) 1814 1815 appGUID, _, _ := actor.UploadAppArgsForCall(0) 1816 Expect(appGUID).To(Equal("existing-app-guid")) 1817 1818 Expect(routeActor.UnbindAllCallCount()).To(Equal(1)) 1819 app := routeActor.UnbindAllArgsForCall(0) 1820 Expect(app.GUID).To(Equal(appGUID)) 1821 1822 Expect(routeActor.FindOrCreateRouteCallCount()).To(BeZero()) 1823 }) 1824 }) 1825 1826 Context("when the --no-hostname flag is given", func() { 1827 BeforeEach(func() { 1828 routeRepo.FindReturns(models.Route{}, errors.NewModelNotFoundError("Org", "existing-app.example.com")) 1829 args = []string{"--no-hostname", "existing-app"} 1830 }) 1831 1832 It("binds the root domain route to an app with a pre-existing route", func() { 1833 Expect(executeErr).NotTo(HaveOccurred()) 1834 1835 Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) 1836 hostname, _, _, _, _ := routeActor.FindOrCreateRouteArgsForCall(0) 1837 Expect(hostname).To(BeEmpty()) 1838 }) 1839 }) 1840 }) 1841 1842 Context("service instances", func() { 1843 BeforeEach(func() { 1844 appRepo.CreateStub = func(params models.AppParams) (models.Application, error) { 1845 a := models.Application{} 1846 a.Name = *params.Name 1847 a.GUID = *params.Name + "-guid" 1848 1849 return a, nil 1850 } 1851 1852 serviceRepo.FindInstanceByNameStub = func(name string) (models.ServiceInstance, error) { 1853 return models.ServiceInstance{ 1854 ServiceInstanceFields: models.ServiceInstanceFields{Name: name}, 1855 }, nil 1856 } 1857 1858 m := &manifest.Manifest{ 1859 Data: generic.NewMap(map[interface{}]interface{}{ 1860 "applications": []interface{}{ 1861 generic.NewMap(map[interface{}]interface{}{ 1862 "name": "app1", 1863 "services": []interface{}{"app1-service", "global-service"}, 1864 "env": generic.NewMap(map[interface{}]interface{}{ 1865 "SOMETHING": "definitely-something", 1866 }), 1867 }), 1868 generic.NewMap(map[interface{}]interface{}{ 1869 "name": "app2", 1870 "services": []interface{}{"app2-service", "global-service"}, 1871 "env": generic.NewMap(map[interface{}]interface{}{ 1872 "SOMETHING": "nothing", 1873 }), 1874 }), 1875 }, 1876 }), 1877 } 1878 manifestRepo.ReadManifestReturns(m, nil) 1879 1880 args = []string{} 1881 }) 1882 1883 Context("when the service is not bound", func() { 1884 BeforeEach(func() { 1885 appRepo.ReadReturns(models.Application{}, errors.NewModelNotFoundError("App", "the-app")) 1886 }) 1887 1888 It("binds service instances to the app", func() { 1889 Expect(executeErr).NotTo(HaveOccurred()) 1890 1891 Expect(len(serviceBinder.AppsToBind)).To(Equal(4)) 1892 Expect(serviceBinder.AppsToBind[0].Name).To(Equal("app1")) 1893 Expect(serviceBinder.AppsToBind[1].Name).To(Equal("app1")) 1894 Expect(serviceBinder.InstancesToBindTo[0].Name).To(Equal("app1-service")) 1895 Expect(serviceBinder.InstancesToBindTo[1].Name).To(Equal("global-service")) 1896 1897 Expect(serviceBinder.AppsToBind[2].Name).To(Equal("app2")) 1898 Expect(serviceBinder.AppsToBind[3].Name).To(Equal("app2")) 1899 Expect(serviceBinder.InstancesToBindTo[2].Name).To(Equal("app2-service")) 1900 Expect(serviceBinder.InstancesToBindTo[3].Name).To(Equal("global-service")) 1901 1902 totalOutputs := terminal.Decolorize(string(output.Contents())) 1903 Expect(totalOutputs).To(ContainSubstring("Creating app app1 in org my-org / space my-space as my-user...\nOK")) 1904 Expect(totalOutputs).To(ContainSubstring("Binding service app1-service to app app1 in org my-org / space my-space as my-user...\nOK")) 1905 Expect(totalOutputs).To(ContainSubstring("Binding service global-service to app app1 in org my-org / space my-space as my-user...\nOK")) 1906 Expect(totalOutputs).To(ContainSubstring("Creating app app2 in org my-org / space my-space as my-user...\nOK")) 1907 Expect(totalOutputs).To(ContainSubstring("Binding service app2-service to app app2 in org my-org / space my-space as my-user...\nOK")) 1908 Expect(totalOutputs).To(ContainSubstring("Binding service global-service to app app2 in org my-org / space my-space as my-user...\nOK")) 1909 }) 1910 }) 1911 1912 Context("when the app is already bound to the service", func() { 1913 BeforeEach(func() { 1914 appRepo.ReadReturns(models.Application{ 1915 ApplicationFields: models.ApplicationFields{Name: "app-name"}, 1916 }, nil) 1917 serviceBinder.BindApplicationReturns.Error = errors.NewHTTPError(500, errors.ServiceBindingAppServiceTaken, "it don't work") 1918 }) 1919 1920 It("gracefully continues", func() { 1921 Expect(executeErr).NotTo(HaveOccurred()) 1922 Expect(len(serviceBinder.AppsToBind)).To(Equal(4)) 1923 }) 1924 }) 1925 1926 Context("when the service instance can't be found", func() { 1927 BeforeEach(func() { 1928 serviceRepo.FindInstanceByNameReturns(models.ServiceInstance{}, errors.New("Error finding instance")) 1929 }) 1930 1931 It("fails with an error", func() { 1932 Expect(executeErr).To(HaveOccurred()) 1933 Expect(executeErr.Error()).To(ContainSubstring("Could not find service app1-service to bind to existing-app")) 1934 }) 1935 }) 1936 }) 1937 1938 Context("checking for bad flags", func() { 1939 BeforeEach(func() { 1940 appRepo.ReadReturns(models.Application{}, errors.NewModelNotFoundError("App", "the-app")) 1941 args = []string{"-t", "FooeyTimeout", "app-name"} 1942 }) 1943 1944 It("fails when a non-numeric start timeout is given", func() { 1945 Expect(executeErr).To(HaveOccurred()) 1946 Expect(executeErr.Error()).To(ContainSubstring("Invalid timeout param: FooeyTimeout")) 1947 }) 1948 }) 1949 1950 Context("displaying information about files being uploaded", func() { 1951 BeforeEach(func() { 1952 appfiles.CountFilesReturns(11) 1953 zipper.ZipReturns(nil) 1954 zipper.GetZipSizeReturns(6100000, nil) 1955 actor.GatherFilesReturns([]resources.AppFileResource{{Path: "path/to/app"}, {Path: "bar"}}, true, nil) 1956 args = []string{"appName"} 1957 }) 1958 1959 It("displays the information", func() { 1960 Expect(executeErr).NotTo(HaveOccurred()) 1961 1962 curDir, err := os.Getwd() 1963 Expect(err).NotTo(HaveOccurred()) 1964 1965 totalOutputs := terminal.Decolorize(string(output.Contents())) 1966 Expect(totalOutputs).To(ContainSubstring("Uploading app files from: " + curDir)) 1967 Expect(totalOutputs).To(ContainSubstring("Uploading 5.8M, 11 files\nOK")) 1968 }) 1969 }) 1970 1971 Context("when the app can't be uploaded", func() { 1972 BeforeEach(func() { 1973 actor.UploadAppReturns(errors.New("Boom!")) 1974 args = []string{"app"} 1975 }) 1976 1977 It("fails when the app can't be uploaded", func() { 1978 Expect(executeErr).To(HaveOccurred()) 1979 Expect(executeErr.Error()).To(ContainSubstring("Error uploading application")) 1980 }) 1981 }) 1982 1983 Context("when no name and no manifest is given", func() { 1984 BeforeEach(func() { 1985 manifestRepo.ReadManifestReturns(manifest.NewEmptyManifest(), errors.New("No such manifest")) 1986 args = []string{} 1987 }) 1988 1989 It("fails", func() { 1990 Expect(executeErr).To(HaveOccurred()) 1991 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.")) 1992 }) 1993 }) 1994 }) 1995 1996 Context("when routes are specified in the manifest", func() { 1997 Context("and the manifest has more than one app", func() { 1998 BeforeEach(func() { 1999 m := &manifest.Manifest{ 2000 Path: "manifest.yml", 2001 Data: generic.NewMap(map[interface{}]interface{}{ 2002 "applications": []interface{}{ 2003 generic.NewMap(map[interface{}]interface{}{ 2004 "routes": []interface{}{ 2005 map[interface{}]interface{}{"route": "app1route1.example.com/path"}, 2006 map[interface{}]interface{}{"route": "app1route2.example.com:8008"}, 2007 }, 2008 "name": "manifest-app-name-1", 2009 }), 2010 generic.NewMap(map[interface{}]interface{}{ 2011 "name": "manifest-app-name-2", 2012 "routes": []interface{}{ 2013 map[interface{}]interface{}{"route": "app2route1.example.com"}, 2014 }, 2015 }), 2016 }, 2017 }), 2018 } 2019 manifestRepo.ReadManifestReturns(m, nil) 2020 2021 appRepo.ReadStub = func(appName string) (models.Application, error) { 2022 return models.Application{ 2023 ApplicationFields: models.ApplicationFields{ 2024 Name: appName, 2025 GUID: appName + "-guid", 2026 }, 2027 }, nil 2028 } 2029 2030 appRepo.UpdateStub = func(appGUID string, appParams models.AppParams) (models.Application, error) { 2031 return models.Application{ 2032 ApplicationFields: models.ApplicationFields{ 2033 GUID: appGUID, 2034 }, 2035 }, nil 2036 } 2037 }) 2038 2039 Context("and there are no flags", func() { 2040 BeforeEach(func() { 2041 args = []string{} 2042 }) 2043 2044 It("maps the routes to the specified apps", func() { 2045 noHostBool := false 2046 emptyAppParams := models.AppParams{ 2047 NoHostname: &noHostBool, 2048 } 2049 2050 Expect(executeErr).ToNot(HaveOccurred()) 2051 2052 Expect(actor.MapManifestRouteCallCount()).To(Equal(3)) 2053 2054 route, app, appParams := actor.MapManifestRouteArgsForCall(0) 2055 Expect(route).To(Equal("app1route1.example.com/path")) 2056 Expect(app.ApplicationFields.GUID).To(Equal("manifest-app-name-1-guid")) 2057 Expect(appParams).To(Equal(emptyAppParams)) 2058 2059 route, app, appParams = actor.MapManifestRouteArgsForCall(1) 2060 Expect(route).To(Equal("app1route2.example.com:8008")) 2061 Expect(app.ApplicationFields.GUID).To(Equal("manifest-app-name-1-guid")) 2062 Expect(appParams).To(Equal(emptyAppParams)) 2063 2064 route, app, appParams = actor.MapManifestRouteArgsForCall(2) 2065 Expect(route).To(Equal("app2route1.example.com")) 2066 Expect(app.ApplicationFields.GUID).To(Equal("manifest-app-name-2-guid")) 2067 Expect(appParams).To(Equal(emptyAppParams)) 2068 }) 2069 }) 2070 2071 Context("and flags other than -f are present", func() { 2072 BeforeEach(func() { 2073 args = []string{"-n", "hostname-flag"} 2074 }) 2075 2076 It("should return an error", func() { 2077 Expect(executeErr).To(HaveOccurred()) 2078 Expect(executeErr.Error()).To(Equal("Incorrect Usage. Command line flags (except -f) cannot be applied when pushing multiple apps from a manifest file.")) 2079 }) 2080 }) 2081 }) 2082 2083 Context("and the manifest has only one app", func() { 2084 BeforeEach(func() { 2085 m := &manifest.Manifest{ 2086 Path: "manifest.yml", 2087 Data: generic.NewMap(map[interface{}]interface{}{ 2088 "applications": []interface{}{ 2089 generic.NewMap(map[interface{}]interface{}{ 2090 "routes": []interface{}{ 2091 map[interface{}]interface{}{"route": "app1route1.example.com/path"}, 2092 }, 2093 "name": "manifest-app-name-1", 2094 }), 2095 }, 2096 }), 2097 } 2098 manifestRepo.ReadManifestReturns(m, nil) 2099 2100 appRepo.ReadStub = func(appName string) (models.Application, error) { 2101 return models.Application{ 2102 ApplicationFields: models.ApplicationFields{ 2103 Name: appName, 2104 GUID: appName + "-guid", 2105 }, 2106 }, nil 2107 } 2108 2109 appRepo.UpdateStub = func(appGUID string, appParams models.AppParams) (models.Application, error) { 2110 return models.Application{ 2111 ApplicationFields: models.ApplicationFields{ 2112 GUID: appGUID, 2113 }, 2114 }, nil 2115 } 2116 }) 2117 2118 Context("and flags are present", func() { 2119 BeforeEach(func() { 2120 args = []string{"-n", "flag-value"} 2121 }) 2122 2123 It("maps the routes to the apps", func() { 2124 noHostBool := false 2125 appParamsFromContext := models.AppParams{ 2126 Hosts: []string{"flag-value"}, 2127 NoHostname: &noHostBool, 2128 } 2129 2130 Expect(executeErr).ToNot(HaveOccurred()) 2131 2132 Expect(actor.MapManifestRouteCallCount()).To(Equal(1)) 2133 2134 route, app, appParams := actor.MapManifestRouteArgsForCall(0) 2135 Expect(route).To(Equal("app1route1.example.com/path")) 2136 Expect(app.ApplicationFields.GUID).To(Equal("manifest-app-name-1-guid")) 2137 Expect(appParams).To(Equal(appParamsFromContext)) 2138 }) 2139 }) 2140 }) 2141 }) 2142 }) 2143 })