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