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 }