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