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