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