github.com/LukasHeimann/cloudfoundrycli/v8@v8.4.4/command/v7/login_command.go (about)

     1  package v7
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/url"
     7  	"strings"
     8  
     9  	"github.com/LukasHeimann/cloudfoundrycli/v8/api/uaa"
    10  	"github.com/LukasHeimann/cloudfoundrycli/v8/resources"
    11  	"github.com/LukasHeimann/cloudfoundrycli/v8/util/ui"
    12  	"code.cloudfoundry.org/clock"
    13  
    14  	"github.com/LukasHeimann/cloudfoundrycli/v8/actor/actionerror"
    15  	"github.com/LukasHeimann/cloudfoundrycli/v8/actor/v7action"
    16  	"github.com/LukasHeimann/cloudfoundrycli/v8/api/uaa/constant"
    17  	"github.com/LukasHeimann/cloudfoundrycli/v8/cf/configuration/coreconfig"
    18  	"github.com/LukasHeimann/cloudfoundrycli/v8/cf/errors"
    19  	"github.com/LukasHeimann/cloudfoundrycli/v8/command"
    20  	"github.com/LukasHeimann/cloudfoundrycli/v8/command/translatableerror"
    21  	"github.com/LukasHeimann/cloudfoundrycli/v8/command/v7/shared"
    22  )
    23  
    24  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . ActorReloader
    25  
    26  type ActorReloader interface {
    27  	Reload(command.Config, command.UI) (Actor, error)
    28  }
    29  
    30  type ActualActorReloader struct{}
    31  
    32  func (a ActualActorReloader) Reload(config command.Config, ui command.UI) (Actor, error) {
    33  	ccClient, uaaClient, routingClient, err := shared.GetNewClientsAndConnectToCF(config, ui, "")
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	return v7action.NewActor(ccClient, config, nil, uaaClient, routingClient, clock.NewClock()), nil
    39  }
    40  
    41  const maxLoginTries = 3
    42  
    43  type LoginCommand struct {
    44  	UI            command.UI
    45  	Actor         Actor
    46  	Config        command.Config
    47  	ActorReloader ActorReloader
    48  
    49  	APIEndpoint       string      `short:"a" description:"API endpoint (e.g. https://api.example.com)"`
    50  	Organization      string      `short:"o" description:"Org"`
    51  	Password          string      `short:"p" description:"Password"`
    52  	Space             string      `short:"s" description:"Space"`
    53  	SkipSSLValidation bool        `long:"skip-ssl-validation" description:"Skip verification of the API endpoint. Not recommended!"`
    54  	SSO               bool        `long:"sso" description:"Prompt for a one-time passcode to login"`
    55  	SSOPasscode       string      `long:"sso-passcode" description:"One-time passcode"`
    56  	Username          string      `short:"u" description:"Username"`
    57  	Origin            string      `long:"origin" description:"Indicates the identity provider to be used for login"`
    58  	usage             interface{} `usage:"CF_NAME login [-a API_URL] [-u USERNAME] [-p PASSWORD] [-o ORG] [-s SPACE] [--sso | --sso-passcode PASSCODE] [--origin ORIGIN]\n\nWARNING:\n   Providing your password as a command line option is highly discouraged\n   Your password may be visible to others and may be recorded in your shell history\n\nEXAMPLES:\n   CF_NAME login (omit username and password to login interactively -- CF_NAME will prompt for both)\n   CF_NAME login -u name@example.com -p pa55woRD (specify username and password as arguments)\n   CF_NAME login -u name@example.com -p \"my password\" (use quotes for passwords with a space)\n   CF_NAME login -u name@example.com -p \"\\\"password\\\"\" (escape quotes if used in password)\n   CF_NAME login --sso (CF_NAME will provide a url to obtain a one-time passcode to login)\n   CF_NAME login --origin ldap"`
    59  	relatedCommands   interface{} `related_commands:"api, auth, target"`
    60  }
    61  
    62  func (cmd *LoginCommand) Setup(config command.Config, ui command.UI) error {
    63  	ccClient := shared.NewWrappedCloudControllerClient(config, ui)
    64  	cmd.Actor = v7action.NewActor(ccClient, config, nil, nil, nil, clock.NewClock())
    65  	cmd.ActorReloader = ActualActorReloader{}
    66  
    67  	cmd.UI = ui
    68  	cmd.Config = config
    69  	return nil
    70  }
    71  
    72  func (cmd *LoginCommand) Execute(args []string) error {
    73  	if cmd.Config.UAAGrantType() == string(constant.GrantTypeClientCredentials) {
    74  		return translatableerror.PasswordGrantTypeLogoutRequiredError{}
    75  	}
    76  
    77  	if cmd.Config.UAAOAuthClient() != "cf" || cmd.Config.UAAOAuthClientSecret() != "" {
    78  		return translatableerror.ManualClientCredentialsError{}
    79  	}
    80  
    81  	err := cmd.validateFlags()
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	endpoint, err := cmd.determineAPIEndpoint()
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	err = cmd.targetAPI(endpoint)
    92  	if err != nil {
    93  		translatedErr := translatableerror.ConvertToTranslatableError(err)
    94  		if invalidSSLErr, ok := translatedErr.(translatableerror.InvalidSSLCertError); ok {
    95  			invalidSSLErr.SuggestedCommand = "login"
    96  			return invalidSSLErr
    97  		}
    98  		return err
    99  	}
   100  
   101  	err = cmd.validateTargetSpecificFlags()
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	versionWarning, err := shared.CheckCCAPIVersion(cmd.Config.APIVersion())
   107  	if err != nil {
   108  		cmd.UI.DisplayWarning("Warning: unable to determine whether targeted API's version meets minimum supported.")
   109  	}
   110  	if versionWarning != "" {
   111  		cmd.UI.DisplayWarning(versionWarning)
   112  	}
   113  
   114  	cmd.UI.DisplayNewline()
   115  
   116  	cmd.Actor, err = cmd.ActorReloader.Reload(cmd.Config, cmd.UI)
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	defer cmd.showStatus()
   122  
   123  	var authErr error
   124  	if cmd.SSO || cmd.SSOPasscode != "" {
   125  		authErr = cmd.authenticateSSO()
   126  	} else {
   127  		authErr = cmd.authenticate()
   128  	}
   129  
   130  	if authErr != nil {
   131  		return errors.New("Unable to authenticate.")
   132  	}
   133  
   134  	err = cmd.Config.WriteConfig()
   135  	if err != nil {
   136  		return fmt.Errorf("Error writing config: %s", err.Error())
   137  	}
   138  
   139  	if cmd.Organization != "" {
   140  		org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.Organization)
   141  		cmd.UI.DisplayWarnings(warnings)
   142  		if err != nil {
   143  			return err
   144  		}
   145  
   146  		cmd.Config.SetOrganizationInformation(org.GUID, org.Name)
   147  	} else {
   148  		orgs, warnings, err := cmd.Actor.GetOrganizations("")
   149  		cmd.UI.DisplayWarnings(warnings)
   150  		if err != nil {
   151  			return err
   152  		}
   153  
   154  		filteredOrgs, err := cmd.filterOrgsForSpace(orgs)
   155  		if err != nil {
   156  			return err
   157  		}
   158  
   159  		if len(filteredOrgs) == 1 {
   160  			cmd.Config.SetOrganizationInformation(filteredOrgs[0].GUID, filteredOrgs[0].Name)
   161  		} else if len(filteredOrgs) > 1 {
   162  			chosenOrg, err := cmd.promptChosenOrg(filteredOrgs)
   163  			if err != nil {
   164  				return err
   165  			}
   166  
   167  			if chosenOrg.GUID != "" {
   168  				cmd.Config.SetOrganizationInformation(chosenOrg.GUID, chosenOrg.Name)
   169  			}
   170  		}
   171  	}
   172  
   173  	targetedOrg := cmd.Config.TargetedOrganization()
   174  
   175  	if targetedOrg.GUID != "" {
   176  		cmd.UI.DisplayTextWithFlavor("Targeted org {{.Organization}}.", map[string]interface{}{
   177  			"Organization": cmd.Config.TargetedOrganizationName(),
   178  		})
   179  		cmd.UI.DisplayNewline()
   180  
   181  		if cmd.Space != "" {
   182  			space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.Space, targetedOrg.GUID)
   183  			cmd.UI.DisplayWarnings(warnings)
   184  			if err != nil {
   185  				return err
   186  			}
   187  			cmd.targetSpace(space)
   188  		} else {
   189  			spaces, warnings, err := cmd.Actor.GetOrganizationSpaces(targetedOrg.GUID)
   190  			cmd.UI.DisplayWarnings(warnings)
   191  			if err != nil {
   192  				return err
   193  			}
   194  
   195  			if len(spaces) == 1 {
   196  				cmd.targetSpace(spaces[0])
   197  			} else if len(spaces) > 1 {
   198  				chosenSpace, err := cmd.promptChosenSpace(spaces)
   199  				if err != nil {
   200  					return err
   201  				}
   202  				if chosenSpace.Name != "" {
   203  					cmd.targetSpace(chosenSpace)
   204  				}
   205  			}
   206  		}
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func (cmd *LoginCommand) determineAPIEndpoint() (v7action.TargetSettings, error) {
   213  	endpoint := cmd.APIEndpoint
   214  	skipSSLValidation := cmd.SkipSSLValidation
   215  
   216  	configTarget := cmd.Config.Target()
   217  
   218  	if endpoint == "" && configTarget != "" {
   219  		endpoint = configTarget
   220  		skipSSLValidation = cmd.Config.SkipSSLValidation() || cmd.SkipSSLValidation
   221  	}
   222  
   223  	if len(endpoint) > 0 {
   224  		cmd.UI.DisplayTextWithFlavor("API endpoint: {{.APIEndpoint}}", map[string]interface{}{
   225  			"APIEndpoint": endpoint,
   226  		})
   227  	} else {
   228  		userInput, err := cmd.UI.DisplayTextPrompt("API endpoint")
   229  		if err != nil {
   230  			return v7action.TargetSettings{}, err
   231  		}
   232  		endpoint = userInput
   233  	}
   234  
   235  	strippedEndpoint := strings.TrimRight(endpoint, "/")
   236  	parsedURL, err := url.Parse(strippedEndpoint)
   237  	if err != nil {
   238  		return v7action.TargetSettings{}, err
   239  	}
   240  	if parsedURL.Scheme == "" {
   241  		parsedURL.Scheme = "https"
   242  	}
   243  
   244  	return v7action.TargetSettings{URL: parsedURL.String(), SkipSSLValidation: skipSSLValidation}, nil
   245  }
   246  
   247  func (cmd *LoginCommand) targetAPI(settings v7action.TargetSettings) error {
   248  	warnings, err := cmd.Actor.SetTarget(settings)
   249  	cmd.UI.DisplayWarnings(warnings)
   250  	if err != nil {
   251  		return err
   252  	}
   253  
   254  	if strings.HasPrefix(settings.URL, "http:") {
   255  		cmd.UI.DisplayWarning("Warning: Insecure http API endpoint detected: secure https API endpoints are recommended")
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  func (cmd *LoginCommand) authenticate() error {
   262  	var err error
   263  	credentials := make(map[string]string)
   264  
   265  	prompts, err := cmd.Actor.GetLoginPrompts()
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	nonPasswordPrompts, passwordPrompts := cmd.groupPrompts(prompts)
   271  
   272  	if value, ok := prompts["username"]; ok {
   273  		credentials["username"], err = cmd.getFlagValOrPrompt(&cmd.Username, value, true)
   274  		if err != nil {
   275  			return err
   276  		}
   277  	}
   278  
   279  	for key, prompt := range nonPasswordPrompts {
   280  		if prompt.Type == coreconfig.AuthPromptTypeMenu {
   281  			credentials[key], err = cmd.UI.DisplayTextMenu(prompt.Entries, prompt.DisplayName)
   282  		} else {
   283  			credentials[key], err = cmd.UI.DisplayTextPrompt(prompt.DisplayName)
   284  		}
   285  		if err != nil {
   286  			return err
   287  		}
   288  	}
   289  
   290  	for i := 0; i < maxLoginTries; i++ {
   291  		// ensure that password gets prompted before other codes (eg. mfa code)
   292  		if prompt, ok := prompts["password"]; ok {
   293  			credentials["password"], err = cmd.getFlagValOrPrompt(&cmd.Password, prompt, false)
   294  			if err != nil {
   295  				return err
   296  			}
   297  		}
   298  
   299  		for key, prompt := range passwordPrompts {
   300  			credentials[key], err = cmd.UI.DisplayPasswordPrompt(prompt.DisplayName)
   301  			if err != nil {
   302  				return err
   303  			}
   304  		}
   305  
   306  		cmd.UI.DisplayNewline()
   307  		cmd.UI.DisplayText("Authenticating...")
   308  
   309  		err = cmd.Actor.Authenticate(credentials, cmd.Origin, constant.GrantTypePassword)
   310  
   311  		if err != nil {
   312  			cmd.UI.DisplayWarning(translatableerror.ConvertToTranslatableError(err).Error())
   313  			cmd.UI.DisplayNewline()
   314  
   315  			if _, ok := err.(uaa.AccountLockedError); ok {
   316  				break
   317  			}
   318  		}
   319  
   320  		if err == nil {
   321  			cmd.UI.DisplayOK()
   322  			break
   323  		}
   324  	}
   325  
   326  	return err
   327  }
   328  
   329  func (cmd *LoginCommand) authenticateSSO() error {
   330  	prompts, err := cmd.Actor.GetLoginPrompts()
   331  	if err != nil {
   332  		return err
   333  	}
   334  
   335  	for i := 0; i < maxLoginTries; i++ {
   336  		var passcode string
   337  
   338  		passcode, err = cmd.getFlagValOrPrompt(&cmd.SSOPasscode, prompts["passcode"], false)
   339  		if err != nil {
   340  			return err
   341  		}
   342  
   343  		credentials := map[string]string{"passcode": passcode}
   344  
   345  		cmd.UI.DisplayText("Authenticating...")
   346  		err = cmd.Actor.Authenticate(credentials, "", constant.GrantTypePassword)
   347  
   348  		if err != nil {
   349  			cmd.UI.DisplayWarning(translatableerror.ConvertToTranslatableError(err).Error())
   350  			cmd.UI.DisplayNewline()
   351  		} else {
   352  			cmd.UI.DisplayOK()
   353  			cmd.UI.DisplayNewline()
   354  			break
   355  		}
   356  	}
   357  	return err
   358  }
   359  
   360  func (cmd *LoginCommand) groupPrompts(prompts map[string]coreconfig.AuthPrompt) (map[string]coreconfig.AuthPrompt, map[string]coreconfig.AuthPrompt) {
   361  	var (
   362  		nonPasswordPrompts = make(map[string]coreconfig.AuthPrompt)
   363  		passwordPrompts    = make(map[string]coreconfig.AuthPrompt)
   364  	)
   365  
   366  	for key, prompt := range prompts {
   367  		if prompt.Type == coreconfig.AuthPromptTypePassword {
   368  			if key == "passcode" || key == "password" {
   369  				continue
   370  			}
   371  
   372  			passwordPrompts[key] = prompt
   373  		} else {
   374  			if key == "username" {
   375  				continue
   376  			}
   377  
   378  			nonPasswordPrompts[key] = prompt
   379  		}
   380  	}
   381  
   382  	return nonPasswordPrompts, passwordPrompts
   383  }
   384  
   385  func (cmd *LoginCommand) getFlagValOrPrompt(field *string, prompt coreconfig.AuthPrompt, isText bool) (string, error) {
   386  	if *field != "" {
   387  		value := *field
   388  		*field = ""
   389  		return value, nil
   390  	} else {
   391  		if isText {
   392  			return cmd.UI.DisplayTextPrompt(prompt.DisplayName)
   393  		}
   394  		return cmd.UI.DisplayPasswordPrompt(prompt.DisplayName)
   395  	}
   396  }
   397  
   398  func (cmd *LoginCommand) showStatus() {
   399  	tableContent := [][]string{
   400  		{
   401  			cmd.UI.TranslateText("API endpoint:"),
   402  			strings.TrimRight(cmd.Config.Target(), "/"),
   403  		},
   404  		{
   405  			cmd.UI.TranslateText("API version:"),
   406  			cmd.Config.APIVersion(),
   407  		},
   408  	}
   409  
   410  	user, err := cmd.Actor.GetCurrentUser()
   411  	if user.Name == "" || err != nil {
   412  		cmd.UI.DisplayKeyValueTable("", tableContent, 3)
   413  		command.DisplayNotLoggedInText(cmd.Config.BinaryName(), cmd.UI)
   414  		return
   415  	}
   416  	tableContent = append(tableContent, []string{cmd.UI.TranslateText("user:"), user.Name})
   417  
   418  	orgName := cmd.Config.TargetedOrganizationName()
   419  	if orgName == "" {
   420  		cmd.UI.DisplayKeyValueTable("", tableContent, 3)
   421  		cmd.UI.DisplayText("No org or space targeted, use '{{.CFTargetCommand}} -o ORG -s SPACE'",
   422  			map[string]interface{}{
   423  				"CFTargetCommand": fmt.Sprintf("%s target", cmd.Config.BinaryName()),
   424  			},
   425  		)
   426  		return
   427  	}
   428  	tableContent = append(tableContent, []string{cmd.UI.TranslateText("org:"), orgName})
   429  
   430  	spaceContent := cmd.Config.TargetedSpace().Name
   431  	if spaceContent == "" {
   432  		spaceContent = cmd.UI.TranslateText("No space targeted, use '{{.Command}}'",
   433  			map[string]interface{}{
   434  				"Command": fmt.Sprintf("%s target -s SPACE", cmd.Config.BinaryName()),
   435  			},
   436  		)
   437  	}
   438  	tableContent = append(tableContent, []string{cmd.UI.TranslateText("space:"), spaceContent})
   439  
   440  	cmd.UI.DisplayKeyValueTable("", tableContent, 3)
   441  }
   442  
   443  func (cmd *LoginCommand) filterOrgsForSpace(allOrgs []resources.Organization) ([]resources.Organization, error) {
   444  	if cmd.Space == "" {
   445  		return allOrgs, nil
   446  	}
   447  
   448  	var filteredOrgs []resources.Organization
   449  	for _, org := range allOrgs {
   450  		_, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.Space, org.GUID)
   451  		cmd.UI.DisplayWarnings(warnings)
   452  		if err == nil {
   453  			filteredOrgs = append(filteredOrgs, org)
   454  			continue
   455  		}
   456  		if _, ok := err.(actionerror.SpaceNotFoundError); !ok {
   457  			return []resources.Organization{}, err
   458  		}
   459  	}
   460  
   461  	return filteredOrgs, nil
   462  }
   463  
   464  func (cmd *LoginCommand) promptChosenOrg(orgs []resources.Organization) (resources.Organization, error) {
   465  	orgNames := make([]string, len(orgs))
   466  	for i, org := range orgs {
   467  		orgNames[i] = org.Name
   468  	}
   469  
   470  	chosenOrgName, err := cmd.promptMenu(orgNames, "Select an org:", "Org")
   471  	if err != nil {
   472  		if invalidChoice, ok := err.(ui.InvalidChoiceError); ok {
   473  			if cmd.Space != "" {
   474  				return resources.Organization{}, translatableerror.OrganizationWithSpaceNotFoundError{Name: invalidChoice.Choice, SpaceName: cmd.Space}
   475  			}
   476  			return resources.Organization{}, translatableerror.OrganizationNotFoundError{Name: invalidChoice.Choice}
   477  		}
   478  
   479  		if err == io.EOF {
   480  			return resources.Organization{}, nil
   481  		}
   482  
   483  		return resources.Organization{}, err
   484  	}
   485  
   486  	for _, org := range orgs {
   487  		if org.Name == chosenOrgName {
   488  			return org, nil
   489  		}
   490  	}
   491  
   492  	return resources.Organization{}, nil
   493  }
   494  
   495  func (cmd *LoginCommand) promptChosenSpace(spaces []resources.Space) (resources.Space, error) {
   496  	spaceNames := make([]string, len(spaces))
   497  	for i, space := range spaces {
   498  		spaceNames[i] = space.Name
   499  	}
   500  
   501  	chosenSpaceName, err := cmd.promptMenu(spaceNames, "Select a space:", "Space")
   502  	if err != nil {
   503  		if invalidChoice, ok := err.(ui.InvalidChoiceError); ok {
   504  			return resources.Space{}, translatableerror.SpaceNotFoundError{Name: invalidChoice.Choice}
   505  		}
   506  
   507  		if err == io.EOF {
   508  			return resources.Space{}, nil
   509  		}
   510  
   511  		return resources.Space{}, err
   512  	}
   513  
   514  	for _, space := range spaces {
   515  		if space.Name == chosenSpaceName {
   516  			return space, nil
   517  		}
   518  	}
   519  	return resources.Space{}, nil
   520  }
   521  
   522  func (cmd *LoginCommand) promptMenu(choices []string, text string, prompt string) (string, error) {
   523  	var choice string
   524  	var err error
   525  
   526  	if len(choices) < 50 {
   527  		for {
   528  			cmd.UI.DisplayText(text)
   529  			choice, err = cmd.UI.DisplayTextMenu(choices, prompt)
   530  			if err != ui.ErrInvalidIndex {
   531  				break
   532  			}
   533  		}
   534  	} else {
   535  		cmd.UI.DisplayText(text)
   536  		cmd.UI.DisplayText("There are too many options to display; please type in the name.")
   537  		cmd.UI.DisplayNewline()
   538  		defaultChoice := "enter to skip"
   539  		choice, err = cmd.UI.DisplayOptionalTextPrompt(defaultChoice, prompt)
   540  
   541  		if choice == defaultChoice {
   542  			return "", nil
   543  		}
   544  		if !contains(choices, choice) {
   545  			return "", ui.InvalidChoiceError{Choice: choice}
   546  		}
   547  	}
   548  
   549  	return choice, err
   550  }
   551  
   552  func (cmd *LoginCommand) targetSpace(space resources.Space) {
   553  	cmd.Config.SetSpaceInformation(space.GUID, space.Name, true)
   554  
   555  	cmd.UI.DisplayTextWithFlavor("Targeted space {{.Space}}.", map[string]interface{}{
   556  		"Space": space.Name,
   557  	})
   558  	cmd.UI.DisplayNewline()
   559  }
   560  
   561  func (cmd *LoginCommand) validateFlags() error {
   562  	if cmd.Origin != "" && cmd.SSO {
   563  		return translatableerror.ArgumentCombinationError{
   564  			Args: []string{"--sso", "--origin"},
   565  		}
   566  	}
   567  
   568  	if cmd.Origin != "" && cmd.SSOPasscode != "" {
   569  		return translatableerror.ArgumentCombinationError{
   570  			Args: []string{"--sso-passcode", "--origin"},
   571  		}
   572  	}
   573  
   574  	if cmd.SSO && cmd.SSOPasscode != "" {
   575  		return translatableerror.ArgumentCombinationError{
   576  			Args: []string{"--sso-passcode", "--sso"},
   577  		}
   578  	}
   579  
   580  	return nil
   581  }
   582  
   583  func (cmd *LoginCommand) validateTargetSpecificFlags() error {
   584  	if !cmd.Config.IsCFOnK8s() {
   585  		return nil
   586  	}
   587  
   588  	if cmd.Password != "" {
   589  		return translatableerror.NotSupportedOnKubernetesArgumentError{Arg: "-p"}
   590  	}
   591  	if cmd.SSO {
   592  		return translatableerror.NotSupportedOnKubernetesArgumentError{Arg: "--sso"}
   593  	}
   594  	if cmd.SSOPasscode != "" {
   595  		return translatableerror.NotSupportedOnKubernetesArgumentError{Arg: "--sso-passcode"}
   596  	}
   597  	if cmd.Username != "" {
   598  		return translatableerror.NotSupportedOnKubernetesArgumentError{Arg: "-u"}
   599  	}
   600  	if cmd.Origin != "" {
   601  		return translatableerror.NotSupportedOnKubernetesArgumentError{Arg: "--origin"}
   602  	}
   603  	return nil
   604  }
   605  
   606  func contains(s []string, v string) bool {
   607  	for _, x := range s {
   608  		if x == v {
   609  			return true
   610  		}
   611  	}
   612  	return false
   613  }