github.com/cloudfoundry-attic/cli-with-i18n@v6.32.1-0.20171002233121-7401370d3b85+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 appParams.DockerImage != nil {
   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 appParams.DockerImage == nil {
   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  		err = checkCombinedDockerProperties(contextApp, manifestApps[0])
   615  		if err != nil {
   616  			return nil, err
   617  		}
   618  
   619  		manifestApps[0].Merge(&contextApp)
   620  		err = addApp(&apps, manifestApps[0])
   621  	default:
   622  		selectedAppName := contextApp.Name
   623  		contextApp.Name = nil
   624  
   625  		if !contextApp.IsEmpty() {
   626  			return nil, errors.New(T("Incorrect Usage. Command line flags (except -f) cannot be applied when pushing multiple apps from a manifest file."))
   627  		}
   628  
   629  		if selectedAppName != nil {
   630  			var foundApp bool
   631  			for _, appParams := range manifestApps {
   632  				if appParams.Name != nil && *appParams.Name == *selectedAppName {
   633  					foundApp = true
   634  					err = addApp(&apps, appParams)
   635  				}
   636  			}
   637  
   638  			if !foundApp {
   639  				err = errors.New(T("Could not find app named '{{.AppName}}' in manifest", map[string]interface{}{"AppName": *selectedAppName}))
   640  			}
   641  		} else {
   642  			for _, manifestApp := range manifestApps {
   643  				err = addApp(&apps, manifestApp)
   644  			}
   645  		}
   646  	}
   647  
   648  	if err != nil {
   649  		return nil, errors.New(T("Error: {{.Err}}", map[string]interface{}{"Err": err.Error()}))
   650  	}
   651  
   652  	return apps, nil
   653  }
   654  
   655  func checkCombinedDockerProperties(flagContext models.AppParams, manifestApp models.AppParams) error {
   656  	if manifestApp.DockerUsername != nil || flagContext.DockerUsername != nil {
   657  		if manifestApp.DockerImage == nil && flagContext.DockerImage == nil {
   658  			return errors.New(T("'--docker-username' requires '--docker-image' to be specified"))
   659  		}
   660  	}
   661  
   662  	dockerPassword := os.Getenv("CF_DOCKER_PASSWORD")
   663  	if flagContext.DockerUsername == nil && manifestApp.DockerUsername != nil && dockerPassword == "" {
   664  		return errors.New(T("No Docker password was provided. Please provide the password by setting the CF_DOCKER_PASSWORD environment variable."))
   665  	}
   666  
   667  	return nil
   668  }
   669  
   670  func addApp(apps *[]models.AppParams, app models.AppParams) error {
   671  	if app.Name == nil {
   672  		return errors.New(T("App name is a required field"))
   673  	}
   674  
   675  	if app.Path == nil {
   676  		cwd, err := os.Getwd()
   677  		if err != nil {
   678  			return err
   679  		}
   680  		app.Path = &cwd
   681  	}
   682  
   683  	*apps = append(*apps, app)
   684  
   685  	return nil
   686  }
   687  
   688  func (cmd *Push) getAppParamsFromContext(c flags.FlagContext) (models.AppParams, error) {
   689  	noHostBool := c.Bool("no-hostname")
   690  	appParams := models.AppParams{
   691  		NoRoute:        c.Bool("no-route"),
   692  		UseRandomRoute: c.Bool("random-route"),
   693  		NoHostname:     &noHostBool,
   694  	}
   695  
   696  	if len(c.Args()) > 0 {
   697  		appParams.Name = &c.Args()[0]
   698  	}
   699  
   700  	if c.String("n") != "" {
   701  		appParams.Hosts = []string{c.String("n")}
   702  	}
   703  
   704  	if c.String("route-path") != "" {
   705  		routePath := c.String("route-path")
   706  		appParams.RoutePath = &routePath
   707  	}
   708  
   709  	if c.String("app-ports") != "" {
   710  		appPortStrings := strings.Split(c.String("app-ports"), ",")
   711  		appPorts := make([]int, len(appPortStrings))
   712  
   713  		for i, s := range appPortStrings {
   714  			p, err := strconv.Atoi(s)
   715  			if err != nil {
   716  				return models.AppParams{}, errors.New(T("Invalid app port: {{.AppPort}}\nApp port must be a number", map[string]interface{}{
   717  					"AppPort": s,
   718  				}))
   719  			}
   720  			appPorts[i] = p
   721  		}
   722  
   723  		appParams.AppPorts = &appPorts
   724  	}
   725  
   726  	if c.String("b") != "" {
   727  		buildpack := c.String("b")
   728  		if buildpack == "null" || buildpack == "default" {
   729  			buildpack = ""
   730  		}
   731  		appParams.BuildpackURL = &buildpack
   732  	}
   733  
   734  	if c.String("c") != "" {
   735  		command := c.String("c")
   736  		if command == "null" || command == "default" {
   737  			command = ""
   738  		}
   739  		appParams.Command = &command
   740  	}
   741  
   742  	if c.String("d") != "" {
   743  		appParams.Domains = []string{c.String("d")}
   744  	}
   745  
   746  	if c.IsSet("i") {
   747  		instances := c.Int("i")
   748  		if instances < 1 {
   749  			return models.AppParams{}, errors.New(T("Invalid instance count: {{.InstancesCount}}\nInstance count must be a positive integer",
   750  				map[string]interface{}{"InstancesCount": instances}))
   751  		}
   752  		appParams.InstanceCount = &instances
   753  	}
   754  
   755  	if c.String("k") != "" {
   756  		diskQuota, err := formatters.ToMegabytes(c.String("k"))
   757  		if err != nil {
   758  			return models.AppParams{}, errors.New(T("Invalid disk quota: {{.DiskQuota}}\n{{.Err}}",
   759  				map[string]interface{}{"DiskQuota": c.String("k"), "Err": err.Error()}))
   760  		}
   761  		appParams.DiskQuota = &diskQuota
   762  	}
   763  
   764  	if c.String("m") != "" {
   765  		memory, err := formatters.ToMegabytes(c.String("m"))
   766  		if err != nil {
   767  			return models.AppParams{}, errors.New(T("Invalid memory limit: {{.MemLimit}}\n{{.Err}}",
   768  				map[string]interface{}{"MemLimit": c.String("m"), "Err": err.Error()}))
   769  		}
   770  		appParams.Memory = &memory
   771  	}
   772  
   773  	if c.String("docker-image") != "" {
   774  		dockerImage := c.String("docker-image")
   775  		appParams.DockerImage = &dockerImage
   776  	}
   777  
   778  	if c.String("docker-username") != "" {
   779  		username := c.String("docker-username")
   780  		appParams.DockerUsername = &username
   781  
   782  		password := os.Getenv("CF_DOCKER_PASSWORD")
   783  		if password != "" {
   784  			cmd.ui.Say(T("Using docker repository password from environment variable CF_DOCKER_PASSWORD."))
   785  		} else {
   786  			cmd.ui.Say(T("Environment variable CF_DOCKER_PASSWORD not set."))
   787  			password = cmd.ui.AskForPassword("Docker password")
   788  			if password == "" {
   789  				return models.AppParams{}, errors.New(T("Please provide a password."))
   790  			}
   791  		}
   792  		appParams.DockerPassword = &password
   793  	}
   794  
   795  	if c.String("p") != "" {
   796  		path := c.String("p")
   797  		appParams.Path = &path
   798  	}
   799  
   800  	if c.String("s") != "" {
   801  		stackName := c.String("s")
   802  		appParams.StackName = &stackName
   803  	}
   804  
   805  	if c.String("t") != "" {
   806  		timeout, err := strconv.Atoi(c.String("t"))
   807  		if err != nil {
   808  			return models.AppParams{}, fmt.Errorf("Error: %s", fmt.Errorf(T("Invalid timeout param: {{.Timeout}}\n{{.Err}}",
   809  				map[string]interface{}{"Timeout": c.String("t"), "Err": err.Error()})))
   810  		}
   811  
   812  		appParams.HealthCheckTimeout = &timeout
   813  	}
   814  
   815  	healthCheckType := c.String("u")
   816  	switch healthCheckType {
   817  	case "":
   818  		// do nothing
   819  	case "http", "none", "port", "process":
   820  		appParams.HealthCheckType = &healthCheckType
   821  	default:
   822  		return models.AppParams{}, fmt.Errorf("Error: %s", fmt.Errorf(T("Invalid health-check-type param: {{.healthCheckType}}",
   823  			map[string]interface{}{"healthCheckType": healthCheckType})))
   824  	}
   825  
   826  	return appParams, nil
   827  }
   828  
   829  func (cmd Push) ValidateContextAndAppParams(appsFromManifest []models.AppParams, appFromContext models.AppParams) error {
   830  	if appFromContext.NoHostname != nil && *appFromContext.NoHostname {
   831  		for _, app := range appsFromManifest {
   832  			if app.Routes != nil {
   833  				return errors.New(T("Option '--no-hostname' cannot be used with an app manifest containing the 'routes' attribute"))
   834  			}
   835  		}
   836  	}
   837  
   838  	return nil
   839  }
   840  
   841  func (cmd *Push) uploadApp(appGUID, appDir, appDirOrZipFile string, localFiles []models.AppFileFields) error {
   842  	uploadDir, err := ioutil.TempDir("", "apps")
   843  	if err != nil {
   844  		return err
   845  	}
   846  
   847  	remoteFiles, hasFileToUpload, err := cmd.actor.GatherFiles(localFiles, appDir, uploadDir, true)
   848  
   849  	if httpError, isHTTPError := err.(errors.HTTPError); isHTTPError && httpError.StatusCode() == 504 {
   850  		cmd.ui.Warn("Resource matching API timed out; pushing all app files.")
   851  		remoteFiles, hasFileToUpload, err = cmd.actor.GatherFiles(localFiles, appDir, uploadDir, false)
   852  	}
   853  
   854  	if err != nil {
   855  		return err
   856  	}
   857  
   858  	zipFile, err := ioutil.TempFile("", "uploads")
   859  	if err != nil {
   860  		return err
   861  	}
   862  	defer func() {
   863  		zipFile.Close()
   864  		os.Remove(zipFile.Name())
   865  	}()
   866  
   867  	if hasFileToUpload {
   868  		err = cmd.zipper.Zip(uploadDir, zipFile)
   869  		if err != nil {
   870  			if emptyDirErr, ok := err.(*errors.EmptyDirError); ok {
   871  				return emptyDirErr
   872  			}
   873  			return fmt.Errorf("%s: %s", T("Error zipping application"), err.Error())
   874  		}
   875  
   876  		var zipFileSize int64
   877  		zipFileSize, err = cmd.zipper.GetZipSize(zipFile)
   878  		if err != nil {
   879  			return err
   880  		}
   881  
   882  		zipFileCount := cmd.appfiles.CountFiles(uploadDir)
   883  		if zipFileCount > 0 {
   884  			cmd.ui.Say(T("Uploading app files from: {{.Path}}", map[string]interface{}{"Path": appDir}))
   885  			cmd.ui.Say(T("Uploading {{.ZipFileBytes}}, {{.FileCount}} files",
   886  				map[string]interface{}{
   887  					"ZipFileBytes": formatters.ByteSize(zipFileSize),
   888  					"FileCount":    zipFileCount}))
   889  		}
   890  	}
   891  
   892  	err = os.RemoveAll(uploadDir)
   893  	if err != nil {
   894  		return err
   895  	}
   896  
   897  	return cmd.actor.UploadApp(appGUID, zipFile, remoteFiles)
   898  }