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