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