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