github.com/ablease/cli@v6.37.1-0.20180613014814-3adbb7d7fb19+incompatible/command/v2/push_command.go (about)

     1  package v2
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  
     7  	"code.cloudfoundry.org/cli/actor/pushaction"
     8  	"code.cloudfoundry.org/cli/actor/sharedaction"
     9  	"code.cloudfoundry.org/cli/actor/v2action"
    10  	"code.cloudfoundry.org/cli/actor/v3action"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccversion"
    12  	"code.cloudfoundry.org/cli/command"
    13  	"code.cloudfoundry.org/cli/command/flag"
    14  	"code.cloudfoundry.org/cli/command/translatableerror"
    15  	"code.cloudfoundry.org/cli/command/v2/shared"
    16  	sharedV3 "code.cloudfoundry.org/cli/command/v3/shared"
    17  	"code.cloudfoundry.org/cli/util/configv3"
    18  	"code.cloudfoundry.org/cli/util/manifest"
    19  	"code.cloudfoundry.org/cli/util/progressbar"
    20  	"github.com/cloudfoundry/bosh-cli/director/template"
    21  	"github.com/cloudfoundry/noaa/consumer"
    22  	log "github.com/sirupsen/logrus"
    23  )
    24  
    25  //go:generate counterfeiter . ProgressBar
    26  
    27  type ProgressBar interface {
    28  	pushaction.ProgressBar
    29  	Complete()
    30  	Ready()
    31  }
    32  
    33  //go:generate counterfeiter . V2PushActor
    34  
    35  type V2PushActor interface {
    36  	Apply(config pushaction.ApplicationConfig, progressBar pushaction.ProgressBar) (<-chan pushaction.ApplicationConfig, <-chan pushaction.Event, <-chan pushaction.Warnings, <-chan error)
    37  	CloudControllerV2APIVersion() string
    38  	CloudControllerV3APIVersion() string
    39  	ConvertToApplicationConfigs(orgGUID string, spaceGUID string, noStart bool, apps []manifest.Application) ([]pushaction.ApplicationConfig, pushaction.Warnings, error)
    40  	MergeAndValidateSettingsAndManifests(cmdSettings pushaction.CommandLineSettings, apps []manifest.Application) ([]manifest.Application, error)
    41  	ReadManifest(pathToManifest string, pathsToVarsFiles []string, vars []template.VarKV) ([]manifest.Application, pushaction.Warnings, error)
    42  }
    43  
    44  type V2PushCommand struct {
    45  	OptionalArgs        flag.OptionalAppName          `positional-args:"yes"`
    46  	Buildpacks          []string                      `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'"`
    47  	Command             flag.Command                  `short:"c" description:"Startup command, set to null to reset to default start command"`
    48  	Domain              string                        `short:"d" description:"Domain (e.g. example.com)"`
    49  	DockerImage         flag.DockerImage              `long:"docker-image" short:"o" description:"Docker-image to be used (e.g. user/docker-image-name)"`
    50  	DockerUsername      string                        `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"`
    51  	DropletPath         flag.PathWithExistenceCheck   `long:"droplet" description:"Path to a tgz file with a pre-staged app"`
    52  	PathToManifest      flag.PathWithExistenceCheck   `short:"f" description:"Path to manifest"`
    53  	HealthCheckType     flag.HealthCheckType          `long:"health-check-type" short:"u" description:"Application health check type (Default: 'port', 'none' accepted for 'process', 'http' implies endpoint '/')"`
    54  	Hostname            string                        `long:"hostname" short:"n" description:"Hostname (e.g. my-subdomain)"`
    55  	Instances           flag.Instances                `short:"i" description:"Number of instances"`
    56  	DiskQuota           flag.Megabytes                `short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"`
    57  	Memory              flag.Megabytes                `short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"`
    58  	NoHostname          bool                          `long:"no-hostname" description:"Map the root domain to this app"`
    59  	NoManifest          bool                          `long:"no-manifest" description:"Ignore manifest file"`
    60  	NoRoute             bool                          `long:"no-route" description:"Do not map a route to this app and remove routes from previous pushes of this app"`
    61  	NoStart             bool                          `long:"no-start" description:"Do not start an app after pushing"`
    62  	AppPath             flag.PathWithExistenceCheck   `short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"`
    63  	RandomRoute         bool                          `long:"random-route" description:"Create a random route for this app"`
    64  	RoutePath           flag.RoutePath                `long:"route-path" description:"Path for the route"`
    65  	StackName           string                        `short:"s" description:"Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)"`
    66  	VarsFilePaths       []flag.PathWithExistenceCheck `long:"vars-file" description:"Path to a variable substitution file for manifest; can specify multiple times"`
    67  	Vars                []template.VarKV              `long:"var" description:"Variable key value pair for variable substitution, (e.g., name=app1); can specify multiple times"`
    68  	HealthCheckTimeout  int                           `short:"t" description:"Time (in seconds) allowed to elapse between starting up an app and the first healthy response from the app"`
    69  	envCFStagingTimeout interface{}                   `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"`
    70  	envCFStartupTimeout interface{}                   `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"`
    71  	dockerPassword      interface{}                   `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"`
    72  
    73  	usage           interface{} `usage:"cf push APP_NAME [-b BUILDPACK_NAME] [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start]\n   [-i NUM_INSTANCES] [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [-u (process | port | http)]\n   [--no-route | --random-route | --hostname HOST | --no-hostname] [-d DOMAIN] [--route-path ROUTE_PATH] [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]...\n\n   cf push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME]\n   [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start]\n   [-i NUM_INSTANCES] [-k DISK] [-m MEMORY] [-t HEALTH_TIMEOUT] [-u (process | port | http)]\n   [--no-route | --random-route | --hostname HOST | --no-hostname] [-d DOMAIN] [--route-path ROUTE_PATH] [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]...\n\n   cf push APP_NAME --droplet DROPLET_PATH\n   [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start]\n   [-i NUM_INSTANCES] [-k DISK] [-m MEMORY] [-t HEALTH_TIMEOUT] [-u (process | port | http)]\n   [--no-route | --random-route | --hostname HOST | --no-hostname] [-d DOMAIN] [--route-path ROUTE_PATH] [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]...\n\n   cf push -f MANIFEST_WITH_MULTIPLE_APPS_PATH [APP_NAME] [--no-start]"`
    74  	relatedCommands interface{} `related_commands:"apps, create-app-manifest, logs, ssh, start"`
    75  
    76  	UI          command.UI
    77  	Config      command.Config
    78  	SharedActor command.SharedActor
    79  	Actor       V2PushActor
    80  	ProgressBar ProgressBar
    81  
    82  	RestartActor RestartActor
    83  	NOAAClient   *consumer.Consumer
    84  }
    85  
    86  func (cmd *V2PushCommand) Setup(config command.Config, ui command.UI) error {
    87  	cmd.UI = ui
    88  	cmd.Config = config
    89  	sharedActor := sharedaction.NewActor(config)
    90  
    91  	ccClient, uaaClient, err := shared.NewClients(config, ui, true)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	ccClientV3, _, err := sharedV3.NewClients(config, ui, true)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	v2Actor := v2action.NewActor(ccClient, uaaClient, config)
   102  	v3Actor := v3action.NewActor(ccClientV3, config, sharedActor, nil)
   103  
   104  	cmd.RestartActor = v2Actor
   105  	cmd.Actor = pushaction.NewActor(v2Actor, v3Actor, sharedActor)
   106  
   107  	cmd.SharedActor = sharedActor
   108  	cmd.NOAAClient = shared.NewNOAAClient(ccClient.DopplerEndpoint(), config, uaaClient, ui)
   109  
   110  	cmd.ProgressBar = progressbar.NewProgressBar()
   111  	return nil
   112  }
   113  
   114  func (cmd V2PushCommand) Execute(args []string) error {
   115  	if cmd.DropletPath != "" {
   116  		if err := command.MinimumAPIVersionCheck(cmd.Actor.CloudControllerV2APIVersion(), ccversion.MinVersionDropletUploadV2, "Option '--droplet'"); err != nil {
   117  			return err
   118  		}
   119  	}
   120  
   121  	if len(cmd.Buildpacks) > 1 {
   122  		if err := command.MinimumAPIVersionCheck(cmd.Actor.CloudControllerV3APIVersion(), ccversion.MinVersionManifestBuildpacksV3, "Multiple option '-b'"); err != nil {
   123  			return err
   124  		}
   125  	}
   126  
   127  	err := cmd.SharedActor.CheckTarget(true, true)
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	user, err := cmd.Config.CurrentUser()
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	log.Info("collating flags")
   138  	cliSettings, err := cmd.GetCommandLineSettings()
   139  	if err != nil {
   140  		log.Errorln("reading flags:", err)
   141  		return err
   142  	}
   143  
   144  	log.Info("checking manifest")
   145  	rawApps, err := cmd.findAndReadManifestWithFlavorText(cliSettings)
   146  	if err != nil {
   147  		log.Errorln("reading manifest:", err)
   148  		return err
   149  	}
   150  
   151  	log.Info("merging manifest and command flags")
   152  	manifestApplications, err := cmd.Actor.MergeAndValidateSettingsAndManifests(cliSettings, rawApps)
   153  	if err != nil {
   154  		log.Errorln("merging manifest:", err)
   155  		return err
   156  	}
   157  
   158  	for _, manifestApplication := range manifestApplications {
   159  		if len(manifestApplication.Buildpacks) > 0 {
   160  			if err = command.MinimumAPIVersionCheck(cmd.Actor.CloudControllerV3APIVersion(), ccversion.MinVersionManifestBuildpacksV3, "'buildpacks' in manifest"); err != nil {
   161  				return err
   162  			}
   163  		}
   164  	}
   165  
   166  	cmd.UI.DisplayText("Getting app info...")
   167  
   168  	log.Info("converting manifests to ApplicationConfigs")
   169  	appConfigs, warnings, err := cmd.Actor.ConvertToApplicationConfigs(
   170  		cmd.Config.TargetedOrganization().GUID,
   171  		cmd.Config.TargetedSpace().GUID,
   172  		cmd.NoStart,
   173  		manifestApplications,
   174  	)
   175  	cmd.UI.DisplayWarnings(warnings)
   176  	if err != nil {
   177  		log.Errorln("converting manifest:", err)
   178  		return err
   179  	}
   180  
   181  	for _, appConfig := range appConfigs {
   182  		if appConfig.CreatingApplication() {
   183  			cmd.UI.DisplayText("Creating app with these attributes...")
   184  		} else {
   185  			cmd.UI.DisplayText("Updating app with these attributes...")
   186  		}
   187  		log.Infoln("starting create/update:", appConfig.DesiredApplication.Name)
   188  		changes := shared.GetApplicationChanges(appConfig)
   189  		err := cmd.UI.DisplayChangesForPush(changes)
   190  		if err != nil {
   191  			log.Errorln("display changes:", err)
   192  			return err
   193  		}
   194  		cmd.UI.DisplayNewline()
   195  	}
   196  
   197  	for appNumber, appConfig := range appConfigs {
   198  		if appConfig.CreatingApplication() {
   199  			cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}}...", map[string]interface{}{
   200  				"AppName": appConfig.DesiredApplication.Name,
   201  			})
   202  		} else {
   203  			cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}}...", map[string]interface{}{
   204  				"AppName": appConfig.DesiredApplication.Name,
   205  			})
   206  		}
   207  
   208  		configStream, eventStream, warningsStream, errorStream := cmd.Actor.Apply(appConfig, cmd.ProgressBar)
   209  		updatedConfig, err := cmd.processApplyStreams(user, appConfig, configStream, eventStream, warningsStream, errorStream)
   210  		if err != nil {
   211  			log.Errorln("process apply stream:", err)
   212  			return err
   213  		}
   214  
   215  		if !cmd.NoStart {
   216  			messages, logErrs, appState, apiWarnings, errs := cmd.RestartActor.RestartApplication(updatedConfig.CurrentApplication.Application, cmd.NOAAClient)
   217  			err = shared.PollStart(cmd.UI, cmd.Config, messages, logErrs, appState, apiWarnings, errs)
   218  			if err != nil {
   219  				return err
   220  			}
   221  		}
   222  
   223  		cmd.UI.DisplayNewline()
   224  		appSummary, warnings, err := cmd.RestartActor.GetApplicationSummaryByNameAndSpace(appConfig.DesiredApplication.Name, cmd.Config.TargetedSpace().GUID)
   225  		cmd.UI.DisplayWarnings(warnings)
   226  		if err != nil {
   227  			return err
   228  		}
   229  
   230  		shared.DisplayAppSummary(cmd.UI, appSummary, true)
   231  
   232  		if appNumber+1 <= len(appConfigs) {
   233  			cmd.UI.DisplayNewline()
   234  		}
   235  	}
   236  
   237  	return nil
   238  }
   239  
   240  // GetCommandLineSettings generates a push CommandLineSettings object from the
   241  // command's command line flags. It also validates those settings, preventing
   242  // contradictory flags.
   243  func (cmd V2PushCommand) GetCommandLineSettings() (pushaction.CommandLineSettings, error) {
   244  	err := cmd.validateArgs()
   245  	if err != nil {
   246  		return pushaction.CommandLineSettings{}, err
   247  	}
   248  
   249  	pwd, err := os.Getwd()
   250  	if err != nil {
   251  		return pushaction.CommandLineSettings{}, err
   252  	}
   253  
   254  	dockerPassword := cmd.Config.DockerPassword()
   255  	if dockerPassword != "" {
   256  		cmd.UI.DisplayText("Using docker repository password from environment variable CF_DOCKER_PASSWORD.")
   257  	} else if cmd.DockerUsername != "" {
   258  		cmd.UI.DisplayText("Environment variable CF_DOCKER_PASSWORD not set.")
   259  		dockerPassword, err = cmd.UI.DisplayPasswordPrompt("Docker password")
   260  		if err != nil {
   261  			return pushaction.CommandLineSettings{}, err
   262  		}
   263  	}
   264  
   265  	config := pushaction.CommandLineSettings{
   266  		Buildpacks:           cmd.Buildpacks,             // -b
   267  		Command:              cmd.Command.FilteredString, // -c
   268  		CurrentDirectory:     pwd,
   269  		DefaultRouteDomain:   cmd.Domain,               // -d
   270  		DefaultRouteHostname: cmd.Hostname,             // -n/--hostname
   271  		DiskQuota:            cmd.DiskQuota.Value,      // -k
   272  		DockerImage:          cmd.DockerImage.Path,     // -o
   273  		DockerPassword:       dockerPassword,           // ENV - CF_DOCKER_PASSWORD
   274  		DockerUsername:       cmd.DockerUsername,       // --docker-username
   275  		DropletPath:          string(cmd.DropletPath),  // --droplet
   276  		HealthCheckTimeout:   cmd.HealthCheckTimeout,   // -t
   277  		HealthCheckType:      cmd.HealthCheckType.Type, // -u/--health-check-type
   278  		Instances:            cmd.Instances.NullInt,    // -i
   279  		Memory:               cmd.Memory.Value,         // -m
   280  		Name:                 cmd.OptionalArgs.AppName, // arg
   281  		NoHostname:           cmd.NoHostname,           // --no-hostname
   282  		NoRoute:              cmd.NoRoute,              // --no-route
   283  		ProvidedAppPath:      string(cmd.AppPath),      // -p
   284  		RandomRoute:          cmd.RandomRoute,          // --random-route
   285  		RoutePath:            cmd.RoutePath.Path,       // --route-path
   286  		StackName:            cmd.StackName,            // -s
   287  	}
   288  
   289  	log.Debugln("Command Line Settings:", config)
   290  	return config, nil
   291  }
   292  
   293  func (cmd V2PushCommand) findAndReadManifestWithFlavorText(settings pushaction.CommandLineSettings) ([]manifest.Application, error) {
   294  	var (
   295  		pathToManifest string
   296  	)
   297  	switch {
   298  	case cmd.NoManifest:
   299  		log.Debug("skipping reading of manifest")
   300  	case cmd.PathToManifest != "":
   301  		log.WithField("file", cmd.PathToManifest).Debug("using specified manifest file")
   302  		pathToManifest = string(cmd.PathToManifest)
   303  
   304  		fileInfo, err := os.Stat(pathToManifest)
   305  		if err != nil {
   306  			return nil, err
   307  		}
   308  
   309  		if fileInfo.IsDir() {
   310  			manifestPaths := []string{
   311  				filepath.Join(pathToManifest, "manifest.yml"),
   312  				filepath.Join(pathToManifest, "manifest.yaml"),
   313  			}
   314  			for _, manifestPath := range manifestPaths {
   315  				if _, err = os.Stat(manifestPath); err == nil {
   316  					pathToManifest = manifestPath
   317  					break
   318  				}
   319  			}
   320  		}
   321  
   322  		if err != nil {
   323  			return nil, translatableerror.ManifestFileNotFoundInDirectoryError{
   324  				PathToManifest: pathToManifest,
   325  			}
   326  		}
   327  	default:
   328  		log.Debug("searching for manifest file")
   329  		pathToManifest = filepath.Join(settings.CurrentDirectory, "manifest.yml")
   330  		if _, err := os.Stat(pathToManifest); os.IsNotExist(err) {
   331  			log.WithField("pathToManifest", pathToManifest).Debug("could not find")
   332  
   333  			// While this is unlikely to be used, it is kept for backwards
   334  			// compatibility.
   335  			pathToManifest = filepath.Join(settings.CurrentDirectory, "manifest.yaml")
   336  			if _, err := os.Stat(pathToManifest); os.IsNotExist(err) {
   337  				log.WithField("pathToManifest", pathToManifest).Debug("could not find")
   338  				pathToManifest = ""
   339  			}
   340  		}
   341  	}
   342  
   343  	user, err := cmd.Config.CurrentUser()
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  
   348  	if pathToManifest == "" {
   349  		cmd.UI.DisplayTextWithFlavor("Pushing app {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   350  			"AppName":   settings.Name,
   351  			"OrgName":   cmd.Config.TargetedOrganization().Name,
   352  			"SpaceName": cmd.Config.TargetedSpace().Name,
   353  			"Username":  user.Name,
   354  		})
   355  		return nil, nil
   356  	}
   357  
   358  	var pathsToVarsFiles []string
   359  	for _, path := range cmd.VarsFilePaths {
   360  		pathsToVarsFiles = append(pathsToVarsFiles, string(path))
   361  	}
   362  
   363  	cmd.UI.DisplayTextWithFlavor("Pushing from manifest to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   364  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   365  		"SpaceName": cmd.Config.TargetedSpace().Name,
   366  		"Username":  user.Name,
   367  	})
   368  	log.WithField("pathToManifest", pathToManifest).Info("reading manifest")
   369  	cmd.UI.DisplayText("Using manifest file {{.Path}}", map[string]interface{}{
   370  		"Path": pathToManifest,
   371  	})
   372  
   373  	apps, warnings, err := cmd.Actor.ReadManifest(pathToManifest, pathsToVarsFiles, cmd.Vars)
   374  	cmd.UI.DisplayWarnings(warnings)
   375  
   376  	return apps, err
   377  }
   378  
   379  func (cmd V2PushCommand) processApplyStreams(
   380  	user configv3.User,
   381  	appConfig pushaction.ApplicationConfig,
   382  	configStream <-chan pushaction.ApplicationConfig,
   383  	eventStream <-chan pushaction.Event,
   384  	warningsStream <-chan pushaction.Warnings,
   385  	errorStream <-chan error,
   386  ) (pushaction.ApplicationConfig, error) {
   387  	var configClosed, eventClosed, warningsClosed, complete bool
   388  	var updatedConfig pushaction.ApplicationConfig
   389  
   390  	for {
   391  		select {
   392  		case config, ok := <-configStream:
   393  			if !ok {
   394  				log.Debug("processing config stream closed")
   395  				configClosed = true
   396  				break
   397  			}
   398  			updatedConfig = config
   399  			log.Debugf("updated config received: %#v", updatedConfig)
   400  		case event, ok := <-eventStream:
   401  			if !ok {
   402  				log.Debug("processing event stream closed")
   403  				eventClosed = true
   404  				break
   405  			}
   406  			complete = cmd.processEvent(user, appConfig, event)
   407  		case warnings, ok := <-warningsStream:
   408  			if !ok {
   409  				log.Debug("processing warnings stream closed")
   410  				warningsClosed = true
   411  				break
   412  			}
   413  			cmd.UI.DisplayWarnings(warnings)
   414  		case err, ok := <-errorStream:
   415  			if !ok {
   416  				log.Debug("processing error stream closed")
   417  				warningsClosed = true
   418  				break
   419  			}
   420  			return pushaction.ApplicationConfig{}, err
   421  		}
   422  
   423  		if configClosed && eventClosed && warningsClosed && complete {
   424  			log.Debug("breaking apply display loop")
   425  			break
   426  		}
   427  	}
   428  
   429  	return updatedConfig, nil
   430  }
   431  
   432  func (cmd V2PushCommand) processEvent(user configv3.User, appConfig pushaction.ApplicationConfig, event pushaction.Event) bool {
   433  	log.Infoln("received apply event:", event)
   434  
   435  	switch event {
   436  	case pushaction.CreatingAndMappingRoutes:
   437  		cmd.UI.DisplayText("Mapping routes...")
   438  	case pushaction.UnmappingRoutes:
   439  		cmd.UI.DisplayText("Unmapping routes...")
   440  	case pushaction.ConfiguringServices:
   441  		cmd.UI.DisplayText("Binding services...")
   442  	case pushaction.ResourceMatching:
   443  		cmd.UI.DisplayText("Comparing local files to remote cache...")
   444  	case pushaction.CreatingArchive:
   445  		cmd.UI.DisplayText("Packaging files to upload...")
   446  	case pushaction.UploadingApplication:
   447  		cmd.UI.DisplayText("All files found in remote cache; nothing to upload.")
   448  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   449  	case pushaction.UploadingDroplet:
   450  		cmd.UI.DisplayText("Uploading droplet...")
   451  		log.Debug("starting progress bar")
   452  		cmd.ProgressBar.Ready()
   453  	case pushaction.UploadingApplicationWithArchive:
   454  		cmd.UI.DisplayText("Uploading files...")
   455  		log.Debug("starting progress bar")
   456  		cmd.ProgressBar.Ready()
   457  	case pushaction.RetryUpload:
   458  		cmd.UI.DisplayText("Retrying upload due to an error...")
   459  	case pushaction.UploadWithArchiveComplete:
   460  		cmd.ProgressBar.Complete()
   461  		cmd.UI.DisplayNewline()
   462  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   463  	case pushaction.UploadDropletComplete:
   464  		cmd.ProgressBar.Complete()
   465  		cmd.UI.DisplayNewline()
   466  		cmd.UI.DisplayText("Waiting for API to complete processing droplet...")
   467  	case pushaction.Complete:
   468  		return true
   469  	default:
   470  		log.WithField("event", event).Debug("ignoring event")
   471  	}
   472  	return false
   473  }
   474  
   475  func (cmd V2PushCommand) validateArgs() error {
   476  	switch {
   477  	case cmd.DropletPath != "" && cmd.AppPath != "":
   478  		return translatableerror.ArgumentCombinationError{
   479  			Args: []string{"--droplet", "-p"},
   480  		}
   481  	case cmd.DropletPath != "" && cmd.DockerImage.Path != "":
   482  		return translatableerror.ArgumentCombinationError{
   483  			Args: []string{"--droplet", "--docker-image", "-o"},
   484  		}
   485  	// ArgumentCombinationError trumps RequiredArgsError in this case (when
   486  	// DockerImage unspecified)
   487  	case cmd.DropletPath != "" && cmd.DockerUsername != "":
   488  		return translatableerror.ArgumentCombinationError{
   489  			Args: []string{"--droplet", "--docker-username", "-p"},
   490  		}
   491  	case cmd.DockerImage.Path != "" && cmd.AppPath != "":
   492  		return translatableerror.ArgumentCombinationError{
   493  			Args: []string{"--docker-image, -o", "-p"},
   494  		}
   495  	case cmd.DockerImage.Path != "" && cmd.Buildpacks != nil:
   496  		return translatableerror.ArgumentCombinationError{
   497  			Args: []string{"-b", "--docker-image, -o"},
   498  		}
   499  	case cmd.DockerUsername != "" && cmd.DockerImage.Path == "":
   500  		return translatableerror.RequiredFlagsError{
   501  			Arg1: "--docker-image, -o",
   502  			Arg2: "--docker-username",
   503  		}
   504  	case cmd.Domain != "" && cmd.NoRoute:
   505  		return translatableerror.ArgumentCombinationError{
   506  			Args: []string{"-d", "--no-route"},
   507  		}
   508  	case cmd.Hostname != "" && cmd.NoHostname:
   509  		return translatableerror.ArgumentCombinationError{
   510  			Args: []string{"--hostname", "-n", "--no-hostname"},
   511  		}
   512  	case cmd.Hostname != "" && cmd.NoRoute:
   513  		return translatableerror.ArgumentCombinationError{
   514  			Args: []string{"--hostname", "-n", "--no-route"},
   515  		}
   516  	case cmd.NoHostname && cmd.NoRoute:
   517  		return translatableerror.ArgumentCombinationError{
   518  			Args: []string{"--no-hostname", "--no-route"},
   519  		}
   520  	case cmd.PathToManifest != "" && cmd.NoManifest:
   521  		return translatableerror.ArgumentCombinationError{
   522  			Args: []string{"-f", "--no-manifest"},
   523  		}
   524  	case cmd.RandomRoute && cmd.Hostname != "":
   525  		return translatableerror.ArgumentCombinationError{
   526  			Args: []string{"--hostname", "-n", "--random-route"},
   527  		}
   528  	case cmd.RandomRoute && cmd.NoHostname:
   529  		return translatableerror.ArgumentCombinationError{
   530  			Args: []string{"--no-hostname", "--random-route"},
   531  		}
   532  	case cmd.RandomRoute && cmd.NoRoute:
   533  		return translatableerror.ArgumentCombinationError{
   534  			Args: []string{"--no-route", "--random-route"},
   535  		}
   536  	case cmd.RandomRoute && cmd.RoutePath.Path != "":
   537  		return translatableerror.ArgumentCombinationError{
   538  			Args: []string{"--random-route", "--route-path"},
   539  		}
   540  	case cmd.RoutePath.Path != "" && cmd.NoRoute:
   541  		return translatableerror.ArgumentCombinationError{
   542  			Args: []string{"--route-path", "--no-route"},
   543  		}
   544  	}
   545  
   546  	return nil
   547  }