github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+incompatible/command/v6/push_command.go (about) 1 package v6 2 3 import ( 4 "os" 5 "path/filepath" 6 7 "code.cloudfoundry.org/cli/actor/pushaction" 8 "code.cloudfoundry.org/cli/actor/sharedaction" 9 "code.cloudfoundry.org/cli/actor/v2action" 10 "code.cloudfoundry.org/cli/actor/v2v3action" 11 "code.cloudfoundry.org/cli/actor/v3action" 12 "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" 13 "code.cloudfoundry.org/cli/command" 14 "code.cloudfoundry.org/cli/command/flag" 15 "code.cloudfoundry.org/cli/command/translatableerror" 16 "code.cloudfoundry.org/cli/command/v6/shared" 17 sharedV3 "code.cloudfoundry.org/cli/command/v6/shared" 18 "code.cloudfoundry.org/cli/util/configv3" 19 "code.cloudfoundry.org/cli/util/manifest" 20 "code.cloudfoundry.org/cli/util/progressbar" 21 "github.com/cloudfoundry/bosh-cli/director/template" 22 "github.com/cloudfoundry/noaa/consumer" 23 log "github.com/sirupsen/logrus" 24 ) 25 26 //go:generate counterfeiter . ProgressBar 27 28 type ProgressBar interface { 29 pushaction.ProgressBar 30 Complete() 31 Ready() 32 } 33 34 //go:generate counterfeiter . V2PushActor 35 36 type V2PushActor interface { 37 Apply(config pushaction.ApplicationConfig, progressBar pushaction.ProgressBar) (<-chan pushaction.ApplicationConfig, <-chan pushaction.Event, <-chan pushaction.Warnings, <-chan error) 38 CloudControllerV2APIVersion() string 39 CloudControllerV3APIVersion() string 40 ConvertToApplicationConfigs(orgGUID string, spaceGUID string, noStart bool, apps []manifest.Application) ([]pushaction.ApplicationConfig, pushaction.Warnings, error) 41 MergeAndValidateSettingsAndManifests(cmdSettings pushaction.CommandLineSettings, apps []manifest.Application) ([]manifest.Application, error) 42 ReadManifest(pathToManifest string, pathsToVarsFiles []string, vars []template.VarKV) ([]manifest.Application, pushaction.Warnings, error) 43 } 44 45 type PushCommand struct { 46 OptionalArgs flag.OptionalAppName `positional-args:"yes"` 47 Buildpacks []string `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'"` 48 Command flag.Command `short:"c" description:"Startup command, set to null to reset to default start command"` 49 Domain string `short:"d" description:"Specify a custom domain (e.g. private-domain.example.com, apps.internal.com) to use instead of the default domain"` 50 DockerImage flag.DockerImage `long:"docker-image" short:"o" description:"Docker-image to be used (e.g. user/docker-image-name)"` 51 DockerUsername string `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"` 52 DropletPath flag.PathWithExistenceCheck `long:"droplet" description:"Path to a tgz file with a pre-staged app"` 53 PathToManifest flag.PathWithExistenceCheck `short:"f" description:"Path to manifest"` 54 HealthCheckType flag.HealthCheckTypeWithDeprecatedValue `long:"health-check-type" short:"u" description:"Application health check type (Default: 'port', 'none' accepted for 'process', 'http' implies endpoint '/')"` 55 Hostname string `long:"hostname" short:"n" description:"Hostname (e.g. my-subdomain)"` 56 Instances flag.Instances `short:"i" description:"Number of instances"` 57 DiskQuota flag.Megabytes `short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"` 58 Memory flag.Megabytes `short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` 59 NoHostname bool `long:"no-hostname" description:"Map the root domain to this app"` 60 NoManifest bool `long:"no-manifest" description:"Ignore manifest file"` 61 NoRoute bool `long:"no-route" description:"Do not map a route to this app and remove routes from previous pushes of this app"` 62 NoStart bool `long:"no-start" description:"Do not start an app after pushing"` 63 AppPath flag.PathWithExistenceCheck `short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"` 64 RandomRoute bool `long:"random-route" description:"Create a random route for this app"` 65 RoutePath flag.RoutePath `long:"route-path" description:"Path for the route"` 66 StackName string `short:"s" description:"Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)"` 67 VarsFilePaths []flag.PathWithExistenceCheck `long:"vars-file" description:"Path to a variable substitution file for manifest; can specify multiple times"` 68 Vars []template.VarKV `long:"var" description:"Variable key value pair for variable substitution, (e.g., name=app1); can specify multiple times"` 69 HealthCheckTimeout int `short:"t" description:"Time (in seconds) allowed to elapse between starting up an app and the first healthy response from the app"` 70 envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"` 71 envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` 72 dockerPassword interface{} `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"` 73 74 usage interface{} `usage:"CF_NAME 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] [--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] [-t HEALTH_TIMEOUT] [-u (process | port | http)]\n [--no-route | --random-route | --hostname HOST | --no-hostname] [-d DOMAIN] [--route-path ROUTE_PATH] [--var KEY=VALUE]... [--vars-file VARS_FILE_PATH]...\n\n CF_NAME push APP_NAME --droplet DROPLET_PATH\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] [--var KEY=VALUE]... [--vars-file VARS_FILE_PATH]...\n\n CF_NAME push -f MANIFEST_WITH_MULTIPLE_APPS_PATH [APP_NAME] [--no-start]"` 75 relatedCommands interface{} `related_commands:"apps, create-app-manifest, logs, ssh, start"` 76 77 UI command.UI 78 Config command.Config 79 SharedActor command.SharedActor 80 Actor V2PushActor 81 ApplicationSummaryActor shared.ApplicationSummaryActor 82 ProgressBar ProgressBar 83 84 RestartActor RestartActor 85 NOAAClient *consumer.Consumer 86 } 87 88 func (cmd *PushCommand) Setup(config command.Config, ui command.UI) error { 89 cmd.UI = ui 90 cmd.Config = config 91 sharedActor := sharedaction.NewActor(config) 92 cmd.SharedActor = sharedActor 93 94 ccClient, uaaClient, err := shared.NewClients(config, ui, true) 95 if err != nil { 96 return err 97 } 98 99 ccClientV3, _, err := sharedV3.NewV3BasedClients(config, ui, true, "") 100 if err != nil { 101 return err 102 } 103 104 v2Actor := v2action.NewActor(ccClient, uaaClient, config) 105 v3Actor := v3action.NewActor(ccClientV3, config, sharedActor, nil) 106 107 cmd.RestartActor = v2Actor 108 cmd.Actor = pushaction.NewActor(v2Actor, v3Actor, sharedActor) 109 110 cmd.ApplicationSummaryActor = v2v3action.NewActor(v2Actor, v3Actor) 111 112 cmd.NOAAClient = shared.NewNOAAClient(ccClient.DopplerEndpoint(), config, uaaClient, ui) 113 114 cmd.ProgressBar = progressbar.NewProgressBar() 115 return nil 116 } 117 118 func (cmd PushCommand) Execute(args []string) error { 119 if len(cmd.Buildpacks) > 1 { 120 if err := command.MinimumCCAPIVersionCheck(cmd.Actor.CloudControllerV3APIVersion(), ccversion.MinVersionManifestBuildpacksV3, "Multiple option '-b'"); err != nil { 121 return err 122 } 123 } 124 125 err := cmd.SharedActor.CheckTarget(true, true) 126 if err != nil { 127 return err 128 } 129 130 user, err := cmd.Config.CurrentUser() 131 if err != nil { 132 return err 133 } 134 135 log.Info("collating flags") 136 cliSettings, err := cmd.GetCommandLineSettings() 137 if err != nil { 138 log.Errorln("reading flags:", err) 139 return err 140 } 141 142 log.Info("checking manifest") 143 rawApps, err := cmd.findAndReadManifestWithFlavorText(cliSettings) 144 if err != nil { 145 log.Errorln("reading manifest:", err) 146 return err 147 } 148 149 log.Info("merging manifest and command flags") 150 manifestApplications, err := cmd.Actor.MergeAndValidateSettingsAndManifests(cliSettings, rawApps) 151 if err != nil { 152 log.Errorln("merging manifest:", err) 153 return err 154 } 155 156 for _, manifestApplication := range manifestApplications { 157 if len(manifestApplication.Buildpacks) > 0 { 158 if err = command.MinimumCCAPIVersionCheck(cmd.Actor.CloudControllerV3APIVersion(), ccversion.MinVersionManifestBuildpacksV3, "'buildpacks' in manifest"); err != nil { 159 return err 160 } 161 } 162 } 163 164 cmd.UI.DisplayText("Getting app info...") 165 166 log.Info("converting manifests to ApplicationConfigs") 167 appConfigs, warnings, err := cmd.Actor.ConvertToApplicationConfigs( 168 cmd.Config.TargetedOrganization().GUID, 169 cmd.Config.TargetedSpace().GUID, 170 cmd.NoStart, 171 manifestApplications, 172 ) 173 cmd.UI.DisplayWarnings(warnings) 174 if err != nil { 175 log.Errorln("converting manifest:", err) 176 return err 177 } 178 179 for _, appConfig := range appConfigs { 180 if appConfig.CreatingApplication() { 181 cmd.UI.DisplayText("Creating app with these attributes...") 182 } else { 183 cmd.UI.DisplayText("Updating app with these attributes...") 184 } 185 log.Infoln("starting create/update:", appConfig.DesiredApplication.Name) 186 changes := shared.GetApplicationChanges(appConfig) 187 err := cmd.UI.DisplayChangesForPush(changes) 188 if err != nil { 189 log.Errorln("display changes:", err) 190 return err 191 } 192 cmd.UI.DisplayNewline() 193 } 194 195 for appNumber, appConfig := range appConfigs { 196 if appConfig.CreatingApplication() { 197 cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}}...", map[string]interface{}{ 198 "AppName": appConfig.DesiredApplication.Name, 199 }) 200 } else { 201 cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}}...", map[string]interface{}{ 202 "AppName": appConfig.DesiredApplication.Name, 203 }) 204 } 205 206 configStream, eventStream, warningsStream, errorStream := cmd.Actor.Apply(appConfig, cmd.ProgressBar) 207 updatedConfig, err := cmd.processApplyStreams(user, appConfig, configStream, eventStream, warningsStream, errorStream) 208 if err != nil { 209 log.Errorln("process apply stream:", err) 210 return err 211 } 212 213 if !cmd.NoStart { 214 messages, logErrs, appState, apiWarnings, errs := cmd.RestartActor.RestartApplication(updatedConfig.CurrentApplication.Application, cmd.NOAAClient) 215 err = shared.PollStart(cmd.UI, cmd.Config, messages, logErrs, appState, apiWarnings, errs) 216 if err != nil { 217 return err 218 } 219 } 220 221 cmd.UI.DisplayNewline() 222 223 if err := command.MinimumCCAPIVersionCheck(cmd.ApplicationSummaryActor.CloudControllerV3APIVersion(), ccversion.MinVersionApplicationFlowV3); err != nil { 224 log.WithField("v3_api_version", cmd.ApplicationSummaryActor.CloudControllerV3APIVersion()).Debug("using v2 for app display") 225 appSummary, warnings, err := cmd.RestartActor.GetApplicationSummaryByNameAndSpace(appConfig.DesiredApplication.Name, cmd.Config.TargetedSpace().GUID) 226 cmd.UI.DisplayWarnings(warnings) 227 if err != nil { 228 return err 229 } 230 shared.DisplayAppSummary(cmd.UI, appSummary, true) 231 } else { 232 log.WithField("v3_api_version", cmd.ApplicationSummaryActor.CloudControllerV3APIVersion()).Debug("using v3 for app display") 233 appSummary, warnings, err := cmd.ApplicationSummaryActor.GetApplicationSummaryByNameAndSpace(appConfig.DesiredApplication.Name, cmd.Config.TargetedSpace().GUID, true) 234 cmd.UI.DisplayWarnings(warnings) 235 if err != nil { 236 return err 237 } 238 shared.NewAppSummaryDisplayer2(cmd.UI).AppDisplay(appSummary, true) 239 } 240 241 if appNumber+1 <= len(appConfigs) { 242 cmd.UI.DisplayNewline() 243 } 244 } 245 246 return nil 247 } 248 249 // GetCommandLineSettings generates a push CommandLineSettings object from the 250 // command's command line flags. It also validates those settings, preventing 251 // contradictory flags. 252 func (cmd PushCommand) GetCommandLineSettings() (pushaction.CommandLineSettings, error) { 253 err := cmd.validateArgs() 254 if err != nil { 255 return pushaction.CommandLineSettings{}, err 256 } 257 258 pwd, err := os.Getwd() 259 if err != nil { 260 return pushaction.CommandLineSettings{}, err 261 } 262 263 dockerPassword := cmd.Config.DockerPassword() 264 if dockerPassword != "" { 265 cmd.UI.DisplayText("Using docker repository password from environment variable CF_DOCKER_PASSWORD.") 266 } else if cmd.DockerUsername != "" { 267 cmd.UI.DisplayText("Environment variable CF_DOCKER_PASSWORD not set.") 268 dockerPassword, err = cmd.UI.DisplayPasswordPrompt("Docker password") 269 if err != nil { 270 return pushaction.CommandLineSettings{}, err 271 } 272 } 273 274 config := pushaction.CommandLineSettings{ 275 Buildpacks: cmd.Buildpacks, // -b 276 Command: cmd.Command.FilteredString, // -c 277 CurrentDirectory: pwd, 278 DefaultRouteDomain: cmd.Domain, // -d 279 DefaultRouteHostname: cmd.Hostname, // -n/--hostname 280 DiskQuota: cmd.DiskQuota.Value, // -k 281 DockerImage: cmd.DockerImage.Path, // -o 282 DockerPassword: dockerPassword, // ENV - CF_DOCKER_PASSWORD 283 DockerUsername: cmd.DockerUsername, // --docker-username 284 DropletPath: string(cmd.DropletPath), // --droplet 285 HealthCheckTimeout: cmd.HealthCheckTimeout, // -t 286 HealthCheckType: cmd.HealthCheckType.Type, // -u/--health-check-type 287 Instances: cmd.Instances.NullInt, // -i 288 Memory: cmd.Memory.Value, // -m 289 Name: cmd.OptionalArgs.AppName, // arg 290 NoHostname: cmd.NoHostname, // --no-hostname 291 NoRoute: cmd.NoRoute, // --no-route 292 ProvidedAppPath: string(cmd.AppPath), // -p 293 RandomRoute: cmd.RandomRoute, // --random-route 294 RoutePath: cmd.RoutePath.Path, // --route-path 295 StackName: cmd.StackName, // -s 296 } 297 298 log.Debugln("Command Line Settings:", config) 299 return config, nil 300 } 301 302 func (cmd PushCommand) findAndReadManifestWithFlavorText(settings pushaction.CommandLineSettings) ([]manifest.Application, error) { 303 var ( 304 pathToManifest string 305 ) 306 switch { 307 case cmd.NoManifest: 308 log.Debug("skipping reading of manifest") 309 case cmd.PathToManifest != "": 310 log.WithField("file", cmd.PathToManifest).Debug("using specified manifest file") 311 pathToManifest = string(cmd.PathToManifest) 312 313 fileInfo, err := os.Stat(pathToManifest) 314 if err != nil { 315 return nil, err 316 } 317 318 if fileInfo.IsDir() { 319 manifestPaths := []string{ 320 filepath.Join(pathToManifest, "manifest.yml"), 321 filepath.Join(pathToManifest, "manifest.yaml"), 322 } 323 for _, manifestPath := range manifestPaths { 324 if _, err = os.Stat(manifestPath); err == nil { 325 pathToManifest = manifestPath 326 break 327 } 328 } 329 } 330 331 if err != nil { 332 return nil, translatableerror.ManifestFileNotFoundInDirectoryError{ 333 PathToManifest: pathToManifest, 334 } 335 } 336 default: 337 log.Debug("searching for manifest file") 338 pathToManifest = filepath.Join(settings.CurrentDirectory, "manifest.yml") 339 if _, err := os.Stat(pathToManifest); os.IsNotExist(err) { 340 log.WithField("pathToManifest", pathToManifest).Debug("could not find") 341 342 // While this is unlikely to be used, it is kept for backwards 343 // compatibility. 344 pathToManifest = filepath.Join(settings.CurrentDirectory, "manifest.yaml") 345 if _, err := os.Stat(pathToManifest); os.IsNotExist(err) { 346 log.WithField("pathToManifest", pathToManifest).Debug("could not find") 347 pathToManifest = "" 348 } 349 } 350 } 351 352 user, err := cmd.Config.CurrentUser() 353 if err != nil { 354 return nil, err 355 } 356 357 if pathToManifest == "" { 358 cmd.UI.DisplayTextWithFlavor("Pushing app {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 359 "AppName": settings.Name, 360 "OrgName": cmd.Config.TargetedOrganization().Name, 361 "SpaceName": cmd.Config.TargetedSpace().Name, 362 "Username": user.Name, 363 }) 364 return nil, nil 365 } 366 367 var pathsToVarsFiles []string 368 for _, path := range cmd.VarsFilePaths { 369 pathsToVarsFiles = append(pathsToVarsFiles, string(path)) 370 } 371 372 cmd.UI.DisplayTextWithFlavor("Pushing from manifest to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 373 "OrgName": cmd.Config.TargetedOrganization().Name, 374 "SpaceName": cmd.Config.TargetedSpace().Name, 375 "Username": user.Name, 376 }) 377 log.WithField("pathToManifest", pathToManifest).Info("reading manifest") 378 cmd.UI.DisplayText("Using manifest file {{.Path}}", map[string]interface{}{ 379 "Path": pathToManifest, 380 }) 381 382 apps, warnings, err := cmd.Actor.ReadManifest(pathToManifest, pathsToVarsFiles, cmd.Vars) 383 cmd.UI.DisplayWarnings(warnings) 384 385 return apps, err 386 } 387 388 func (cmd PushCommand) processApplyStreams( 389 user configv3.User, 390 appConfig pushaction.ApplicationConfig, 391 configStream <-chan pushaction.ApplicationConfig, 392 eventStream <-chan pushaction.Event, 393 warningsStream <-chan pushaction.Warnings, 394 errorStream <-chan error, 395 ) (pushaction.ApplicationConfig, error) { 396 var configClosed, eventClosed, warningsClosed, complete bool 397 var updatedConfig pushaction.ApplicationConfig 398 399 for { 400 select { 401 case config, ok := <-configStream: 402 if !ok { 403 log.Debug("processing config stream closed") 404 configClosed = true 405 break 406 } 407 updatedConfig = config 408 log.Debugf("updated config received: %#v", updatedConfig) 409 case event, ok := <-eventStream: 410 if !ok { 411 log.Debug("processing event stream closed") 412 eventClosed = true 413 break 414 } 415 complete = cmd.processEvent(user, appConfig, event) 416 case warnings, ok := <-warningsStream: 417 if !ok { 418 log.Debug("processing warnings stream closed") 419 warningsClosed = true 420 break 421 } 422 cmd.UI.DisplayWarnings(warnings) 423 case err, ok := <-errorStream: 424 if !ok { 425 log.Debug("processing error stream closed") 426 warningsClosed = true 427 break 428 } 429 return pushaction.ApplicationConfig{}, err 430 } 431 432 if configClosed && eventClosed && warningsClosed && complete { 433 log.Debug("breaking apply display loop") 434 break 435 } 436 } 437 438 return updatedConfig, nil 439 } 440 441 func (cmd PushCommand) processEvent(user configv3.User, appConfig pushaction.ApplicationConfig, event pushaction.Event) bool { 442 log.Infoln("received apply event:", event) 443 444 switch event { 445 case pushaction.CreatingAndMappingRoutes: 446 cmd.UI.DisplayText("Mapping routes...") 447 case pushaction.UnmappingRoutes: 448 cmd.UI.DisplayText("Unmapping routes...") 449 case pushaction.ConfiguringServices: 450 cmd.UI.DisplayText("Binding services...") 451 case pushaction.ResourceMatching: 452 cmd.UI.DisplayText("Comparing local files to remote cache...") 453 case pushaction.CreatingArchive: 454 cmd.UI.DisplayText("Packaging files to upload...") 455 case pushaction.UploadingApplication: 456 cmd.UI.DisplayText("All files found in remote cache; nothing to upload.") 457 cmd.UI.DisplayText("Waiting for API to complete processing files...") 458 case pushaction.UploadingDroplet: 459 cmd.UI.DisplayText("Uploading droplet...") 460 log.Debug("starting progress bar") 461 cmd.ProgressBar.Ready() 462 case pushaction.UploadingApplicationWithArchive: 463 cmd.UI.DisplayText("Uploading files...") 464 log.Debug("starting progress bar") 465 cmd.ProgressBar.Ready() 466 case pushaction.RetryUpload: 467 cmd.UI.DisplayText("Retrying upload due to an error...") 468 case pushaction.UploadWithArchiveComplete: 469 cmd.ProgressBar.Complete() 470 cmd.UI.DisplayNewline() 471 cmd.UI.DisplayText("Waiting for API to complete processing files...") 472 case pushaction.UploadDropletComplete: 473 cmd.ProgressBar.Complete() 474 cmd.UI.DisplayNewline() 475 cmd.UI.DisplayText("Waiting for API to complete processing droplet...") 476 case pushaction.Complete: 477 return true 478 default: 479 log.WithField("event", event).Debug("ignoring event") 480 } 481 return false 482 } 483 484 func (cmd PushCommand) validateArgs() error { 485 switch { 486 case cmd.DropletPath != "" && cmd.AppPath != "": 487 return translatableerror.ArgumentCombinationError{ 488 Args: []string{"--droplet", "-p"}, 489 } 490 case cmd.DropletPath != "" && cmd.DockerImage.Path != "": 491 return translatableerror.ArgumentCombinationError{ 492 Args: []string{"--droplet", "--docker-image", "-o"}, 493 } 494 // ArgumentCombinationError trumps RequiredArgsError in this case (when 495 // DockerImage unspecified) 496 case cmd.DropletPath != "" && cmd.DockerUsername != "": 497 return translatableerror.ArgumentCombinationError{ 498 Args: []string{"--droplet", "--docker-username", "-p"}, 499 } 500 case cmd.DockerImage.Path != "" && cmd.AppPath != "": 501 return translatableerror.ArgumentCombinationError{ 502 Args: []string{"--docker-image, -o", "-p"}, 503 } 504 case cmd.DockerImage.Path != "" && cmd.Buildpacks != nil: 505 return translatableerror.ArgumentCombinationError{ 506 Args: []string{"-b", "--docker-image, -o"}, 507 } 508 case cmd.DockerUsername != "" && cmd.DockerImage.Path == "": 509 return translatableerror.RequiredFlagsError{ 510 Arg1: "--docker-image, -o", 511 Arg2: "--docker-username", 512 } 513 case cmd.Domain != "" && cmd.NoRoute: 514 return translatableerror.ArgumentCombinationError{ 515 Args: []string{"-d", "--no-route"}, 516 } 517 case cmd.Hostname != "" && cmd.NoHostname: 518 return translatableerror.ArgumentCombinationError{ 519 Args: []string{"--hostname", "-n", "--no-hostname"}, 520 } 521 case cmd.Hostname != "" && cmd.NoRoute: 522 return translatableerror.ArgumentCombinationError{ 523 Args: []string{"--hostname", "-n", "--no-route"}, 524 } 525 case cmd.NoHostname && cmd.NoRoute: 526 return translatableerror.ArgumentCombinationError{ 527 Args: []string{"--no-hostname", "--no-route"}, 528 } 529 case cmd.PathToManifest != "" && cmd.NoManifest: 530 return translatableerror.ArgumentCombinationError{ 531 Args: []string{"-f", "--no-manifest"}, 532 } 533 case cmd.RandomRoute && cmd.Hostname != "": 534 return translatableerror.ArgumentCombinationError{ 535 Args: []string{"--hostname", "-n", "--random-route"}, 536 } 537 case cmd.RandomRoute && cmd.NoHostname: 538 return translatableerror.ArgumentCombinationError{ 539 Args: []string{"--no-hostname", "--random-route"}, 540 } 541 case cmd.RandomRoute && cmd.NoRoute: 542 return translatableerror.ArgumentCombinationError{ 543 Args: []string{"--no-route", "--random-route"}, 544 } 545 case cmd.RandomRoute && cmd.RoutePath.Path != "": 546 return translatableerror.ArgumentCombinationError{ 547 Args: []string{"--random-route", "--route-path"}, 548 } 549 case cmd.RoutePath.Path != "" && cmd.NoRoute: 550 return translatableerror.ArgumentCombinationError{ 551 Args: []string{"--route-path", "--no-route"}, 552 } 553 } 554 555 return nil 556 }