github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+incompatible/command/v6/push_command.go (about)

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