github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+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/command" 13 "code.cloudfoundry.org/cli/command/flag" 14 "code.cloudfoundry.org/cli/command/translatableerror" 15 "code.cloudfoundry.org/cli/command/v6/shared" 16 sharedV3 "code.cloudfoundry.org/cli/command/v6/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 PushCommand 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:"Specify a custom domain (e.g. private-domain.example.com, apps.internal.com) to use instead of the default domain"` 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.HealthCheckTypeWithDeprecatedValue `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 uint64 `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_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]"` 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 ApplicationSummaryActor shared.ApplicationSummaryActor 81 ProgressBar ProgressBar 82 83 RestartActor RestartActor 84 NOAAClient *consumer.Consumer 85 } 86 87 func (cmd *PushCommand) Setup(config command.Config, ui command.UI) error { 88 cmd.UI = ui 89 cmd.Config = config 90 sharedActor := sharedaction.NewActor(config) 91 cmd.SharedActor = sharedActor 92 93 ccClient, uaaClient, err := shared.NewClients(config, ui, true) 94 if err != nil { 95 return err 96 } 97 98 ccClientV3, _, err := sharedV3.NewV3BasedClients(config, ui, true, "") 99 if err != nil { 100 return err 101 } 102 103 v2Actor := v2action.NewActor(ccClient, uaaClient, config) 104 v3Actor := v3action.NewActor(ccClientV3, config, sharedActor, nil) 105 106 cmd.RestartActor = v2Actor 107 cmd.Actor = pushaction.NewActor(v2Actor, v3Actor, sharedActor) 108 109 cmd.ApplicationSummaryActor = v2v3action.NewActor(v2Actor, v3Actor) 110 111 cmd.NOAAClient = shared.NewNOAAClient(ccClient.DopplerEndpoint(), config, uaaClient, ui) 112 113 cmd.ProgressBar = progressbar.NewProgressBar() 114 return nil 115 } 116 117 func (cmd PushCommand) Execute(args []string) error { 118 err := cmd.SharedActor.CheckTarget(true, true) 119 if err != nil { 120 return err 121 } 122 123 user, err := cmd.Config.CurrentUser() 124 if err != nil { 125 return err 126 } 127 128 log.Info("collating flags") 129 cliSettings, err := cmd.GetCommandLineSettings() 130 if err != nil { 131 log.Errorln("reading flags:", err) 132 return err 133 } 134 135 log.Info("checking manifest") 136 rawApps, err := cmd.findAndReadManifestWithFlavorText(cliSettings) 137 if err != nil { 138 log.Errorln("reading manifest:", err) 139 return err 140 } 141 142 log.Info("merging manifest and command flags") 143 manifestApplications, err := cmd.Actor.MergeAndValidateSettingsAndManifests(cliSettings, rawApps) 144 if err != nil { 145 log.Errorln("merging manifest:", err) 146 return err 147 } 148 149 cmd.UI.DisplayText("Getting app info...") 150 151 log.Info("converting manifests to ApplicationConfigs") 152 appConfigs, warnings, err := cmd.Actor.ConvertToApplicationConfigs( 153 cmd.Config.TargetedOrganization().GUID, 154 cmd.Config.TargetedSpace().GUID, 155 cmd.NoStart, 156 manifestApplications, 157 ) 158 cmd.UI.DisplayWarnings(warnings) 159 if err != nil { 160 log.Errorln("converting manifest:", err) 161 return err 162 } 163 164 for _, appConfig := range appConfigs { 165 if appConfig.CreatingApplication() { 166 cmd.UI.DisplayText("Creating app with these attributes...") 167 } else { 168 cmd.UI.DisplayText("Updating app with these attributes...") 169 } 170 log.Infoln("starting create/update:", appConfig.DesiredApplication.Name) 171 changes := shared.GetApplicationChanges(appConfig) 172 err := cmd.UI.DisplayChangesForPush(changes) 173 if err != nil { 174 log.Errorln("display changes:", err) 175 return err 176 } 177 cmd.UI.DisplayNewline() 178 } 179 180 for appNumber, appConfig := range appConfigs { 181 if appConfig.CreatingApplication() { 182 cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}}...", map[string]interface{}{ 183 "AppName": appConfig.DesiredApplication.Name, 184 }) 185 } else { 186 cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}}...", map[string]interface{}{ 187 "AppName": appConfig.DesiredApplication.Name, 188 }) 189 } 190 191 configStream, eventStream, warningsStream, errorStream := cmd.Actor.Apply(appConfig, cmd.ProgressBar) 192 updatedConfig, err := cmd.processApplyStreams(user, appConfig, configStream, eventStream, warningsStream, errorStream) 193 if err != nil { 194 log.Errorln("process apply stream:", err) 195 return err 196 } 197 198 if !cmd.NoStart { 199 messages, logErrs, appState, apiWarnings, errs := cmd.RestartActor.RestartApplication(updatedConfig.CurrentApplication.Application, cmd.NOAAClient) 200 err = shared.PollStart(cmd.UI, cmd.Config, messages, logErrs, appState, apiWarnings, errs) 201 if err != nil { 202 return err 203 } 204 } 205 206 cmd.UI.DisplayNewline() 207 208 log.WithField("v3_api_version", cmd.ApplicationSummaryActor.CloudControllerV3APIVersion()).Debug("using v3 for app display") 209 appSummary, warnings, err := cmd.ApplicationSummaryActor.GetApplicationSummaryByNameAndSpace(appConfig.DesiredApplication.Name, cmd.Config.TargetedSpace().GUID, true) 210 cmd.UI.DisplayWarnings(warnings) 211 if err != nil { 212 return err 213 } 214 shared.NewAppSummaryDisplayer2(cmd.UI).AppDisplay(appSummary, true) 215 216 if appNumber+1 <= len(appConfigs) { 217 cmd.UI.DisplayNewline() 218 } 219 } 220 221 return nil 222 } 223 224 // GetCommandLineSettings generates a push CommandLineSettings object from the 225 // command's command line flags. It also validates those settings, preventing 226 // contradictory flags. 227 func (cmd PushCommand) GetCommandLineSettings() (pushaction.CommandLineSettings, error) { 228 err := cmd.validateArgs() 229 if err != nil { 230 return pushaction.CommandLineSettings{}, err 231 } 232 233 pwd, err := os.Getwd() 234 if err != nil { 235 return pushaction.CommandLineSettings{}, err 236 } 237 238 dockerPassword := cmd.Config.DockerPassword() 239 if dockerPassword != "" { 240 cmd.UI.DisplayText("Using docker repository password from environment variable CF_DOCKER_PASSWORD.") 241 } else if cmd.DockerUsername != "" { 242 cmd.UI.DisplayText("Environment variable CF_DOCKER_PASSWORD not set.") 243 dockerPassword, err = cmd.UI.DisplayPasswordPrompt("Docker password") 244 if err != nil { 245 return pushaction.CommandLineSettings{}, err 246 } 247 } 248 249 config := pushaction.CommandLineSettings{ 250 Buildpacks: cmd.Buildpacks, // -b 251 Command: cmd.Command.FilteredString, // -c 252 CurrentDirectory: pwd, 253 DefaultRouteDomain: cmd.Domain, // -d 254 DefaultRouteHostname: cmd.Hostname, // -n/--hostname 255 DiskQuota: cmd.DiskQuota.Value, // -k 256 DockerImage: cmd.DockerImage.Path, // -o 257 DockerPassword: dockerPassword, // ENV - CF_DOCKER_PASSWORD 258 DockerUsername: cmd.DockerUsername, // --docker-username 259 DropletPath: string(cmd.DropletPath), // --droplet 260 HealthCheckTimeout: cmd.HealthCheckTimeout, // -t 261 HealthCheckType: cmd.HealthCheckType.Type, // -u/--health-check-type 262 Instances: cmd.Instances.NullInt, // -i 263 Memory: cmd.Memory.Value, // -m 264 Name: cmd.OptionalArgs.AppName, // arg 265 NoHostname: cmd.NoHostname, // --no-hostname 266 NoRoute: cmd.NoRoute, // --no-route 267 ProvidedAppPath: string(cmd.AppPath), // -p 268 RandomRoute: cmd.RandomRoute, // --random-route 269 RoutePath: cmd.RoutePath.Path, // --route-path 270 StackName: cmd.StackName, // -s 271 } 272 273 log.Debugln("Command Line Settings:", config) 274 return config, nil 275 } 276 277 func (cmd PushCommand) findAndReadManifestWithFlavorText(settings pushaction.CommandLineSettings) ([]manifest.Application, error) { 278 var ( 279 pathToManifest string 280 ) 281 switch { 282 case cmd.NoManifest: 283 log.Debug("skipping reading of manifest") 284 case cmd.PathToManifest != "": 285 log.WithField("file", cmd.PathToManifest).Debug("using specified manifest file") 286 pathToManifest = string(cmd.PathToManifest) 287 288 fileInfo, err := os.Stat(pathToManifest) 289 if err != nil { 290 return nil, err 291 } 292 293 if fileInfo.IsDir() { 294 manifestPaths := []string{ 295 filepath.Join(pathToManifest, "manifest.yml"), 296 filepath.Join(pathToManifest, "manifest.yaml"), 297 } 298 for _, manifestPath := range manifestPaths { 299 if _, err = os.Stat(manifestPath); err == nil { 300 pathToManifest = manifestPath 301 break 302 } 303 } 304 } 305 306 if err != nil { 307 return nil, translatableerror.ManifestFileNotFoundInDirectoryError{ 308 PathToManifest: pathToManifest, 309 } 310 } 311 default: 312 log.Debug("searching for manifest file") 313 pathToManifest = filepath.Join(settings.CurrentDirectory, "manifest.yml") 314 if _, err := os.Stat(pathToManifest); os.IsNotExist(err) { 315 log.WithField("pathToManifest", pathToManifest).Debug("could not find") 316 317 // While this is unlikely to be used, it is kept for backwards 318 // compatibility. 319 pathToManifest = filepath.Join(settings.CurrentDirectory, "manifest.yaml") 320 if _, err := os.Stat(pathToManifest); os.IsNotExist(err) { 321 log.WithField("pathToManifest", pathToManifest).Debug("could not find") 322 pathToManifest = "" 323 } 324 } 325 } 326 327 user, err := cmd.Config.CurrentUser() 328 if err != nil { 329 return nil, err 330 } 331 332 if pathToManifest == "" { 333 cmd.UI.DisplayTextWithFlavor("Pushing app {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 334 "AppName": settings.Name, 335 "OrgName": cmd.Config.TargetedOrganization().Name, 336 "SpaceName": cmd.Config.TargetedSpace().Name, 337 "Username": user.Name, 338 }) 339 return nil, nil 340 } 341 342 var pathsToVarsFiles []string 343 for _, path := range cmd.VarsFilePaths { 344 pathsToVarsFiles = append(pathsToVarsFiles, string(path)) 345 } 346 347 cmd.UI.DisplayTextWithFlavor("Pushing from manifest to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 348 "OrgName": cmd.Config.TargetedOrganization().Name, 349 "SpaceName": cmd.Config.TargetedSpace().Name, 350 "Username": user.Name, 351 }) 352 log.WithField("pathToManifest", pathToManifest).Info("reading manifest") 353 cmd.UI.DisplayText("Using manifest file {{.Path}}", map[string]interface{}{ 354 "Path": pathToManifest, 355 }) 356 357 apps, warnings, err := cmd.Actor.ReadManifest(pathToManifest, pathsToVarsFiles, cmd.Vars) 358 cmd.UI.DisplayWarnings(warnings) 359 360 return apps, err 361 } 362 363 func (cmd PushCommand) processApplyStreams( 364 user configv3.User, 365 appConfig pushaction.ApplicationConfig, 366 configStream <-chan pushaction.ApplicationConfig, 367 eventStream <-chan pushaction.Event, 368 warningsStream <-chan pushaction.Warnings, 369 errorStream <-chan error, 370 ) (pushaction.ApplicationConfig, error) { 371 var configClosed, eventClosed, warningsClosed, complete bool 372 var updatedConfig pushaction.ApplicationConfig 373 374 for { 375 select { 376 case config, ok := <-configStream: 377 if !ok { 378 log.Debug("processing config stream closed") 379 configClosed = true 380 break 381 } 382 updatedConfig = config 383 log.Debugf("updated config received: %#v", updatedConfig) 384 case event, ok := <-eventStream: 385 if !ok { 386 log.Debug("processing event stream closed") 387 eventClosed = true 388 break 389 } 390 complete = cmd.processEvent(user, appConfig, event) 391 case warnings, ok := <-warningsStream: 392 if !ok { 393 log.Debug("processing warnings stream closed") 394 warningsClosed = true 395 break 396 } 397 cmd.UI.DisplayWarnings(warnings) 398 case err, ok := <-errorStream: 399 if !ok { 400 log.Debug("processing error stream closed") 401 warningsClosed = true 402 break 403 } 404 return pushaction.ApplicationConfig{}, err 405 } 406 407 if configClosed && eventClosed && warningsClosed && complete { 408 log.Debug("breaking apply display loop") 409 break 410 } 411 } 412 413 return updatedConfig, nil 414 } 415 416 func (cmd PushCommand) processEvent(user configv3.User, appConfig pushaction.ApplicationConfig, event pushaction.Event) bool { 417 log.Infoln("received apply event:", event) 418 419 switch event { 420 case pushaction.CreatingAndMappingRoutes: 421 cmd.UI.DisplayText("Mapping routes...") 422 case pushaction.UnmappingRoutes: 423 cmd.UI.DisplayText("Unmapping routes...") 424 case pushaction.ConfiguringServices: 425 cmd.UI.DisplayText("Binding services...") 426 case pushaction.ResourceMatching: 427 cmd.UI.DisplayText("Comparing local files to remote cache...") 428 case pushaction.CreatingArchive: 429 cmd.UI.DisplayText("Packaging files to upload...") 430 case pushaction.UploadingApplication: 431 cmd.UI.DisplayText("All files found in remote cache; nothing to upload.") 432 cmd.UI.DisplayText("Waiting for API to complete processing files...") 433 case pushaction.UploadingDroplet: 434 cmd.UI.DisplayText("Uploading droplet...") 435 log.Debug("starting progress bar") 436 cmd.ProgressBar.Ready() 437 case pushaction.UploadingApplicationWithArchive: 438 cmd.UI.DisplayText("Uploading files...") 439 log.Debug("starting progress bar") 440 cmd.ProgressBar.Ready() 441 case pushaction.RetryUpload: 442 cmd.UI.DisplayText("Retrying upload due to an error...") 443 case pushaction.UploadWithArchiveComplete: 444 cmd.ProgressBar.Complete() 445 cmd.UI.DisplayNewline() 446 cmd.UI.DisplayText("Waiting for API to complete processing files...") 447 case pushaction.UploadDropletComplete: 448 cmd.ProgressBar.Complete() 449 cmd.UI.DisplayNewline() 450 cmd.UI.DisplayText("Waiting for API to complete processing droplet...") 451 case pushaction.Complete: 452 return true 453 default: 454 log.WithField("event", event).Debug("ignoring event") 455 } 456 return false 457 } 458 459 func (cmd PushCommand) validateArgs() error { 460 switch { 461 case cmd.DropletPath != "" && cmd.AppPath != "": 462 return translatableerror.ArgumentCombinationError{ 463 Args: []string{"--droplet", "-p"}, 464 } 465 case cmd.DropletPath != "" && cmd.DockerImage.Path != "": 466 return translatableerror.ArgumentCombinationError{ 467 Args: []string{"--droplet", "--docker-image", "-o"}, 468 } 469 // ArgumentCombinationError trumps RequiredArgsError in this case (when 470 // DockerImage unspecified) 471 case cmd.DropletPath != "" && cmd.DockerUsername != "": 472 return translatableerror.ArgumentCombinationError{ 473 Args: []string{"--droplet", "--docker-username", "-p"}, 474 } 475 case cmd.DockerImage.Path != "" && cmd.AppPath != "": 476 return translatableerror.ArgumentCombinationError{ 477 Args: []string{"--docker-image, -o", "-p"}, 478 } 479 case cmd.DockerImage.Path != "" && cmd.Buildpacks != nil: 480 return translatableerror.ArgumentCombinationError{ 481 Args: []string{"-b", "--docker-image, -o"}, 482 } 483 case cmd.DockerUsername != "" && cmd.DockerImage.Path == "": 484 return translatableerror.RequiredFlagsError{ 485 Arg1: "--docker-image, -o", 486 Arg2: "--docker-username", 487 } 488 case cmd.Domain != "" && cmd.NoRoute: 489 return translatableerror.ArgumentCombinationError{ 490 Args: []string{"-d", "--no-route"}, 491 } 492 case cmd.Hostname != "" && cmd.NoHostname: 493 return translatableerror.ArgumentCombinationError{ 494 Args: []string{"--hostname", "-n", "--no-hostname"}, 495 } 496 case cmd.Hostname != "" && cmd.NoRoute: 497 return translatableerror.ArgumentCombinationError{ 498 Args: []string{"--hostname", "-n", "--no-route"}, 499 } 500 case cmd.NoHostname && cmd.NoRoute: 501 return translatableerror.ArgumentCombinationError{ 502 Args: []string{"--no-hostname", "--no-route"}, 503 } 504 case cmd.PathToManifest != "" && cmd.NoManifest: 505 return translatableerror.ArgumentCombinationError{ 506 Args: []string{"-f", "--no-manifest"}, 507 } 508 case cmd.RandomRoute && cmd.Hostname != "": 509 return translatableerror.ArgumentCombinationError{ 510 Args: []string{"--hostname", "-n", "--random-route"}, 511 } 512 case cmd.RandomRoute && cmd.NoHostname: 513 return translatableerror.ArgumentCombinationError{ 514 Args: []string{"--no-hostname", "--random-route"}, 515 } 516 case cmd.RandomRoute && cmd.NoRoute: 517 return translatableerror.ArgumentCombinationError{ 518 Args: []string{"--no-route", "--random-route"}, 519 } 520 case cmd.RandomRoute && cmd.RoutePath.Path != "": 521 return translatableerror.ArgumentCombinationError{ 522 Args: []string{"--random-route", "--route-path"}, 523 } 524 case cmd.RoutePath.Path != "" && cmd.NoRoute: 525 return translatableerror.ArgumentCombinationError{ 526 Args: []string{"--route-path", "--no-route"}, 527 } 528 } 529 530 return nil 531 }