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