github.com/loafoe/cli@v7.1.0+incompatible/command/v7/push_command.go (about) 1 package v7 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "strings" 8 9 "code.cloudfoundry.org/cli/actor/actionerror" 10 "code.cloudfoundry.org/cli/actor/sharedaction" 11 "code.cloudfoundry.org/cli/actor/v7action" 12 "code.cloudfoundry.org/cli/actor/v7pushaction" 13 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 14 "code.cloudfoundry.org/cli/cf/errors" 15 "code.cloudfoundry.org/cli/command" 16 "code.cloudfoundry.org/cli/command/flag" 17 "code.cloudfoundry.org/cli/command/translatableerror" 18 "code.cloudfoundry.org/cli/command/v7/shared" 19 "code.cloudfoundry.org/cli/resources" 20 "code.cloudfoundry.org/cli/util/configv3" 21 "code.cloudfoundry.org/cli/util/manifestparser" 22 "code.cloudfoundry.org/cli/util/progressbar" 23 "github.com/cloudfoundry/bosh-cli/director/template" 24 log "github.com/sirupsen/logrus" 25 "gopkg.in/yaml.v2" 26 ) 27 28 //go:generate counterfeiter . ProgressBar 29 30 type ProgressBar interface { 31 v7pushaction.ProgressBar 32 Complete() 33 Ready() 34 } 35 36 //go:generate counterfeiter . PushActor 37 38 type PushActor interface { 39 HandleFlagOverrides(baseManifest manifestparser.Manifest, flagOverrides v7pushaction.FlagOverrides) (manifestparser.Manifest, error) 40 CreatePushPlans(spaceGUID string, orgGUID string, manifest manifestparser.Manifest, overrides v7pushaction.FlagOverrides) ([]v7pushaction.PushPlan, v7action.Warnings, error) 41 // Actualize applies any necessary changes. 42 Actualize(plan v7pushaction.PushPlan, progressBar v7pushaction.ProgressBar) <-chan *v7pushaction.PushEvent 43 } 44 45 //go:generate counterfeiter . V7ActorForPush 46 47 type V7ActorForPush interface { 48 GetApplicationByNameAndSpace(name string, spaceGUID string) (resources.Application, v7action.Warnings, error) 49 GetDetailedAppSummary(appName string, spaceGUID string, withObfuscatedValues bool) (v7action.DetailedApplicationSummary, v7action.Warnings, error) 50 SetSpaceManifest(spaceGUID string, rawManifest []byte) (v7action.Warnings, error) 51 GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client sharedaction.LogCacheClient) (<-chan sharedaction.LogMessage, <-chan error, context.CancelFunc, v7action.Warnings, error) 52 RestartApplication(appGUID string, noWait bool) (v7action.Warnings, error) 53 } 54 55 //go:generate counterfeiter . ManifestParser 56 57 type ManifestParser interface { 58 InterpolateAndParse(pathToManifest string, pathsToVarsFiles []string, vars []template.VarKV) (manifestparser.Manifest, error) 59 MarshalManifest(manifest manifestparser.Manifest) ([]byte, error) 60 } 61 62 //go:generate counterfeiter . ManifestLocator 63 64 type ManifestLocator interface { 65 Path(filepathOrDirectory string) (string, bool, error) 66 } 67 68 type PushCommand struct { 69 BaseCommand 70 71 OptionalArgs flag.OptionalAppName `positional-args:"yes"` 72 HealthCheckTimeout flag.PositiveInteger `long:"app-start-timeout" short:"t" description:"Time (in seconds) allowed to elapse between starting up an app and the first healthy response from the app"` 73 Buildpacks []string `long:"buildpack" short:"b" description:"Custom buildpack by name (e.g. my-buildpack) or Git URL (e.g. 'https://github.com/cloudfoundry/java-buildpack.git') or Git URL with a branch or tag (e.g. 'https://github.com/cloudfoundry/java-buildpack.git#v3.3.0' for 'v3.3.0' tag). To use built-in buildpacks only, specify 'default' or 'null'"` 74 Disk string `long:"disk" short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"` 75 DockerImage flag.DockerImage `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"` 76 DockerUsername string `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"` 77 DropletPath flag.PathWithExistenceCheck `long:"droplet" description:"Path to a tgz file with a pre-staged app"` 78 HealthCheckHTTPEndpoint string `long:"endpoint" description:"Valid path on the app for an HTTP health check. Only used when specifying --health-check-type=http"` 79 HealthCheckType flag.HealthCheckType `long:"health-check-type" short:"u" description:"Application health check type. Defaults to 'port'. 'http' requires a valid endpoint, for example, '/health'."` 80 Instances flag.Instances `long:"instances" short:"i" description:"Number of instances"` 81 PathToManifest flag.ManifestPathWithExistenceCheck `long:"manifest" short:"f" description:"Path to manifest"` 82 Memory string `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` 83 NoManifest bool `long:"no-manifest" description:"Ignore manifest file"` 84 NoRoute bool `long:"no-route" description:"Do not map a route to this app"` 85 NoStart bool `long:"no-start" description:"Do not stage and start the app after pushing"` 86 NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"` 87 AppPath flag.PathWithExistenceCheck `long:"path" short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"` 88 RandomRoute bool `long:"random-route" description:"Create a random route for this app (except when no-route is specified in the manifest)"` 89 Stack string `long:"stack" short:"s" description:"Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)"` 90 StartCommand flag.Command `long:"start-command" short:"c" description:"Startup command, set to null to reset to default start command"` 91 Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy, either rolling or null."` 92 Task bool `long:"task" description:"Push an app that is used only to execute tasks. The app will be staged, but not started and will have no route assigned."` 93 Vars []template.VarKV `long:"var" description:"Variable key value pair for variable substitution, (e.g., name=app1); can specify multiple times"` 94 PathsToVarsFiles []flag.PathWithExistenceCheck `long:"vars-file" description:"Path to a variable substitution file for manifest; can specify multiple times"` 95 dockerPassword interface{} `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"` 96 usage interface{} `usage:"CF_NAME push APP_NAME [-b BUILDPACK_NAME]\n [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start] [--no-wait] [-i NUM_INSTANCES]\n [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [--task TASK]\n [-u (process | port | http)] [--no-route | --random-route]\n [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]...\n \n CF_NAME push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME]\n [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start] [--no-wait] [-i NUM_INSTANCES]\n [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [--task TASK]\n [-u (process | port | http)] [--no-route | --random-route ]\n [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]..."` 97 envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for staging, in minutes" environmentDefault:"15"` 98 envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` 99 100 LogCacheClient sharedaction.LogCacheClient 101 PushActor PushActor 102 VersionActor V7ActorForPush 103 ProgressBar ProgressBar 104 CWD string 105 ManifestLocator ManifestLocator 106 ManifestParser ManifestParser 107 108 stopStreamingFunc func() 109 } 110 111 func (cmd *PushCommand) Setup(config command.Config, ui command.UI) error { 112 err := cmd.BaseCommand.Setup(config, ui) 113 if err != nil { 114 return err 115 } 116 117 cmd.ProgressBar = progressbar.NewProgressBar() 118 cmd.VersionActor = cmd.Actor 119 cmd.PushActor = v7pushaction.NewActor(cmd.Actor, sharedaction.NewActor(config)) 120 121 logCacheEndpoint, _, err := cmd.Actor.GetLogCacheEndpoint() 122 if err != nil { 123 return err 124 } 125 cmd.LogCacheClient = command.NewLogCacheClient(logCacheEndpoint, config, ui) 126 127 currentDir, err := os.Getwd() 128 cmd.CWD = currentDir 129 130 cmd.ManifestLocator = manifestparser.NewLocator() 131 cmd.ManifestParser = manifestparser.ManifestParser{} 132 133 return err 134 } 135 136 func (cmd PushCommand) Execute(args []string) error { 137 cmd.stopStreamingFunc = nil 138 err := cmd.SharedActor.CheckTarget(true, true) 139 if err != nil { 140 return err 141 } 142 143 user, err := cmd.Config.CurrentUser() 144 if err != nil { 145 return err 146 } 147 148 flagOverrides, err := cmd.GetFlagOverrides() 149 if err != nil { 150 return err 151 } 152 153 err = cmd.ValidateFlags() 154 if err != nil { 155 return err 156 } 157 158 baseManifest, err := cmd.GetBaseManifest(flagOverrides) 159 if err != nil { 160 return err 161 } 162 163 transformedManifest, err := cmd.PushActor.HandleFlagOverrides(baseManifest, flagOverrides) 164 if err != nil { 165 return err 166 } 167 168 flagOverrides.DockerPassword, err = cmd.GetDockerPassword(flagOverrides.DockerUsername, transformedManifest.ContainsPrivateDockerImages()) 169 if err != nil { 170 return err 171 } 172 173 transformedRawManifest, err := cmd.ManifestParser.MarshalManifest(transformedManifest) 174 if err != nil { 175 return err 176 } 177 178 cmd.announcePushing(transformedManifest.AppNames(), user) 179 180 hasManifest := transformedManifest.PathToManifest != "" 181 182 if hasManifest { 183 cmd.UI.DisplayText("Applying manifest file {{.Path}}...", map[string]interface{}{ 184 "Path": transformedManifest.PathToManifest, 185 }) 186 } 187 188 v7ActionWarnings, err := cmd.VersionActor.SetSpaceManifest( 189 cmd.Config.TargetedSpace().GUID, 190 transformedRawManifest, 191 ) 192 193 cmd.UI.DisplayWarnings(v7ActionWarnings) 194 if err != nil { 195 return err 196 } 197 if hasManifest { 198 cmd.UI.DisplayText("Manifest applied") 199 } 200 201 pushPlans, warnings, err := cmd.PushActor.CreatePushPlans( 202 cmd.Config.TargetedSpace().GUID, 203 cmd.Config.TargetedOrganization().GUID, 204 transformedManifest, 205 flagOverrides, 206 ) 207 cmd.UI.DisplayWarnings(warnings) 208 if err != nil { 209 return err 210 } 211 212 log.WithField("number of plans", len(pushPlans)).Debug("completed generating plan") 213 defer func() { 214 if cmd.stopStreamingFunc != nil { 215 cmd.stopStreamingFunc() 216 } 217 }() 218 219 for _, plan := range pushPlans { 220 log.WithField("app_name", plan.Application.Name).Info("actualizing") 221 eventStream := cmd.PushActor.Actualize(plan, cmd.ProgressBar) 222 err := cmd.eventStreamHandler(eventStream) 223 224 if cmd.shouldDisplaySummary(err) { 225 summaryErr := cmd.displayAppSummary(plan) 226 if summaryErr != nil { 227 return summaryErr 228 } 229 } 230 if err != nil { 231 return cmd.mapErr(plan.Application.Name, err) 232 } 233 } 234 235 return nil 236 } 237 238 func (cmd PushCommand) GetBaseManifest(flagOverrides v7pushaction.FlagOverrides) (manifestparser.Manifest, error) { 239 defaultManifest := manifestparser.Manifest{ 240 Applications: []manifestparser.Application{ 241 {Name: flagOverrides.AppName}, 242 }, 243 } 244 if cmd.NoManifest { 245 log.Debugf("No manifest given, generating manifest") 246 return defaultManifest, nil 247 } 248 249 log.Info("reading manifest if exists") 250 readPath := cmd.CWD 251 if flagOverrides.ManifestPath != "" { 252 log.WithField("manifestPath", flagOverrides.ManifestPath).Debug("reading '-f' provided manifest") 253 readPath = flagOverrides.ManifestPath 254 } 255 256 pathToManifest, exists, err := cmd.ManifestLocator.Path(readPath) 257 if err != nil { 258 return manifestparser.Manifest{}, err 259 } 260 261 if !exists { 262 log.Debugf("No manifest given, generating manifest") 263 return defaultManifest, nil 264 } 265 266 log.WithField("manifestPath", pathToManifest).Debug("path to manifest") 267 manifest, err := cmd.ManifestParser.InterpolateAndParse(pathToManifest, flagOverrides.PathsToVarsFiles, flagOverrides.Vars) 268 if err != nil { 269 log.Errorln("reading manifest:", err) 270 if _, ok := err.(*yaml.TypeError); ok { 271 return manifestparser.Manifest{}, errors.New(fmt.Sprintf("Unable to push app because manifest %s is not valid yaml.", pathToManifest)) 272 } 273 return manifestparser.Manifest{}, err 274 } 275 276 return manifest, nil 277 } 278 279 func (cmd PushCommand) GetDockerPassword(dockerUsername string, containsPrivateDockerImages bool) (string, error) { 280 if dockerUsername == "" && !containsPrivateDockerImages { // no need for a password without a username 281 return "", nil 282 } 283 284 if cmd.Config.DockerPassword() == "" { 285 cmd.UI.DisplayText("Environment variable CF_DOCKER_PASSWORD not set.") 286 return cmd.UI.DisplayPasswordPrompt("Docker password") 287 } 288 289 cmd.UI.DisplayText("Using docker repository password from environment variable CF_DOCKER_PASSWORD.") 290 return cmd.Config.DockerPassword(), nil 291 } 292 293 func (cmd PushCommand) GetFlagOverrides() (v7pushaction.FlagOverrides, error) { 294 var pathsToVarsFiles []string 295 for _, varFilePath := range cmd.PathsToVarsFiles { 296 pathsToVarsFiles = append(pathsToVarsFiles, string(varFilePath)) 297 } 298 299 return v7pushaction.FlagOverrides{ 300 AppName: cmd.OptionalArgs.AppName, 301 Buildpacks: cmd.Buildpacks, 302 Stack: cmd.Stack, 303 Disk: cmd.Disk, 304 DropletPath: string(cmd.DropletPath), 305 DockerImage: cmd.DockerImage.Path, 306 DockerUsername: cmd.DockerUsername, 307 HealthCheckEndpoint: cmd.HealthCheckHTTPEndpoint, 308 HealthCheckType: cmd.HealthCheckType.Type, 309 HealthCheckTimeout: cmd.HealthCheckTimeout.Value, 310 Instances: cmd.Instances.NullInt, 311 Memory: cmd.Memory, 312 NoStart: cmd.NoStart, 313 NoWait: cmd.NoWait, 314 ProvidedAppPath: string(cmd.AppPath), 315 NoRoute: cmd.NoRoute, 316 RandomRoute: cmd.RandomRoute, 317 StartCommand: cmd.StartCommand.FilteredString, 318 Strategy: cmd.Strategy.Name, 319 ManifestPath: string(cmd.PathToManifest), 320 PathsToVarsFiles: pathsToVarsFiles, 321 Vars: cmd.Vars, 322 NoManifest: cmd.NoManifest, 323 Task: cmd.Task, 324 }, nil 325 } 326 327 func (cmd PushCommand) ValidateFlags() error { 328 switch { 329 case cmd.DockerUsername != "" && cmd.DockerImage.Path == "": 330 return translatableerror.RequiredFlagsError{ 331 Arg1: "--docker-image, -o", 332 Arg2: "--docker-username", 333 } 334 335 case cmd.DockerImage.Path != "" && cmd.Buildpacks != nil: 336 return translatableerror.ArgumentCombinationError{ 337 Args: []string{ 338 "--buildpack, -b", 339 "--docker-image, -o", 340 }, 341 } 342 343 case cmd.DockerImage.Path != "" && cmd.AppPath != "": 344 return translatableerror.ArgumentCombinationError{ 345 Args: []string{ 346 "--docker-image, -o", 347 "--path, -p", 348 }, 349 } 350 351 case cmd.DockerImage.Path != "" && cmd.Stack != "": 352 return translatableerror.ArgumentCombinationError{ 353 Args: []string{ 354 "--stack, -s", 355 "--docker-image, -o", 356 }, 357 } 358 359 case cmd.NoManifest && cmd.PathToManifest != "": 360 return translatableerror.ArgumentCombinationError{ 361 Args: []string{ 362 "--no-manifest", 363 "--manifest, -f", 364 }, 365 } 366 367 case cmd.NoManifest && len(cmd.PathsToVarsFiles) > 0: 368 return translatableerror.ArgumentCombinationError{ 369 Args: []string{ 370 "--no-manifest", 371 "--vars-file", 372 }, 373 } 374 375 case cmd.NoManifest && len(cmd.Vars) > 0: 376 return translatableerror.ArgumentCombinationError{ 377 Args: []string{ 378 "--no-manifest", 379 "--vars", 380 }, 381 } 382 383 case cmd.HealthCheckType.Type == constant.HTTP && cmd.HealthCheckHTTPEndpoint == "": 384 return translatableerror.RequiredFlagsError{ 385 Arg1: "--endpoint", 386 Arg2: "--health-check-type=http, -u=http", 387 } 388 389 case cmd.DropletPath != "" && (cmd.DockerImage.Path != "" || cmd.DockerUsername != "" || cmd.AppPath != ""): 390 return translatableerror.ArgumentCombinationError{ 391 Args: []string{ 392 "--droplet", 393 "--docker-image, -o", 394 "--docker-username", 395 "-p", 396 }, 397 } 398 399 case cmd.NoStart && cmd.Strategy == flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling}: 400 return translatableerror.ArgumentCombinationError{ 401 Args: []string{ 402 "--no-start", 403 "--strategy=rolling", 404 }, 405 } 406 407 case cmd.Task && cmd.Strategy == flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling}: 408 return translatableerror.ArgumentCombinationError{ 409 Args: []string{ 410 "--task", 411 "--strategy=rolling", 412 }, 413 } 414 415 case cmd.NoStart && cmd.NoWait: 416 return translatableerror.ArgumentCombinationError{ 417 Args: []string{ 418 "--no-start", 419 "--no-wait", 420 }, 421 } 422 423 case cmd.NoRoute && cmd.RandomRoute: 424 return translatableerror.ArgumentCombinationError{ 425 Args: []string{ 426 "--no-route", 427 "--random-route", 428 }, 429 } 430 case !cmd.validBuildpacks(): 431 return translatableerror.InvalidBuildpacksError{} 432 } 433 434 return nil 435 } 436 437 func (cmd PushCommand) validBuildpacks() bool { 438 for _, buildpack := range cmd.Buildpacks { 439 if (buildpack == "null" || buildpack == "default") && len(cmd.Buildpacks) > 1 { 440 return false 441 } 442 } 443 return true 444 } 445 446 func (cmd PushCommand) shouldDisplaySummary(err error) bool { 447 if err == nil { 448 return true 449 } 450 _, ok := err.(actionerror.AllInstancesCrashedError) 451 return ok 452 } 453 454 func (cmd PushCommand) mapErr(appName string, err error) error { 455 switch err.(type) { 456 case actionerror.AllInstancesCrashedError: 457 return translatableerror.ApplicationUnableToStartError{ 458 AppName: appName, 459 BinaryName: cmd.Config.BinaryName(), 460 } 461 case actionerror.StartupTimeoutError: 462 return translatableerror.StartupTimeoutError{ 463 AppName: appName, 464 BinaryName: cmd.Config.BinaryName(), 465 } 466 } 467 return err 468 } 469 470 func (cmd PushCommand) announcePushing(appNames []string, user configv3.User) { 471 tokens := map[string]interface{}{ 472 "AppName": strings.Join(appNames, ", "), 473 "OrgName": cmd.Config.TargetedOrganization().Name, 474 "SpaceName": cmd.Config.TargetedSpace().Name, 475 "Username": user.Name, 476 } 477 singular := "Pushing app {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..." 478 plural := "Pushing apps {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..." 479 480 if len(appNames) == 1 { 481 cmd.UI.DisplayTextWithFlavor(singular, tokens) 482 } else { 483 cmd.UI.DisplayTextWithFlavor(plural, tokens) 484 } 485 } 486 487 func (cmd PushCommand) displayAppSummary(plan v7pushaction.PushPlan) error { 488 log.Info("getting application summary info") 489 summary, warnings, err := cmd.VersionActor.GetDetailedAppSummary( 490 plan.Application.Name, 491 cmd.Config.TargetedSpace().GUID, 492 true, 493 ) 494 cmd.UI.DisplayWarnings(warnings) 495 if err != nil { 496 return err 497 } 498 cmd.UI.DisplayNewline() 499 appSummaryDisplayer := shared.NewAppSummaryDisplayer(cmd.UI) 500 appSummaryDisplayer.AppDisplay(summary, true) 501 return nil 502 } 503 504 func (cmd *PushCommand) eventStreamHandler(eventStream <-chan *v7pushaction.PushEvent) error { 505 for event := range eventStream { 506 cmd.UI.DisplayWarnings(event.Warnings) 507 if event.Err != nil { 508 return event.Err 509 } 510 err := cmd.processEvent(event.Event, event.Plan.Application.Name) 511 if err != nil { 512 return err 513 } 514 } 515 return nil 516 } 517 518 func (cmd *PushCommand) processEvent(event v7pushaction.Event, appName string) error { 519 switch event { 520 case v7pushaction.CreatingArchive: 521 cmd.UI.DisplayText("Packaging files to upload...") 522 case v7pushaction.UploadingApplicationWithArchive: 523 cmd.UI.DisplayText("Uploading files...") 524 log.Debug("starting progress bar") 525 cmd.ProgressBar.Ready() 526 case v7pushaction.UploadingApplication: 527 cmd.UI.DisplayText("All files found in remote cache; nothing to upload.") 528 cmd.UI.DisplayText("Waiting for API to complete processing files...") 529 case v7pushaction.RetryUpload: 530 cmd.UI.DisplayText("Retrying upload due to an error...") 531 case v7pushaction.UploadWithArchiveComplete: 532 cmd.ProgressBar.Complete() 533 cmd.UI.DisplayNewline() 534 cmd.UI.DisplayText("Waiting for API to complete processing files...") 535 case v7pushaction.UploadingDroplet: 536 cmd.UI.DisplayText("Uploading droplet bits...") 537 cmd.ProgressBar.Ready() 538 case v7pushaction.UploadDropletComplete: 539 cmd.ProgressBar.Complete() 540 cmd.UI.DisplayNewline() 541 cmd.UI.DisplayText("Waiting for API to complete processing files...") 542 case v7pushaction.StoppingApplication: 543 cmd.UI.DisplayText("Stopping Application...") 544 case v7pushaction.StoppingApplicationComplete: 545 cmd.UI.DisplayText("Application Stopped") 546 case v7pushaction.ApplyManifest: 547 cmd.UI.DisplayText("Applying manifest...") 548 case v7pushaction.ApplyManifestComplete: 549 cmd.UI.DisplayText("Manifest applied") 550 case v7pushaction.StartingStaging: 551 cmd.UI.DisplayNewline() 552 cmd.UI.DisplayText("Staging app and tracing logs...") 553 logStream, errStream, cancelFunc, warnings, err := cmd.VersionActor.GetStreamingLogsForApplicationByNameAndSpace(appName, cmd.Config.TargetedSpace().GUID, cmd.LogCacheClient) 554 cmd.UI.DisplayWarnings(warnings) 555 if err != nil { 556 return err 557 } 558 if cmd.stopStreamingFunc != nil { 559 cmd.stopStreamingFunc() 560 } 561 cmd.stopStreamingFunc = cancelFunc 562 go cmd.getLogs(logStream, errStream) 563 case v7pushaction.StagingComplete: 564 if cmd.stopStreamingFunc != nil { 565 cmd.stopStreamingFunc() 566 cmd.stopStreamingFunc = nil 567 } 568 case v7pushaction.RestartingApplication: 569 cmd.UI.DisplayNewline() 570 cmd.UI.DisplayTextWithFlavor( 571 "Waiting for app {{.AppName}} to start...", 572 map[string]interface{}{ 573 "AppName": appName, 574 }, 575 ) 576 cmd.UI.DisplayNewline() 577 case v7pushaction.StartingDeployment: 578 cmd.UI.DisplayNewline() 579 cmd.UI.DisplayTextWithFlavor( 580 "Starting deployment for app {{.AppName}}...", 581 map[string]interface{}{ 582 "AppName": appName, 583 }, 584 ) 585 case v7pushaction.WaitingForDeployment: 586 cmd.UI.DisplayText("Waiting for app to deploy...") 587 cmd.UI.DisplayNewline() 588 default: 589 log.WithField("event", event).Debug("ignoring event") 590 } 591 592 return nil 593 } 594 595 func (cmd PushCommand) getLogs(logStream <-chan sharedaction.LogMessage, errStream <-chan error) { 596 for { 597 select { 598 case logMessage, open := <-logStream: 599 if !open { 600 return 601 } 602 if logMessage.Staging() { 603 cmd.UI.DisplayLogMessage(logMessage, false) 604 } 605 case err, open := <-errStream: 606 if !open { 607 return 608 } 609 _, ok := err.(actionerror.LogCacheTimeoutError) 610 if ok { 611 cmd.UI.DisplayWarning("timeout connecting to log server, no log will be shown") 612 } 613 cmd.UI.DisplayWarning("Failed to retrieve logs from Log Cache: " + err.Error()) 614 } 615 } 616 }