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