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