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  }