github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/commands/application/push.go (about)

     1  package application
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"code.cloudfoundry.org/cli/cf/flags"
    12  	. "code.cloudfoundry.org/cli/cf/i18n"
    13  
    14  	"code.cloudfoundry.org/cli/cf/actors"
    15  	"code.cloudfoundry.org/cli/cf/api"
    16  	"code.cloudfoundry.org/cli/cf/api/applications"
    17  	"code.cloudfoundry.org/cli/cf/api/authentication"
    18  	"code.cloudfoundry.org/cli/cf/api/stacks"
    19  	"code.cloudfoundry.org/cli/cf/appfiles"
    20  	"code.cloudfoundry.org/cli/cf/commandregistry"
    21  	"code.cloudfoundry.org/cli/cf/commands/service"
    22  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    23  	"code.cloudfoundry.org/cli/cf/errors"
    24  	"code.cloudfoundry.org/cli/cf/formatters"
    25  	"code.cloudfoundry.org/cli/cf/manifest"
    26  	"code.cloudfoundry.org/cli/cf/models"
    27  	"code.cloudfoundry.org/cli/cf/requirements"
    28  	"code.cloudfoundry.org/cli/cf/terminal"
    29  )
    30  
    31  type Push struct {
    32  	ui            terminal.UI
    33  	config        coreconfig.Reader
    34  	manifestRepo  manifest.Repository
    35  	appStarter    Starter
    36  	appStopper    Stopper
    37  	serviceBinder service.Binder
    38  	appRepo       applications.Repository
    39  	domainRepo    api.DomainRepository
    40  	routeRepo     api.RouteRepository
    41  	serviceRepo   api.ServiceRepository
    42  	stackRepo     stacks.StackRepository
    43  	authRepo      authentication.Repository
    44  	wordGenerator commandregistry.RandomWordGenerator
    45  	actor         actors.PushActor
    46  	routeActor    actors.RouteActor
    47  	zipper        appfiles.Zipper
    48  	appfiles      appfiles.AppFiles
    49  }
    50  
    51  func init() {
    52  	commandregistry.Register(&Push{})
    53  }
    54  
    55  func (cmd *Push) MetaData() commandregistry.CommandMetadata {
    56  	fs := make(map[string]flags.FlagSet)
    57  	fs["b"] = &flags.StringFlag{ShortName: "b", Usage: T("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'")}
    58  	fs["c"] = &flags.StringFlag{ShortName: "c", Usage: T("Startup command, set to null to reset to default start command")}
    59  	fs["d"] = &flags.StringFlag{ShortName: "d", Usage: T("Domain (e.g. example.com)")}
    60  	fs["f"] = &flags.StringFlag{ShortName: "f", Usage: T("Path to manifest")}
    61  	fs["i"] = &flags.IntFlag{ShortName: "i", Usage: T("Number of instances")}
    62  	fs["k"] = &flags.StringFlag{ShortName: "k", Usage: T("Disk limit (e.g. 256M, 1024M, 1G)")}
    63  	fs["m"] = &flags.StringFlag{ShortName: "m", Usage: T("Memory limit (e.g. 256M, 1024M, 1G)")}
    64  	fs["hostname"] = &flags.StringFlag{Name: "hostname", ShortName: "n", Usage: T("Hostname (e.g. my-subdomain)")}
    65  	fs["p"] = &flags.StringFlag{ShortName: "p", Usage: T("Path to app directory or to a zip file of the contents of the app directory")}
    66  	fs["s"] = &flags.StringFlag{ShortName: "s", Usage: T("Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)")}
    67  	fs["vars-file"] = &flags.StringFlag{Usage: T("Path to a variable substitution file for manifest; can specify multiple times")}
    68  	fs["var"] = &flags.StringFlag{Usage: T("Variable key value pair for variable substitution, (e.g., name=app1); can specify multiple times")}
    69  	fs["t"] = &flags.StringFlag{ShortName: "t", Usage: T("Time (in seconds) allowed to elapse between starting up an app and the first healthy response from the app")}
    70  	fs["docker-image"] = &flags.StringFlag{Name: "docker-image", ShortName: "o", Usage: T("Docker-image to be used (e.g. user/docker-image-name)")}
    71  	fs["docker-username"] = &flags.StringFlag{Name: "docker-username", Usage: T("Repository username; used with password from environment variable CF_DOCKER_PASSWORD")}
    72  	fs["health-check-type"] = &flags.StringFlag{Name: "health-check-type", ShortName: "u", Usage: T("Application health check type (Default: 'port', 'none' accepted for 'process', 'http' implies endpoint '/')")}
    73  	fs["no-hostname"] = &flags.BoolFlag{Name: "no-hostname", Usage: T("Map the root domain to this app")}
    74  	fs["no-manifest"] = &flags.BoolFlag{Name: "no-manifest", Usage: T("Ignore manifest file")}
    75  	fs["no-route"] = &flags.BoolFlag{Name: "no-route", Usage: T("Do not map a route to this app and remove routes from previous pushes of this app")}
    76  	fs["no-start"] = &flags.BoolFlag{Name: "no-start", Usage: T("Do not start an app after pushing")}
    77  	fs["random-route"] = &flags.BoolFlag{Name: "random-route", Usage: T("Create a random route for this app")}
    78  	fs["route-path"] = &flags.StringFlag{Name: "route-path", Usage: T("Path for the route")}
    79  
    80  	return commandregistry.CommandMetadata{
    81  		Name:        "push",
    82  		ShortName:   "p",
    83  		Description: T("Push a new app or sync changes to an existing app"),
    84  		// strings.Replace \\n with newline so this string matches the new usage string but still gets displayed correctly
    85  		Usage: []string{strings.Replace(T("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]\\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]\\n\\n   cf push -f MANIFEST_WITH_MULTIPLE_APPS_PATH [APP_NAME] [--no-start]"), "\\n", "\n", -1)},
    86  		Flags: fs,
    87  	}
    88  }
    89  
    90  func (cmd *Push) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) ([]requirements.Requirement, error) {
    91  	var reqs []requirements.Requirement
    92  
    93  	usageReq := requirementsFactory.NewUsageRequirement(commandregistry.CLICommandUsagePresenter(cmd), "",
    94  		func() bool {
    95  			return len(fc.Args()) > 1
    96  		},
    97  	)
    98  
    99  	reqs = append(reqs, usageReq)
   100  
   101  	if fc.String("vars-file") != "" || fc.String("var") != "" {
   102  		var flags []string
   103  		if fc.String("vars-file") != "" {
   104  			flags = append(flags, "vars-file")
   105  		}
   106  		if fc.String("var") != "" {
   107  			flags = append(flags, "var")
   108  		}
   109  		reqs = append(reqs, requirementsFactory.NewUnsupportedLegacyFlagRequirement(flags...))
   110  	}
   111  
   112  	reqs = append(reqs, []requirements.Requirement{
   113  		requirementsFactory.NewLoginRequirement(),
   114  		requirementsFactory.NewTargetedSpaceRequirement(),
   115  	}...)
   116  
   117  	return reqs, nil
   118  }
   119  
   120  func (cmd *Push) SetDependency(deps commandregistry.Dependency, pluginCall bool) commandregistry.Command {
   121  	cmd.ui = deps.UI
   122  	cmd.config = deps.Config
   123  	cmd.manifestRepo = deps.ManifestRepo
   124  
   125  	//set appStarter
   126  	appCommand := commandregistry.Commands.FindCommand("start")
   127  	appCommand = appCommand.SetDependency(deps, false)
   128  	cmd.appStarter = appCommand.(Starter)
   129  
   130  	//set appStopper
   131  	appCommand = commandregistry.Commands.FindCommand("stop")
   132  	appCommand = appCommand.SetDependency(deps, false)
   133  	cmd.appStopper = appCommand.(Stopper)
   134  
   135  	//set serviceBinder
   136  	appCommand = commandregistry.Commands.FindCommand("bind-service")
   137  	appCommand = appCommand.SetDependency(deps, false)
   138  	cmd.serviceBinder = appCommand.(service.Binder)
   139  
   140  	cmd.appRepo = deps.RepoLocator.GetApplicationRepository()
   141  	cmd.domainRepo = deps.RepoLocator.GetDomainRepository()
   142  	cmd.routeRepo = deps.RepoLocator.GetRouteRepository()
   143  	cmd.serviceRepo = deps.RepoLocator.GetServiceRepository()
   144  	cmd.stackRepo = deps.RepoLocator.GetStackRepository()
   145  	cmd.authRepo = deps.RepoLocator.GetAuthenticationRepository()
   146  	cmd.wordGenerator = deps.WordGenerator
   147  	cmd.actor = deps.PushActor
   148  	cmd.routeActor = deps.RouteActor
   149  	cmd.zipper = deps.AppZipper
   150  	cmd.appfiles = deps.AppFiles
   151  
   152  	return cmd
   153  }
   154  
   155  func (cmd *Push) Execute(c flags.FlagContext) error {
   156  	appsFromManifest, err := cmd.getAppParamsFromManifest(c)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	errs := cmd.actor.ValidateAppParams(appsFromManifest)
   162  	if len(errs) > 0 {
   163  		errStr := T("Invalid application configuration") + ":"
   164  
   165  		for _, e := range errs {
   166  			errStr = fmt.Sprintf("%s\n%s", errStr, e.Error())
   167  		}
   168  
   169  		return fmt.Errorf("%s", errStr)
   170  	}
   171  
   172  	appFromContext, err := cmd.getAppParamsFromContext(c)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	err = cmd.ValidateContextAndAppParams(appsFromManifest, appFromContext)
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	appSet, err := cmd.createAppSetFromContextAndManifest(appFromContext, appsFromManifest)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	_, err = cmd.authRepo.RefreshAuthToken()
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	for _, appParams := range appSet {
   193  		if appParams.Name == nil {
   194  			return errors.New(T("Error: No name found for app"))
   195  		}
   196  
   197  		err = cmd.fetchStackGUID(&appParams)
   198  		if err != nil {
   199  			return err
   200  		}
   201  
   202  		var app, existingApp models.Application
   203  		existingApp, err = cmd.appRepo.Read(*appParams.Name)
   204  		switch err.(type) {
   205  		case nil:
   206  			cmd.ui.Say(T("Updating app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...",
   207  				map[string]interface{}{
   208  					"AppName":   terminal.EntityNameColor(existingApp.Name),
   209  					"OrgName":   terminal.EntityNameColor(cmd.config.OrganizationFields().Name),
   210  					"SpaceName": terminal.EntityNameColor(cmd.config.SpaceFields().Name),
   211  					"Username":  terminal.EntityNameColor(cmd.config.Username())}))
   212  
   213  			if appParams.EnvironmentVars != nil {
   214  				for key, val := range existingApp.EnvironmentVars {
   215  					if _, ok := (*appParams.EnvironmentVars)[key]; !ok {
   216  						(*appParams.EnvironmentVars)[key] = val
   217  					}
   218  				}
   219  			}
   220  
   221  			// if the user did not provide a health-check-http-endpoint
   222  			// and one doesn't exist already in the cloud
   223  			// set to default
   224  			if appParams.HealthCheckType != nil && *appParams.HealthCheckType == "http" {
   225  				if appParams.HealthCheckHTTPEndpoint == nil && existingApp.HealthCheckHTTPEndpoint == "" {
   226  					endpoint := "/"
   227  					appParams.HealthCheckHTTPEndpoint = &endpoint
   228  				}
   229  			}
   230  
   231  			app, err = cmd.appRepo.Update(existingApp.GUID, appParams)
   232  			if err != nil {
   233  				return err
   234  			}
   235  		case *errors.ModelNotFoundError:
   236  			spaceGUID := cmd.config.SpaceFields().GUID
   237  			appParams.SpaceGUID = &spaceGUID
   238  
   239  			cmd.ui.Say(T("Creating app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...",
   240  				map[string]interface{}{
   241  					"AppName":   terminal.EntityNameColor(*appParams.Name),
   242  					"OrgName":   terminal.EntityNameColor(cmd.config.OrganizationFields().Name),
   243  					"SpaceName": terminal.EntityNameColor(cmd.config.SpaceFields().Name),
   244  					"Username":  terminal.EntityNameColor(cmd.config.Username())}))
   245  
   246  			// if the user did not provide a health-check-http-endpoint
   247  			// set to default
   248  			if appParams.HealthCheckType != nil && *appParams.HealthCheckType == "http" {
   249  				if appParams.HealthCheckHTTPEndpoint == nil {
   250  					endpoint := "/"
   251  					appParams.HealthCheckHTTPEndpoint = &endpoint
   252  				}
   253  			}
   254  			app, err = cmd.appRepo.Create(appParams)
   255  			if err != nil {
   256  				return err
   257  			}
   258  		default:
   259  			return err
   260  		}
   261  
   262  		cmd.ui.Ok()
   263  		cmd.ui.Say("")
   264  
   265  		err = cmd.updateRoutes(app, appParams, appFromContext)
   266  		if err != nil {
   267  			return err
   268  		}
   269  
   270  		if appParams.DockerImage == nil {
   271  			err = cmd.actor.ProcessPath(*appParams.Path, cmd.processPathCallback(*appParams.Path, app))
   272  			if err != nil {
   273  				return errors.New(
   274  					T("Error processing app files: {{.Error}}",
   275  						map[string]interface{}{
   276  							"Error": err.Error(),
   277  						}),
   278  				)
   279  			}
   280  		}
   281  
   282  		if appParams.ServicesToBind != nil {
   283  			err = cmd.bindAppToServices(appParams.ServicesToBind, app)
   284  			if err != nil {
   285  				return err
   286  			}
   287  		}
   288  
   289  		err = cmd.restart(app, appParams, c)
   290  		if err != nil {
   291  			return errors.New(
   292  				T("Error restarting application: {{.Error}}",
   293  					map[string]interface{}{
   294  						"Error": err.Error(),
   295  					}),
   296  			)
   297  		}
   298  	}
   299  	return nil
   300  }
   301  
   302  func (cmd *Push) processPathCallback(path string, app models.Application) func(string) error {
   303  	return func(appDir string) error {
   304  		localFiles, err := cmd.appfiles.AppFilesInDir(appDir)
   305  		if err != nil {
   306  			return errors.New(
   307  				T("Error processing app files in '{{.Path}}': {{.Error}}",
   308  					map[string]interface{}{
   309  						"Path":  path,
   310  						"Error": err.Error(),
   311  					}))
   312  		}
   313  
   314  		if len(localFiles) == 0 {
   315  			return errors.New(
   316  				T("No app files found in '{{.Path}}'",
   317  					map[string]interface{}{
   318  						"Path": path,
   319  					}))
   320  		}
   321  
   322  		cmd.ui.Say(T("Uploading {{.AppName}}...",
   323  			map[string]interface{}{"AppName": terminal.EntityNameColor(app.Name)}))
   324  
   325  		err = cmd.uploadApp(app.GUID, appDir, path, localFiles)
   326  		if err != nil {
   327  			return errors.New(T("Error uploading application.\n{{.APIErr}}",
   328  				map[string]interface{}{"APIErr": err.Error()}))
   329  		}
   330  		cmd.ui.Ok()
   331  		return nil
   332  	}
   333  }
   334  
   335  func (cmd *Push) updateRoutes(app models.Application, appParams models.AppParams, appParamsFromContext models.AppParams) error {
   336  	defaultRouteAcceptable := len(app.Routes) == 0
   337  	routeDefined := appParams.Domains != nil || !appParams.IsHostEmpty() || appParams.IsNoHostnameTrue()
   338  
   339  	switch {
   340  	case appParams.NoRoute:
   341  		if len(app.Routes) == 0 {
   342  			cmd.ui.Say(T("App {{.AppName}} is a worker, skipping route creation",
   343  				map[string]interface{}{"AppName": terminal.EntityNameColor(app.Name)}))
   344  		} else {
   345  			err := cmd.routeActor.UnbindAll(app)
   346  			if err != nil {
   347  				return err
   348  			}
   349  		}
   350  	case len(appParams.Routes) > 0:
   351  		for _, manifestRoute := range appParams.Routes {
   352  			err := cmd.actor.MapManifestRoute(manifestRoute.Route, app, appParamsFromContext)
   353  			if err != nil {
   354  				return err
   355  			}
   356  		}
   357  	case (routeDefined || defaultRouteAcceptable) && appParams.Domains == nil:
   358  		domain, err := cmd.findDomain(nil)
   359  		if err != nil {
   360  			return err
   361  		}
   362  		appParams.UseRandomPort = isTCP(domain)
   363  		err = cmd.processDomainsAndBindRoutes(appParams, app, domain)
   364  		if err != nil {
   365  			return err
   366  		}
   367  	case routeDefined || defaultRouteAcceptable:
   368  		for _, d := range appParams.Domains {
   369  			domain, err := cmd.findDomain(&d)
   370  			if err != nil {
   371  				return err
   372  			}
   373  			appParams.UseRandomPort = isTCP(domain)
   374  			err = cmd.processDomainsAndBindRoutes(appParams, app, domain)
   375  			if err != nil {
   376  				return err
   377  			}
   378  		}
   379  	}
   380  	return nil
   381  }
   382  
   383  const TCP = "tcp"
   384  
   385  func isTCP(domain models.DomainFields) bool {
   386  	return domain.RouterGroupType == TCP
   387  }
   388  
   389  func (cmd *Push) processDomainsAndBindRoutes(
   390  	appParams models.AppParams,
   391  	app models.Application,
   392  	domain models.DomainFields,
   393  ) error {
   394  	if appParams.IsHostEmpty() {
   395  		err := cmd.createAndBindRoute(
   396  			nil,
   397  			appParams.UseRandomRoute,
   398  			appParams.UseRandomPort,
   399  			app,
   400  			appParams.IsNoHostnameTrue(),
   401  			domain,
   402  			appParams.RoutePath,
   403  		)
   404  		if err != nil {
   405  			return err
   406  		}
   407  	} else {
   408  		for _, host := range appParams.Hosts {
   409  			err := cmd.createAndBindRoute(
   410  				&host,
   411  				appParams.UseRandomRoute,
   412  				appParams.UseRandomPort,
   413  				app,
   414  				appParams.IsNoHostnameTrue(),
   415  				domain,
   416  				appParams.RoutePath,
   417  			)
   418  			if err != nil {
   419  				return err
   420  			}
   421  		}
   422  	}
   423  	return nil
   424  }
   425  
   426  func (cmd *Push) createAndBindRoute(
   427  	host *string,
   428  	UseRandomRoute bool,
   429  	UseRandomPort bool,
   430  	app models.Application,
   431  	noHostName bool,
   432  	domain models.DomainFields,
   433  	routePath *string,
   434  ) error {
   435  	var hostname string
   436  	if !noHostName {
   437  		switch {
   438  		case host != nil:
   439  			hostname = *host
   440  		case UseRandomPort:
   441  			//do nothing
   442  		case UseRandomRoute:
   443  			hostname = hostNameForString(app.Name) + "-" + cmd.wordGenerator.Babble()
   444  		default:
   445  			hostname = hostNameForString(app.Name)
   446  		}
   447  	}
   448  
   449  	var route models.Route
   450  	var err error
   451  	if routePath != nil {
   452  		route, err = cmd.routeActor.FindOrCreateRoute(hostname, domain, *routePath, 0, UseRandomPort)
   453  	} else {
   454  		route, err = cmd.routeActor.FindOrCreateRoute(hostname, domain, "", 0, UseRandomPort)
   455  	}
   456  	if err != nil {
   457  		return err
   458  	}
   459  	return cmd.routeActor.BindRoute(app, route)
   460  }
   461  
   462  var forbiddenHostCharRegex = regexp.MustCompile("[^a-z0-9-]")
   463  var whitespaceRegex = regexp.MustCompile(`[\s_]+`)
   464  
   465  func hostNameForString(name string) string {
   466  	name = strings.ToLower(name)
   467  	name = whitespaceRegex.ReplaceAllString(name, "-")
   468  	name = forbiddenHostCharRegex.ReplaceAllString(name, "")
   469  	return name
   470  }
   471  
   472  func (cmd *Push) findDomain(domainName *string) (models.DomainFields, error) {
   473  	domain, err := cmd.domainRepo.FirstOrDefault(cmd.config.OrganizationFields().GUID, domainName)
   474  	if err != nil {
   475  		return models.DomainFields{}, err
   476  	}
   477  
   478  	return domain, nil
   479  }
   480  
   481  func (cmd *Push) bindAppToServices(services []string, app models.Application) error {
   482  	for _, serviceName := range services {
   483  		serviceInstance, err := cmd.serviceRepo.FindInstanceByName(serviceName)
   484  
   485  		if err != nil {
   486  			return errors.New(T("Could not find service {{.ServiceName}} to bind to {{.AppName}}",
   487  				map[string]interface{}{"ServiceName": serviceName, "AppName": app.Name}))
   488  		}
   489  
   490  		cmd.ui.Say(T("Binding service {{.ServiceName}} to app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...",
   491  			map[string]interface{}{
   492  				"ServiceName": terminal.EntityNameColor(serviceInstance.Name),
   493  				"AppName":     terminal.EntityNameColor(app.Name),
   494  				"OrgName":     terminal.EntityNameColor(cmd.config.OrganizationFields().Name),
   495  				"SpaceName":   terminal.EntityNameColor(cmd.config.SpaceFields().Name),
   496  				"Username":    terminal.EntityNameColor(cmd.config.Username())}))
   497  
   498  		err = cmd.serviceBinder.BindApplication(app, serviceInstance, nil)
   499  
   500  		switch httpErr := err.(type) {
   501  		case errors.HTTPError:
   502  			if httpErr.ErrorCode() == errors.ServiceBindingAppServiceTaken {
   503  				err = nil
   504  			}
   505  		}
   506  
   507  		if err != nil {
   508  			return errors.New(T("Could not bind to service {{.ServiceName}}\nError: {{.Err}}",
   509  				map[string]interface{}{"ServiceName": serviceName, "Err": err.Error()}))
   510  		}
   511  
   512  		cmd.ui.Ok()
   513  	}
   514  	return nil
   515  }
   516  
   517  func (cmd *Push) fetchStackGUID(appParams *models.AppParams) error {
   518  	if appParams.StackName == nil {
   519  		return nil
   520  	}
   521  
   522  	stackName := *appParams.StackName
   523  	cmd.ui.Say(T("Using stack {{.StackName}}...",
   524  		map[string]interface{}{"StackName": terminal.EntityNameColor(stackName)}))
   525  
   526  	stack, err := cmd.stackRepo.FindByName(stackName)
   527  	if err != nil {
   528  		return err
   529  	}
   530  
   531  	cmd.ui.Ok()
   532  	appParams.StackGUID = &stack.GUID
   533  	return nil
   534  }
   535  
   536  func (cmd *Push) restart(app models.Application, params models.AppParams, c flags.FlagContext) error {
   537  	if app.State != T("stopped") {
   538  		cmd.ui.Say("")
   539  		app, _ = cmd.appStopper.ApplicationStop(app, cmd.config.OrganizationFields().Name, cmd.config.SpaceFields().Name)
   540  	}
   541  
   542  	cmd.ui.Say("")
   543  
   544  	if c.Bool("no-start") {
   545  		return nil
   546  	}
   547  
   548  	if params.HealthCheckTimeout != nil {
   549  		cmd.appStarter.SetStartTimeoutInSeconds(*params.HealthCheckTimeout)
   550  	}
   551  
   552  	_, err := cmd.appStarter.ApplicationStart(app, cmd.config.OrganizationFields().Name, cmd.config.SpaceFields().Name)
   553  	if err != nil {
   554  		return err
   555  	}
   556  
   557  	return nil
   558  }
   559  
   560  func (cmd *Push) getAppParamsFromManifest(c flags.FlagContext) ([]models.AppParams, error) {
   561  	if c.Bool("no-manifest") {
   562  		return []models.AppParams{}, nil
   563  	}
   564  
   565  	var path string
   566  	if c.String("f") != "" {
   567  		path = c.String("f")
   568  	} else {
   569  		var err error
   570  		path, err = os.Getwd()
   571  		if err != nil {
   572  			return nil, errors.New(fmt.Sprint(T("Could not determine the current working directory!"), err))
   573  		}
   574  	}
   575  
   576  	m, err := cmd.manifestRepo.ReadManifest(path)
   577  
   578  	if err != nil {
   579  		if m.Path == "" && c.String("f") == "" {
   580  			return []models.AppParams{}, nil
   581  		}
   582  		return nil, errors.New(T("Error reading manifest file:\n{{.Err}}", map[string]interface{}{"Err": err.Error()}))
   583  	}
   584  
   585  	apps, err := m.Applications()
   586  	if err != nil {
   587  		return nil, errors.New(T("Error reading manifest file:\n{{.Err}}", map[string]interface{}{"Err": err.Error()}))
   588  	}
   589  
   590  	cmd.ui.Say(T("Using manifest file {{.Path}}\n",
   591  		map[string]interface{}{"Path": terminal.EntityNameColor(m.Path)}))
   592  	return apps, nil
   593  }
   594  
   595  func (cmd *Push) createAppSetFromContextAndManifest(contextApp models.AppParams, manifestApps []models.AppParams) ([]models.AppParams, error) {
   596  	var err error
   597  	var apps []models.AppParams
   598  
   599  	switch len(manifestApps) {
   600  	case 0:
   601  		if contextApp.Name == nil {
   602  			return nil, errors.New(
   603  				T("Incorrect Usage. The push command requires an app name. The app name can be supplied as an argument or with a manifest.yml file.") +
   604  					"\n\n" +
   605  					commandregistry.Commands.CommandUsage("push"),
   606  			)
   607  		}
   608  		err = addApp(&apps, contextApp)
   609  	case 1:
   610  		err = checkCombinedDockerProperties(contextApp, manifestApps[0])
   611  		if err != nil {
   612  			return nil, err
   613  		}
   614  
   615  		manifestApps[0].Merge(&contextApp)
   616  		err = addApp(&apps, manifestApps[0])
   617  	default:
   618  		selectedAppName := contextApp.Name
   619  		contextApp.Name = nil
   620  
   621  		if !contextApp.IsEmpty() {
   622  			return nil, errors.New(T("Incorrect Usage. Command line flags (except -f) cannot be applied when pushing multiple apps from a manifest file."))
   623  		}
   624  
   625  		if selectedAppName != nil {
   626  			var foundApp bool
   627  			for _, appParams := range manifestApps {
   628  				if appParams.Name != nil && *appParams.Name == *selectedAppName {
   629  					foundApp = true
   630  					err = addApp(&apps, appParams)
   631  				}
   632  			}
   633  
   634  			if !foundApp {
   635  				err = errors.New(T("Could not find app named '{{.AppName}}' in manifest", map[string]interface{}{"AppName": *selectedAppName}))
   636  			}
   637  		} else {
   638  			for _, manifestApp := range manifestApps {
   639  				err = addApp(&apps, manifestApp)
   640  			}
   641  		}
   642  	}
   643  
   644  	if err != nil {
   645  		return nil, errors.New(T("Error: {{.Err}}", map[string]interface{}{"Err": err.Error()}))
   646  	}
   647  
   648  	return apps, nil
   649  }
   650  
   651  func checkCombinedDockerProperties(flagContext models.AppParams, manifestApp models.AppParams) error {
   652  	if manifestApp.DockerUsername != nil || flagContext.DockerUsername != nil {
   653  		if manifestApp.DockerImage == nil && flagContext.DockerImage == nil {
   654  			return errors.New(T("'--docker-username' requires '--docker-image' to be specified"))
   655  		}
   656  	}
   657  
   658  	dockerPassword := os.Getenv("CF_DOCKER_PASSWORD")
   659  	if flagContext.DockerUsername == nil && manifestApp.DockerUsername != nil && dockerPassword == "" {
   660  		return errors.New(T("No Docker password was provided. Please provide the password by setting the CF_DOCKER_PASSWORD environment variable."))
   661  	}
   662  
   663  	return nil
   664  }
   665  
   666  func addApp(apps *[]models.AppParams, app models.AppParams) error {
   667  	if app.Name == nil {
   668  		return errors.New(T("App name is a required field"))
   669  	}
   670  
   671  	if app.Path == nil {
   672  		cwd, err := os.Getwd()
   673  		if err != nil {
   674  			return err
   675  		}
   676  		app.Path = &cwd
   677  	}
   678  
   679  	*apps = append(*apps, app)
   680  
   681  	return nil
   682  }
   683  
   684  func (cmd *Push) getAppParamsFromContext(c flags.FlagContext) (models.AppParams, error) {
   685  	noHostBool := c.Bool("no-hostname")
   686  	appParams := models.AppParams{
   687  		NoRoute:        c.Bool("no-route"),
   688  		UseRandomRoute: c.Bool("random-route"),
   689  		NoHostname:     &noHostBool,
   690  	}
   691  
   692  	if len(c.Args()) > 0 {
   693  		appParams.Name = &c.Args()[0]
   694  	}
   695  
   696  	if c.String("n") != "" {
   697  		appParams.Hosts = []string{c.String("n")}
   698  	}
   699  
   700  	if c.String("route-path") != "" {
   701  		routePath := c.String("route-path")
   702  		appParams.RoutePath = &routePath
   703  	}
   704  
   705  	if c.String("b") != "" {
   706  		buildpack := c.String("b")
   707  		if buildpack == "null" || buildpack == "default" {
   708  			buildpack = ""
   709  		}
   710  		appParams.BuildpackURL = &buildpack
   711  	}
   712  
   713  	if c.String("c") != "" {
   714  		command := c.String("c")
   715  		if command == "null" || command == "default" {
   716  			command = ""
   717  		}
   718  		appParams.Command = &command
   719  	}
   720  
   721  	if c.String("d") != "" {
   722  		appParams.Domains = []string{c.String("d")}
   723  	}
   724  
   725  	if c.IsSet("i") {
   726  		instances := c.Int("i")
   727  		if instances < 1 {
   728  			return models.AppParams{}, errors.New(T("Invalid instance count: {{.InstancesCount}}\nInstance count must be a positive integer",
   729  				map[string]interface{}{"InstancesCount": instances}))
   730  		}
   731  		appParams.InstanceCount = &instances
   732  	}
   733  
   734  	if c.String("k") != "" {
   735  		diskQuota, err := formatters.ToMegabytes(c.String("k"))
   736  		if err != nil {
   737  			return models.AppParams{}, errors.New(T("Invalid disk quota: {{.DiskQuota}}\n{{.Err}}",
   738  				map[string]interface{}{"DiskQuota": c.String("k"), "Err": err.Error()}))
   739  		}
   740  		appParams.DiskQuota = &diskQuota
   741  	}
   742  
   743  	if c.String("m") != "" {
   744  		memory, err := formatters.ToMegabytes(c.String("m"))
   745  		if err != nil {
   746  			return models.AppParams{}, errors.New(T("Invalid memory limit: {{.MemLimit}}\n{{.Err}}",
   747  				map[string]interface{}{"MemLimit": c.String("m"), "Err": err.Error()}))
   748  		}
   749  		appParams.Memory = &memory
   750  	}
   751  
   752  	if c.String("docker-image") != "" {
   753  		dockerImage := c.String("docker-image")
   754  		appParams.DockerImage = &dockerImage
   755  	}
   756  
   757  	if c.String("docker-username") != "" {
   758  		username := c.String("docker-username")
   759  		appParams.DockerUsername = &username
   760  
   761  		password := os.Getenv("CF_DOCKER_PASSWORD")
   762  		if password != "" {
   763  			cmd.ui.Say(T("Using docker repository password from environment variable CF_DOCKER_PASSWORD."))
   764  		} else {
   765  			cmd.ui.Say(T("Environment variable CF_DOCKER_PASSWORD not set."))
   766  			password = cmd.ui.AskForPassword("Docker password")
   767  			if password == "" {
   768  				return models.AppParams{}, errors.New(T("Please provide a password."))
   769  			}
   770  		}
   771  		appParams.DockerPassword = &password
   772  	}
   773  
   774  	if c.String("p") != "" {
   775  		path := c.String("p")
   776  		appParams.Path = &path
   777  	}
   778  
   779  	if c.String("s") != "" {
   780  		stackName := c.String("s")
   781  		appParams.StackName = &stackName
   782  	}
   783  
   784  	if c.String("t") != "" {
   785  		timeout, err := strconv.Atoi(c.String("t"))
   786  		if err != nil {
   787  			return models.AppParams{}, fmt.Errorf("Error: %s", fmt.Errorf(T("Invalid timeout param: {{.Timeout}}\n{{.Err}}",
   788  				map[string]interface{}{"Timeout": c.String("t"), "Err": err.Error()})))
   789  		}
   790  
   791  		appParams.HealthCheckTimeout = &timeout
   792  	}
   793  
   794  	healthCheckType := c.String("u")
   795  	switch healthCheckType {
   796  	case "":
   797  		// do nothing
   798  	case "http", "none", "port", "process":
   799  		appParams.HealthCheckType = &healthCheckType
   800  	default:
   801  		return models.AppParams{}, fmt.Errorf("Error: %s", fmt.Errorf(T("Invalid health-check-type param: {{.healthCheckType}}",
   802  			map[string]interface{}{"healthCheckType": healthCheckType})))
   803  	}
   804  
   805  	return appParams, nil
   806  }
   807  
   808  func (cmd Push) ValidateContextAndAppParams(appsFromManifest []models.AppParams, appFromContext models.AppParams) error {
   809  	if appFromContext.NoHostname != nil && *appFromContext.NoHostname {
   810  		for _, app := range appsFromManifest {
   811  			if app.Routes != nil {
   812  				return errors.New(T("Option '--no-hostname' cannot be used with an app manifest containing the 'routes' attribute"))
   813  			}
   814  		}
   815  	}
   816  
   817  	return nil
   818  }
   819  
   820  func (cmd *Push) uploadApp(appGUID, appDir, appDirOrZipFile string, localFiles []models.AppFileFields) error {
   821  	uploadDir, err := ioutil.TempDir("", "apps")
   822  	if err != nil {
   823  		return err
   824  	}
   825  
   826  	remoteFiles, hasFileToUpload, err := cmd.actor.GatherFiles(localFiles, appDir, uploadDir, true)
   827  
   828  	if httpError, isHTTPError := err.(errors.HTTPError); isHTTPError && httpError.StatusCode() == 504 {
   829  		cmd.ui.Warn("Resource matching API timed out; pushing all app files.")
   830  		remoteFiles, hasFileToUpload, err = cmd.actor.GatherFiles(localFiles, appDir, uploadDir, false)
   831  	}
   832  
   833  	if err != nil {
   834  		return err
   835  	}
   836  
   837  	zipFile, err := ioutil.TempFile("", "uploads")
   838  	if err != nil {
   839  		return err
   840  	}
   841  	defer func() {
   842  		zipFile.Close()
   843  		os.Remove(zipFile.Name())
   844  	}()
   845  
   846  	if hasFileToUpload {
   847  		err = cmd.zipper.Zip(uploadDir, zipFile)
   848  		if err != nil {
   849  			if emptyDirErr, ok := err.(*errors.EmptyDirError); ok {
   850  				return emptyDirErr
   851  			}
   852  			return fmt.Errorf("%s: %s", T("Error zipping application"), err.Error())
   853  		}
   854  
   855  		var zipFileSize int64
   856  		zipFileSize, err = cmd.zipper.GetZipSize(zipFile)
   857  		if err != nil {
   858  			return err
   859  		}
   860  
   861  		zipFileCount := cmd.appfiles.CountFiles(uploadDir)
   862  		if zipFileCount > 0 {
   863  			cmd.ui.Say(T("Uploading app files from: {{.Path}}", map[string]interface{}{"Path": appDir}))
   864  			cmd.ui.Say(T("Uploading {{.ZipFileBytes}}, {{.FileCount}} files",
   865  				map[string]interface{}{
   866  					"ZipFileBytes": formatters.ByteSize(zipFileSize),
   867  					"FileCount":    zipFileCount}))
   868  		}
   869  	}
   870  
   871  	err = os.RemoveAll(uploadDir)
   872  	if err != nil {
   873  		return err
   874  	}
   875  
   876  	return cmd.actor.UploadApp(appGUID, zipFile, remoteFiles)
   877  }