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