github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/command/v7/push_command.go (about)

     1  package v7
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"strings"
     7  
     8  	"code.cloudfoundry.org/cli/actor/actionerror"
     9  	"code.cloudfoundry.org/cli/actor/sharedaction"
    10  	"code.cloudfoundry.org/cli/actor/v7action"
    11  	"code.cloudfoundry.org/cli/actor/v7pushaction"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    13  	"code.cloudfoundry.org/cli/command"
    14  	"code.cloudfoundry.org/cli/command/flag"
    15  	"code.cloudfoundry.org/cli/command/translatableerror"
    16  	"code.cloudfoundry.org/cli/command/v7/shared"
    17  	"code.cloudfoundry.org/cli/util/configv3"
    18  	"code.cloudfoundry.org/cli/util/manifestparser"
    19  	"code.cloudfoundry.org/cli/util/progressbar"
    20  	"code.cloudfoundry.org/clock"
    21  	"github.com/cloudfoundry/bosh-cli/director/template"
    22  	log "github.com/sirupsen/logrus"
    23  )
    24  
    25  //go:generate counterfeiter . ProgressBar
    26  
    27  type ProgressBar interface {
    28  	v7pushaction.ProgressBar
    29  	Complete()
    30  	Ready()
    31  }
    32  
    33  //go:generate counterfeiter . PushActor
    34  
    35  type PushActor interface {
    36  	HandleFlagOverrides(baseManifest manifestparser.Manifest, flagOverrides v7pushaction.FlagOverrides) (manifestparser.Manifest, error)
    37  	CreatePushPlans(spaceGUID string, orgGUID string, manifest manifestparser.Manifest, overrides v7pushaction.FlagOverrides) ([]v7pushaction.PushPlan, v7action.Warnings, error)
    38  	// Actualize applies any necessary changes.
    39  	Actualize(plan v7pushaction.PushPlan, progressBar v7pushaction.ProgressBar) <-chan *v7pushaction.PushEvent
    40  }
    41  
    42  //go:generate counterfeiter . V7ActorForPush
    43  
    44  type V7ActorForPush interface {
    45  	AppActor
    46  	SetSpaceManifest(spaceGUID string, rawManifest []byte) (v7action.Warnings, error)
    47  	GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client v7action.LogCacheClient) (<-chan v7action.LogMessage, <-chan error, context.CancelFunc, v7action.Warnings, error)
    48  	RestartApplication(appGUID string, noWait bool) (v7action.Warnings, error)
    49  }
    50  
    51  //go:generate counterfeiter . ManifestParser
    52  
    53  type ManifestParser interface {
    54  	InterpolateAndParse(pathToManifest string, pathsToVarsFiles []string, vars []template.VarKV) (manifestparser.Manifest, error)
    55  	MarshalManifest(manifest manifestparser.Manifest) ([]byte, error)
    56  }
    57  
    58  //go:generate counterfeiter . ManifestLocator
    59  
    60  type ManifestLocator interface {
    61  	Path(filepathOrDirectory string) (string, bool, error)
    62  }
    63  
    64  type PushCommand struct {
    65  	OptionalArgs            flag.OptionalAppName                `positional-args:"yes"`
    66  	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"`
    67  	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'"`
    68  	Disk                    string                              `long:"disk" short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"`
    69  	DockerImage             flag.DockerImage                    `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"`
    70  	DockerUsername          string                              `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"`
    71  	DropletPath             flag.PathWithExistenceCheck         `long:"droplet" description:"Path to a tgz file with a pre-staged app"`
    72  	HealthCheckHTTPEndpoint string                              `long:"endpoint"  description:"Valid path on the app for an HTTP health check. Only used when specifying --health-check-type=http"`
    73  	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'."`
    74  	Instances               flag.Instances                      `long:"instances" short:"i" description:"Number of instances"`
    75  	PathToManifest          flag.ManifestPathWithExistenceCheck `long:"manifest" short:"f" description:"Path to manifest"`
    76  	Memory                  string                              `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"`
    77  	NoManifest              bool                                `long:"no-manifest" description:"Ignore manifest file"`
    78  	NoRoute                 bool                                `long:"no-route" description:"Do not map a route to this app"`
    79  	NoStart                 bool                                `long:"no-start" description:"Do not stage and start the app after pushing"`
    80  	NoWait                  bool                                `long:"no-wait" description:"Do not wait for the long-running operation to complete; push exits when one instance of the web process is healthy"`
    81  	AppPath                 flag.PathWithExistenceCheck         `long:"path" short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"`
    82  	RandomRoute             bool                                `long:"random-route" description:"Create a random route for this app (except when no-route is specified in the manifest)"`
    83  	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)"`
    84  	StartCommand            flag.Command                        `long:"start-command" short:"c" description:"Startup command, set to null to reset to default start command"`
    85  	Strategy                flag.DeploymentStrategy             `long:"strategy" description:"Deployment strategy, either rolling or null."`
    86  	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."`
    87  	Vars                    []template.VarKV                    `long:"var" description:"Variable key value pair for variable substitution, (e.g., name=app1); can specify multiple times"`
    88  	PathsToVarsFiles        []flag.PathWithExistenceCheck       `long:"vars-file" description:"Path to a variable substitution file for manifest; can specify multiple times"`
    89  	dockerPassword          interface{}                         `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"`
    90  	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]..."`
    91  	envCFStagingTimeout     interface{}                         `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for staging, in minutes" environmentDefault:"15"`
    92  	envCFStartupTimeout     interface{}                         `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"`
    93  
    94  	Config          command.Config
    95  	UI              command.UI
    96  	LogCacheClient  v7action.LogCacheClient
    97  	Actor           PushActor
    98  	VersionActor    V7ActorForPush
    99  	SharedActor     command.SharedActor
   100  	ProgressBar     ProgressBar
   101  	CWD             string
   102  	ManifestLocator ManifestLocator
   103  	ManifestParser  ManifestParser
   104  
   105  	stopStreamingFunc func()
   106  }
   107  
   108  func (cmd *PushCommand) Setup(config command.Config, ui command.UI) error {
   109  	cmd.Config = config
   110  	cmd.UI = ui
   111  	cmd.ProgressBar = progressbar.NewProgressBar()
   112  
   113  	sharedActor := sharedaction.NewActor(config)
   114  	cmd.SharedActor = sharedActor
   115  
   116  	ccClient, uaaClient, err := shared.GetNewClientsAndConnectToCF(config, ui, "")
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	v7actor := v7action.NewActor(ccClient, config, sharedActor, uaaClient, clock.NewClock())
   122  	cmd.VersionActor = v7actor
   123  	cmd.Actor = v7pushaction.NewActor(v7actor, sharedActor)
   124  
   125  	cmd.LogCacheClient = shared.NewLogCacheClient(ccClient.Info.LogCache(), 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.Actor.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.Actor.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.Actor.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  		return manifestparser.Manifest{}, err
   271  	}
   272  
   273  	return manifest, nil
   274  }
   275  
   276  func (cmd PushCommand) GetDockerPassword(dockerUsername string, containsPrivateDockerImages bool) (string, error) {
   277  	if dockerUsername == "" && !containsPrivateDockerImages { // no need for a password without a username
   278  		return "", nil
   279  	}
   280  
   281  	if cmd.Config.DockerPassword() == "" {
   282  		cmd.UI.DisplayText("Environment variable CF_DOCKER_PASSWORD not set.")
   283  		return cmd.UI.DisplayPasswordPrompt("Docker password")
   284  	}
   285  
   286  	cmd.UI.DisplayText("Using docker repository password from environment variable CF_DOCKER_PASSWORD.")
   287  	return cmd.Config.DockerPassword(), nil
   288  }
   289  
   290  func (cmd PushCommand) GetFlagOverrides() (v7pushaction.FlagOverrides, error) {
   291  	var pathsToVarsFiles []string
   292  	for _, varFilePath := range cmd.PathsToVarsFiles {
   293  		pathsToVarsFiles = append(pathsToVarsFiles, string(varFilePath))
   294  	}
   295  
   296  	return v7pushaction.FlagOverrides{
   297  		AppName:             cmd.OptionalArgs.AppName,
   298  		Buildpacks:          cmd.Buildpacks,
   299  		Stack:               cmd.Stack,
   300  		Disk:                cmd.Disk,
   301  		DropletPath:         string(cmd.DropletPath),
   302  		DockerImage:         cmd.DockerImage.Path,
   303  		DockerUsername:      cmd.DockerUsername,
   304  		HealthCheckEndpoint: cmd.HealthCheckHTTPEndpoint,
   305  		HealthCheckType:     cmd.HealthCheckType.Type,
   306  		HealthCheckTimeout:  cmd.HealthCheckTimeout.Value,
   307  		Instances:           cmd.Instances.NullInt,
   308  		Memory:              cmd.Memory,
   309  		NoStart:             cmd.NoStart,
   310  		NoWait:              cmd.NoWait,
   311  		ProvidedAppPath:     string(cmd.AppPath),
   312  		NoRoute:             cmd.NoRoute,
   313  		RandomRoute:         cmd.RandomRoute,
   314  		StartCommand:        cmd.StartCommand.FilteredString,
   315  		Strategy:            cmd.Strategy.Name,
   316  		ManifestPath:        string(cmd.PathToManifest),
   317  		PathsToVarsFiles:    pathsToVarsFiles,
   318  		Vars:                cmd.Vars,
   319  		NoManifest:          cmd.NoManifest,
   320  		Task:                cmd.Task,
   321  	}, nil
   322  }
   323  
   324  func (cmd PushCommand) ValidateFlags() error {
   325  	switch {
   326  	case cmd.DockerUsername != "" && cmd.DockerImage.Path == "":
   327  		return translatableerror.RequiredFlagsError{
   328  			Arg1: "--docker-image, -o",
   329  			Arg2: "--docker-username",
   330  		}
   331  
   332  	case cmd.DockerImage.Path != "" && cmd.Buildpacks != nil:
   333  		return translatableerror.ArgumentCombinationError{
   334  			Args: []string{
   335  				"--buildpack, -b",
   336  				"--docker-image, -o",
   337  			},
   338  		}
   339  
   340  	case cmd.DockerImage.Path != "" && cmd.AppPath != "":
   341  		return translatableerror.ArgumentCombinationError{
   342  			Args: []string{
   343  				"--docker-image, -o",
   344  				"--path, -p",
   345  			},
   346  		}
   347  
   348  	case cmd.DockerImage.Path != "" && cmd.Stack != "":
   349  		return translatableerror.ArgumentCombinationError{
   350  			Args: []string{
   351  				"--stack, -s",
   352  				"--docker-image, -o",
   353  			},
   354  		}
   355  
   356  	case cmd.NoManifest && cmd.PathToManifest != "":
   357  		return translatableerror.ArgumentCombinationError{
   358  			Args: []string{
   359  				"--no-manifest",
   360  				"--manifest, -f",
   361  			},
   362  		}
   363  
   364  	case cmd.NoManifest && len(cmd.PathsToVarsFiles) > 0:
   365  		return translatableerror.ArgumentCombinationError{
   366  			Args: []string{
   367  				"--no-manifest",
   368  				"--vars-file",
   369  			},
   370  		}
   371  
   372  	case cmd.NoManifest && len(cmd.Vars) > 0:
   373  		return translatableerror.ArgumentCombinationError{
   374  			Args: []string{
   375  				"--no-manifest",
   376  				"--vars",
   377  			},
   378  		}
   379  
   380  	case cmd.HealthCheckType.Type == constant.HTTP && cmd.HealthCheckHTTPEndpoint == "":
   381  		return translatableerror.RequiredFlagsError{
   382  			Arg1: "--endpoint",
   383  			Arg2: "--health-check-type=http, -u=http",
   384  		}
   385  
   386  	case cmd.DropletPath != "" && (cmd.DockerImage.Path != "" || cmd.DockerUsername != "" || cmd.AppPath != ""):
   387  		return translatableerror.ArgumentCombinationError{
   388  			Args: []string{
   389  				"--droplet",
   390  				"--docker-image, -o",
   391  				"--docker-username",
   392  				"-p",
   393  			},
   394  		}
   395  
   396  	case cmd.NoStart && cmd.Strategy == flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling}:
   397  		return translatableerror.ArgumentCombinationError{
   398  			Args: []string{
   399  				"--no-start",
   400  				"--strategy=rolling",
   401  			},
   402  		}
   403  
   404  	case cmd.Task && cmd.Strategy == flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling}:
   405  		return translatableerror.ArgumentCombinationError{
   406  			Args: []string{
   407  				"--task",
   408  				"--strategy=rolling",
   409  			},
   410  		}
   411  
   412  	case cmd.NoStart && cmd.NoWait:
   413  		return translatableerror.ArgumentCombinationError{
   414  			Args: []string{
   415  				"--no-start",
   416  				"--no-wait",
   417  			},
   418  		}
   419  
   420  	case cmd.NoRoute && cmd.RandomRoute:
   421  		return translatableerror.ArgumentCombinationError{
   422  			Args: []string{
   423  				"--no-route",
   424  				"--random-route",
   425  			},
   426  		}
   427  	case !cmd.validBuildpacks():
   428  		return translatableerror.InvalidBuildpacksError{}
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  func (cmd PushCommand) validBuildpacks() bool {
   435  	for _, buildpack := range cmd.Buildpacks {
   436  		if (buildpack == "null" || buildpack == "default") && len(cmd.Buildpacks) > 1 {
   437  			return false
   438  		}
   439  	}
   440  	return true
   441  }
   442  
   443  func (cmd PushCommand) shouldDisplaySummary(err error) bool {
   444  	if err == nil {
   445  		return true
   446  	}
   447  	_, ok := err.(actionerror.AllInstancesCrashedError)
   448  	return ok
   449  }
   450  
   451  func (cmd PushCommand) mapErr(appName string, err error) error {
   452  	switch err.(type) {
   453  	case actionerror.AllInstancesCrashedError:
   454  		return translatableerror.ApplicationUnableToStartError{
   455  			AppName:    appName,
   456  			BinaryName: cmd.Config.BinaryName(),
   457  		}
   458  	case actionerror.StartupTimeoutError:
   459  		return translatableerror.StartupTimeoutError{
   460  			AppName:    appName,
   461  			BinaryName: cmd.Config.BinaryName(),
   462  		}
   463  	}
   464  	return err
   465  }
   466  
   467  func (cmd PushCommand) announcePushing(appNames []string, user configv3.User) {
   468  	tokens := map[string]interface{}{
   469  		"AppName":   strings.Join(appNames, ", "),
   470  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   471  		"SpaceName": cmd.Config.TargetedSpace().Name,
   472  		"Username":  user.Name,
   473  	}
   474  	singular := "Pushing app {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..."
   475  	plural := "Pushing apps {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..."
   476  
   477  	if len(appNames) == 1 {
   478  		cmd.UI.DisplayTextWithFlavor(singular, tokens)
   479  	} else {
   480  		cmd.UI.DisplayTextWithFlavor(plural, tokens)
   481  	}
   482  }
   483  
   484  func (cmd PushCommand) displayAppSummary(plan v7pushaction.PushPlan) error {
   485  	log.Info("getting application summary info")
   486  	summary, warnings, err := cmd.VersionActor.GetDetailedAppSummary(
   487  		plan.Application.Name,
   488  		cmd.Config.TargetedSpace().GUID,
   489  		true,
   490  	)
   491  	cmd.UI.DisplayWarnings(warnings)
   492  	if err != nil {
   493  		return err
   494  	}
   495  	cmd.UI.DisplayNewline()
   496  	appSummaryDisplayer := shared.NewAppSummaryDisplayer(cmd.UI)
   497  	appSummaryDisplayer.AppDisplay(summary, true)
   498  	return nil
   499  }
   500  
   501  func (cmd *PushCommand) eventStreamHandler(eventStream <-chan *v7pushaction.PushEvent) error {
   502  	for event := range eventStream {
   503  		cmd.UI.DisplayWarnings(event.Warnings)
   504  		if event.Err != nil {
   505  			return event.Err
   506  		}
   507  		err := cmd.processEvent(event.Event, event.Plan.Application.Name)
   508  		if err != nil {
   509  			return err
   510  		}
   511  	}
   512  	return nil
   513  }
   514  
   515  func (cmd *PushCommand) processEvent(event v7pushaction.Event, appName string) error {
   516  	switch event {
   517  	case v7pushaction.CreatingArchive:
   518  		cmd.UI.DisplayText("Packaging files to upload...")
   519  	case v7pushaction.UploadingApplicationWithArchive:
   520  		cmd.UI.DisplayText("Uploading files...")
   521  		log.Debug("starting progress bar")
   522  		cmd.ProgressBar.Ready()
   523  	case v7pushaction.UploadingApplication:
   524  		cmd.UI.DisplayText("All files found in remote cache; nothing to upload.")
   525  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   526  	case v7pushaction.RetryUpload:
   527  		cmd.UI.DisplayText("Retrying upload due to an error...")
   528  	case v7pushaction.UploadWithArchiveComplete:
   529  		cmd.ProgressBar.Complete()
   530  		cmd.UI.DisplayNewline()
   531  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   532  	case v7pushaction.UploadingDroplet:
   533  		cmd.UI.DisplayText("Uploading droplet bits...")
   534  		cmd.ProgressBar.Ready()
   535  	case v7pushaction.UploadDropletComplete:
   536  		cmd.ProgressBar.Complete()
   537  		cmd.UI.DisplayNewline()
   538  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   539  	case v7pushaction.StoppingApplication:
   540  		cmd.UI.DisplayText("Stopping Application...")
   541  	case v7pushaction.StoppingApplicationComplete:
   542  		cmd.UI.DisplayText("Application Stopped")
   543  	case v7pushaction.ApplyManifest:
   544  		cmd.UI.DisplayText("Applying manifest...")
   545  	case v7pushaction.ApplyManifestComplete:
   546  		cmd.UI.DisplayText("Manifest applied")
   547  	case v7pushaction.StartingStaging:
   548  		cmd.UI.DisplayNewline()
   549  		cmd.UI.DisplayText("Staging app and tracing logs...")
   550  		logStream, errStream, cancelFunc, warnings, err := cmd.VersionActor.GetStreamingLogsForApplicationByNameAndSpace(appName, cmd.Config.TargetedSpace().GUID, cmd.LogCacheClient)
   551  		cmd.UI.DisplayWarnings(warnings)
   552  		if err != nil {
   553  			return err
   554  		}
   555  		if cmd.stopStreamingFunc != nil {
   556  			cmd.stopStreamingFunc()
   557  		}
   558  		cmd.stopStreamingFunc = cancelFunc
   559  		go cmd.getLogs(logStream, errStream)
   560  	case v7pushaction.StagingComplete:
   561  		if cmd.stopStreamingFunc != nil {
   562  			cmd.stopStreamingFunc()
   563  			cmd.stopStreamingFunc = nil
   564  		}
   565  	case v7pushaction.RestartingApplication:
   566  		cmd.UI.DisplayNewline()
   567  		cmd.UI.DisplayTextWithFlavor(
   568  			"Waiting for app {{.AppName}} to start...",
   569  			map[string]interface{}{
   570  				"AppName": appName,
   571  			},
   572  		)
   573  	case v7pushaction.StartingDeployment:
   574  		cmd.UI.DisplayNewline()
   575  		cmd.UI.DisplayTextWithFlavor(
   576  			"Starting deployment for app {{.AppName}}...",
   577  			map[string]interface{}{
   578  				"AppName": appName,
   579  			},
   580  		)
   581  	case v7pushaction.WaitingForDeployment:
   582  		cmd.UI.DisplayText("Waiting for app to deploy...")
   583  	default:
   584  		log.WithField("event", event).Debug("ignoring event")
   585  	}
   586  
   587  	return nil
   588  }
   589  
   590  func (cmd PushCommand) getLogs(logStream <-chan v7action.LogMessage, errStream <-chan error) {
   591  	for {
   592  		select {
   593  		case logMessage, open := <-logStream:
   594  			if !open {
   595  				return
   596  			}
   597  			if logMessage.Staging() {
   598  				cmd.UI.DisplayLogMessage(logMessage, false)
   599  			}
   600  		case err, open := <-errStream:
   601  			if !open {
   602  				return
   603  			}
   604  			_, ok := err.(actionerror.LogCacheTimeoutError)
   605  			if ok {
   606  				cmd.UI.DisplayWarning("timeout connecting to log server, no log will be shown")
   607  			}
   608  			cmd.UI.DisplayWarning(err.Error())
   609  		}
   610  	}
   611  }