github.com/rakutentech/cli@v6.12.5-0.20151006231303-24468b65536e+incompatible/cf/commands/application/push.go (about)

     1  package application
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  
    10  	. "github.com/cloudfoundry/cli/cf/i18n"
    11  	"github.com/cloudfoundry/cli/fileutils"
    12  	"github.com/cloudfoundry/cli/flags"
    13  	"github.com/cloudfoundry/cli/flags/flag"
    14  
    15  	"github.com/cloudfoundry/cli/cf/actors"
    16  	"github.com/cloudfoundry/cli/cf/api"
    17  	"github.com/cloudfoundry/cli/cf/api/applications"
    18  	"github.com/cloudfoundry/cli/cf/api/authentication"
    19  	"github.com/cloudfoundry/cli/cf/api/stacks"
    20  	"github.com/cloudfoundry/cli/cf/app_files"
    21  	"github.com/cloudfoundry/cli/cf/command_registry"
    22  	"github.com/cloudfoundry/cli/cf/commands/service"
    23  	"github.com/cloudfoundry/cli/cf/configuration/core_config"
    24  	"github.com/cloudfoundry/cli/cf/errors"
    25  	"github.com/cloudfoundry/cli/cf/formatters"
    26  	"github.com/cloudfoundry/cli/cf/manifest"
    27  	"github.com/cloudfoundry/cli/cf/models"
    28  	"github.com/cloudfoundry/cli/cf/requirements"
    29  	"github.com/cloudfoundry/cli/cf/terminal"
    30  	"github.com/cloudfoundry/cli/words/generator"
    31  )
    32  
    33  type Push struct {
    34  	ui            terminal.UI
    35  	config        core_config.Reader
    36  	manifestRepo  manifest.ManifestRepository
    37  	appStarter    ApplicationStarter
    38  	appStopper    ApplicationStopper
    39  	serviceBinder service.ServiceBinder
    40  	appRepo       applications.ApplicationRepository
    41  	domainRepo    api.DomainRepository
    42  	routeRepo     api.RouteRepository
    43  	serviceRepo   api.ServiceRepository
    44  	stackRepo     stacks.StackRepository
    45  	authRepo      authentication.AuthenticationRepository
    46  	wordGenerator generator.WordGenerator
    47  	actor         actors.PushActor
    48  	zipper        app_files.Zipper
    49  	app_files     app_files.AppFiles
    50  }
    51  
    52  func init() {
    53  	command_registry.Register(&Push{})
    54  }
    55  
    56  func (cmd *Push) MetaData() command_registry.CommandMetadata {
    57  	fs := make(map[string]flags.FlagSet)
    58  	fs["b"] = &cliFlags.StringFlag{Name: "b", Usage: T("Custom buildpack by name (e.g. my-buildpack) or GIT URL (e.g. 'https://github.com/heroku/heroku-buildpack-play.git') or GIT BRANCH URL (e.g. 'https://github.com/heroku/heroku-buildpack-play.git#develop' for 'develop' branch). Use built-in buildpacks only by setting value to 'null' or 'default'")}
    59  	fs["c"] = &cliFlags.StringFlag{Name: "c", Usage: T("Startup command, set to null to reset to default start command")}
    60  	fs["d"] = &cliFlags.StringFlag{Name: "d", Usage: T("Domain (e.g. example.com)")}
    61  	fs["f"] = &cliFlags.StringFlag{Name: "f", Usage: T("Path to manifest")}
    62  	fs["i"] = &cliFlags.IntFlag{Name: "i", Usage: T("Number of instances")}
    63  	fs["k"] = &cliFlags.StringFlag{Name: "k", Usage: T("Disk limit (e.g. 256M, 1024M, 1G)")}
    64  	fs["m"] = &cliFlags.StringFlag{Name: "m", Usage: T("Memory limit (e.g. 256M, 1024M, 1G)")}
    65  	fs["n"] = &cliFlags.StringFlag{Name: "n", Usage: T("Hostname (e.g. my-subdomain)")}
    66  	fs["p"] = &cliFlags.StringFlag{Name: "p", Usage: T("Path to app directory or to a zip file of the contents of the app directory")}
    67  	fs["s"] = &cliFlags.StringFlag{Name: "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"] = &cliFlags.StringFlag{Name: "t", Usage: T("Maximum time (in seconds) for CLI to wait for application start, other server side timeouts may apply")}
    69  	fs["no-hostname"] = &cliFlags.BoolFlag{Name: "no-hostname", Usage: T("Map the root domain to this app")}
    70  	fs["no-manifest"] = &cliFlags.BoolFlag{Name: "no-manifest", Usage: T("Ignore manifest file")}
    71  	fs["no-route"] = &cliFlags.BoolFlag{Name: "no-route", Usage: T("Do not map a route to this app and remove routes from previous pushes of this app.")}
    72  	fs["no-start"] = &cliFlags.BoolFlag{Name: "no-start", Usage: T("Do not start an app after pushing")}
    73  	fs["random-route"] = &cliFlags.BoolFlag{Name: "random-route", Usage: T("Create a random route for this app")}
    74  
    75  	return command_registry.CommandMetadata{
    76  		Name:        "push",
    77  		ShortName:   "p",
    78  		Description: T("Push a new app or sync changes to an existing app"),
    79  		Usage: T("Push a single app (with or without a manifest):\n") + T("   CF_NAME push APP_NAME [-b BUILDPACK_NAME] [-c COMMAND] [-d DOMAIN] [-f MANIFEST_PATH]\n") + T("   [-i NUM_INSTANCES] [-k DISK] [-m MEMORY] [-n HOST] [-p PATH] [-s STACK] [-t TIMEOUT]\n") +
    80  			"   [--no-hostname] [--no-manifest] [--no-route] [--no-start]\n" +
    81  			"\n" + T("   Push multiple apps with a manifest:\n") + T("   CF_NAME push [-f MANIFEST_PATH]\n"),
    82  		Flags: fs,
    83  	}
    84  }
    85  
    86  func (cmd *Push) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) (reqs []requirements.Requirement, err error) {
    87  	if len(fc.Args()) > 1 {
    88  		cmd.ui.Failed(T("Incorrect Usage.\n\n") + command_registry.Commands.CommandUsage("push"))
    89  	}
    90  
    91  	reqs = []requirements.Requirement{
    92  		requirementsFactory.NewLoginRequirement(),
    93  		requirementsFactory.NewTargetedSpaceRequirement(),
    94  	}
    95  	return
    96  }
    97  
    98  func (cmd *Push) SetDependency(deps command_registry.Dependency, pluginCall bool) command_registry.Command {
    99  	cmd.ui = deps.Ui
   100  	cmd.config = deps.Config
   101  	cmd.manifestRepo = deps.ManifestRepo
   102  
   103  	//set appStarter
   104  	appCommand := command_registry.Commands.FindCommand("start")
   105  	appCommand = appCommand.SetDependency(deps, false)
   106  	cmd.appStarter = appCommand.(ApplicationStarter)
   107  
   108  	//set appStopper
   109  	appCommand = command_registry.Commands.FindCommand("stop")
   110  	appCommand = appCommand.SetDependency(deps, false)
   111  	cmd.appStopper = appCommand.(ApplicationStopper)
   112  
   113  	//set serviceBinder
   114  	appCommand = command_registry.Commands.FindCommand("bind-service")
   115  	appCommand = appCommand.SetDependency(deps, false)
   116  	cmd.serviceBinder = appCommand.(service.ServiceBinder)
   117  
   118  	cmd.appRepo = deps.RepoLocator.GetApplicationRepository()
   119  	cmd.domainRepo = deps.RepoLocator.GetDomainRepository()
   120  	cmd.routeRepo = deps.RepoLocator.GetRouteRepository()
   121  	cmd.serviceRepo = deps.RepoLocator.GetServiceRepository()
   122  	cmd.stackRepo = deps.RepoLocator.GetStackRepository()
   123  	cmd.authRepo = deps.RepoLocator.GetAuthenticationRepository()
   124  	cmd.wordGenerator = deps.WordGenerator
   125  	cmd.actor = deps.PushActor
   126  	cmd.zipper = deps.AppZipper
   127  	cmd.app_files = deps.AppFiles
   128  
   129  	return cmd
   130  }
   131  
   132  func (cmd *Push) Execute(c flags.FlagContext) {
   133  	appSet := cmd.findAndValidateAppsToPush(c)
   134  	_, apiErr := cmd.authRepo.RefreshAuthToken()
   135  	if apiErr != nil {
   136  		cmd.ui.Failed(apiErr.Error())
   137  		return
   138  	}
   139  
   140  	routeActor := actors.NewRouteActor(cmd.ui, cmd.routeRepo)
   141  
   142  	for _, appParams := range appSet {
   143  		cmd.fetchStackGuid(&appParams)
   144  		app := cmd.createOrUpdateApp(appParams)
   145  
   146  		cmd.updateRoutes(routeActor, app, appParams)
   147  
   148  		cmd.ui.Say(T("Uploading {{.AppName}}...",
   149  			map[string]interface{}{"AppName": terminal.EntityNameColor(app.Name)}))
   150  
   151  		apiErr := cmd.uploadApp(app.Guid, *appParams.Path)
   152  		if apiErr != nil {
   153  			cmd.ui.Failed(fmt.Sprintf(T("Error uploading application.\n{{.ApiErr}}",
   154  				map[string]interface{}{"ApiErr": apiErr.Error()})))
   155  			return
   156  		}
   157  		cmd.ui.Ok()
   158  
   159  		if appParams.ServicesToBind != nil {
   160  			cmd.bindAppToServices(*appParams.ServicesToBind, app)
   161  		}
   162  
   163  		cmd.restart(app, appParams, c)
   164  	}
   165  }
   166  
   167  func (cmd *Push) updateRoutes(routeActor actors.RouteActor, app models.Application, appParams models.AppParams) {
   168  	defaultRouteAcceptable := len(app.Routes) == 0
   169  	routeDefined := appParams.Domains != nil || !appParams.IsHostEmpty() || appParams.NoHostname
   170  
   171  	if appParams.NoRoute {
   172  		cmd.removeRoutes(app, routeActor)
   173  		return
   174  	}
   175  
   176  	if routeDefined || defaultRouteAcceptable {
   177  		if appParams.Domains == nil {
   178  			cmd.processDomainsAndBindRoutes(appParams, routeActor, app, cmd.findDomain(nil))
   179  		} else {
   180  			for _, d := range *(appParams.Domains) {
   181  				cmd.processDomainsAndBindRoutes(appParams, routeActor, app, cmd.findDomain(&d))
   182  			}
   183  		}
   184  	}
   185  }
   186  
   187  func (cmd *Push) processDomainsAndBindRoutes(appParams models.AppParams, routeActor actors.RouteActor, app models.Application, domain models.DomainFields) {
   188  	if appParams.IsHostEmpty() {
   189  		cmd.createAndBindRoute(nil, appParams.UseRandomHostname, routeActor, app, appParams.NoHostname, domain)
   190  	} else {
   191  		for _, host := range *(appParams.Hosts) {
   192  			cmd.createAndBindRoute(&host, appParams.UseRandomHostname, routeActor, app, appParams.NoHostname, domain)
   193  		}
   194  	}
   195  }
   196  
   197  func (cmd *Push) createAndBindRoute(host *string, UseRandomHostname bool, routeActor actors.RouteActor, app models.Application, noHostName bool, domain models.DomainFields) {
   198  	hostname := cmd.hostnameForApp(host, UseRandomHostname, app.Name, noHostName)
   199  	route := routeActor.FindOrCreateRoute(hostname, domain)
   200  	routeActor.BindRoute(app, route)
   201  }
   202  
   203  func (cmd *Push) removeRoutes(app models.Application, routeActor actors.RouteActor) {
   204  	if len(app.Routes) == 0 {
   205  		cmd.ui.Say(T("App {{.AppName}} is a worker, skipping route creation",
   206  			map[string]interface{}{"AppName": terminal.EntityNameColor(app.Name)}))
   207  	} else {
   208  		routeActor.UnbindAll(app)
   209  	}
   210  }
   211  
   212  func (cmd *Push) hostnameForApp(host *string, useRandomHostName bool, name string, noHostName bool) string {
   213  	if noHostName {
   214  		return ""
   215  	}
   216  
   217  	if host != nil {
   218  		return *host
   219  	} else if useRandomHostName {
   220  		return hostNameForString(name) + "-" + cmd.wordGenerator.Babble()
   221  	} else {
   222  		return hostNameForString(name)
   223  	}
   224  }
   225  
   226  var forbiddenHostCharRegex = regexp.MustCompile("[^a-z0-9-]")
   227  var whitespaceRegex = regexp.MustCompile(`[\s_]+`)
   228  
   229  func hostNameForString(name string) string {
   230  	name = strings.ToLower(name)
   231  	name = whitespaceRegex.ReplaceAllString(name, "-")
   232  	name = forbiddenHostCharRegex.ReplaceAllString(name, "")
   233  	return name
   234  }
   235  
   236  func (cmd *Push) findDomain(domainName *string) (domain models.DomainFields) {
   237  	domain, err := cmd.domainRepo.FirstOrDefault(cmd.config.OrganizationFields().Guid, domainName)
   238  	if err != nil {
   239  		cmd.ui.Failed(err.Error())
   240  	}
   241  
   242  	return
   243  }
   244  
   245  func (cmd *Push) bindAppToServices(services []string, app models.Application) {
   246  	for _, serviceName := range services {
   247  		serviceInstance, err := cmd.serviceRepo.FindInstanceByName(serviceName)
   248  
   249  		if err != nil {
   250  			cmd.ui.Failed(T("Could not find service {{.ServiceName}} to bind to {{.AppName}}",
   251  				map[string]interface{}{"ServiceName": serviceName, "AppName": app.Name}))
   252  			return
   253  		}
   254  
   255  		cmd.ui.Say(T("Binding service {{.ServiceName}} to app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...",
   256  			map[string]interface{}{
   257  				"ServiceName": terminal.EntityNameColor(serviceInstance.Name),
   258  				"AppName":     terminal.EntityNameColor(app.Name),
   259  				"OrgName":     terminal.EntityNameColor(cmd.config.OrganizationFields().Name),
   260  				"SpaceName":   terminal.EntityNameColor(cmd.config.SpaceFields().Name),
   261  				"Username":    terminal.EntityNameColor(cmd.config.Username())}))
   262  
   263  		err = cmd.serviceBinder.BindApplication(app, serviceInstance, nil)
   264  
   265  		switch httpErr := err.(type) {
   266  		case errors.HttpError:
   267  			if httpErr.ErrorCode() == errors.APP_ALREADY_BOUND {
   268  				err = nil
   269  			}
   270  		}
   271  
   272  		if err != nil {
   273  			cmd.ui.Failed(T("Could not bind to service {{.ServiceName}}\nError: {{.Err}}",
   274  				map[string]interface{}{"ServiceName": serviceName, "Err": err.Error()}))
   275  		}
   276  
   277  		cmd.ui.Ok()
   278  	}
   279  }
   280  
   281  func (cmd *Push) fetchStackGuid(appParams *models.AppParams) {
   282  	if appParams.StackName == nil {
   283  		return
   284  	}
   285  
   286  	stackName := *appParams.StackName
   287  	cmd.ui.Say(T("Using stack {{.StackName}}...",
   288  		map[string]interface{}{"StackName": terminal.EntityNameColor(stackName)}))
   289  
   290  	stack, apiErr := cmd.stackRepo.FindByName(stackName)
   291  	if apiErr != nil {
   292  		cmd.ui.Failed(apiErr.Error())
   293  		return
   294  	}
   295  
   296  	cmd.ui.Ok()
   297  	appParams.StackGuid = &stack.Guid
   298  }
   299  
   300  func (cmd *Push) restart(app models.Application, params models.AppParams, c flags.FlagContext) {
   301  	if app.State != T("stopped") {
   302  		cmd.ui.Say("")
   303  		app, _ = cmd.appStopper.ApplicationStop(app, cmd.config.OrganizationFields().Name, cmd.config.SpaceFields().Name)
   304  	}
   305  
   306  	cmd.ui.Say("")
   307  
   308  	if c.Bool("no-start") {
   309  		return
   310  	}
   311  
   312  	if params.HealthCheckTimeout != nil {
   313  		cmd.appStarter.SetStartTimeoutInSeconds(*params.HealthCheckTimeout)
   314  	}
   315  
   316  	cmd.appStarter.ApplicationStart(app, cmd.config.OrganizationFields().Name, cmd.config.SpaceFields().Name)
   317  }
   318  
   319  func (cmd *Push) createOrUpdateApp(appParams models.AppParams) (app models.Application) {
   320  	if appParams.Name == nil {
   321  		cmd.ui.Failed(T("Error: No name found for app"))
   322  	}
   323  
   324  	app, apiErr := cmd.appRepo.Read(*appParams.Name)
   325  
   326  	switch apiErr.(type) {
   327  	case nil:
   328  		app = cmd.updateApp(app, appParams)
   329  	case *errors.ModelNotFoundError:
   330  		app = cmd.createApp(appParams)
   331  	default:
   332  		cmd.ui.Failed(apiErr.Error())
   333  	}
   334  
   335  	return
   336  }
   337  
   338  func (cmd *Push) createApp(appParams models.AppParams) (app models.Application) {
   339  	spaceGuid := cmd.config.SpaceFields().Guid
   340  	appParams.SpaceGuid = &spaceGuid
   341  
   342  	cmd.ui.Say(T("Creating app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...",
   343  		map[string]interface{}{
   344  			"AppName":   terminal.EntityNameColor(*appParams.Name),
   345  			"OrgName":   terminal.EntityNameColor(cmd.config.OrganizationFields().Name),
   346  			"SpaceName": terminal.EntityNameColor(cmd.config.SpaceFields().Name),
   347  			"Username":  terminal.EntityNameColor(cmd.config.Username())}))
   348  
   349  	app, apiErr := cmd.appRepo.Create(appParams)
   350  	if apiErr != nil {
   351  		cmd.ui.Failed(apiErr.Error())
   352  	}
   353  
   354  	cmd.ui.Ok()
   355  	cmd.ui.Say("")
   356  
   357  	return
   358  }
   359  
   360  func (cmd *Push) updateApp(app models.Application, appParams models.AppParams) (updatedApp models.Application) {
   361  	cmd.ui.Say(T("Updating app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...",
   362  		map[string]interface{}{
   363  			"AppName":   terminal.EntityNameColor(app.Name),
   364  			"OrgName":   terminal.EntityNameColor(cmd.config.OrganizationFields().Name),
   365  			"SpaceName": terminal.EntityNameColor(cmd.config.SpaceFields().Name),
   366  			"Username":  terminal.EntityNameColor(cmd.config.Username())}))
   367  
   368  	if appParams.EnvironmentVars != nil {
   369  		for key, val := range app.EnvironmentVars {
   370  			if _, ok := (*appParams.EnvironmentVars)[key]; !ok {
   371  				(*appParams.EnvironmentVars)[key] = val
   372  			}
   373  		}
   374  	}
   375  
   376  	var apiErr error
   377  	updatedApp, apiErr = cmd.appRepo.Update(app.Guid, appParams)
   378  	if apiErr != nil {
   379  		cmd.ui.Failed(apiErr.Error())
   380  	}
   381  
   382  	cmd.ui.Ok()
   383  	cmd.ui.Say("")
   384  
   385  	return
   386  }
   387  
   388  func (cmd *Push) findAndValidateAppsToPush(c flags.FlagContext) []models.AppParams {
   389  	appsFromManifest := cmd.getAppParamsFromManifest(c)
   390  	appFromContext := cmd.getAppParamsFromContext(c)
   391  	return cmd.createAppSetFromContextAndManifest(appFromContext, appsFromManifest)
   392  }
   393  
   394  func (cmd *Push) getAppParamsFromManifest(c flags.FlagContext) []models.AppParams {
   395  	if c.Bool("no-manifest") {
   396  		return []models.AppParams{}
   397  	}
   398  
   399  	var path string
   400  	if c.String("f") != "" {
   401  		path = c.String("f")
   402  	} else {
   403  		var err error
   404  		path, err = os.Getwd()
   405  		if err != nil {
   406  			cmd.ui.Failed(T("Could not determine the current working directory!"), err)
   407  		}
   408  	}
   409  
   410  	m, err := cmd.manifestRepo.ReadManifest(path)
   411  
   412  	if err != nil {
   413  		if m.Path == "" && c.String("f") == "" {
   414  			return []models.AppParams{}
   415  		} else {
   416  			cmd.ui.Failed(T("Error reading manifest file:\n{{.Err}}", map[string]interface{}{"Err": err.Error()}))
   417  		}
   418  	}
   419  
   420  	apps, err := m.Applications()
   421  	if err != nil {
   422  		cmd.ui.Failed("Error reading manifest file:\n%s", err)
   423  	}
   424  
   425  	cmd.ui.Say(T("Using manifest file {{.Path}}\n",
   426  		map[string]interface{}{"Path": terminal.EntityNameColor(m.Path)}))
   427  	return apps
   428  }
   429  
   430  func (cmd *Push) createAppSetFromContextAndManifest(contextApp models.AppParams, manifestApps []models.AppParams) (apps []models.AppParams) {
   431  	var err error
   432  
   433  	switch len(manifestApps) {
   434  	case 0:
   435  		if contextApp.Name == nil {
   436  			cmd.ui.Failed(
   437  				T("Manifest file is not found in the current directory, please provide either an app name or manifest") +
   438  					"\n\n" +
   439  					command_registry.Commands.CommandUsage("push"),
   440  			)
   441  		} else {
   442  			err = addApp(&apps, contextApp)
   443  		}
   444  	case 1:
   445  		manifestApps[0].Merge(&contextApp)
   446  		err = addApp(&apps, manifestApps[0])
   447  	default:
   448  		selectedAppName := contextApp.Name
   449  		contextApp.Name = nil
   450  
   451  		if !contextApp.IsEmpty() {
   452  			cmd.ui.Failed("%s", T("Incorrect Usage. Command line flags (except -f) cannot be applied when pushing multiple apps from a manifest file."))
   453  		}
   454  
   455  		if selectedAppName != nil {
   456  			var manifestApp models.AppParams
   457  			manifestApp, err = findAppWithNameInManifest(*selectedAppName, manifestApps)
   458  			if err == nil {
   459  				addApp(&apps, manifestApp)
   460  			}
   461  		} else {
   462  			for _, manifestApp := range manifestApps {
   463  				addApp(&apps, manifestApp)
   464  			}
   465  		}
   466  	}
   467  
   468  	if err != nil {
   469  		cmd.ui.Failed(T("Error: {{.Err}}", map[string]interface{}{"Err": err.Error()}))
   470  	}
   471  
   472  	return
   473  }
   474  
   475  func addApp(apps *[]models.AppParams, app models.AppParams) (err error) {
   476  	if app.Name == nil {
   477  		err = errors.New(T("App name is a required field"))
   478  	}
   479  	if app.Path == nil {
   480  		cwd, _ := os.Getwd()
   481  		app.Path = &cwd
   482  	}
   483  	*apps = append(*apps, app)
   484  	return
   485  }
   486  
   487  func findAppWithNameInManifest(name string, manifestApps []models.AppParams) (app models.AppParams, err error) {
   488  	for _, appParams := range manifestApps {
   489  		if appParams.Name != nil && *appParams.Name == name {
   490  			app = appParams
   491  			return
   492  		}
   493  	}
   494  
   495  	err = errors.New(T("Could not find app named '{{.AppName}}' in manifest",
   496  		map[string]interface{}{"AppName": name}))
   497  	return
   498  }
   499  
   500  func (cmd *Push) getAppParamsFromContext(c flags.FlagContext) (appParams models.AppParams) {
   501  	if len(c.Args()) > 0 {
   502  		appParams.Name = &c.Args()[0]
   503  	}
   504  
   505  	appParams.NoRoute = c.Bool("no-route")
   506  	appParams.UseRandomHostname = c.Bool("random-route")
   507  	appParams.NoHostname = c.Bool("no-hostname")
   508  
   509  	if c.String("n") != "" {
   510  		appParams.Hosts = &[]string{c.String("n")}
   511  	}
   512  
   513  	if c.String("b") != "" {
   514  		buildpack := c.String("b")
   515  		if buildpack == "null" || buildpack == "default" {
   516  			buildpack = ""
   517  		}
   518  		appParams.BuildpackUrl = &buildpack
   519  	}
   520  
   521  	if c.String("c") != "" {
   522  		command := c.String("c")
   523  		if command == "null" || command == "default" {
   524  			command = ""
   525  		}
   526  		appParams.Command = &command
   527  	}
   528  
   529  	if c.String("d") != "" {
   530  		appParams.Domains = &[]string{c.String("d")}
   531  	}
   532  
   533  	if c.IsSet("i") {
   534  		instances := c.Int("i")
   535  		if instances < 1 {
   536  			cmd.ui.Failed(T("Invalid instance count: {{.InstancesCount}}\nInstance count must be a positive integer",
   537  				map[string]interface{}{"InstancesCount": instances}))
   538  		}
   539  		appParams.InstanceCount = &instances
   540  	}
   541  
   542  	if c.String("k") != "" {
   543  		diskQuota, err := formatters.ToMegabytes(c.String("k"))
   544  		if err != nil {
   545  			cmd.ui.Failed(T("Invalid disk quota: {{.DiskQuota}}\n{{.Err}}",
   546  				map[string]interface{}{"DiskQuota": c.String("k"), "Err": err.Error()}))
   547  		}
   548  		appParams.DiskQuota = &diskQuota
   549  	}
   550  
   551  	if c.String("m") != "" {
   552  		memory, err := formatters.ToMegabytes(c.String("m"))
   553  		if err != nil {
   554  			cmd.ui.Failed(T("Invalid memory limit: {{.MemLimit}}\n{{.Err}}",
   555  				map[string]interface{}{"MemLimit": c.String("m"), "Err": err.Error()}))
   556  		}
   557  		appParams.Memory = &memory
   558  	}
   559  
   560  	if c.String("p") != "" {
   561  		path := c.String("p")
   562  		appParams.Path = &path
   563  	}
   564  
   565  	if c.String("s") != "" {
   566  		stackName := c.String("s")
   567  		appParams.StackName = &stackName
   568  	}
   569  
   570  	if c.String("t") != "" {
   571  		timeout, err := strconv.Atoi(c.String("t"))
   572  		if err != nil {
   573  			cmd.ui.Failed("Error: %s", errors.NewWithFmt(T("Invalid timeout param: {{.Timeout}}\n{{.Err}}",
   574  				map[string]interface{}{"Timeout": c.String("t"), "Err": err.Error()})))
   575  		}
   576  
   577  		appParams.HealthCheckTimeout = &timeout
   578  	}
   579  
   580  	return
   581  }
   582  
   583  func (cmd *Push) uploadApp(appGuid string, appDir string) (apiErr error) {
   584  	fileutils.TempDir("apps", func(uploadDir string, err error) {
   585  		if err != nil {
   586  			apiErr = err
   587  			return
   588  		}
   589  
   590  		presentFiles, hasFileToUpload, err := cmd.actor.GatherFiles(appDir, uploadDir)
   591  		if err != nil {
   592  			apiErr = err
   593  			return
   594  		}
   595  
   596  		fileutils.TempFile("uploads", func(zipFile *os.File, err error) {
   597  			if hasFileToUpload {
   598  				err = cmd.zipAppFiles(zipFile, appDir, uploadDir)
   599  				if err != nil {
   600  					apiErr = err
   601  					return
   602  				}
   603  			}
   604  
   605  			err = cmd.actor.UploadApp(appGuid, zipFile, presentFiles)
   606  			if err != nil {
   607  				apiErr = err
   608  				return
   609  			}
   610  		})
   611  		return
   612  	})
   613  	return
   614  }
   615  
   616  func (cmd *Push) zipAppFiles(zipFile *os.File, appDir string, uploadDir string) (zipErr error) {
   617  	zipErr = cmd.zipWithBetterErrors(uploadDir, zipFile)
   618  	if zipErr != nil {
   619  		return
   620  	}
   621  
   622  	zipFileSize, zipErr := cmd.zipper.GetZipSize(zipFile)
   623  	if zipErr != nil {
   624  		return
   625  	}
   626  
   627  	zipFileCount := cmd.app_files.CountFiles(uploadDir)
   628  
   629  	cmd.describeUploadOperation(appDir, zipFileSize, zipFileCount)
   630  	return
   631  }
   632  
   633  func (cmd *Push) zipWithBetterErrors(uploadDir string, zipFile *os.File) error {
   634  	zipError := cmd.zipper.Zip(uploadDir, zipFile)
   635  	switch err := zipError.(type) {
   636  	case nil:
   637  		return nil
   638  	case *errors.EmptyDirError:
   639  		zipFile = nil
   640  		return zipError
   641  	default:
   642  		return errors.NewWithError(T("Error zipping application"), err)
   643  	}
   644  }
   645  
   646  func (cmd *Push) describeUploadOperation(path string, zipFileBytes, fileCount int64) {
   647  	if fileCount > 0 {
   648  		cmd.ui.Say(T("Uploading app files from: {{.Path}}", map[string]interface{}{"Path": path}))
   649  		cmd.ui.Say(T("Uploading {{.ZipFileBytes}}, {{.FileCount}} files",
   650  			map[string]interface{}{
   651  				"ZipFileBytes": formatters.ByteSize(zipFileBytes),
   652  				"FileCount":    fileCount}))
   653  	}
   654  }