github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+incompatible/command/v7/push_command.go (about) 1 package v7 2 3 import ( 4 "os" 5 "strings" 6 7 "code.cloudfoundry.org/cli/command/v7/shared" 8 "code.cloudfoundry.org/cli/util/configv3" 9 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 11 12 "code.cloudfoundry.org/cli/actor/actionerror" 13 "code.cloudfoundry.org/cli/actor/sharedaction" 14 "code.cloudfoundry.org/cli/actor/v2action" 15 "code.cloudfoundry.org/cli/actor/v3action" 16 "code.cloudfoundry.org/cli/actor/v7action" 17 "code.cloudfoundry.org/cli/actor/v7pushaction" 18 "code.cloudfoundry.org/cli/command" 19 "code.cloudfoundry.org/cli/command/flag" 20 "code.cloudfoundry.org/cli/command/translatableerror" 21 v6shared "code.cloudfoundry.org/cli/command/v6/shared" 22 "code.cloudfoundry.org/cli/util/manifestparser" 23 "code.cloudfoundry.org/cli/util/progressbar" 24 25 "github.com/cloudfoundry/bosh-cli/director/template" 26 log "github.com/sirupsen/logrus" 27 ) 28 29 //go:generate counterfeiter . ProgressBar 30 31 type ProgressBar interface { 32 v7pushaction.ProgressBar 33 Complete() 34 Ready() 35 } 36 37 //go:generate counterfeiter . PushActor 38 39 type PushActor interface { 40 CreatePushPlans(appNameArg string, spaceGUID string, orgGUID string, parser v7pushaction.ManifestParser, overrides v7pushaction.FlagOverrides) ([]v7pushaction.PushPlan, error) 41 // Prepare the space by creating needed apps/applying the manifest 42 PrepareSpace(pushPlans []v7pushaction.PushPlan, parser v7pushaction.ManifestParser) (<-chan []v7pushaction.PushPlan, <-chan v7pushaction.Event, <-chan v7pushaction.Warnings, <-chan error) 43 // UpdateApplicationSettings figures out the state of the world. 44 UpdateApplicationSettings(pushPlans []v7pushaction.PushPlan) ([]v7pushaction.PushPlan, v7pushaction.Warnings, error) 45 // Actualize applies any necessary changes. 46 Actualize(plan v7pushaction.PushPlan, progressBar v7pushaction.ProgressBar) (<-chan v7pushaction.PushPlan, <-chan v7pushaction.Event, <-chan v7pushaction.Warnings, <-chan error) 47 } 48 49 //go:generate counterfeiter . V7ActorForPush 50 51 type V7ActorForPush interface { 52 AppActor 53 GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client v7action.NOAAClient) (<-chan *v7action.LogMessage, <-chan error, v7action.Warnings, error) 54 RestartApplication(appGUID string) (v7action.Warnings, error) 55 } 56 57 //go:generate counterfeiter . ManifestParser 58 59 type ManifestParser interface { 60 v7pushaction.ManifestParser 61 ContainsMultipleApps() bool 62 InterpolateAndParse(pathToManifest string, pathsToVarsFiles []string, vars []template.VarKV) error 63 ContainsPrivateDockerImages() bool 64 } 65 66 //go:generate counterfeiter . ManifestLocator 67 68 type ManifestLocator interface { 69 Path(filepathOrDirectory string) (string, bool, error) 70 } 71 72 type PushCommand struct { 73 OptionalArgs flag.OptionalAppName `positional-args:"yes"` 74 HealthCheckTimeout flag.PositiveInteger `long:"app-start-timeout" short:"t" description:"Time (in seconds) allowed to elapse between starting up an app and the first healthy response from the app"` 75 Buildpacks []string `long:"buildpack" short:"b" description:"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'"` 76 Disk flag.Megabytes `long:"disk" short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"` 77 DockerImage flag.DockerImage `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"` 78 DockerUsername string `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"` 79 HealthCheckHTTPEndpoint string `long:"endpoint" description:"Valid path on the app for an HTTP health check. Only used when specifying --health-check-type=http"` 80 HealthCheckType flag.HealthCheckType `long:"health-check-type" short:"u" description:"Application health check type. Defaults to 'port'. 'http' requires a valid endpoint, for example, '/health'."` 81 Instances flag.Instances `long:"instances" short:"i" description:"Number of instances"` 82 PathToManifest flag.PathWithExistenceCheck `long:"manifest" short:"f" description:"Path to manifest"` 83 Memory flag.Megabytes `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` 84 NoManifest bool `long:"no-manifest" description:""` 85 NoRoute bool `long:"no-route" description:"Do not map a route to this app"` 86 NoStart bool `long:"no-start" description:"Do not stage and start the app after pushing"` 87 AppPath flag.PathWithExistenceCheck `long:"path" short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"` 88 Stack string `long:"stack" short:"s" description:"Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)"` 89 StartCommand flag.Command `long:"start-command" short:"c" description:"Startup command, set to null to reset to default start command"` 90 Vars []template.VarKV `long:"var" description:"Variable key value pair for variable substitution, (e.g., name=app1); can specify multiple times"` 91 PathsToVarsFiles []flag.PathWithExistenceCheck `long:"vars-file" description:"Path to a variable substitution file for manifest; can specify multiple times"` 92 dockerPassword interface{} `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"` 93 usage interface{} `usage:"CF_NAME push APP_NAME [-b BUILDPACK_NAME] [-c COMMAND]\n [-f MANIFEST_PATH | --no-manifest] [--no-start] [-i NUM_INSTANCES]\n [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT]\n [-u (process | port | http)] [--no-route | --random-route]\n [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]...\n \n CF_NAME 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] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [-u (process | port | http)]\n [--no-route | --random-route ] [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]..."` 94 envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"` 95 envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` 96 97 Config command.Config 98 UI command.UI 99 NOAAClient v3action.NOAAClient 100 Actor PushActor 101 VersionActor V7ActorForPush 102 SharedActor command.SharedActor 103 RouteActor v7action.RouteActor 104 ProgressBar ProgressBar 105 PWD string 106 ManifestLocator ManifestLocator 107 ManifestParser ManifestParser 108 } 109 110 func (cmd *PushCommand) Setup(config command.Config, ui command.UI) error { 111 cmd.Config = config 112 cmd.UI = ui 113 cmd.ProgressBar = progressbar.NewProgressBar() 114 115 sharedActor := sharedaction.NewActor(config) 116 cmd.SharedActor = sharedActor 117 118 ccClient, uaaClient, err := v6shared.NewV3BasedClients(config, ui, true, "") 119 if err != nil { 120 return err 121 } 122 123 v7actor := v7action.NewActor(ccClient, config, sharedActor, uaaClient) 124 cmd.VersionActor = v7actor 125 ccClientV2, uaaClientV2, err := v6shared.NewClients(config, ui, true) 126 if err != nil { 127 return err 128 } 129 130 v2Actor := v2action.NewActor(ccClientV2, uaaClientV2, config) 131 cmd.RouteActor = v2Actor 132 cmd.Actor = v7pushaction.NewActor(v2Actor, v7actor, sharedActor) 133 134 cmd.NOAAClient = v6shared.NewNOAAClient(ccClient.Info.Logging(), config, uaaClient, ui) 135 136 currentDir, err := os.Getwd() 137 cmd.PWD = currentDir 138 139 cmd.ManifestLocator = manifestparser.NewLocator() 140 cmd.ManifestParser = manifestparser.NewParser() 141 142 return err 143 } 144 145 func (cmd PushCommand) Execute(args []string) error { 146 cmd.UI.DisplayWarning(command.ExperimentalWarning) 147 148 err := cmd.SharedActor.CheckTarget(true, true) 149 if err != nil { 150 return err 151 } 152 153 if !cmd.NoManifest { 154 if err = cmd.ReadManifest(); err != nil { 155 return err 156 } 157 } 158 159 err = cmd.ValidateFlags() 160 if err != nil { 161 return err 162 } 163 164 flagOverrides, err := cmd.GetFlagOverrides() 165 if err != nil { 166 return err 167 } 168 169 err = cmd.ValidateAllowedFlagsForMultipleApps(cmd.ManifestParser.ContainsMultipleApps()) 170 if err != nil { 171 return err 172 } 173 174 flagOverrides.DockerPassword, err = cmd.GetDockerPassword(flagOverrides.DockerUsername, cmd.ManifestParser.ContainsPrivateDockerImages()) 175 if err != nil { 176 return err 177 } 178 179 pushPlans, err := cmd.Actor.CreatePushPlans( 180 cmd.OptionalArgs.AppName, 181 cmd.Config.TargetedSpace().GUID, 182 cmd.Config.TargetedOrganization().GUID, 183 cmd.ManifestParser, 184 flagOverrides, 185 ) 186 if err != nil { 187 return err 188 } 189 190 pushPlansStream, eventStream, warningsStream, errorStream := cmd.Actor.PrepareSpace(pushPlans, cmd.ManifestParser) 191 appNames, err := cmd.processStreamsFromPrepareSpace(pushPlansStream, eventStream, warningsStream, errorStream) 192 193 if err != nil { 194 return err 195 } 196 197 if len(appNames) == 0 { 198 return translatableerror.AppNameOrManifestRequiredError{} 199 } 200 201 user, err := cmd.Config.CurrentUser() 202 if err != nil { 203 return err 204 } 205 206 cmd.announcePushing(appNames, user) 207 208 cmd.UI.DisplayText("Getting app info...") 209 log.Info("generating the app plan") 210 211 pushPlans, warnings, err := cmd.Actor.UpdateApplicationSettings(pushPlans) 212 cmd.UI.DisplayWarnings(warnings) 213 if err != nil { 214 return err 215 } 216 log.WithField("number of plans", len(pushPlans)).Debug("completed generating plan") 217 218 for _, plan := range pushPlans { 219 log.WithField("app_name", plan.Application.Name).Info("actualizing") 220 planStream, eventStream, warningsStream, errorStream := cmd.Actor.Actualize(plan, cmd.ProgressBar) 221 updatedPlan, err := cmd.processApplyStreams(plan.Application.Name, planStream, eventStream, warningsStream, errorStream) 222 if err != nil { 223 return err 224 } 225 226 anyProcessCrashed, err := cmd.appRestarter(plan.Application.Name, updatedPlan.Application.GUID) 227 if err != nil { 228 return err 229 } 230 err = cmd.displayAppSummary(plan) 231 if err != nil { 232 return err 233 } 234 if anyProcessCrashed { 235 return translatableerror.ApplicationUnableToStartError{ 236 AppName: plan.Application.Name, 237 BinaryName: cmd.Config.BinaryName(), 238 } 239 } 240 } 241 242 return nil 243 } 244 245 func (cmd PushCommand) announcePushing(appNames []string, user configv3.User) { 246 tokens := map[string]interface{}{ 247 "AppName": strings.Join(appNames, ", "), 248 "OrgName": cmd.Config.TargetedOrganization().Name, 249 "SpaceName": cmd.Config.TargetedSpace().Name, 250 "Username": user.Name, 251 } 252 singular := "Pushing app {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..." 253 plural := "Pushing apps {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..." 254 255 if len(appNames) == 1 { 256 cmd.UI.DisplayTextWithFlavor(singular, tokens) 257 } else { 258 cmd.UI.DisplayTextWithFlavor(plural, tokens) 259 } 260 } 261 262 func (cmd PushCommand) appRestarter(appName, appGUID string) (bool, error) { 263 anyProcessCrashed := false 264 if !cmd.NoStart { 265 cmd.UI.DisplayNewline() 266 cmd.UI.DisplayTextWithFlavor( 267 "Waiting for app {{.AppName}} to start...", 268 map[string]interface{}{ 269 "AppName": appName, 270 }, 271 ) 272 warnings, restartErr := cmd.VersionActor.RestartApplication(appGUID) 273 cmd.UI.DisplayWarnings(warnings) 274 275 if _, ok := restartErr.(actionerror.StartupTimeoutError); ok { 276 return anyProcessCrashed, translatableerror.StartupTimeoutError{ 277 AppName: appName, 278 BinaryName: cmd.Config.BinaryName(), 279 } 280 } else if _, ok := restartErr.(actionerror.AllInstancesCrashedError); ok { 281 anyProcessCrashed = true 282 } else if restartErr != nil { 283 return anyProcessCrashed, restartErr 284 } 285 } 286 return anyProcessCrashed, nil 287 } 288 289 func (cmd PushCommand) displayAppSummary(plan v7pushaction.PushPlan) error { 290 log.Info("getting application summary info") 291 summary, warnings, err := cmd.VersionActor.GetApplicationSummaryByNameAndSpace( 292 plan.Application.Name, 293 cmd.Config.TargetedSpace().GUID, 294 true, 295 cmd.RouteActor, 296 ) 297 cmd.UI.DisplayWarnings(warnings) 298 if err != nil { 299 return err 300 } 301 cmd.UI.DisplayNewline() 302 appSummaryDisplayer := shared.NewAppSummaryDisplayer(cmd.UI) 303 appSummaryDisplayer.AppDisplay(summary, true) 304 return nil 305 } 306 307 func (cmd PushCommand) processStreamsFromPrepareSpace( 308 pushPlansStream <-chan []v7pushaction.PushPlan, 309 eventStream <-chan v7pushaction.Event, 310 warningsStream <-chan v7pushaction.Warnings, 311 errorStream <-chan error, 312 ) ([]string, error) { 313 var namesClosed, eventClosed, warningsClosed, errClosed bool 314 var appNames []string 315 var err error 316 317 for { 318 select { 319 case plans, ok := <-pushPlansStream: 320 if !ok { 321 if !namesClosed { 322 log.Debug("processing config stream closed") 323 } 324 namesClosed = true 325 break 326 } 327 for _, plan := range plans { 328 appNames = append(appNames, plan.Application.Name) 329 } 330 case event, ok := <-eventStream: 331 if !ok { 332 if !eventClosed { 333 log.Debug("processing event stream closed") 334 } 335 eventClosed = true 336 break 337 } 338 _, err := cmd.processEvent(event, cmd.OptionalArgs.AppName) 339 if err != nil { 340 return nil, err 341 } 342 case warnings, ok := <-warningsStream: 343 if !ok { 344 if !warningsClosed { 345 log.Debug("processing warnings stream closed") 346 } 347 warningsClosed = true 348 break 349 } 350 cmd.UI.DisplayWarnings(warnings) 351 case receivedError, ok := <-errorStream: 352 if !ok { 353 if !errClosed { 354 log.Debug("processing error stream closed") 355 } 356 errClosed = true 357 break 358 } 359 return nil, receivedError 360 } 361 362 if namesClosed && eventClosed && warningsClosed && errClosed { 363 break 364 } 365 } 366 367 return appNames, err 368 } 369 370 func (cmd PushCommand) processApplyStreams( 371 appName string, 372 planStream <-chan v7pushaction.PushPlan, 373 eventStream <-chan v7pushaction.Event, 374 warningsStream <-chan v7pushaction.Warnings, 375 errorStream <-chan error, 376 ) (v7pushaction.PushPlan, error) { 377 var planClosed, eventClosed, warningsClosed, errClosed, complete bool 378 var updatePlan v7pushaction.PushPlan 379 380 for { 381 select { 382 case plan, ok := <-planStream: 383 if !ok { 384 if !planClosed { 385 log.Debug("processing config stream closed") 386 } 387 planClosed = true 388 break 389 } 390 updatePlan = plan 391 case event, ok := <-eventStream: 392 if !ok { 393 if !eventClosed { 394 log.Debug("processing event stream closed") 395 } 396 eventClosed = true 397 break 398 } 399 var err error 400 complete, err = cmd.processEvent(event, appName) 401 if err != nil { 402 return v7pushaction.PushPlan{}, err 403 } 404 case warnings, ok := <-warningsStream: 405 if !ok { 406 if !warningsClosed { 407 log.Debug("processing warnings stream closed") 408 } 409 warningsClosed = true 410 break 411 } 412 cmd.UI.DisplayWarnings(warnings) 413 case err, ok := <-errorStream: 414 if !ok { 415 if !errClosed { 416 log.Debug("processing error stream closed") 417 } 418 errClosed = true 419 break 420 } 421 return v7pushaction.PushPlan{}, err 422 } 423 424 if planClosed && eventClosed && warningsClosed && complete { 425 break 426 } 427 } 428 429 return updatePlan, nil 430 } 431 432 func (cmd PushCommand) processEvent(event v7pushaction.Event, appName string) (bool, error) { 433 switch event { 434 case v7pushaction.SkippingApplicationCreation: 435 cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}}...", map[string]interface{}{ 436 "AppName": appName, 437 }) 438 case v7pushaction.CreatingApplication: 439 cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}}...", map[string]interface{}{ 440 "AppName": appName, 441 }) 442 case v7pushaction.CreatingAndMappingRoutes: 443 cmd.UI.DisplayText("Mapping routes...") 444 case v7pushaction.CreatingArchive: 445 cmd.UI.DisplayText("Packaging files to upload...") 446 case v7pushaction.UploadingApplicationWithArchive: 447 cmd.UI.DisplayText("Uploading files...") 448 log.Debug("starting progress bar") 449 cmd.ProgressBar.Ready() 450 case v7pushaction.UploadingApplication: 451 cmd.UI.DisplayText("All files found in remote cache; nothing to upload.") 452 cmd.UI.DisplayText("Waiting for API to complete processing files...") 453 case v7pushaction.RetryUpload: 454 cmd.UI.DisplayText("Retrying upload due to an error...") 455 case v7pushaction.UploadWithArchiveComplete: 456 cmd.ProgressBar.Complete() 457 cmd.UI.DisplayNewline() 458 cmd.UI.DisplayText("Waiting for API to complete processing files...") 459 case v7pushaction.StoppingApplication: 460 cmd.UI.DisplayText("Stopping Application...") 461 case v7pushaction.StoppingApplicationComplete: 462 cmd.UI.DisplayText("Application Stopped") 463 case v7pushaction.ApplyManifest: 464 cmd.UI.DisplayText("Applying manifest...") 465 case v7pushaction.ApplyManifestComplete: 466 cmd.UI.DisplayText("Manifest applied") 467 case v7pushaction.StartingStaging: 468 cmd.UI.DisplayNewline() 469 cmd.UI.DisplayText("Staging app and tracing logs...") 470 logStream, errStream, warnings, err := cmd.VersionActor.GetStreamingLogsForApplicationByNameAndSpace(appName, cmd.Config.TargetedSpace().GUID, cmd.NOAAClient) 471 cmd.UI.DisplayWarnings(warnings) 472 if err != nil { 473 return false, err 474 } 475 go cmd.getLogs(logStream, errStream) 476 case v7pushaction.StagingComplete: 477 cmd.NOAAClient.Close() 478 case v7pushaction.Complete: 479 return true, nil 480 default: 481 log.WithField("event", event).Debug("ignoring event") 482 } 483 return false, nil 484 } 485 486 func (cmd PushCommand) getLogs(logStream <-chan *v7action.LogMessage, errStream <-chan error) { 487 for { 488 select { 489 case logMessage, open := <-logStream: 490 if !open { 491 return 492 } 493 if logMessage.Staging() { 494 cmd.UI.DisplayLogMessage(logMessage, false) 495 } 496 case err, open := <-errStream: 497 if !open { 498 return 499 } 500 _, ok := err.(actionerror.NOAATimeoutError) 501 if ok { 502 cmd.UI.DisplayWarning("timeout connecting to log server, no log will be shown") 503 } 504 cmd.UI.DisplayWarning(err.Error()) 505 } 506 } 507 } 508 509 func (cmd PushCommand) ReadManifest() error { 510 log.Info("reading manifest if exists") 511 pathsToVarsFiles := []string{} 512 for _, varfilepath := range cmd.PathsToVarsFiles { 513 pathsToVarsFiles = append(pathsToVarsFiles, string(varfilepath)) 514 } 515 516 readPath := cmd.PWD 517 if len(cmd.PathToManifest) != 0 { 518 log.WithField("manifestPath", cmd.PathToManifest).Debug("reading '-f' provided manifest") 519 readPath = string(cmd.PathToManifest) 520 } 521 522 pathToManifest, exists, err := cmd.ManifestLocator.Path(readPath) 523 if err != nil { 524 return err 525 } 526 527 if exists { 528 log.WithField("manifestPath", pathToManifest).Debug("path to manifest") 529 err = cmd.ManifestParser.InterpolateAndParse(pathToManifest, pathsToVarsFiles, cmd.Vars) 530 if err != nil { 531 log.Errorln("reading manifest:", err) 532 return err 533 } 534 535 cmd.UI.DisplayText("Using manifest file {{.Path}}", map[string]interface{}{"Path": pathToManifest}) 536 } 537 538 return nil 539 } 540 541 func (cmd PushCommand) GetFlagOverrides() (v7pushaction.FlagOverrides, error) { 542 return v7pushaction.FlagOverrides{ 543 Buildpacks: cmd.Buildpacks, 544 Stack: cmd.Stack, 545 Disk: cmd.Disk.NullUint64, 546 DockerImage: cmd.DockerImage.Path, 547 DockerUsername: cmd.DockerUsername, 548 HealthCheckEndpoint: cmd.HealthCheckHTTPEndpoint, 549 HealthCheckType: cmd.HealthCheckType.Type, 550 HealthCheckTimeout: cmd.HealthCheckTimeout.Value, Instances: cmd.Instances.NullInt, 551 Memory: cmd.Memory.NullUint64, 552 NoStart: cmd.NoStart, 553 ProvidedAppPath: string(cmd.AppPath), 554 SkipRouteCreation: cmd.NoRoute, 555 StartCommand: cmd.StartCommand.FilteredString, 556 }, nil 557 } 558 559 func (cmd PushCommand) ValidateAllowedFlagsForMultipleApps(containsMultipleApps bool) error { 560 if cmd.OptionalArgs.AppName != "" { 561 return nil 562 } 563 564 allowedFlagsMultipleApps := !(len(cmd.Buildpacks) > 0 || 565 cmd.Disk.IsSet || 566 cmd.DockerImage.Path != "" || 567 cmd.DockerUsername != "" || 568 cmd.HealthCheckType.Type != "" || 569 cmd.HealthCheckHTTPEndpoint != "" || 570 cmd.HealthCheckTimeout.Value > 0 || 571 cmd.Instances.IsSet || 572 cmd.Stack != "" || 573 cmd.Memory.IsSet || 574 cmd.AppPath != "" || 575 cmd.NoRoute || 576 cmd.StartCommand.IsSet) 577 578 if containsMultipleApps && !allowedFlagsMultipleApps { 579 return translatableerror.CommandLineArgsWithMultipleAppsError{} 580 } 581 582 return nil 583 } 584 585 func (cmd PushCommand) ValidateFlags() error { 586 switch { 587 case cmd.DockerUsername != "" && cmd.DockerImage.Path == "": 588 return translatableerror.RequiredFlagsError{ 589 Arg1: "--docker-image, -o", 590 Arg2: "--docker-username", 591 } 592 593 case cmd.DockerImage.Path != "" && cmd.Buildpacks != nil: 594 return translatableerror.ArgumentCombinationError{ 595 Args: []string{ 596 "--buildpack, -b", 597 "--docker-image, -o", 598 }, 599 } 600 601 case cmd.DockerImage.Path != "" && cmd.AppPath != "": 602 return translatableerror.ArgumentCombinationError{ 603 Args: []string{ 604 "--docker-image, -o", 605 "--path, -p", 606 }, 607 } 608 case cmd.DockerImage.Path != "" && cmd.Stack != "": 609 return translatableerror.ArgumentCombinationError{ 610 Args: []string{ 611 "--stack, -s", 612 "--docker-image, -o", 613 }, 614 } 615 case cmd.NoManifest && cmd.PathToManifest != "": 616 return translatableerror.ArgumentCombinationError{ 617 Args: []string{ 618 "--no-manifest", 619 "--manifest, -f", 620 }, 621 } 622 case cmd.NoManifest && len(cmd.PathsToVarsFiles) > 0: 623 return translatableerror.ArgumentCombinationError{ 624 Args: []string{ 625 "--no-manifest", 626 "--vars-file", 627 }, 628 } 629 case cmd.NoManifest && len(cmd.Vars) > 0: 630 return translatableerror.ArgumentCombinationError{ 631 Args: []string{ 632 "--no-manifest", 633 "--vars", 634 }, 635 } 636 case cmd.HealthCheckType.Type == constant.HTTP && cmd.HealthCheckHTTPEndpoint == "": 637 return translatableerror.RequiredFlagsError{ 638 Arg1: "--endpoint", 639 Arg2: "--health-check-type=http, -u=http", 640 } 641 case 0 < len(cmd.HealthCheckHTTPEndpoint) && cmd.HealthCheckType.Type != constant.HTTP: 642 return translatableerror.RequiredFlagsError{ 643 Arg1: "--health-check-type=http, -u=http", 644 Arg2: "--endpoint", 645 } 646 647 } 648 return nil 649 }