github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+incompatible/command/v7/push_command_test.go (about) 1 package v7_test 2 3 import ( 4 "errors" 5 "time" 6 7 . "github.com/onsi/gomega/gstruct" 8 9 "code.cloudfoundry.org/cli/actor/actionerror" 10 "code.cloudfoundry.org/cli/actor/v7action" 11 "code.cloudfoundry.org/cli/actor/v7action/v7actionfakes" 12 "code.cloudfoundry.org/cli/actor/v7pushaction" 13 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 14 "code.cloudfoundry.org/cli/command/commandfakes" 15 "code.cloudfoundry.org/cli/command/flag" 16 "code.cloudfoundry.org/cli/command/translatableerror" 17 "code.cloudfoundry.org/cli/command/v6/v6fakes" 18 . "code.cloudfoundry.org/cli/command/v7" 19 "code.cloudfoundry.org/cli/command/v7/v7fakes" 20 "code.cloudfoundry.org/cli/types" 21 "code.cloudfoundry.org/cli/util/configv3" 22 "code.cloudfoundry.org/cli/util/ui" 23 "github.com/cloudfoundry/bosh-cli/director/template" 24 . "github.com/onsi/ginkgo" 25 . "github.com/onsi/ginkgo/extensions/table" 26 . "github.com/onsi/gomega" 27 . "github.com/onsi/gomega/gbytes" 28 ) 29 30 type Step struct { 31 Error error 32 Event v7pushaction.Event 33 Warnings v7pushaction.Warnings 34 } 35 36 func FillInEvents(tuples []Step) (<-chan v7pushaction.Event, <-chan v7pushaction.Warnings, <-chan error) { 37 eventStream := make(chan v7pushaction.Event) 38 warningsStream := make(chan v7pushaction.Warnings) 39 errorStream := make(chan error) 40 41 go func() { 42 defer close(eventStream) 43 defer close(warningsStream) 44 defer close(errorStream) 45 46 for _, tuple := range tuples { 47 warningsStream <- tuple.Warnings 48 if tuple.Error != nil { 49 errorStream <- tuple.Error 50 return 51 } else { 52 eventStream <- tuple.Event 53 } 54 } 55 }() 56 57 return eventStream, warningsStream, errorStream 58 } 59 60 func FillInValues(tuples []Step, state v7pushaction.PushPlan) func(v7pushaction.PushPlan, v7pushaction.ProgressBar) (<-chan v7pushaction.PushPlan, <-chan v7pushaction.Event, <-chan v7pushaction.Warnings, <-chan error) { 61 return func(v7pushaction.PushPlan, v7pushaction.ProgressBar) (<-chan v7pushaction.PushPlan, <-chan v7pushaction.Event, <-chan v7pushaction.Warnings, <-chan error) { 62 stateStream := make(chan v7pushaction.PushPlan) 63 64 eventStream := make(chan v7pushaction.Event) 65 warningsStream := make(chan v7pushaction.Warnings) 66 errorStream := make(chan error) 67 68 go func() { 69 defer close(stateStream) 70 defer close(eventStream) 71 defer close(warningsStream) 72 defer close(errorStream) 73 74 for _, tuple := range tuples { 75 warningsStream <- tuple.Warnings 76 if tuple.Error != nil { 77 errorStream <- tuple.Error 78 return 79 } else { 80 eventStream <- tuple.Event 81 } 82 } 83 84 stateStream <- state 85 eventStream <- v7pushaction.Complete 86 }() 87 88 return stateStream, eventStream, warningsStream, errorStream 89 } 90 } 91 92 type LogEvent struct { 93 Log *v7action.LogMessage 94 Error error 95 } 96 97 func ReturnLogs(logevents []LogEvent, passedWarnings v7action.Warnings, passedError error) func(appName string, spaceGUID string, client v7action.NOAAClient) (<-chan *v7action.LogMessage, <-chan error, v7action.Warnings, error) { 98 return func(appName string, spaceGUID string, client v7action.NOAAClient) (<-chan *v7action.LogMessage, <-chan error, v7action.Warnings, error) { 99 logStream := make(chan *v7action.LogMessage) 100 errStream := make(chan error) 101 go func() { 102 defer close(logStream) 103 defer close(errStream) 104 105 for _, log := range logevents { 106 if log.Log != nil { 107 logStream <- log.Log 108 } 109 if log.Error != nil { 110 errStream <- log.Error 111 } 112 } 113 }() 114 115 return logStream, errStream, passedWarnings, passedError 116 } 117 } 118 119 var _ = Describe("push Command", func() { 120 var ( 121 cmd PushCommand 122 input *Buffer 123 testUI *ui.UI 124 fakeConfig *commandfakes.FakeConfig 125 fakeSharedActor *commandfakes.FakeSharedActor 126 fakeActor *v7fakes.FakePushActor 127 fakeVersionActor *v7fakes.FakeV7ActorForPush 128 fakeProgressBar *v6fakes.FakeProgressBar 129 fakeNOAAClient *v7actionfakes.FakeNOAAClient 130 fakeManifestLocator *v7fakes.FakeManifestLocator 131 binaryName string 132 executeErr error 133 134 appName1 string 135 appName2 string 136 userName string 137 spaceName string 138 orgName string 139 pwd string 140 fakeManifestParser *v7fakes.FakeManifestParser 141 ) 142 143 BeforeEach(func() { 144 input = NewBuffer() 145 testUI = ui.NewTestUI(input, NewBuffer(), NewBuffer()) 146 fakeConfig = new(commandfakes.FakeConfig) 147 fakeSharedActor = new(commandfakes.FakeSharedActor) 148 fakeActor = new(v7fakes.FakePushActor) 149 fakeVersionActor = new(v7fakes.FakeV7ActorForPush) 150 fakeProgressBar = new(v6fakes.FakeProgressBar) 151 fakeNOAAClient = new(v7actionfakes.FakeNOAAClient) 152 153 appName1 = "first-app" 154 appName2 = "second-app" 155 userName = "some-user" 156 spaceName = "some-space" 157 orgName = "some-org" 158 pwd = "/push/cmd/test" 159 fakeManifestLocator = new(v7fakes.FakeManifestLocator) 160 fakeManifestParser = new(v7fakes.FakeManifestParser) 161 162 binaryName = "faceman" 163 fakeConfig.BinaryNameReturns(binaryName) 164 fakeConfig.ExperimentalReturns(true) // TODO: Delete once we remove the experimental flag 165 166 cmd = PushCommand{ 167 UI: testUI, 168 Config: fakeConfig, 169 Actor: fakeActor, 170 VersionActor: fakeVersionActor, 171 SharedActor: fakeSharedActor, 172 ProgressBar: fakeProgressBar, 173 NOAAClient: fakeNOAAClient, 174 PWD: pwd, 175 ManifestLocator: fakeManifestLocator, 176 ManifestParser: fakeManifestParser, 177 } 178 }) 179 180 Describe("Execute", func() { 181 JustBeforeEach(func() { 182 executeErr = cmd.Execute(nil) 183 }) 184 185 BeforeEach(func() { 186 pushPlanChannel := make(chan []v7pushaction.PushPlan, 1) 187 pushPlanChannel <- []v7pushaction.PushPlan{ 188 {Application: v7action.Application{Name: appName1}}, 189 {Application: v7action.Application{Name: appName2}}, 190 } 191 close(pushPlanChannel) 192 events, warnings, errors := FillInEvents([]Step{ 193 { 194 Warnings: v7pushaction.Warnings{"some-warning-1"}, 195 Event: v7pushaction.ApplyManifest, 196 }, 197 { 198 Warnings: v7pushaction.Warnings{"some-warning-2"}, 199 Event: v7pushaction.ApplyManifestComplete, 200 }, 201 }) 202 203 fakeActor.PrepareSpaceReturns(pushPlanChannel, events, warnings, errors) 204 205 fakeActor.ActualizeStub = FillInValues([]Step{{}}, v7pushaction.PushPlan{}) 206 }) 207 208 When("checking target fails", func() { 209 BeforeEach(func() { 210 fakeSharedActor.CheckTargetReturns(actionerror.NoOrganizationTargetedError{BinaryName: binaryName}) 211 }) 212 213 It("returns an error", func() { 214 Expect(executeErr).To(MatchError(actionerror.NoOrganizationTargetedError{BinaryName: binaryName})) 215 216 Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) 217 checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) 218 Expect(checkTargetedOrg).To(BeTrue()) 219 Expect(checkTargetedSpace).To(BeTrue()) 220 }) 221 }) 222 223 When("checking target fails because the user is not logged in", func() { 224 BeforeEach(func() { 225 fakeSharedActor.CheckTargetReturns(actionerror.NotLoggedInError{BinaryName: binaryName}) 226 }) 227 228 It("returns an error", func() { 229 Expect(executeErr).To(MatchError(actionerror.NotLoggedInError{BinaryName: binaryName})) 230 231 Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) 232 checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) 233 Expect(checkTargetedOrg).To(BeTrue()) 234 Expect(checkTargetedSpace).To(BeTrue()) 235 }) 236 }) 237 238 When("the user is logged in, and org and space are targeted", func() { 239 BeforeEach(func() { 240 fakeConfig.CurrentUserReturns(configv3.User{Name: userName}, nil) 241 242 fakeConfig.TargetedOrganizationReturns(configv3.Organization{ 243 Name: orgName, 244 GUID: "some-org-guid", 245 }) 246 fakeConfig.TargetedSpaceReturns(configv3.Space{ 247 Name: spaceName, 248 GUID: "some-space-guid", 249 }) 250 }) 251 252 It("displays the experimental warning", func() { 253 Expect(testUI.Err).To(Say("This command is in EXPERIMENTAL stage and may change without notice")) 254 }) 255 256 When("invalid flags are passed", func() { 257 BeforeEach(func() { 258 cmd.DockerUsername = "some-docker-username" 259 }) 260 261 It("returns a validation error", func() { 262 Expect(executeErr).To(MatchError(translatableerror.RequiredFlagsError{Arg1: "--docker-image, -o", Arg2: "--docker-username"})) 263 }) 264 }) 265 266 Describe("reading manifest", func() { 267 BeforeEach(func() { 268 fakeManifestLocator.PathReturns("", true, nil) 269 }) 270 271 When("Reading the manifest fails", func() { 272 BeforeEach(func() { 273 fakeManifestParser.InterpolateAndParseReturns(errors.New("oh no")) 274 }) 275 It("returns the error", func() { 276 Expect(executeErr).To(MatchError("oh no")) 277 }) 278 }) 279 280 When("Reading the manifest succeeds", func() { 281 It("interpolates the manifest", func() { 282 Expect(executeErr).ToNot(HaveOccurred()) 283 Expect(fakeManifestParser.InterpolateAndParseCallCount()).To(Equal(1)) 284 }) 285 286 When("the manifest contains private docker images", func() { 287 It("returns docker password", func() { 288 Expect(executeErr).ToNot(HaveOccurred()) 289 Expect(fakeManifestParser.ContainsPrivateDockerImagesCallCount()).To(Equal(1)) 290 }) 291 }) 292 }) 293 294 When("no manifest flag", func() { 295 BeforeEach(func() { 296 cmd.NoManifest = true 297 }) 298 299 It("does not read the manifest", func() { 300 Expect(executeErr).ToNot(HaveOccurred()) 301 302 Expect(fakeManifestParser.InterpolateAndParseCallCount()).To(Equal(0)) 303 }) 304 }) 305 306 When("multi app manifest + flag overrides", func() { 307 BeforeEach(func() { 308 fakeManifestParser.ContainsMultipleAppsReturns(true) 309 cmd.NoRoute = true 310 }) 311 312 It("returns an error", func() { 313 Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) 314 }) 315 }) 316 }) 317 318 Describe("delegating to Actor.CreatePushPlans", func() { 319 BeforeEach(func() { 320 cmd.OptionalArgs.AppName = appName1 321 }) 322 323 It("delegates the correct values", func() { 324 Expect(fakeActor.CreatePushPlansCallCount()).To(Equal(1)) 325 actualAppName, actualSpaceGUID, actualOrgGUID, _, _ := fakeActor.CreatePushPlansArgsForCall(0) 326 327 Expect(actualAppName).To(Equal(appName1)) 328 Expect(actualSpaceGUID).To(Equal("some-space-guid")) 329 Expect(actualOrgGUID).To(Equal("some-org-guid")) 330 }) 331 332 When("Creating the pushPlans errors", func() { 333 BeforeEach(func() { 334 fakeActor.CreatePushPlansReturns(nil, errors.New("panic")) 335 }) 336 337 It("passes up the error", func() { 338 Expect(executeErr).To(MatchError(errors.New("panic"))) 339 Expect(fakeActor.PrepareSpaceCallCount()).To(Equal(0)) 340 }) 341 }) 342 343 When("creating push plans succeeds", func() { 344 BeforeEach(func() { 345 fakeActor.CreatePushPlansReturns( 346 []v7pushaction.PushPlan{ 347 {Application: v7action.Application{Name: appName1}, SpaceGUID: "some-space-guid"}, 348 {Application: v7action.Application{Name: appName2}, SpaceGUID: "some-space-guid"}, 349 }, nil, 350 ) 351 }) 352 353 Describe("delegating to Actor.PrepareSpace", func() { 354 It("delegates to PrepareSpace", func() { 355 actualPushPlans, actualParser := fakeActor.PrepareSpaceArgsForCall(0) 356 Expect(actualPushPlans).To(ConsistOf( 357 v7pushaction.PushPlan{Application: v7action.Application{Name: appName1}, SpaceGUID: "some-space-guid"}, 358 v7pushaction.PushPlan{Application: v7action.Application{Name: appName2}, SpaceGUID: "some-space-guid"}, 359 )) 360 Expect(actualParser).To(Equal(fakeManifestParser)) 361 }) 362 363 When("Actor.PrepareSpace has no errors", func() { 364 Describe("delegating to Actor.UpdateApplicationSettings", func() { 365 When("there are no flag overrides", func() { 366 BeforeEach(func() { 367 fakeActor.UpdateApplicationSettingsReturns( 368 []v7pushaction.PushPlan{ 369 {Application: v7action.Application{Name: appName1}}, 370 {Application: v7action.Application{Name: appName2}}, 371 }, 372 v7pushaction.Warnings{"conceptualize-warning-1"}, nil) 373 }) 374 375 It("generates a push plan with the specified app path", func() { 376 Expect(executeErr).ToNot(HaveOccurred()) 377 Expect(testUI.Out).To(Say( 378 "Pushing apps %s, %s to org some-org / space some-space as some-user", 379 appName1, 380 appName2, 381 )) 382 Expect(testUI.Out).To(Say(`Getting app info\.\.\.`)) 383 Expect(testUI.Err).To(Say("conceptualize-warning-1")) 384 385 Expect(fakeActor.UpdateApplicationSettingsCallCount()).To(Equal(1)) 386 actualPushPlans := fakeActor.UpdateApplicationSettingsArgsForCall(0) 387 Expect(actualPushPlans).To(ConsistOf( 388 v7pushaction.PushPlan{Application: v7action.Application{Name: appName1}, SpaceGUID: "some-space-guid"}, 389 v7pushaction.PushPlan{Application: v7action.Application{Name: appName2}, SpaceGUID: "some-space-guid"}, 390 )) 391 }) 392 393 Describe("delegating to Actor.Actualize", func() { 394 When("Actualize returns success", func() { 395 BeforeEach(func() { 396 fakeActor.ActualizeStub = FillInValues([]Step{ 397 {}, 398 }, v7pushaction.PushPlan{Application: v7action.Application{GUID: "potato"}}) 399 }) 400 401 Describe("actualize events", func() { 402 BeforeEach(func() { 403 fakeActor.ActualizeStub = FillInValues([]Step{ 404 { 405 Event: v7pushaction.SkippingApplicationCreation, 406 Warnings: v7pushaction.Warnings{"skipping app creation warnings"}, 407 }, 408 { 409 Event: v7pushaction.CreatingApplication, 410 Warnings: v7pushaction.Warnings{"app creation warnings"}, 411 }, 412 { 413 Event: v7pushaction.CreatingAndMappingRoutes, 414 }, 415 { 416 Event: v7pushaction.CreatedRoutes, 417 Warnings: v7pushaction.Warnings{"routes warnings"}, 418 }, 419 { 420 Event: v7pushaction.CreatingArchive, 421 }, 422 { 423 Event: v7pushaction.UploadingApplicationWithArchive, 424 Warnings: v7pushaction.Warnings{"upload app archive warning"}, 425 }, 426 { 427 Event: v7pushaction.RetryUpload, 428 Warnings: v7pushaction.Warnings{"retry upload warning"}, 429 }, 430 { 431 Event: v7pushaction.UploadWithArchiveComplete, 432 }, 433 }, v7pushaction.PushPlan{}) 434 }) 435 436 It("actualizes the application and displays events/warnings", func() { 437 Expect(executeErr).ToNot(HaveOccurred()) 438 439 Expect(fakeProgressBar.ReadyCallCount()).Should(Equal(2)) 440 Expect(fakeProgressBar.CompleteCallCount()).Should(Equal(2)) 441 442 Expect(testUI.Out).To(Say("Updating app first-app...")) 443 Expect(testUI.Err).To(Say("skipping app creation warnings")) 444 445 Expect(testUI.Out).To(Say("Creating app first-app...")) 446 Expect(testUI.Err).To(Say("app creation warnings")) 447 448 Expect(testUI.Out).To(Say("Mapping routes...")) 449 Expect(testUI.Err).To(Say("routes warnings")) 450 451 Expect(testUI.Out).To(Say("Packaging files to upload...")) 452 453 Expect(testUI.Out).To(Say("Uploading files...")) 454 Expect(testUI.Err).To(Say("upload app archive warning")) 455 456 Expect(testUI.Out).To(Say("Retrying upload due to an error...")) 457 Expect(testUI.Err).To(Say("retry upload warning")) 458 459 Expect(testUI.Out).To(Say("Waiting for API to complete processing files...")) 460 461 Expect(testUI.Out).To(Say("Waiting for app first-app to start...")) 462 463 Expect(testUI.Out).To(Say("Updating app second-app...")) 464 Expect(testUI.Err).To(Say("skipping app creation warnings")) 465 466 Expect(testUI.Out).To(Say("Creating app second-app...")) 467 Expect(testUI.Err).To(Say("app creation warnings")) 468 469 Expect(testUI.Out).To(Say("Mapping routes...")) 470 Expect(testUI.Err).To(Say("routes warnings")) 471 472 Expect(testUI.Out).To(Say("Packaging files to upload...")) 473 474 Expect(testUI.Out).To(Say("Uploading files...")) 475 Expect(testUI.Err).To(Say("upload app archive warning")) 476 477 Expect(testUI.Out).To(Say("Retrying upload due to an error...")) 478 Expect(testUI.Err).To(Say("retry upload warning")) 479 480 Expect(testUI.Out).To(Say("Waiting for API to complete processing files...")) 481 482 Expect(testUI.Out).To(Say("Waiting for app second-app to start...")) 483 }) 484 }) 485 486 Describe("staging logs", func() { 487 BeforeEach(func() { 488 fakeActor.ActualizeStub = FillInValues([]Step{ 489 { 490 Event: v7pushaction.StartingStaging, 491 }, 492 }, v7pushaction.PushPlan{}) 493 }) 494 495 When("there are no logging errors", func() { 496 BeforeEach(func() { 497 fakeVersionActor.GetStreamingLogsForApplicationByNameAndSpaceStub = ReturnLogs( 498 []LogEvent{ 499 {Log: v7action.NewLogMessage("log-message-1", 1, time.Now(), v7action.StagingLog, "source-instance")}, 500 {Log: v7action.NewLogMessage("log-message-2", 1, time.Now(), v7action.StagingLog, "source-instance")}, 501 {Log: v7action.NewLogMessage("log-message-3", 1, time.Now(), "potato", "source-instance")}, 502 }, 503 v7action.Warnings{"log-warning-1", "log-warning-2"}, 504 nil, 505 ) 506 }) 507 508 It("displays the staging logs and warnings", func() { 509 Expect(testUI.Out).To(Say("Staging app and tracing logs...")) 510 511 Expect(testUI.Err).To(Say("log-warning-1")) 512 Expect(testUI.Err).To(Say("log-warning-2")) 513 514 Eventually(testUI.Out).Should(Say("log-message-1")) 515 Eventually(testUI.Out).Should(Say("log-message-2")) 516 Eventually(testUI.Out).ShouldNot(Say("log-message-3")) 517 518 Expect(fakeVersionActor.GetStreamingLogsForApplicationByNameAndSpaceCallCount()).To(Equal(2)) 519 passedAppName, spaceGUID, _ := fakeVersionActor.GetStreamingLogsForApplicationByNameAndSpaceArgsForCall(0) 520 Expect(passedAppName).To(Equal(appName1)) 521 Expect(spaceGUID).To(Equal("some-space-guid")) 522 passedAppName, spaceGUID, _ = fakeVersionActor.GetStreamingLogsForApplicationByNameAndSpaceArgsForCall(1) 523 Expect(passedAppName).To(Equal(appName2)) 524 Expect(spaceGUID).To(Equal("some-space-guid")) 525 526 }) 527 }) 528 529 When("there are logging errors", func() { 530 BeforeEach(func() { 531 fakeVersionActor.GetStreamingLogsForApplicationByNameAndSpaceStub = ReturnLogs( 532 []LogEvent{ 533 {Error: errors.New("some-random-err")}, 534 {Error: actionerror.NOAATimeoutError{}}, 535 {Log: v7action.NewLogMessage("log-message-1", 1, time.Now(), v7action.StagingLog, "source-instance")}, 536 }, 537 v7action.Warnings{"log-warning-1", "log-warning-2"}, 538 nil, 539 ) 540 }) 541 542 It("displays the errors as warnings", func() { 543 Expect(testUI.Out).To(Say("Staging app and tracing logs...")) 544 545 Expect(testUI.Err).To(Say("log-warning-1")) 546 Expect(testUI.Err).To(Say("log-warning-2")) 547 Eventually(testUI.Err).Should(Say("some-random-err")) 548 Eventually(testUI.Err).Should(Say("timeout connecting to log server, no log will be shown")) 549 550 Eventually(testUI.Out).Should(Say("log-message-1")) 551 }) 552 }) 553 }) 554 555 When("user does not request --no-start", func() { 556 BeforeEach(func() { 557 cmd.NoStart = false 558 }) 559 560 When("restarting the app succeeds", func() { 561 BeforeEach(func() { 562 fakeVersionActor.RestartApplicationReturns(v7action.Warnings{"some-restart-warning"}, nil) 563 }) 564 565 It("restarts the app and displays warnings", func() { 566 Expect(executeErr).ToNot(HaveOccurred()) 567 568 Expect(testUI.Err).To(Say("some-restart-warning")) 569 570 Expect(fakeVersionActor.RestartApplicationCallCount()).To(Equal(2)) 571 Expect(fakeVersionActor.RestartApplicationArgsForCall(0)).To(Equal("potato")) 572 Expect(fakeVersionActor.RestartApplicationArgsForCall(1)).To(Equal("potato")) 573 }) 574 575 When("when getting the application summary succeeds", func() { 576 BeforeEach(func() { 577 summary := v7action.ApplicationSummary{ 578 Application: v7action.Application{}, 579 CurrentDroplet: v7action.Droplet{}, 580 ProcessSummaries: v7action.ProcessSummaries{}, 581 } 582 fakeVersionActor.GetApplicationSummaryByNameAndSpaceReturnsOnCall(0, summary, v7action.Warnings{"app-1-summary-warning-1", "app-1-summary-warning-2"}, nil) 583 fakeVersionActor.GetApplicationSummaryByNameAndSpaceReturnsOnCall(1, summary, v7action.Warnings{"app-2-summary-warning-1", "app-2-summary-warning-2"}, nil) 584 }) 585 586 // TODO: Don't test the shared.AppSummaryDisplayer.AppDisplay method. 587 // Use DI to pass in a new AppSummaryDisplayer to the Command instead. 588 It("displays the app summary", func() { 589 Expect(executeErr).ToNot(HaveOccurred()) 590 Expect(fakeVersionActor.GetApplicationSummaryByNameAndSpaceCallCount()).To(Equal(2)) 591 }) 592 593 }) 594 595 When("getting the application summary fails", func() { 596 BeforeEach(func() { 597 fakeVersionActor.GetApplicationSummaryByNameAndSpaceReturns( 598 v7action.ApplicationSummary{}, 599 v7action.Warnings{"get-application-summary-warnings"}, 600 errors.New("get-application-summary-error"), 601 ) 602 }) 603 604 It("does not display the app summary", func() { 605 Expect(testUI.Out).ToNot(Say(`requested state:`)) 606 }) 607 608 It("returns the error from GetApplicationSummaryByNameAndSpace", func() { 609 Expect(executeErr).To(MatchError("get-application-summary-error")) 610 }) 611 612 It("prints the warnings", func() { 613 Expect(testUI.Err).To(Say("get-application-summary-warnings")) 614 }) 615 }) 616 617 }) 618 619 When("restarting the app fails", func() { 620 When("restarting fails in a generic way", func() { 621 BeforeEach(func() { 622 fakeVersionActor.RestartApplicationReturns(v7action.Warnings{"some-restart-warning"}, errors.New("restart failure")) 623 }) 624 625 It("returns an error and any warnings", func() { 626 Expect(executeErr).To(MatchError("restart failure")) 627 Expect(testUI.Err).To(Say("some-restart-warning")) 628 }) 629 }) 630 631 When("the error is an AllInstancesCrashedError", func() { 632 BeforeEach(func() { 633 fakeVersionActor.RestartApplicationReturns(nil, actionerror.AllInstancesCrashedError{}) 634 }) 635 636 It("returns the ApplicationUnableToStartError", func() { 637 Expect(executeErr).To(MatchError(translatableerror.ApplicationUnableToStartError{ 638 AppName: "first-app", 639 BinaryName: binaryName, 640 })) 641 }) 642 643 }) 644 645 When("restart times out", func() { 646 BeforeEach(func() { 647 fakeVersionActor.RestartApplicationReturns(v7action.Warnings{"some-restart-warning"}, actionerror.StartupTimeoutError{}) 648 }) 649 650 It("returns the StartupTimeoutError and prints warnings", func() { 651 Expect(executeErr).To(MatchError(translatableerror.StartupTimeoutError{ 652 AppName: "first-app", 653 BinaryName: binaryName, 654 })) 655 656 Expect(testUI.Err).To(Say("some-restart-warning")) 657 }) 658 }) 659 }) 660 }) 661 662 When("user requests --no-start", func() { 663 BeforeEach(func() { 664 cmd.NoStart = true 665 }) 666 667 It("does not attempt to restart the app", func() { 668 Expect(fakeVersionActor.RestartApplicationCallCount()).To(Equal(0)) 669 }) 670 }) 671 }) 672 673 When("Actualize returns an error", func() { 674 BeforeEach(func() { 675 fakeActor.ActualizeStub = FillInValues([]Step{ 676 { 677 Error: errors.New("anti avant garde naming"), 678 }, 679 }, v7pushaction.PushPlan{}) 680 }) 681 682 It("returns the error", func() { 683 Expect(executeErr).To(MatchError("anti avant garde naming")) 684 }) 685 }) 686 }) 687 }) 688 689 When("flag overrides are specified", func() { 690 BeforeEach(func() { 691 cmd.AppPath = "some/app/path" 692 }) 693 694 It("generates a push plan with the specified flag overrides", func() { 695 Expect(fakeActor.CreatePushPlansCallCount()).To(Equal(1)) 696 _, _, _, _, overrides := fakeActor.CreatePushPlansArgsForCall(0) 697 Expect(overrides).To(MatchFields(IgnoreExtras, Fields{ 698 "ProvidedAppPath": Equal("some/app/path"), 699 })) 700 }) 701 }) 702 703 When("conceptualize returns an error", func() { 704 var expectedErr error 705 706 BeforeEach(func() { 707 expectedErr = errors.New("some-error") 708 fakeActor.UpdateApplicationSettingsReturns(nil, v7pushaction.Warnings{"some-warning-1"}, expectedErr) 709 }) 710 711 It("generates a push plan with the specified app path", func() { 712 Expect(executeErr).To(MatchError(expectedErr)) 713 Expect(testUI.Err).To(Say("some-warning-1")) 714 }) 715 }) 716 }) 717 }) 718 719 When("Actor.PrepareSpace has an error", func() { 720 var pushPlansChannel chan []v7pushaction.PushPlan 721 722 BeforeEach(func() { 723 pushPlansChannel = make(chan []v7pushaction.PushPlan) 724 close(pushPlansChannel) 725 events, warnings, errors := FillInEvents([]Step{ 726 { 727 Warnings: v7pushaction.Warnings{"prepare-space-warning-1"}, 728 Error: errors.New("prepare-space-error-1"), 729 }, 730 }) 731 732 fakeActor.PrepareSpaceReturns(pushPlansChannel, events, warnings, errors) 733 }) 734 735 It("returns the error", func() { 736 Expect(executeErr).To(MatchError(errors.New("prepare-space-error-1"))) 737 Expect(testUI.Err).To(Say("prepare-space-warning-1")) 738 }) 739 740 It("does not delegate to UpdateApplicationSettings", func() { 741 Expect(fakeActor.UpdateApplicationSettingsCallCount()).To(Equal(0)) 742 }) 743 744 It("does not delegate to Actualize", func() { 745 Expect(fakeActor.ActualizeCallCount()).To(Equal(0)) 746 }) 747 }) 748 749 When("Actor.PrepareSpace has no errors but returns no apps", func() { 750 var pushPlansChannel chan []v7pushaction.PushPlan 751 752 BeforeEach(func() { 753 pushPlansChannel = make(chan []v7pushaction.PushPlan) 754 close(pushPlansChannel) 755 events, warnings, errors := FillInEvents([]Step{ 756 { 757 Warnings: v7pushaction.Warnings{"prepare-no-app-or-manifest-space-warning"}, 758 Error: nil, 759 }, 760 }) 761 762 fakeActor.PrepareSpaceReturns(pushPlansChannel, events, warnings, errors) 763 }) 764 765 It("returns the error", func() { 766 Expect(executeErr).To(MatchError(translatableerror.AppNameOrManifestRequiredError{})) 767 Expect(testUI.Err).To(Say("prepare-no-app-or-manifest-space-warning")) 768 }) 769 770 It("does not delegate to UpdateApplicationSettings", func() { 771 Expect(fakeActor.UpdateApplicationSettingsCallCount()).To(Equal(0)) 772 }) 773 774 It("does not delegate to Actualize", func() { 775 Expect(fakeActor.ActualizeCallCount()).To(Equal(0)) 776 }) 777 778 }) 779 }) 780 }) 781 }) 782 }) 783 }) 784 785 Describe("ValidateAllowedFlagsForMultipleApps", func() { 786 When("manifest contains a single app", func() { 787 DescribeTable("returns nil when", 788 func(setup func()) { 789 setup() 790 Expect(cmd.ValidateAllowedFlagsForMultipleApps(false)).ToNot(HaveOccurred()) 791 }, 792 Entry("buildpacks is specified", 793 func() { 794 cmd.Buildpacks = []string{"buildpack-1", "buildpack-2"} 795 }), 796 Entry("disk is specified", 797 func() { 798 cmd.Disk = flag.Megabytes{NullUint64: types.NullUint64{IsSet: true}} 799 }), 800 ) 801 }) 802 803 When("manifest contains multiple apps", func() { 804 DescribeTable("throws an error when", 805 func(setup func()) { 806 setup() 807 Expect(cmd.ValidateAllowedFlagsForMultipleApps(true)).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) 808 }, 809 810 Entry("buildpacks is specified", 811 func() { 812 cmd.Buildpacks = []string{"buildpack-1", "buildpack-2"} 813 }), 814 Entry("disk is specified", 815 func() { 816 cmd.Disk = flag.Megabytes{NullUint64: types.NullUint64{IsSet: true}} 817 }), 818 Entry("docker image is specified", 819 func() { 820 cmd.DockerImage = flag.DockerImage{Path: "some-docker"} 821 }), 822 Entry("docker username is specified", 823 func() { 824 fakeConfig.DockerPasswordReturns("some-password") 825 cmd.DockerUsername = "docker-username" 826 }), 827 Entry("health check type is specified", 828 func() { 829 cmd.HealthCheckType = flag.HealthCheckType{Type: constant.HTTP} 830 }), 831 Entry("health check HTTP endpoint is specified", 832 func() { 833 cmd.HealthCheckHTTPEndpoint = "some-endpoint" 834 }), 835 Entry("health check timeout is specified", 836 func() { 837 cmd.HealthCheckTimeout = flag.PositiveInteger{Value: 5} 838 }), 839 Entry("instances is specified", 840 func() { 841 cmd.Instances = flag.Instances{NullInt: types.NullInt{IsSet: true}} 842 }), 843 Entry("stack is specified", 844 func() { 845 cmd.Stack = "some-stack" 846 }), 847 Entry("memory is specified", 848 func() { 849 cmd.Memory = flag.Megabytes{NullUint64: types.NullUint64{IsSet: true}} 850 }), 851 Entry("provided app path is specified", 852 func() { 853 cmd.AppPath = "some-app-path" 854 }), 855 Entry("skip route creation is specified", 856 func() { 857 cmd.NoRoute = true 858 }), 859 Entry("start command is specified", 860 func() { 861 cmd.StartCommand = flag.Command{FilteredString: types.FilteredString{IsSet: true}} 862 }), 863 ) 864 865 DescribeTable("is nil when", 866 func(setup func()) { 867 setup() 868 Expect(cmd.ValidateAllowedFlagsForMultipleApps(true)).ToNot(HaveOccurred()) 869 }, 870 Entry("no flags are specified", func() {}), 871 Entry("path is specified", 872 func() { 873 cmd.PathToManifest = flag.PathWithExistenceCheck("/some/path") 874 }), 875 Entry("no-start is specified", 876 func() { 877 cmd.NoStart = true 878 }), 879 Entry("single app name is specified, even with disallowed flags", 880 func() { 881 cmd.OptionalArgs.AppName = "some-app-name" 882 883 cmd.Stack = "some-stack" 884 cmd.NoRoute = true 885 cmd.DockerImage = flag.DockerImage{Path: "some-docker"} 886 cmd.Instances = flag.Instances{NullInt: types.NullInt{IsSet: true}} 887 }), 888 ) 889 }) 890 }) 891 892 Describe("GetFlagOverrides", func() { 893 var ( 894 overrides v7pushaction.FlagOverrides 895 overridesErr error 896 ) 897 898 BeforeEach(func() { 899 cmd.Buildpacks = []string{"buildpack-1", "buildpack-2"} 900 cmd.Stack = "validStack" 901 cmd.HealthCheckType = flag.HealthCheckType{Type: constant.Port} 902 cmd.HealthCheckHTTPEndpoint = "/health-check-http-endpoint" 903 cmd.HealthCheckTimeout = flag.PositiveInteger{Value: 7} 904 cmd.Memory = flag.Megabytes{NullUint64: types.NullUint64{Value: 100, IsSet: true}} 905 cmd.Disk = flag.Megabytes{NullUint64: types.NullUint64{Value: 1024, IsSet: true}} 906 cmd.StartCommand = flag.Command{FilteredString: types.FilteredString{IsSet: true, Value: "some-start-command"}} 907 cmd.NoRoute = true 908 cmd.NoStart = true 909 cmd.Instances = flag.Instances{NullInt: types.NullInt{Value: 10, IsSet: true}} 910 }) 911 912 JustBeforeEach(func() { 913 overrides, overridesErr = cmd.GetFlagOverrides() 914 Expect(overridesErr).ToNot(HaveOccurred()) 915 }) 916 917 It("sets them on the flag overrides", func() { 918 Expect(overridesErr).ToNot(HaveOccurred()) 919 Expect(overrides.Buildpacks).To(ConsistOf("buildpack-1", "buildpack-2")) 920 Expect(overrides.Stack).To(Equal("validStack")) 921 Expect(overrides.HealthCheckType).To(Equal(constant.Port)) 922 Expect(overrides.HealthCheckEndpoint).To(Equal("/health-check-http-endpoint")) 923 Expect(overrides.HealthCheckTimeout).To(BeEquivalentTo(7)) 924 Expect(overrides.Memory).To(Equal(types.NullUint64{Value: 100, IsSet: true})) 925 Expect(overrides.Disk).To(Equal(types.NullUint64{Value: 1024, IsSet: true})) 926 Expect(overrides.StartCommand).To(Equal(types.FilteredString{IsSet: true, Value: "some-start-command"})) 927 Expect(overrides.SkipRouteCreation).To(BeTrue()) 928 Expect(overrides.NoStart).To(BeTrue()) 929 Expect(overrides.Instances).To(Equal(types.NullInt{Value: 10, IsSet: true})) 930 }) 931 932 When("a docker image is provided", func() { 933 BeforeEach(func() { 934 cmd.DockerImage = flag.DockerImage{Path: "some-docker-image"} 935 }) 936 937 It("sets docker image on the flag overrides", func() { 938 Expect(overridesErr).ToNot(HaveOccurred()) 939 Expect(overrides.DockerImage).To(Equal("some-docker-image")) 940 }) 941 }) 942 }) 943 944 Describe("ReadManifest", func() { 945 var ( 946 somePath string 947 executeErr error 948 ) 949 950 BeforeEach(func() { 951 somePath = "/some/path" 952 }) 953 954 JustBeforeEach(func() { 955 executeErr = cmd.ReadManifest() 956 }) 957 958 When("No path is provided", func() { 959 BeforeEach(func() { 960 cmd.PWD = somePath 961 }) 962 963 When("a manifest exists in the current dir", func() { 964 BeforeEach(func() { 965 fakeManifestLocator.PathReturns("/manifest/path", true, nil) 966 }) 967 968 It("uses the manifest in the current directory", func() { 969 Expect(executeErr).ToNot(HaveOccurred()) 970 971 Expect(fakeManifestLocator.PathCallCount()).To(Equal(1)) 972 Expect(fakeManifestLocator.PathArgsForCall(0)).To(Equal(cmd.PWD)) 973 974 Expect(fakeManifestParser.InterpolateAndParseCallCount()).To(Equal(1)) 975 actualManifestPath, _, _ := fakeManifestParser.InterpolateAndParseArgsForCall(0) 976 Expect(actualManifestPath).To(Equal("/manifest/path")) 977 }) 978 }) 979 980 When("there is not a manifest in the current dir", func() { 981 BeforeEach(func() { 982 fakeManifestLocator.PathReturns("", false, nil) 983 }) 984 985 It("ignores the file not found error", func() { 986 Expect(executeErr).ToNot(HaveOccurred()) 987 988 Expect(fakeManifestParser.InterpolateAndParseCallCount()).To(Equal(0)) 989 }) 990 }) 991 992 When("when there is an error locating the manifest in the current directory", func() { 993 BeforeEach(func() { 994 fakeManifestLocator.PathReturns("", false, errors.New("err-location")) 995 }) 996 997 It("ignores the file not found error", func() { 998 Expect(executeErr).To(MatchError("err-location")) 999 1000 Expect(fakeManifestParser.InterpolateAndParseCallCount()).To(Equal(0)) 1001 }) 1002 }) 1003 }) 1004 1005 When("The -f flag is specified", func() { 1006 BeforeEach(func() { 1007 cmd.PathToManifest = flag.PathWithExistenceCheck(somePath) 1008 fakeManifestLocator.PathReturns("/manifest/path", true, nil) 1009 }) 1010 1011 It("reads the manifest and passes through to PrepareSpace", func() { 1012 Expect(executeErr).ToNot(HaveOccurred()) 1013 1014 Expect(fakeManifestLocator.PathCallCount()).To(Equal(1)) 1015 Expect(fakeManifestLocator.PathArgsForCall(0)).To(Equal(somePath)) 1016 1017 Expect(fakeManifestParser.InterpolateAndParseCallCount()).To(Equal(1)) 1018 actualManifestPath, _, _ := fakeManifestParser.InterpolateAndParseArgsForCall(0) 1019 Expect(actualManifestPath).To(Equal("/manifest/path")) 1020 }) 1021 }) 1022 1023 When("--vars-files are specified", func() { 1024 var varsFiles []string 1025 1026 BeforeEach(func() { 1027 fakeManifestLocator.PathReturns("/manifest/path", true, nil) 1028 varsFiles = []string{"path1", "path2"} 1029 for _, path := range varsFiles { 1030 cmd.PathsToVarsFiles = append(cmd.PathsToVarsFiles, flag.PathWithExistenceCheck(path)) 1031 } 1032 }) 1033 1034 It("passes vars files to the manifest parser", func() { 1035 Expect(executeErr).ToNot(HaveOccurred()) 1036 1037 Expect(fakeManifestParser.InterpolateAndParseCallCount()).To(Equal(1)) 1038 _, actualVarsFiles, _ := fakeManifestParser.InterpolateAndParseArgsForCall(0) 1039 Expect(actualVarsFiles).To(Equal(varsFiles)) 1040 }) 1041 }) 1042 1043 When("The --var flag is provided", func() { 1044 var vars []template.VarKV 1045 1046 BeforeEach(func() { 1047 fakeManifestLocator.PathReturns("/manifest/path", true, nil) 1048 vars = []template.VarKV{ 1049 {Name: "put-var-here", Value: "turtle"}, 1050 } 1051 cmd.Vars = vars 1052 }) 1053 1054 It("passes vars files to the manifest parser", func() { 1055 Expect(executeErr).ToNot(HaveOccurred()) 1056 1057 Expect(fakeManifestParser.InterpolateAndParseCallCount()).To(Equal(1)) 1058 _, _, actualVars := fakeManifestParser.InterpolateAndParseArgsForCall(0) 1059 Expect(actualVars).To(Equal(vars)) 1060 }) 1061 }) 1062 }) 1063 1064 DescribeTable("ValidateFlags returns an error", 1065 func(setup func(), expectedErr error) { 1066 setup() 1067 err := cmd.ValidateFlags() 1068 if expectedErr == nil { 1069 Expect(err).To(BeNil()) 1070 } else { 1071 Expect(err).To(MatchError(expectedErr)) 1072 } 1073 }, 1074 1075 Entry("when docker username flag is passed *without* docker flag", 1076 func() { 1077 cmd.DockerUsername = "some-docker-username" 1078 }, 1079 translatableerror.RequiredFlagsError{Arg1: "--docker-image, -o", Arg2: "--docker-username"}), 1080 1081 Entry("when docker and buildpacks flags are passed", 1082 func() { 1083 cmd.DockerImage.Path = "some-docker-image" 1084 cmd.Buildpacks = []string{"some-buildpack"} 1085 }, 1086 translatableerror.ArgumentCombinationError{Args: []string{"--buildpack, -b", "--docker-image, -o"}}), 1087 1088 Entry("when docker and stack flags are passed", 1089 func() { 1090 cmd.DockerImage.Path = "some-docker-image" 1091 cmd.Stack = "validStack" 1092 }, 1093 translatableerror.ArgumentCombinationError{Args: []string{"--stack, -s", "--docker-image, -o"}}), 1094 1095 Entry("when docker and path flags are passed", 1096 func() { 1097 cmd.DockerImage.Path = "some-docker-image" 1098 cmd.AppPath = "some-directory-path" 1099 }, 1100 translatableerror.ArgumentCombinationError{Args: []string{"--docker-image, -o", "--path, -p"}}), 1101 1102 Entry("when -u http does not have a matching --endpoint", 1103 func() { 1104 cmd.HealthCheckType.Type = constant.HTTP 1105 }, 1106 translatableerror.RequiredFlagsError{Arg1: "--endpoint", Arg2: "--health-check-type=http, -u=http"}), 1107 1108 Entry("when --endpoint does not have a matching -u", 1109 func() { 1110 cmd.HealthCheckHTTPEndpoint = "/health" 1111 }, 1112 translatableerror.RequiredFlagsError{Arg1: "--health-check-type=http, -u=http", Arg2: "--endpoint"}), 1113 1114 Entry("when --endpoint has a matching -u=process instead of a -u=http", 1115 func() { 1116 cmd.HealthCheckHTTPEndpoint = "/health" 1117 cmd.HealthCheckType.Type = constant.Process 1118 }, 1119 translatableerror.RequiredFlagsError{Arg1: "--health-check-type=http, -u=http", Arg2: "--endpoint"}), 1120 1121 Entry("when --endpoint has a matching -u=port instead of a -u=http", 1122 func() { 1123 cmd.HealthCheckHTTPEndpoint = "/health" 1124 cmd.HealthCheckType.Type = constant.Port 1125 }, 1126 translatableerror.RequiredFlagsError{Arg1: "--health-check-type=http, -u=http", Arg2: "--endpoint"}), 1127 1128 Entry("when -u http does have a matching --endpoint", 1129 func() { 1130 cmd.HealthCheckType.Type = constant.HTTP 1131 cmd.HealthCheckHTTPEndpoint = "/health" 1132 }, 1133 nil), 1134 ) 1135 1136 })