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