github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+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 ) 31 32 type Push struct { 33 ui terminal.UI 34 config coreconfig.Reader 35 manifestRepo manifest.Repository 36 appStarter Starter 37 appStopper Stopper 38 serviceBinder service.Binder 39 appRepo applications.Repository 40 domainRepo api.DomainRepository 41 routeRepo api.RouteRepository 42 serviceRepo api.ServiceRepository 43 stackRepo stacks.StackRepository 44 authRepo authentication.Repository 45 wordGenerator commandregistry.RandomWordGenerator 46 actor actors.PushActor 47 routeActor actors.RouteActor 48 zipper appfiles.Zipper 49 appfiles appfiles.AppFiles 50 } 51 52 func init() { 53 commandregistry.Register(&Push{}) 54 } 55 56 func (cmd *Push) MetaData() commandregistry.CommandMetadata { 57 fs := make(map[string]flags.FlagSet) 58 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'")} 59 fs["c"] = &flags.StringFlag{ShortName: "c", Usage: T("Startup command, set to null to reset to default start command")} 60 fs["d"] = &flags.StringFlag{ShortName: "d", Usage: T("Domain (e.g. example.com)")} 61 fs["f"] = &flags.StringFlag{ShortName: "f", Usage: T("Path to manifest")} 62 fs["i"] = &flags.IntFlag{ShortName: "i", Usage: T("Number of instances")} 63 fs["k"] = &flags.StringFlag{ShortName: "k", Usage: T("Disk limit (e.g. 256M, 1024M, 1G)")} 64 fs["m"] = &flags.StringFlag{ShortName: "m", Usage: T("Memory limit (e.g. 256M, 1024M, 1G)")} 65 fs["hostname"] = &flags.StringFlag{Name: "hostname", ShortName: "n", Usage: T("Hostname (e.g. my-subdomain)")} 66 fs["p"] = &flags.StringFlag{ShortName: "p", Usage: T("Path to app directory or to a zip file of the contents of the app directory")} 67 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)")} 68 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")} 69 fs["docker-image"] = &flags.StringFlag{Name: "docker-image", ShortName: "o", Usage: T("Docker-image to be used (e.g. user/docker-image-name)")} 70 fs["docker-username"] = &flags.StringFlag{Name: "docker-username", Usage: T("Repository username; used with password from environment variable CF_DOCKER_PASSWORD")} 71 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 '/')")} 72 fs["no-hostname"] = &flags.BoolFlag{Name: "no-hostname", Usage: T("Map the root domain to this app")} 73 fs["no-manifest"] = &flags.BoolFlag{Name: "no-manifest", Usage: T("Ignore manifest file")} 74 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")} 75 fs["no-start"] = &flags.BoolFlag{Name: "no-start", Usage: T("Do not start an app after pushing")} 76 fs["random-route"] = &flags.BoolFlag{Name: "random-route", Usage: T("Create a random route for this app")} 77 fs["route-path"] = &flags.StringFlag{Name: "route-path", Usage: T("Path for the route")} 78 // Hidden:true to hide app-ports for release #117189491 79 fs["app-ports"] = &flags.StringFlag{Name: "app-ports", Usage: T("Comma delimited list of ports the application may listen on"), Hidden: true} 80 81 return commandregistry.CommandMetadata{ 82 Name: "push", 83 ShortName: "p", 84 Description: T("Push a new app or sync changes to an existing app"), 85 // strings.Replace \\n with newline so this string matches the new usage string but still gets displayed correctly 86 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)}, 87 Flags: fs, 88 } 89 } 90 91 func (cmd *Push) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) ([]requirements.Requirement, error) { 92 var reqs []requirements.Requirement 93 94 usageReq := requirementsFactory.NewUsageRequirement(commandregistry.CLICommandUsagePresenter(cmd), "", 95 func() bool { 96 return len(fc.Args()) > 1 97 }, 98 ) 99 100 reqs = append(reqs, usageReq) 101 102 if fc.String("route-path") != "" { 103 reqs = append(reqs, requirementsFactory.NewMinAPIVersionRequirement("Option '--route-path'", cf.RoutePathMinimumAPIVersion)) 104 } 105 106 if fc.String("app-ports") != "" { 107 reqs = append(reqs, requirementsFactory.NewMinAPIVersionRequirement("Option '--app-ports'", cf.MultipleAppPortsMinimumAPIVersion)) 108 } 109 110 reqs = append(reqs, []requirements.Requirement{ 111 requirementsFactory.NewLoginRequirement(), 112 requirementsFactory.NewTargetedSpaceRequirement(), 113 }...) 114 115 return reqs, nil 116 } 117 118 func (cmd *Push) SetDependency(deps commandregistry.Dependency, pluginCall bool) commandregistry.Command { 119 cmd.ui = deps.UI 120 cmd.config = deps.Config 121 cmd.manifestRepo = deps.ManifestRepo 122 123 //set appStarter 124 appCommand := commandregistry.Commands.FindCommand("start") 125 appCommand = appCommand.SetDependency(deps, false) 126 cmd.appStarter = appCommand.(Starter) 127 128 //set appStopper 129 appCommand = commandregistry.Commands.FindCommand("stop") 130 appCommand = appCommand.SetDependency(deps, false) 131 cmd.appStopper = appCommand.(Stopper) 132 133 //set serviceBinder 134 appCommand = commandregistry.Commands.FindCommand("bind-service") 135 appCommand = appCommand.SetDependency(deps, false) 136 cmd.serviceBinder = appCommand.(service.Binder) 137 138 cmd.appRepo = deps.RepoLocator.GetApplicationRepository() 139 cmd.domainRepo = deps.RepoLocator.GetDomainRepository() 140 cmd.routeRepo = deps.RepoLocator.GetRouteRepository() 141 cmd.serviceRepo = deps.RepoLocator.GetServiceRepository() 142 cmd.stackRepo = deps.RepoLocator.GetStackRepository() 143 cmd.authRepo = deps.RepoLocator.GetAuthenticationRepository() 144 cmd.wordGenerator = deps.WordGenerator 145 cmd.actor = deps.PushActor 146 cmd.routeActor = deps.RouteActor 147 cmd.zipper = deps.AppZipper 148 cmd.appfiles = deps.AppFiles 149 150 return cmd 151 } 152 153 func (cmd *Push) Execute(c flags.FlagContext) error { 154 appsFromManifest, err := cmd.getAppParamsFromManifest(c) 155 if err != nil { 156 return err 157 } 158 159 errs := cmd.actor.ValidateAppParams(appsFromManifest) 160 if len(errs) > 0 { 161 errStr := T("Invalid application configuration") + ":" 162 163 for _, e := range errs { 164 errStr = fmt.Sprintf("%s\n%s", errStr, e.Error()) 165 } 166 167 return fmt.Errorf("%s", errStr) 168 } 169 170 appFromContext, err := cmd.getAppParamsFromContext(c) 171 if err != nil { 172 return err 173 } 174 175 err = cmd.ValidateContextAndAppParams(appsFromManifest, appFromContext) 176 if err != nil { 177 return err 178 } 179 180 appSet, err := cmd.createAppSetFromContextAndManifest(appFromContext, appsFromManifest) 181 if err != nil { 182 return err 183 } 184 185 _, err = cmd.authRepo.RefreshAuthToken() 186 if err != nil { 187 return err 188 } 189 190 for _, appParams := range appSet { 191 if appParams.Name == nil { 192 return errors.New(T("Error: No name found for app")) 193 } 194 195 err = cmd.fetchStackGUID(&appParams) 196 if err != nil { 197 return err 198 } 199 200 if appParams.DockerImage != nil { 201 diego := true 202 appParams.Diego = &diego 203 } 204 205 var app, existingApp models.Application 206 existingApp, err = cmd.appRepo.Read(*appParams.Name) 207 switch err.(type) { 208 case nil: 209 cmd.ui.Say(T("Updating app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", 210 map[string]interface{}{ 211 "AppName": terminal.EntityNameColor(existingApp.Name), 212 "OrgName": terminal.EntityNameColor(cmd.config.OrganizationFields().Name), 213 "SpaceName": terminal.EntityNameColor(cmd.config.SpaceFields().Name), 214 "Username": terminal.EntityNameColor(cmd.config.Username())})) 215 216 if appParams.EnvironmentVars != nil { 217 for key, val := range existingApp.EnvironmentVars { 218 if _, ok := (*appParams.EnvironmentVars)[key]; !ok { 219 (*appParams.EnvironmentVars)[key] = val 220 } 221 } 222 } 223 224 // if the user did not provide a health-check-http-endpoint 225 // and one doesn't exist already in the cloud 226 // set to default 227 if appParams.HealthCheckType != nil && *appParams.HealthCheckType == "http" { 228 if appParams.HealthCheckHTTPEndpoint == nil && existingApp.HealthCheckHTTPEndpoint == "" { 229 endpoint := "/" 230 appParams.HealthCheckHTTPEndpoint = &endpoint 231 } 232 } 233 234 app, err = cmd.appRepo.Update(existingApp.GUID, appParams) 235 if err != nil { 236 return err 237 } 238 case *errors.ModelNotFoundError: 239 spaceGUID := cmd.config.SpaceFields().GUID 240 appParams.SpaceGUID = &spaceGUID 241 242 cmd.ui.Say(T("Creating app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", 243 map[string]interface{}{ 244 "AppName": terminal.EntityNameColor(*appParams.Name), 245 "OrgName": terminal.EntityNameColor(cmd.config.OrganizationFields().Name), 246 "SpaceName": terminal.EntityNameColor(cmd.config.SpaceFields().Name), 247 "Username": terminal.EntityNameColor(cmd.config.Username())})) 248 249 // if the user did not provide a health-check-http-endpoint 250 // set to default 251 if appParams.HealthCheckType != nil && *appParams.HealthCheckType == "http" { 252 if appParams.HealthCheckHTTPEndpoint == nil { 253 endpoint := "/" 254 appParams.HealthCheckHTTPEndpoint = &endpoint 255 } 256 } 257 app, err = cmd.appRepo.Create(appParams) 258 if err != nil { 259 return err 260 } 261 default: 262 return err 263 } 264 265 cmd.ui.Ok() 266 cmd.ui.Say("") 267 268 err = cmd.updateRoutes(app, appParams, appFromContext) 269 if err != nil { 270 return err 271 } 272 273 if appParams.DockerImage == nil { 274 err = cmd.actor.ProcessPath(*appParams.Path, cmd.processPathCallback(*appParams.Path, app)) 275 if err != nil { 276 return errors.New( 277 T("Error processing app files: {{.Error}}", 278 map[string]interface{}{ 279 "Error": err.Error(), 280 }), 281 ) 282 } 283 } 284 285 if appParams.ServicesToBind != nil { 286 err = cmd.bindAppToServices(appParams.ServicesToBind, app) 287 if err != nil { 288 return err 289 } 290 } 291 292 err = cmd.restart(app, appParams, c) 293 if err != nil { 294 return errors.New( 295 T("Error restarting application: {{.Error}}", 296 map[string]interface{}{ 297 "Error": err.Error(), 298 }), 299 ) 300 } 301 } 302 return nil 303 } 304 305 func (cmd *Push) processPathCallback(path string, app models.Application) func(string) error { 306 return func(appDir string) error { 307 localFiles, err := cmd.appfiles.AppFilesInDir(appDir) 308 if err != nil { 309 return errors.New( 310 T("Error processing app files in '{{.Path}}': {{.Error}}", 311 map[string]interface{}{ 312 "Path": path, 313 "Error": err.Error(), 314 })) 315 } 316 317 if len(localFiles) == 0 { 318 return errors.New( 319 T("No app files found in '{{.Path}}'", 320 map[string]interface{}{ 321 "Path": path, 322 })) 323 } 324 325 cmd.ui.Say(T("Uploading {{.AppName}}...", 326 map[string]interface{}{"AppName": terminal.EntityNameColor(app.Name)})) 327 328 err = cmd.uploadApp(app.GUID, appDir, path, localFiles) 329 if err != nil { 330 return errors.New(T("Error uploading application.\n{{.APIErr}}", 331 map[string]interface{}{"APIErr": err.Error()})) 332 } 333 cmd.ui.Ok() 334 return nil 335 } 336 } 337 338 func (cmd *Push) updateRoutes(app models.Application, appParams models.AppParams, appParamsFromContext models.AppParams) error { 339 defaultRouteAcceptable := len(app.Routes) == 0 340 routeDefined := appParams.Domains != nil || !appParams.IsHostEmpty() || appParams.IsNoHostnameTrue() 341 342 switch { 343 case appParams.NoRoute: 344 if len(app.Routes) == 0 { 345 cmd.ui.Say(T("App {{.AppName}} is a worker, skipping route creation", 346 map[string]interface{}{"AppName": terminal.EntityNameColor(app.Name)})) 347 } else { 348 err := cmd.routeActor.UnbindAll(app) 349 if err != nil { 350 return err 351 } 352 } 353 case len(appParams.Routes) > 0: 354 for _, manifestRoute := range appParams.Routes { 355 err := cmd.actor.MapManifestRoute(manifestRoute.Route, app, appParamsFromContext) 356 if err != nil { 357 return err 358 } 359 } 360 case (routeDefined || defaultRouteAcceptable) && appParams.Domains == nil: 361 domain, err := cmd.findDomain(nil) 362 if err != nil { 363 return err 364 } 365 appParams.UseRandomPort = isTCP(domain) 366 err = cmd.processDomainsAndBindRoutes(appParams, app, domain) 367 if err != nil { 368 return err 369 } 370 case routeDefined || defaultRouteAcceptable: 371 for _, d := range appParams.Domains { 372 domain, err := cmd.findDomain(&d) 373 if err != nil { 374 return err 375 } 376 appParams.UseRandomPort = isTCP(domain) 377 err = cmd.processDomainsAndBindRoutes(appParams, app, domain) 378 if err != nil { 379 return err 380 } 381 } 382 } 383 return nil 384 } 385 386 const TCP = "tcp" 387 388 func isTCP(domain models.DomainFields) bool { 389 return domain.RouterGroupType == TCP 390 } 391 392 func (cmd *Push) processDomainsAndBindRoutes( 393 appParams models.AppParams, 394 app models.Application, 395 domain models.DomainFields, 396 ) error { 397 if appParams.IsHostEmpty() { 398 err := cmd.createAndBindRoute( 399 nil, 400 appParams.UseRandomRoute, 401 appParams.UseRandomPort, 402 app, 403 appParams.IsNoHostnameTrue(), 404 domain, 405 appParams.RoutePath, 406 ) 407 if err != nil { 408 return err 409 } 410 } else { 411 for _, host := range appParams.Hosts { 412 err := cmd.createAndBindRoute( 413 &host, 414 appParams.UseRandomRoute, 415 appParams.UseRandomPort, 416 app, 417 appParams.IsNoHostnameTrue(), 418 domain, 419 appParams.RoutePath, 420 ) 421 if err != nil { 422 return err 423 } 424 } 425 } 426 return nil 427 } 428 429 func (cmd *Push) createAndBindRoute( 430 host *string, 431 UseRandomRoute bool, 432 UseRandomPort bool, 433 app models.Application, 434 noHostName bool, 435 domain models.DomainFields, 436 routePath *string, 437 ) error { 438 var hostname string 439 if !noHostName { 440 switch { 441 case host != nil: 442 hostname = *host 443 case UseRandomPort: 444 //do nothing 445 case UseRandomRoute: 446 hostname = hostNameForString(app.Name) + "-" + cmd.wordGenerator.Babble() 447 default: 448 hostname = hostNameForString(app.Name) 449 } 450 } 451 452 var route models.Route 453 var err error 454 if routePath != nil { 455 route, err = cmd.routeActor.FindOrCreateRoute(hostname, domain, *routePath, 0, UseRandomPort) 456 } else { 457 route, err = cmd.routeActor.FindOrCreateRoute(hostname, domain, "", 0, UseRandomPort) 458 } 459 if err != nil { 460 return err 461 } 462 return cmd.routeActor.BindRoute(app, route) 463 } 464 465 var forbiddenHostCharRegex = regexp.MustCompile("[^a-z0-9-]") 466 var whitespaceRegex = regexp.MustCompile(`[\s_]+`) 467 468 func hostNameForString(name string) string { 469 name = strings.ToLower(name) 470 name = whitespaceRegex.ReplaceAllString(name, "-") 471 name = forbiddenHostCharRegex.ReplaceAllString(name, "") 472 return name 473 } 474 475 func (cmd *Push) findDomain(domainName *string) (models.DomainFields, error) { 476 domain, err := cmd.domainRepo.FirstOrDefault(cmd.config.OrganizationFields().GUID, domainName) 477 if err != nil { 478 return models.DomainFields{}, err 479 } 480 481 return domain, nil 482 } 483 484 func (cmd *Push) bindAppToServices(services []string, app models.Application) error { 485 for _, serviceName := range services { 486 serviceInstance, err := cmd.serviceRepo.FindInstanceByName(serviceName) 487 488 if err != nil { 489 return errors.New(T("Could not find service {{.ServiceName}} to bind to {{.AppName}}", 490 map[string]interface{}{"ServiceName": serviceName, "AppName": app.Name})) 491 } 492 493 cmd.ui.Say(T("Binding service {{.ServiceName}} to app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", 494 map[string]interface{}{ 495 "ServiceName": terminal.EntityNameColor(serviceInstance.Name), 496 "AppName": terminal.EntityNameColor(app.Name), 497 "OrgName": terminal.EntityNameColor(cmd.config.OrganizationFields().Name), 498 "SpaceName": terminal.EntityNameColor(cmd.config.SpaceFields().Name), 499 "Username": terminal.EntityNameColor(cmd.config.Username())})) 500 501 err = cmd.serviceBinder.BindApplication(app, serviceInstance, nil) 502 503 switch httpErr := err.(type) { 504 case errors.HTTPError: 505 if httpErr.ErrorCode() == errors.ServiceBindingAppServiceTaken { 506 err = nil 507 } 508 } 509 510 if err != nil { 511 return errors.New(T("Could not bind to service {{.ServiceName}}\nError: {{.Err}}", 512 map[string]interface{}{"ServiceName": serviceName, "Err": err.Error()})) 513 } 514 515 cmd.ui.Ok() 516 } 517 return nil 518 } 519 520 func (cmd *Push) fetchStackGUID(appParams *models.AppParams) error { 521 if appParams.StackName == nil { 522 return nil 523 } 524 525 stackName := *appParams.StackName 526 cmd.ui.Say(T("Using stack {{.StackName}}...", 527 map[string]interface{}{"StackName": terminal.EntityNameColor(stackName)})) 528 529 stack, err := cmd.stackRepo.FindByName(stackName) 530 if err != nil { 531 return err 532 } 533 534 cmd.ui.Ok() 535 appParams.StackGUID = &stack.GUID 536 return nil 537 } 538 539 func (cmd *Push) restart(app models.Application, params models.AppParams, c flags.FlagContext) error { 540 if app.State != T("stopped") { 541 cmd.ui.Say("") 542 app, _ = cmd.appStopper.ApplicationStop(app, cmd.config.OrganizationFields().Name, cmd.config.SpaceFields().Name) 543 } 544 545 cmd.ui.Say("") 546 547 if c.Bool("no-start") { 548 return nil 549 } 550 551 if params.HealthCheckTimeout != nil { 552 cmd.appStarter.SetStartTimeoutInSeconds(*params.HealthCheckTimeout) 553 } 554 555 _, err := cmd.appStarter.ApplicationStart(app, cmd.config.OrganizationFields().Name, cmd.config.SpaceFields().Name) 556 if err != nil { 557 return err 558 } 559 560 return nil 561 } 562 563 func (cmd *Push) getAppParamsFromManifest(c flags.FlagContext) ([]models.AppParams, error) { 564 if c.Bool("no-manifest") { 565 return []models.AppParams{}, nil 566 } 567 568 var path string 569 if c.String("f") != "" { 570 path = c.String("f") 571 } else { 572 var err error 573 path, err = os.Getwd() 574 if err != nil { 575 return nil, errors.New(fmt.Sprint(T("Could not determine the current working directory!"), err)) 576 } 577 } 578 579 m, err := cmd.manifestRepo.ReadManifest(path) 580 581 if err != nil { 582 if m.Path == "" && c.String("f") == "" { 583 return []models.AppParams{}, nil 584 } 585 return nil, errors.New(T("Error reading manifest file:\n{{.Err}}", map[string]interface{}{"Err": err.Error()})) 586 } 587 588 apps, err := m.Applications() 589 if err != nil { 590 return nil, errors.New(T("Error reading manifest file:\n{{.Err}}", map[string]interface{}{"Err": err.Error()})) 591 } 592 593 cmd.ui.Say(T("Using manifest file {{.Path}}\n", 594 map[string]interface{}{"Path": terminal.EntityNameColor(m.Path)})) 595 return apps, nil 596 } 597 598 func (cmd *Push) createAppSetFromContextAndManifest(contextApp models.AppParams, manifestApps []models.AppParams) ([]models.AppParams, error) { 599 var err error 600 var apps []models.AppParams 601 602 switch len(manifestApps) { 603 case 0: 604 if contextApp.Name == nil { 605 return nil, errors.New( 606 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.") + 607 "\n\n" + 608 commandregistry.Commands.CommandUsage("push"), 609 ) 610 } 611 err = addApp(&apps, contextApp) 612 case 1: 613 err = checkCombinedDockerProperties(contextApp, manifestApps[0]) 614 if err != nil { 615 return nil, err 616 } 617 618 manifestApps[0].Merge(&contextApp) 619 err = addApp(&apps, manifestApps[0]) 620 default: 621 selectedAppName := contextApp.Name 622 contextApp.Name = nil 623 624 if !contextApp.IsEmpty() { 625 return nil, errors.New(T("Incorrect Usage. Command line flags (except -f) cannot be applied when pushing multiple apps from a manifest file.")) 626 } 627 628 if selectedAppName != nil { 629 var foundApp bool 630 for _, appParams := range manifestApps { 631 if appParams.Name != nil && *appParams.Name == *selectedAppName { 632 foundApp = true 633 err = addApp(&apps, appParams) 634 } 635 } 636 637 if !foundApp { 638 err = errors.New(T("Could not find app named '{{.AppName}}' in manifest", map[string]interface{}{"AppName": *selectedAppName})) 639 } 640 } else { 641 for _, manifestApp := range manifestApps { 642 err = addApp(&apps, manifestApp) 643 } 644 } 645 } 646 647 if err != nil { 648 return nil, errors.New(T("Error: {{.Err}}", map[string]interface{}{"Err": err.Error()})) 649 } 650 651 return apps, nil 652 } 653 654 func checkCombinedDockerProperties(flagContext models.AppParams, manifestApp models.AppParams) error { 655 if manifestApp.DockerUsername != nil || flagContext.DockerUsername != nil { 656 if manifestApp.DockerImage == nil && flagContext.DockerImage == nil { 657 return errors.New(T("'--docker-username' requires '--docker-image' to be specified")) 658 } 659 } 660 661 dockerPassword := os.Getenv("CF_DOCKER_PASSWORD") 662 if flagContext.DockerUsername == nil && manifestApp.DockerUsername != nil && dockerPassword == "" { 663 return errors.New(T("No Docker password was provided. Please provide the password by setting the CF_DOCKER_PASSWORD environment variable.")) 664 } 665 666 return nil 667 } 668 669 func addApp(apps *[]models.AppParams, app models.AppParams) error { 670 if app.Name == nil { 671 return errors.New(T("App name is a required field")) 672 } 673 674 if app.Path == nil { 675 cwd, err := os.Getwd() 676 if err != nil { 677 return err 678 } 679 app.Path = &cwd 680 } 681 682 *apps = append(*apps, app) 683 684 return nil 685 } 686 687 func (cmd *Push) getAppParamsFromContext(c flags.FlagContext) (models.AppParams, error) { 688 noHostBool := c.Bool("no-hostname") 689 appParams := models.AppParams{ 690 NoRoute: c.Bool("no-route"), 691 UseRandomRoute: c.Bool("random-route"), 692 NoHostname: &noHostBool, 693 } 694 695 if len(c.Args()) > 0 { 696 appParams.Name = &c.Args()[0] 697 } 698 699 if c.String("n") != "" { 700 appParams.Hosts = []string{c.String("n")} 701 } 702 703 if c.String("route-path") != "" { 704 routePath := c.String("route-path") 705 appParams.RoutePath = &routePath 706 } 707 708 if c.String("app-ports") != "" { 709 appPortStrings := strings.Split(c.String("app-ports"), ",") 710 appPorts := make([]int, len(appPortStrings)) 711 712 for i, s := range appPortStrings { 713 p, err := strconv.Atoi(s) 714 if err != nil { 715 return models.AppParams{}, errors.New(T("Invalid app port: {{.AppPort}}\nApp port must be a number", map[string]interface{}{ 716 "AppPort": s, 717 })) 718 } 719 appPorts[i] = p 720 } 721 722 appParams.AppPorts = &appPorts 723 } 724 725 if c.String("b") != "" { 726 buildpack := c.String("b") 727 if buildpack == "null" || buildpack == "default" { 728 buildpack = "" 729 } 730 appParams.BuildpackURL = &buildpack 731 } 732 733 if c.String("c") != "" { 734 command := c.String("c") 735 if command == "null" || command == "default" { 736 command = "" 737 } 738 appParams.Command = &command 739 } 740 741 if c.String("d") != "" { 742 appParams.Domains = []string{c.String("d")} 743 } 744 745 if c.IsSet("i") { 746 instances := c.Int("i") 747 if instances < 1 { 748 return models.AppParams{}, errors.New(T("Invalid instance count: {{.InstancesCount}}\nInstance count must be a positive integer", 749 map[string]interface{}{"InstancesCount": instances})) 750 } 751 appParams.InstanceCount = &instances 752 } 753 754 if c.String("k") != "" { 755 diskQuota, err := formatters.ToMegabytes(c.String("k")) 756 if err != nil { 757 return models.AppParams{}, errors.New(T("Invalid disk quota: {{.DiskQuota}}\n{{.Err}}", 758 map[string]interface{}{"DiskQuota": c.String("k"), "Err": err.Error()})) 759 } 760 appParams.DiskQuota = &diskQuota 761 } 762 763 if c.String("m") != "" { 764 memory, err := formatters.ToMegabytes(c.String("m")) 765 if err != nil { 766 return models.AppParams{}, errors.New(T("Invalid memory limit: {{.MemLimit}}\n{{.Err}}", 767 map[string]interface{}{"MemLimit": c.String("m"), "Err": err.Error()})) 768 } 769 appParams.Memory = &memory 770 } 771 772 if c.String("docker-image") != "" { 773 dockerImage := c.String("docker-image") 774 appParams.DockerImage = &dockerImage 775 } 776 777 if c.String("docker-username") != "" { 778 username := c.String("docker-username") 779 appParams.DockerUsername = &username 780 781 password := os.Getenv("CF_DOCKER_PASSWORD") 782 if password != "" { 783 cmd.ui.Say(T("Using docker repository password from environment variable CF_DOCKER_PASSWORD.")) 784 } else { 785 cmd.ui.Say(T("Environment variable CF_DOCKER_PASSWORD not set.")) 786 password = cmd.ui.AskForPassword("Docker password") 787 if password == "" { 788 return models.AppParams{}, errors.New(T("Please provide a password.")) 789 } 790 } 791 appParams.DockerPassword = &password 792 } 793 794 if c.String("p") != "" { 795 path := c.String("p") 796 appParams.Path = &path 797 } 798 799 if c.String("s") != "" { 800 stackName := c.String("s") 801 appParams.StackName = &stackName 802 } 803 804 if c.String("t") != "" { 805 timeout, err := strconv.Atoi(c.String("t")) 806 if err != nil { 807 return models.AppParams{}, fmt.Errorf("Error: %s", fmt.Errorf(T("Invalid timeout param: {{.Timeout}}\n{{.Err}}", 808 map[string]interface{}{"Timeout": c.String("t"), "Err": err.Error()}))) 809 } 810 811 appParams.HealthCheckTimeout = &timeout 812 } 813 814 healthCheckType := c.String("u") 815 switch healthCheckType { 816 case "": 817 // do nothing 818 case "http", "none", "port", "process": 819 appParams.HealthCheckType = &healthCheckType 820 default: 821 return models.AppParams{}, fmt.Errorf("Error: %s", fmt.Errorf(T("Invalid health-check-type param: {{.healthCheckType}}", 822 map[string]interface{}{"healthCheckType": healthCheckType}))) 823 } 824 825 return appParams, nil 826 } 827 828 func (cmd Push) ValidateContextAndAppParams(appsFromManifest []models.AppParams, appFromContext models.AppParams) error { 829 if appFromContext.NoHostname != nil && *appFromContext.NoHostname { 830 for _, app := range appsFromManifest { 831 if app.Routes != nil { 832 return errors.New(T("Option '--no-hostname' cannot be used with an app manifest containing the 'routes' attribute")) 833 } 834 } 835 } 836 837 return nil 838 } 839 840 func (cmd *Push) uploadApp(appGUID, appDir, appDirOrZipFile string, localFiles []models.AppFileFields) error { 841 uploadDir, err := ioutil.TempDir("", "apps") 842 if err != nil { 843 return err 844 } 845 846 remoteFiles, hasFileToUpload, err := cmd.actor.GatherFiles(localFiles, appDir, uploadDir, true) 847 848 if httpError, isHTTPError := err.(errors.HTTPError); isHTTPError && httpError.StatusCode() == 504 { 849 cmd.ui.Warn("Resource matching API timed out; pushing all app files.") 850 remoteFiles, hasFileToUpload, err = cmd.actor.GatherFiles(localFiles, appDir, uploadDir, false) 851 } 852 853 if err != nil { 854 return err 855 } 856 857 zipFile, err := ioutil.TempFile("", "uploads") 858 if err != nil { 859 return err 860 } 861 defer func() { 862 zipFile.Close() 863 os.Remove(zipFile.Name()) 864 }() 865 866 if hasFileToUpload { 867 err = cmd.zipper.Zip(uploadDir, zipFile) 868 if err != nil { 869 if emptyDirErr, ok := err.(*errors.EmptyDirError); ok { 870 return emptyDirErr 871 } 872 return fmt.Errorf("%s: %s", T("Error zipping application"), err.Error()) 873 } 874 875 var zipFileSize int64 876 zipFileSize, err = cmd.zipper.GetZipSize(zipFile) 877 if err != nil { 878 return err 879 } 880 881 zipFileCount := cmd.appfiles.CountFiles(uploadDir) 882 if zipFileCount > 0 { 883 cmd.ui.Say(T("Uploading app files from: {{.Path}}", map[string]interface{}{"Path": appDir})) 884 cmd.ui.Say(T("Uploading {{.ZipFileBytes}}, {{.FileCount}} files", 885 map[string]interface{}{ 886 "ZipFileBytes": formatters.ByteSize(zipFileSize), 887 "FileCount": zipFileCount})) 888 } 889 } 890 891 err = os.RemoveAll(uploadDir) 892 if err != nil { 893 return err 894 } 895 896 return cmd.actor.UploadApp(appGUID, zipFile, remoteFiles) 897 }