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