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