github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+incompatible/command/v7/push_command.go (about)

     1  package v7
     2  
     3  import (
     4  	"os"
     5  	"strings"
     6  
     7  	"code.cloudfoundry.org/cli/command/v7/shared"
     8  	"code.cloudfoundry.org/cli/util/configv3"
     9  
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    11  
    12  	"code.cloudfoundry.org/cli/actor/actionerror"
    13  	"code.cloudfoundry.org/cli/actor/sharedaction"
    14  	"code.cloudfoundry.org/cli/actor/v2action"
    15  	"code.cloudfoundry.org/cli/actor/v3action"
    16  	"code.cloudfoundry.org/cli/actor/v7action"
    17  	"code.cloudfoundry.org/cli/actor/v7pushaction"
    18  	"code.cloudfoundry.org/cli/command"
    19  	"code.cloudfoundry.org/cli/command/flag"
    20  	"code.cloudfoundry.org/cli/command/translatableerror"
    21  	v6shared "code.cloudfoundry.org/cli/command/v6/shared"
    22  	"code.cloudfoundry.org/cli/util/manifestparser"
    23  	"code.cloudfoundry.org/cli/util/progressbar"
    24  
    25  	"github.com/cloudfoundry/bosh-cli/director/template"
    26  	log "github.com/sirupsen/logrus"
    27  )
    28  
    29  //go:generate counterfeiter . ProgressBar
    30  
    31  type ProgressBar interface {
    32  	v7pushaction.ProgressBar
    33  	Complete()
    34  	Ready()
    35  }
    36  
    37  //go:generate counterfeiter . PushActor
    38  
    39  type PushActor interface {
    40  	CreatePushPlans(appNameArg string, spaceGUID string, orgGUID string, parser v7pushaction.ManifestParser, overrides v7pushaction.FlagOverrides) ([]v7pushaction.PushPlan, error)
    41  	// Prepare the space by creating needed apps/applying the manifest
    42  	PrepareSpace(pushPlans []v7pushaction.PushPlan, parser v7pushaction.ManifestParser) (<-chan []v7pushaction.PushPlan, <-chan v7pushaction.Event, <-chan v7pushaction.Warnings, <-chan error)
    43  	// UpdateApplicationSettings figures out the state of the world.
    44  	UpdateApplicationSettings(pushPlans []v7pushaction.PushPlan) ([]v7pushaction.PushPlan, v7pushaction.Warnings, error)
    45  	// Actualize applies any necessary changes.
    46  	Actualize(plan v7pushaction.PushPlan, progressBar v7pushaction.ProgressBar) (<-chan v7pushaction.PushPlan, <-chan v7pushaction.Event, <-chan v7pushaction.Warnings, <-chan error)
    47  }
    48  
    49  //go:generate counterfeiter . V7ActorForPush
    50  
    51  type V7ActorForPush interface {
    52  	AppActor
    53  	GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client v7action.NOAAClient) (<-chan *v7action.LogMessage, <-chan error, v7action.Warnings, error)
    54  	RestartApplication(appGUID string) (v7action.Warnings, error)
    55  }
    56  
    57  //go:generate counterfeiter . ManifestParser
    58  
    59  type ManifestParser interface {
    60  	v7pushaction.ManifestParser
    61  	ContainsMultipleApps() bool
    62  	InterpolateAndParse(pathToManifest string, pathsToVarsFiles []string, vars []template.VarKV) error
    63  	ContainsPrivateDockerImages() bool
    64  }
    65  
    66  //go:generate counterfeiter . ManifestLocator
    67  
    68  type ManifestLocator interface {
    69  	Path(filepathOrDirectory string) (string, bool, error)
    70  }
    71  
    72  type PushCommand struct {
    73  	OptionalArgs            flag.OptionalAppName          `positional-args:"yes"`
    74  	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"`
    75  	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'"`
    76  	Disk                    flag.Megabytes                `long:"disk" short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"`
    77  	DockerImage             flag.DockerImage              `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"`
    78  	DockerUsername          string                        `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"`
    79  	HealthCheckHTTPEndpoint string                        `long:"endpoint"  description:"Valid path on the app for an HTTP health check. Only used when specifying --health-check-type=http"`
    80  	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'."`
    81  	Instances               flag.Instances                `long:"instances" short:"i" description:"Number of instances"`
    82  	PathToManifest          flag.PathWithExistenceCheck   `long:"manifest" short:"f" description:"Path to manifest"`
    83  	Memory                  flag.Megabytes                `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"`
    84  	NoManifest              bool                          `long:"no-manifest" description:""`
    85  	NoRoute                 bool                          `long:"no-route" description:"Do not map a route to this app"`
    86  	NoStart                 bool                          `long:"no-start" description:"Do not stage and start the app after pushing"`
    87  	AppPath                 flag.PathWithExistenceCheck   `long:"path" short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"`
    88  	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)"`
    89  	StartCommand            flag.Command                  `long:"start-command" short:"c" description:"Startup command, set to null to reset to default start command"`
    90  	Vars                    []template.VarKV              `long:"var" description:"Variable key value pair for variable substitution, (e.g., name=app1); can specify multiple times"`
    91  	PathsToVarsFiles        []flag.PathWithExistenceCheck `long:"vars-file" description:"Path to a variable substitution file for manifest; can specify multiple times"`
    92  	dockerPassword          interface{}                   `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"`
    93  	usage                   interface{}                   `usage:"CF_NAME push APP_NAME [-b BUILDPACK_NAME] [-c COMMAND]\n   [-f MANIFEST_PATH | --no-manifest] [--no-start] [-i NUM_INSTANCES]\n   [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT]\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]\n   [-i NUM_INSTANCES] [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [-u (process | port | http)]\n   [--no-route | --random-route ] [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]..."`
    94  	envCFStagingTimeout     interface{}                   `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"`
    95  	envCFStartupTimeout     interface{}                   `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"`
    96  
    97  	Config          command.Config
    98  	UI              command.UI
    99  	NOAAClient      v3action.NOAAClient
   100  	Actor           PushActor
   101  	VersionActor    V7ActorForPush
   102  	SharedActor     command.SharedActor
   103  	RouteActor      v7action.RouteActor
   104  	ProgressBar     ProgressBar
   105  	PWD             string
   106  	ManifestLocator ManifestLocator
   107  	ManifestParser  ManifestParser
   108  }
   109  
   110  func (cmd *PushCommand) Setup(config command.Config, ui command.UI) error {
   111  	cmd.Config = config
   112  	cmd.UI = ui
   113  	cmd.ProgressBar = progressbar.NewProgressBar()
   114  
   115  	sharedActor := sharedaction.NewActor(config)
   116  	cmd.SharedActor = sharedActor
   117  
   118  	ccClient, uaaClient, err := v6shared.NewV3BasedClients(config, ui, true, "")
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	v7actor := v7action.NewActor(ccClient, config, sharedActor, uaaClient)
   124  	cmd.VersionActor = v7actor
   125  	ccClientV2, uaaClientV2, err := v6shared.NewClients(config, ui, true)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	v2Actor := v2action.NewActor(ccClientV2, uaaClientV2, config)
   131  	cmd.RouteActor = v2Actor
   132  	cmd.Actor = v7pushaction.NewActor(v2Actor, v7actor, sharedActor)
   133  
   134  	cmd.NOAAClient = v6shared.NewNOAAClient(ccClient.Info.Logging(), config, uaaClient, ui)
   135  
   136  	currentDir, err := os.Getwd()
   137  	cmd.PWD = currentDir
   138  
   139  	cmd.ManifestLocator = manifestparser.NewLocator()
   140  	cmd.ManifestParser = manifestparser.NewParser()
   141  
   142  	return err
   143  }
   144  
   145  func (cmd PushCommand) Execute(args []string) error {
   146  	cmd.UI.DisplayWarning(command.ExperimentalWarning)
   147  
   148  	err := cmd.SharedActor.CheckTarget(true, true)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	if !cmd.NoManifest {
   154  		if err = cmd.ReadManifest(); err != nil {
   155  			return err
   156  		}
   157  	}
   158  
   159  	err = cmd.ValidateFlags()
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	flagOverrides, err := cmd.GetFlagOverrides()
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	err = cmd.ValidateAllowedFlagsForMultipleApps(cmd.ManifestParser.ContainsMultipleApps())
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	flagOverrides.DockerPassword, err = cmd.GetDockerPassword(flagOverrides.DockerUsername, cmd.ManifestParser.ContainsPrivateDockerImages())
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	pushPlans, err := cmd.Actor.CreatePushPlans(
   180  		cmd.OptionalArgs.AppName,
   181  		cmd.Config.TargetedSpace().GUID,
   182  		cmd.Config.TargetedOrganization().GUID,
   183  		cmd.ManifestParser,
   184  		flagOverrides,
   185  	)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	pushPlansStream, eventStream, warningsStream, errorStream := cmd.Actor.PrepareSpace(pushPlans, cmd.ManifestParser)
   191  	appNames, err := cmd.processStreamsFromPrepareSpace(pushPlansStream, eventStream, warningsStream, errorStream)
   192  
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	if len(appNames) == 0 {
   198  		return translatableerror.AppNameOrManifestRequiredError{}
   199  	}
   200  
   201  	user, err := cmd.Config.CurrentUser()
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	cmd.announcePushing(appNames, user)
   207  
   208  	cmd.UI.DisplayText("Getting app info...")
   209  	log.Info("generating the app plan")
   210  
   211  	pushPlans, warnings, err := cmd.Actor.UpdateApplicationSettings(pushPlans)
   212  	cmd.UI.DisplayWarnings(warnings)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	log.WithField("number of plans", len(pushPlans)).Debug("completed generating plan")
   217  
   218  	for _, plan := range pushPlans {
   219  		log.WithField("app_name", plan.Application.Name).Info("actualizing")
   220  		planStream, eventStream, warningsStream, errorStream := cmd.Actor.Actualize(plan, cmd.ProgressBar)
   221  		updatedPlan, err := cmd.processApplyStreams(plan.Application.Name, planStream, eventStream, warningsStream, errorStream)
   222  		if err != nil {
   223  			return err
   224  		}
   225  
   226  		anyProcessCrashed, err := cmd.appRestarter(plan.Application.Name, updatedPlan.Application.GUID)
   227  		if err != nil {
   228  			return err
   229  		}
   230  		err = cmd.displayAppSummary(plan)
   231  		if err != nil {
   232  			return err
   233  		}
   234  		if anyProcessCrashed {
   235  			return translatableerror.ApplicationUnableToStartError{
   236  				AppName:    plan.Application.Name,
   237  				BinaryName: cmd.Config.BinaryName(),
   238  			}
   239  		}
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  func (cmd PushCommand) announcePushing(appNames []string, user configv3.User) {
   246  	tokens := map[string]interface{}{
   247  		"AppName":   strings.Join(appNames, ", "),
   248  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   249  		"SpaceName": cmd.Config.TargetedSpace().Name,
   250  		"Username":  user.Name,
   251  	}
   252  	singular := "Pushing app {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..."
   253  	plural := "Pushing apps {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..."
   254  
   255  	if len(appNames) == 1 {
   256  		cmd.UI.DisplayTextWithFlavor(singular, tokens)
   257  	} else {
   258  		cmd.UI.DisplayTextWithFlavor(plural, tokens)
   259  	}
   260  }
   261  
   262  func (cmd PushCommand) appRestarter(appName, appGUID string) (bool, error) {
   263  	anyProcessCrashed := false
   264  	if !cmd.NoStart {
   265  		cmd.UI.DisplayNewline()
   266  		cmd.UI.DisplayTextWithFlavor(
   267  			"Waiting for app {{.AppName}} to start...",
   268  			map[string]interface{}{
   269  				"AppName": appName,
   270  			},
   271  		)
   272  		warnings, restartErr := cmd.VersionActor.RestartApplication(appGUID)
   273  		cmd.UI.DisplayWarnings(warnings)
   274  
   275  		if _, ok := restartErr.(actionerror.StartupTimeoutError); ok {
   276  			return anyProcessCrashed, translatableerror.StartupTimeoutError{
   277  				AppName:    appName,
   278  				BinaryName: cmd.Config.BinaryName(),
   279  			}
   280  		} else if _, ok := restartErr.(actionerror.AllInstancesCrashedError); ok {
   281  			anyProcessCrashed = true
   282  		} else if restartErr != nil {
   283  			return anyProcessCrashed, restartErr
   284  		}
   285  	}
   286  	return anyProcessCrashed, nil
   287  }
   288  
   289  func (cmd PushCommand) displayAppSummary(plan v7pushaction.PushPlan) error {
   290  	log.Info("getting application summary info")
   291  	summary, warnings, err := cmd.VersionActor.GetApplicationSummaryByNameAndSpace(
   292  		plan.Application.Name,
   293  		cmd.Config.TargetedSpace().GUID,
   294  		true,
   295  		cmd.RouteActor,
   296  	)
   297  	cmd.UI.DisplayWarnings(warnings)
   298  	if err != nil {
   299  		return err
   300  	}
   301  	cmd.UI.DisplayNewline()
   302  	appSummaryDisplayer := shared.NewAppSummaryDisplayer(cmd.UI)
   303  	appSummaryDisplayer.AppDisplay(summary, true)
   304  	return nil
   305  }
   306  
   307  func (cmd PushCommand) processStreamsFromPrepareSpace(
   308  	pushPlansStream <-chan []v7pushaction.PushPlan,
   309  	eventStream <-chan v7pushaction.Event,
   310  	warningsStream <-chan v7pushaction.Warnings,
   311  	errorStream <-chan error,
   312  ) ([]string, error) {
   313  	var namesClosed, eventClosed, warningsClosed, errClosed bool
   314  	var appNames []string
   315  	var err error
   316  
   317  	for {
   318  		select {
   319  		case plans, ok := <-pushPlansStream:
   320  			if !ok {
   321  				if !namesClosed {
   322  					log.Debug("processing config stream closed")
   323  				}
   324  				namesClosed = true
   325  				break
   326  			}
   327  			for _, plan := range plans {
   328  				appNames = append(appNames, plan.Application.Name)
   329  			}
   330  		case event, ok := <-eventStream:
   331  			if !ok {
   332  				if !eventClosed {
   333  					log.Debug("processing event stream closed")
   334  				}
   335  				eventClosed = true
   336  				break
   337  			}
   338  			_, err := cmd.processEvent(event, cmd.OptionalArgs.AppName)
   339  			if err != nil {
   340  				return nil, err
   341  			}
   342  		case warnings, ok := <-warningsStream:
   343  			if !ok {
   344  				if !warningsClosed {
   345  					log.Debug("processing warnings stream closed")
   346  				}
   347  				warningsClosed = true
   348  				break
   349  			}
   350  			cmd.UI.DisplayWarnings(warnings)
   351  		case receivedError, ok := <-errorStream:
   352  			if !ok {
   353  				if !errClosed {
   354  					log.Debug("processing error stream closed")
   355  				}
   356  				errClosed = true
   357  				break
   358  			}
   359  			return nil, receivedError
   360  		}
   361  
   362  		if namesClosed && eventClosed && warningsClosed && errClosed {
   363  			break
   364  		}
   365  	}
   366  
   367  	return appNames, err
   368  }
   369  
   370  func (cmd PushCommand) processApplyStreams(
   371  	appName string,
   372  	planStream <-chan v7pushaction.PushPlan,
   373  	eventStream <-chan v7pushaction.Event,
   374  	warningsStream <-chan v7pushaction.Warnings,
   375  	errorStream <-chan error,
   376  ) (v7pushaction.PushPlan, error) {
   377  	var planClosed, eventClosed, warningsClosed, errClosed, complete bool
   378  	var updatePlan v7pushaction.PushPlan
   379  
   380  	for {
   381  		select {
   382  		case plan, ok := <-planStream:
   383  			if !ok {
   384  				if !planClosed {
   385  					log.Debug("processing config stream closed")
   386  				}
   387  				planClosed = true
   388  				break
   389  			}
   390  			updatePlan = plan
   391  		case event, ok := <-eventStream:
   392  			if !ok {
   393  				if !eventClosed {
   394  					log.Debug("processing event stream closed")
   395  				}
   396  				eventClosed = true
   397  				break
   398  			}
   399  			var err error
   400  			complete, err = cmd.processEvent(event, appName)
   401  			if err != nil {
   402  				return v7pushaction.PushPlan{}, err
   403  			}
   404  		case warnings, ok := <-warningsStream:
   405  			if !ok {
   406  				if !warningsClosed {
   407  					log.Debug("processing warnings stream closed")
   408  				}
   409  				warningsClosed = true
   410  				break
   411  			}
   412  			cmd.UI.DisplayWarnings(warnings)
   413  		case err, ok := <-errorStream:
   414  			if !ok {
   415  				if !errClosed {
   416  					log.Debug("processing error stream closed")
   417  				}
   418  				errClosed = true
   419  				break
   420  			}
   421  			return v7pushaction.PushPlan{}, err
   422  		}
   423  
   424  		if planClosed && eventClosed && warningsClosed && complete {
   425  			break
   426  		}
   427  	}
   428  
   429  	return updatePlan, nil
   430  }
   431  
   432  func (cmd PushCommand) processEvent(event v7pushaction.Event, appName string) (bool, error) {
   433  	switch event {
   434  	case v7pushaction.SkippingApplicationCreation:
   435  		cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}}...", map[string]interface{}{
   436  			"AppName": appName,
   437  		})
   438  	case v7pushaction.CreatingApplication:
   439  		cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}}...", map[string]interface{}{
   440  			"AppName": appName,
   441  		})
   442  	case v7pushaction.CreatingAndMappingRoutes:
   443  		cmd.UI.DisplayText("Mapping routes...")
   444  	case v7pushaction.CreatingArchive:
   445  		cmd.UI.DisplayText("Packaging files to upload...")
   446  	case v7pushaction.UploadingApplicationWithArchive:
   447  		cmd.UI.DisplayText("Uploading files...")
   448  		log.Debug("starting progress bar")
   449  		cmd.ProgressBar.Ready()
   450  	case v7pushaction.UploadingApplication:
   451  		cmd.UI.DisplayText("All files found in remote cache; nothing to upload.")
   452  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   453  	case v7pushaction.RetryUpload:
   454  		cmd.UI.DisplayText("Retrying upload due to an error...")
   455  	case v7pushaction.UploadWithArchiveComplete:
   456  		cmd.ProgressBar.Complete()
   457  		cmd.UI.DisplayNewline()
   458  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   459  	case v7pushaction.StoppingApplication:
   460  		cmd.UI.DisplayText("Stopping Application...")
   461  	case v7pushaction.StoppingApplicationComplete:
   462  		cmd.UI.DisplayText("Application Stopped")
   463  	case v7pushaction.ApplyManifest:
   464  		cmd.UI.DisplayText("Applying manifest...")
   465  	case v7pushaction.ApplyManifestComplete:
   466  		cmd.UI.DisplayText("Manifest applied")
   467  	case v7pushaction.StartingStaging:
   468  		cmd.UI.DisplayNewline()
   469  		cmd.UI.DisplayText("Staging app and tracing logs...")
   470  		logStream, errStream, warnings, err := cmd.VersionActor.GetStreamingLogsForApplicationByNameAndSpace(appName, cmd.Config.TargetedSpace().GUID, cmd.NOAAClient)
   471  		cmd.UI.DisplayWarnings(warnings)
   472  		if err != nil {
   473  			return false, err
   474  		}
   475  		go cmd.getLogs(logStream, errStream)
   476  	case v7pushaction.StagingComplete:
   477  		cmd.NOAAClient.Close()
   478  	case v7pushaction.Complete:
   479  		return true, nil
   480  	default:
   481  		log.WithField("event", event).Debug("ignoring event")
   482  	}
   483  	return false, nil
   484  }
   485  
   486  func (cmd PushCommand) getLogs(logStream <-chan *v7action.LogMessage, errStream <-chan error) {
   487  	for {
   488  		select {
   489  		case logMessage, open := <-logStream:
   490  			if !open {
   491  				return
   492  			}
   493  			if logMessage.Staging() {
   494  				cmd.UI.DisplayLogMessage(logMessage, false)
   495  			}
   496  		case err, open := <-errStream:
   497  			if !open {
   498  				return
   499  			}
   500  			_, ok := err.(actionerror.NOAATimeoutError)
   501  			if ok {
   502  				cmd.UI.DisplayWarning("timeout connecting to log server, no log will be shown")
   503  			}
   504  			cmd.UI.DisplayWarning(err.Error())
   505  		}
   506  	}
   507  }
   508  
   509  func (cmd PushCommand) ReadManifest() error {
   510  	log.Info("reading manifest if exists")
   511  	pathsToVarsFiles := []string{}
   512  	for _, varfilepath := range cmd.PathsToVarsFiles {
   513  		pathsToVarsFiles = append(pathsToVarsFiles, string(varfilepath))
   514  	}
   515  
   516  	readPath := cmd.PWD
   517  	if len(cmd.PathToManifest) != 0 {
   518  		log.WithField("manifestPath", cmd.PathToManifest).Debug("reading '-f' provided manifest")
   519  		readPath = string(cmd.PathToManifest)
   520  	}
   521  
   522  	pathToManifest, exists, err := cmd.ManifestLocator.Path(readPath)
   523  	if err != nil {
   524  		return err
   525  	}
   526  
   527  	if exists {
   528  		log.WithField("manifestPath", pathToManifest).Debug("path to manifest")
   529  		err = cmd.ManifestParser.InterpolateAndParse(pathToManifest, pathsToVarsFiles, cmd.Vars)
   530  		if err != nil {
   531  			log.Errorln("reading manifest:", err)
   532  			return err
   533  		}
   534  
   535  		cmd.UI.DisplayText("Using manifest file {{.Path}}", map[string]interface{}{"Path": pathToManifest})
   536  	}
   537  
   538  	return nil
   539  }
   540  
   541  func (cmd PushCommand) GetFlagOverrides() (v7pushaction.FlagOverrides, error) {
   542  	return v7pushaction.FlagOverrides{
   543  		Buildpacks:          cmd.Buildpacks,
   544  		Stack:               cmd.Stack,
   545  		Disk:                cmd.Disk.NullUint64,
   546  		DockerImage:         cmd.DockerImage.Path,
   547  		DockerUsername:      cmd.DockerUsername,
   548  		HealthCheckEndpoint: cmd.HealthCheckHTTPEndpoint,
   549  		HealthCheckType:     cmd.HealthCheckType.Type,
   550  		HealthCheckTimeout:  cmd.HealthCheckTimeout.Value, Instances: cmd.Instances.NullInt,
   551  		Memory:            cmd.Memory.NullUint64,
   552  		NoStart:           cmd.NoStart,
   553  		ProvidedAppPath:   string(cmd.AppPath),
   554  		SkipRouteCreation: cmd.NoRoute,
   555  		StartCommand:      cmd.StartCommand.FilteredString,
   556  	}, nil
   557  }
   558  
   559  func (cmd PushCommand) ValidateAllowedFlagsForMultipleApps(containsMultipleApps bool) error {
   560  	if cmd.OptionalArgs.AppName != "" {
   561  		return nil
   562  	}
   563  
   564  	allowedFlagsMultipleApps := !(len(cmd.Buildpacks) > 0 ||
   565  		cmd.Disk.IsSet ||
   566  		cmd.DockerImage.Path != "" ||
   567  		cmd.DockerUsername != "" ||
   568  		cmd.HealthCheckType.Type != "" ||
   569  		cmd.HealthCheckHTTPEndpoint != "" ||
   570  		cmd.HealthCheckTimeout.Value > 0 ||
   571  		cmd.Instances.IsSet ||
   572  		cmd.Stack != "" ||
   573  		cmd.Memory.IsSet ||
   574  		cmd.AppPath != "" ||
   575  		cmd.NoRoute ||
   576  		cmd.StartCommand.IsSet)
   577  
   578  	if containsMultipleApps && !allowedFlagsMultipleApps {
   579  		return translatableerror.CommandLineArgsWithMultipleAppsError{}
   580  	}
   581  
   582  	return nil
   583  }
   584  
   585  func (cmd PushCommand) ValidateFlags() error {
   586  	switch {
   587  	case cmd.DockerUsername != "" && cmd.DockerImage.Path == "":
   588  		return translatableerror.RequiredFlagsError{
   589  			Arg1: "--docker-image, -o",
   590  			Arg2: "--docker-username",
   591  		}
   592  
   593  	case cmd.DockerImage.Path != "" && cmd.Buildpacks != nil:
   594  		return translatableerror.ArgumentCombinationError{
   595  			Args: []string{
   596  				"--buildpack, -b",
   597  				"--docker-image, -o",
   598  			},
   599  		}
   600  
   601  	case cmd.DockerImage.Path != "" && cmd.AppPath != "":
   602  		return translatableerror.ArgumentCombinationError{
   603  			Args: []string{
   604  				"--docker-image, -o",
   605  				"--path, -p",
   606  			},
   607  		}
   608  	case cmd.DockerImage.Path != "" && cmd.Stack != "":
   609  		return translatableerror.ArgumentCombinationError{
   610  			Args: []string{
   611  				"--stack, -s",
   612  				"--docker-image, -o",
   613  			},
   614  		}
   615  	case cmd.NoManifest && cmd.PathToManifest != "":
   616  		return translatableerror.ArgumentCombinationError{
   617  			Args: []string{
   618  				"--no-manifest",
   619  				"--manifest, -f",
   620  			},
   621  		}
   622  	case cmd.NoManifest && len(cmd.PathsToVarsFiles) > 0:
   623  		return translatableerror.ArgumentCombinationError{
   624  			Args: []string{
   625  				"--no-manifest",
   626  				"--vars-file",
   627  			},
   628  		}
   629  	case cmd.NoManifest && len(cmd.Vars) > 0:
   630  		return translatableerror.ArgumentCombinationError{
   631  			Args: []string{
   632  				"--no-manifest",
   633  				"--vars",
   634  			},
   635  		}
   636  	case cmd.HealthCheckType.Type == constant.HTTP && cmd.HealthCheckHTTPEndpoint == "":
   637  		return translatableerror.RequiredFlagsError{
   638  			Arg1: "--endpoint",
   639  			Arg2: "--health-check-type=http, -u=http",
   640  		}
   641  	case 0 < len(cmd.HealthCheckHTTPEndpoint) && cmd.HealthCheckType.Type != constant.HTTP:
   642  		return translatableerror.RequiredFlagsError{
   643  			Arg1: "--health-check-type=http, -u=http",
   644  			Arg2: "--endpoint",
   645  		}
   646  
   647  	}
   648  	return nil
   649  }