github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/main.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"runtime"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/containerd/log"
    29  	"github.com/containerd/nerdctl/pkg/config"
    30  	ncdefaults "github.com/containerd/nerdctl/pkg/defaults"
    31  	"github.com/containerd/nerdctl/pkg/errutil"
    32  	"github.com/containerd/nerdctl/pkg/logging"
    33  	"github.com/containerd/nerdctl/pkg/rootlessutil"
    34  	"github.com/containerd/nerdctl/pkg/version"
    35  	"github.com/fatih/color"
    36  	"github.com/pelletier/go-toml/v2"
    37  
    38  	"github.com/spf13/cobra"
    39  	"github.com/spf13/pflag"
    40  )
    41  
    42  const (
    43  	Category   = "category"
    44  	Management = "management"
    45  )
    46  
    47  var (
    48  	// To print Bold Text
    49  	Bold = color.New(color.Bold).SprintfFunc()
    50  )
    51  
    52  // usage was derived from https://github.com/spf13/cobra/blob/v1.2.1/command.go#L491-L514
    53  func usage(c *cobra.Command) error {
    54  	s := "Usage: "
    55  	if c.Runnable() {
    56  		s += c.UseLine() + "\n"
    57  	} else {
    58  		s += c.CommandPath() + " [command]\n"
    59  	}
    60  	s += "\n"
    61  	if len(c.Aliases) > 0 {
    62  		s += "Aliases: " + c.NameAndAliases() + "\n"
    63  	}
    64  	if c.HasExample() {
    65  		s += "Example:\n"
    66  		s += c.Example + "\n"
    67  	}
    68  
    69  	var managementCommands, nonManagementCommands []*cobra.Command
    70  	for _, f := range c.Commands() {
    71  		f := f
    72  		if f.Hidden {
    73  			continue
    74  		}
    75  		if f.Annotations[Category] == Management {
    76  			managementCommands = append(managementCommands, f)
    77  		} else {
    78  			nonManagementCommands = append(nonManagementCommands, f)
    79  		}
    80  	}
    81  	printCommands := func(title string, commands []*cobra.Command) string {
    82  		if len(commands) == 0 {
    83  			return ""
    84  		}
    85  		var longest int
    86  		for _, f := range commands {
    87  			if l := len(f.Name()); l > longest {
    88  				longest = l
    89  			}
    90  		}
    91  
    92  		title = Bold(title)
    93  		t := title + ":\n"
    94  		for _, f := range commands {
    95  			t += "  "
    96  			t += f.Name()
    97  			t += strings.Repeat(" ", longest-len(f.Name()))
    98  			t += "  " + f.Short + "\n"
    99  		}
   100  		t += "\n"
   101  		return t
   102  	}
   103  	s += printCommands("Management commands", managementCommands)
   104  	s += printCommands("Commands", nonManagementCommands)
   105  
   106  	s += Bold("Flags") + ":\n"
   107  	s += c.LocalFlags().FlagUsages() + "\n"
   108  
   109  	if c == c.Root() {
   110  		s += "Run '" + c.CommandPath() + " COMMAND --help' for more information on a command.\n"
   111  	} else {
   112  		s += "See also '" + c.Root().CommandPath() + " --help' for the global flags such as '--namespace', '--snapshotter', and '--cgroup-manager'."
   113  	}
   114  	fmt.Fprintln(c.OutOrStdout(), s)
   115  	return nil
   116  }
   117  
   118  func main() {
   119  	if err := xmain(); err != nil {
   120  		errutil.HandleExitCoder(err)
   121  		log.L.Fatal(err)
   122  	}
   123  }
   124  
   125  func xmain() error {
   126  	if len(os.Args) == 3 && os.Args[1] == logging.MagicArgv1 {
   127  		// containerd runtime v2 logging plugin mode.
   128  		// "binary://BIN?KEY=VALUE" URI is parsed into Args {BIN, KEY, VALUE}.
   129  		return logging.Main(os.Args[2])
   130  	}
   131  	// nerdctl CLI mode
   132  	app, err := newApp()
   133  	if err != nil {
   134  		return err
   135  	}
   136  	return app.Execute()
   137  }
   138  
   139  func initRootCmdFlags(rootCmd *cobra.Command, tomlPath string) (*pflag.FlagSet, error) {
   140  	cfg := config.New()
   141  	if r, err := os.Open(tomlPath); err == nil {
   142  		log.L.Debugf("Loading config from %q", tomlPath)
   143  		defer r.Close()
   144  		dec := toml.NewDecoder(r).DisallowUnknownFields() // set Strict to detect typo
   145  		if err := dec.Decode(cfg); err != nil {
   146  			return nil, fmt.Errorf("failed to load nerdctl config (not daemon config) from %q (Hint: don't mix up daemon's `config.toml` with `nerdctl.toml`): %w", tomlPath, err)
   147  		}
   148  		log.L.Debugf("Loaded config %+v", cfg)
   149  	} else {
   150  		log.L.WithError(err).Debugf("Not loading config from %q", tomlPath)
   151  		if !errors.Is(err, os.ErrNotExist) {
   152  			return nil, err
   153  		}
   154  	}
   155  	aliasToBeInherited := pflag.NewFlagSet(rootCmd.Name(), pflag.ExitOnError)
   156  
   157  	rootCmd.PersistentFlags().Bool("debug", cfg.Debug, "debug mode")
   158  	rootCmd.PersistentFlags().Bool("debug-full", cfg.DebugFull, "debug mode (with full output)")
   159  	// -a is aliases (conflicts with nerdctl images -a)
   160  	AddPersistentStringFlag(rootCmd, "address", []string{"a", "H"}, nil, []string{"host"}, aliasToBeInherited, cfg.Address, "CONTAINERD_ADDRESS", `containerd address, optionally with "unix://" prefix`)
   161  	// -n is aliases (conflicts with nerdctl logs -n)
   162  	AddPersistentStringFlag(rootCmd, "namespace", []string{"n"}, nil, nil, aliasToBeInherited, cfg.Namespace, "CONTAINERD_NAMESPACE", `containerd namespace, such as "moby" for Docker, "k8s.io" for Kubernetes`)
   163  	rootCmd.RegisterFlagCompletionFunc("namespace", shellCompleteNamespaceNames)
   164  	AddPersistentStringFlag(rootCmd, "snapshotter", nil, nil, []string{"storage-driver"}, aliasToBeInherited, cfg.Snapshotter, "CONTAINERD_SNAPSHOTTER", "containerd snapshotter")
   165  	rootCmd.RegisterFlagCompletionFunc("snapshotter", shellCompleteSnapshotterNames)
   166  	rootCmd.RegisterFlagCompletionFunc("storage-driver", shellCompleteSnapshotterNames)
   167  	AddPersistentStringFlag(rootCmd, "cni-path", nil, nil, nil, aliasToBeInherited, cfg.CNIPath, "CNI_PATH", "cni plugins binary directory")
   168  	AddPersistentStringFlag(rootCmd, "cni-netconfpath", nil, nil, nil, aliasToBeInherited, cfg.CNINetConfPath, "NETCONFPATH", "cni config directory")
   169  	rootCmd.PersistentFlags().String("data-root", cfg.DataRoot, "Root directory of persistent nerdctl state (managed by nerdctl, not by containerd)")
   170  	rootCmd.PersistentFlags().String("cgroup-manager", cfg.CgroupManager, `Cgroup manager to use ("cgroupfs"|"systemd")`)
   171  	rootCmd.RegisterFlagCompletionFunc("cgroup-manager", shellCompleteCgroupManagerNames)
   172  	rootCmd.PersistentFlags().Bool("insecure-registry", cfg.InsecureRegistry, "skips verifying HTTPS certs, and allows falling back to plain HTTP")
   173  	// hosts-dir is defined as StringSlice, not StringArray, to allow specifying "--hosts-dir=/etc/containerd/certs.d,/etc/docker/certs.d"
   174  	rootCmd.PersistentFlags().StringSlice("hosts-dir", cfg.HostsDir, "A directory that contains <HOST:PORT>/hosts.toml (containerd style) or <HOST:PORT>/{ca.cert, cert.pem, key.pem} (docker style)")
   175  	// Experimental enable experimental feature, see in https://github.com/containerd/nerdctl/blob/main/docs/experimental.md
   176  	AddPersistentBoolFlag(rootCmd, "experimental", nil, nil, cfg.Experimental, "NERDCTL_EXPERIMENTAL", "Control experimental: https://github.com/containerd/nerdctl/blob/main/docs/experimental.md")
   177  	AddPersistentStringFlag(rootCmd, "host-gateway-ip", nil, nil, nil, aliasToBeInherited, cfg.HostGatewayIP, "NERDCTL_HOST_GATEWAY_IP", "IP address that the special 'host-gateway' string in --add-host resolves to. Defaults to the IP address of the host. It has no effect without setting --add-host")
   178  	return aliasToBeInherited, nil
   179  }
   180  
   181  func newApp() (*cobra.Command, error) {
   182  
   183  	tomlPath := ncdefaults.NerdctlTOML()
   184  	if v, ok := os.LookupEnv("NERDCTL_TOML"); ok {
   185  		tomlPath = v
   186  	}
   187  
   188  	short := "nerdctl is a command line interface for containerd"
   189  	long := fmt.Sprintf(`%s
   190  
   191  Config file ($NERDCTL_TOML): %s
   192  `, short, tomlPath)
   193  	var rootCmd = &cobra.Command{
   194  		Use:              "nerdctl",
   195  		Short:            short,
   196  		Long:             long,
   197  		Version:          strings.TrimPrefix(version.GetVersion(), "v"),
   198  		SilenceUsage:     true,
   199  		SilenceErrors:    true,
   200  		TraverseChildren: true, // required for global short hands like -a, -H, -n
   201  	}
   202  
   203  	rootCmd.SetUsageFunc(usage)
   204  	aliasToBeInherited, err := initRootCmdFlags(rootCmd, tomlPath)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
   210  		globalOptions, err := processRootCmdFlags(cmd)
   211  		if err != nil {
   212  			return err
   213  		}
   214  		debug := globalOptions.DebugFull
   215  		if !debug {
   216  			debug = globalOptions.Debug
   217  		}
   218  		if debug {
   219  			log.SetLevel(log.DebugLevel.String())
   220  		}
   221  		address := globalOptions.Address
   222  		if strings.Contains(address, "://") && !strings.HasPrefix(address, "unix://") {
   223  			return fmt.Errorf("invalid address %q", address)
   224  		}
   225  		cgroupManager := globalOptions.CgroupManager
   226  		if runtime.GOOS == "linux" {
   227  			switch cgroupManager {
   228  			case "systemd", "cgroupfs", "none":
   229  			default:
   230  				return fmt.Errorf("invalid cgroup-manager %q (supported values: \"systemd\", \"cgroupfs\", \"none\")", cgroupManager)
   231  			}
   232  		}
   233  		if appNeedsRootlessParentMain(cmd, args) {
   234  			// reexec /proc/self/exe with `nsenter` into RootlessKit namespaces
   235  			return rootlessutil.ParentMain(globalOptions.HostGatewayIP)
   236  		}
   237  		return nil
   238  	}
   239  	rootCmd.RunE = unknownSubcommandAction
   240  	rootCmd.AddCommand(
   241  		newCreateCommand(),
   242  		// #region Run & Exec
   243  		newRunCommand(),
   244  		newUpdateCommand(),
   245  		newExecCommand(),
   246  		// #endregion
   247  
   248  		// #region Container management
   249  		newPsCommand(),
   250  		newLogsCommand(),
   251  		newPortCommand(),
   252  		newStopCommand(),
   253  		newStartCommand(),
   254  		newDiffCommand(),
   255  		newRestartCommand(),
   256  		newKillCommand(),
   257  		newRmCommand(),
   258  		newPauseCommand(),
   259  		newUnpauseCommand(),
   260  		newCommitCommand(),
   261  		newWaitCommand(),
   262  		newRenameCommand(),
   263  		newAttachCommand(),
   264  		// #endregion
   265  
   266  		// Build
   267  		newBuildCommand(),
   268  
   269  		// #region Image management
   270  		newImagesCommand(),
   271  		newPullCommand(),
   272  		newPushCommand(),
   273  		newLoadCommand(),
   274  		newSaveCommand(),
   275  		newTagCommand(),
   276  		newRmiCommand(),
   277  		newHistoryCommand(),
   278  		// #endregion
   279  
   280  		// #region System
   281  		newEventsCommand(),
   282  		newInfoCommand(),
   283  		newVersionCommand(),
   284  		// #endregion
   285  
   286  		// Inspect
   287  		newInspectCommand(),
   288  
   289  		// stats
   290  		newTopCommand(),
   291  		newStatsCommand(),
   292  
   293  		// #region Management
   294  		newContainerCommand(),
   295  		newImageCommand(),
   296  		newNetworkCommand(),
   297  		newVolumeCommand(),
   298  		newSystemCommand(),
   299  		newNamespaceCommand(),
   300  		newBuilderCommand(),
   301  		// #endregion
   302  
   303  		// Internal
   304  		newInternalCommand(),
   305  
   306  		// login
   307  		newLoginCommand(),
   308  
   309  		// Logout
   310  		newLogoutCommand(),
   311  
   312  		// Compose
   313  		newComposeCommand(),
   314  
   315  		// IPFS
   316  		newIPFSCommand(),
   317  	)
   318  	addApparmorCommand(rootCmd)
   319  	addCpCommand(rootCmd)
   320  
   321  	// add aliasToBeInherited to subCommand(s) InheritedFlags
   322  	for _, subCmd := range rootCmd.Commands() {
   323  		subCmd.InheritedFlags().AddFlagSet(aliasToBeInherited)
   324  	}
   325  	return rootCmd, nil
   326  }
   327  
   328  func globalFlags(cmd *cobra.Command) (string, []string) {
   329  	args0, err := os.Executable()
   330  	if err != nil {
   331  		log.L.WithError(err).Warnf("cannot call os.Executable(), assuming the executable to be %q", os.Args[0])
   332  		args0 = os.Args[0]
   333  	}
   334  	if len(os.Args) < 2 {
   335  		return args0, nil
   336  	}
   337  
   338  	rootCmd := cmd.Root()
   339  	flagSet := rootCmd.Flags()
   340  	args := []string{}
   341  	flagSet.VisitAll(func(f *pflag.Flag) {
   342  		key := f.Name
   343  		val := f.Value.String()
   344  		if f.Changed {
   345  			args = append(args, "--"+key+"="+val)
   346  		}
   347  	})
   348  	return args0, args
   349  }
   350  
   351  // unknownSubcommandAction is needed to let `nerdctl system non-existent-command` fail
   352  // https://github.com/containerd/nerdctl/issues/487
   353  //
   354  // Ideally this should be implemented in Cobra itself.
   355  func unknownSubcommandAction(cmd *cobra.Command, args []string) error {
   356  	if len(args) == 0 {
   357  		return cmd.Help()
   358  	}
   359  	// The output mimics https://github.com/spf13/cobra/blob/v1.2.1/command.go#L647-L662
   360  	msg := fmt.Sprintf("unknown subcommand %q for %q", args[0], cmd.Name())
   361  	if suggestions := cmd.SuggestionsFor(args[0]); len(suggestions) > 0 {
   362  		msg += "\n\nDid you mean this?\n"
   363  		for _, s := range suggestions {
   364  			msg += fmt.Sprintf("\t%v\n", s)
   365  		}
   366  	}
   367  	return errors.New(msg)
   368  }
   369  
   370  // AddStringFlag is similar to cmd.Flags().String but supports aliases and env var
   371  func AddStringFlag(cmd *cobra.Command, name string, aliases []string, value string, env, usage string) {
   372  	if env != "" {
   373  		usage = fmt.Sprintf("%s [$%s]", usage, env)
   374  	}
   375  	if envV, ok := os.LookupEnv(env); ok {
   376  		value = envV
   377  	}
   378  	aliasesUsage := fmt.Sprintf("Alias of --%s", name)
   379  	p := new(string)
   380  	flags := cmd.Flags()
   381  	flags.StringVar(p, name, value, usage)
   382  	for _, a := range aliases {
   383  		if len(a) == 1 {
   384  			// pflag doesn't support short-only flags, so we have to register long one as well here
   385  			flags.StringVarP(p, a, a, value, aliasesUsage)
   386  		} else {
   387  			flags.StringVar(p, a, value, aliasesUsage)
   388  		}
   389  	}
   390  }
   391  
   392  // AddIntFlag is similar to cmd.Flags().Int but supports aliases and env var
   393  func AddIntFlag(cmd *cobra.Command, name string, aliases []string, value int, env, usage string) {
   394  	if env != "" {
   395  		usage = fmt.Sprintf("%s [$%s]", usage, env)
   396  	}
   397  	if envV, ok := os.LookupEnv(env); ok {
   398  		v, err := strconv.ParseInt(envV, 10, 64)
   399  		if err != nil {
   400  			log.L.WithError(err).Warnf("Invalid int value for `%s`", env)
   401  		}
   402  		value = int(v)
   403  	}
   404  	aliasesUsage := fmt.Sprintf("Alias of --%s", name)
   405  	p := new(int)
   406  	flags := cmd.Flags()
   407  	flags.IntVar(p, name, value, usage)
   408  	for _, a := range aliases {
   409  		if len(a) == 1 {
   410  			// pflag doesn't support short-only flags, so we have to register long one as well here
   411  			flags.IntVarP(p, a, a, value, aliasesUsage)
   412  		} else {
   413  			flags.IntVar(p, a, value, aliasesUsage)
   414  		}
   415  	}
   416  }
   417  
   418  // AddDurationFlag is similar to cmd.Flags().Duration but supports aliases and env var
   419  func AddDurationFlag(cmd *cobra.Command, name string, aliases []string, value time.Duration, env, usage string) {
   420  	if env != "" {
   421  		usage = fmt.Sprintf("%s [$%s]", usage, env)
   422  	}
   423  	if envV, ok := os.LookupEnv(env); ok {
   424  		var err error
   425  		value, err = time.ParseDuration(envV)
   426  		if err != nil {
   427  			log.L.WithError(err).Warnf("Invalid duration value for `%s`", env)
   428  		}
   429  	}
   430  	aliasesUsage := fmt.Sprintf("Alias of --%s", name)
   431  	p := new(time.Duration)
   432  	flags := cmd.Flags()
   433  	flags.DurationVar(p, name, value, usage)
   434  	for _, a := range aliases {
   435  		if len(a) == 1 {
   436  			// pflag doesn't support short-only flags, so we have to register long one as well here
   437  			flags.DurationVarP(p, a, a, value, aliasesUsage)
   438  		} else {
   439  			flags.DurationVar(p, a, value, aliasesUsage)
   440  		}
   441  	}
   442  }
   443  
   444  // AddPersistentStringFlag is similar to AddStringFlag but persistent.
   445  // See https://github.com/spf13/cobra/blob/main/user_guide.md#persistent-flags to learn what is "persistent".
   446  func AddPersistentStringFlag(cmd *cobra.Command, name string, aliases, localAliases, persistentAliases []string, aliasToBeInherited *pflag.FlagSet, value string, env, usage string) {
   447  	if env != "" {
   448  		usage = fmt.Sprintf("%s [$%s]", usage, env)
   449  	}
   450  	if envV, ok := os.LookupEnv(env); ok {
   451  		value = envV
   452  	}
   453  	aliasesUsage := fmt.Sprintf("Alias of --%s", name)
   454  	p := new(string)
   455  
   456  	// flags is full set of flag(s)
   457  	// flags can redefine alias already used in subcommands
   458  	flags := cmd.Flags()
   459  	for _, a := range aliases {
   460  		if len(a) == 1 {
   461  			// pflag doesn't support short-only flags, so we have to register long one as well here
   462  			flags.StringVarP(p, a, a, value, aliasesUsage)
   463  		} else {
   464  			flags.StringVar(p, a, value, aliasesUsage)
   465  		}
   466  		// non-persistent flags are not added to the InheritedFlags, so we should add them manually
   467  		f := flags.Lookup(a)
   468  		aliasToBeInherited.AddFlag(f)
   469  	}
   470  
   471  	// localFlags are local to the rootCmd
   472  	localFlags := cmd.LocalFlags()
   473  	for _, a := range localAliases {
   474  		if len(a) == 1 {
   475  			// pflag doesn't support short-only flags, so we have to register long one as well here
   476  			localFlags.StringVarP(p, a, a, value, aliasesUsage)
   477  		} else {
   478  			localFlags.StringVar(p, a, value, aliasesUsage)
   479  		}
   480  	}
   481  
   482  	// persistentFlags cannot redefine alias already used in subcommands
   483  	persistentFlags := cmd.PersistentFlags()
   484  	persistentFlags.StringVar(p, name, value, usage)
   485  	for _, a := range persistentAliases {
   486  		if len(a) == 1 {
   487  			// pflag doesn't support short-only flags, so we have to register long one as well here
   488  			persistentFlags.StringVarP(p, a, a, value, aliasesUsage)
   489  		} else {
   490  			persistentFlags.StringVar(p, a, value, aliasesUsage)
   491  		}
   492  	}
   493  }
   494  
   495  // AddPersistentBoolFlag is similar to AddBoolFlag but persistent.
   496  // See https://github.com/spf13/cobra/blob/main/user_guide.md#persistent-flags to learn what is "persistent".
   497  func AddPersistentBoolFlag(cmd *cobra.Command, name string, aliases, nonPersistentAliases []string, value bool, env, usage string) {
   498  	if env != "" {
   499  		usage = fmt.Sprintf("%s [$%s]", usage, env)
   500  	}
   501  	if envV, ok := os.LookupEnv(env); ok {
   502  		var err error
   503  		value, err = strconv.ParseBool(envV)
   504  		if err != nil {
   505  			log.L.WithError(err).Warnf("Invalid boolean value for `%s`", env)
   506  		}
   507  	}
   508  	aliasesUsage := fmt.Sprintf("Alias of --%s", name)
   509  	p := new(bool)
   510  	flags := cmd.Flags()
   511  	for _, a := range nonPersistentAliases {
   512  		if len(a) == 1 {
   513  			// pflag doesn't support short-only flags, so we have to register long one as well here
   514  			flags.BoolVarP(p, a, a, value, aliasesUsage)
   515  		} else {
   516  			flags.BoolVar(p, a, value, aliasesUsage)
   517  		}
   518  	}
   519  
   520  	persistentFlags := cmd.PersistentFlags()
   521  	persistentFlags.BoolVar(p, name, value, usage)
   522  	for _, a := range aliases {
   523  		if len(a) == 1 {
   524  			// pflag doesn't support short-only flags, so we have to register long one as well here
   525  			persistentFlags.BoolVarP(p, a, a, value, aliasesUsage)
   526  		} else {
   527  			persistentFlags.BoolVar(p, a, value, aliasesUsage)
   528  		}
   529  	}
   530  }
   531  
   532  // AddPersistentStringArrayFlag is similar to cmd.Flags().StringArray but supports aliases and env var and persistent.
   533  // See https://github.com/spf13/cobra/blob/main/user_guide.md#persistent-flags to learn what is "persistent".
   534  func AddPersistentStringArrayFlag(cmd *cobra.Command, name string, aliases, nonPersistentAliases []string, value []string, env string, usage string) {
   535  	if env != "" {
   536  		usage = fmt.Sprintf("%s [$%s]", usage, env)
   537  	}
   538  	if envV, ok := os.LookupEnv(env); ok {
   539  		value = []string{envV}
   540  	}
   541  	aliasesUsage := fmt.Sprintf("Alias of --%s", name)
   542  	p := new([]string)
   543  	flags := cmd.Flags()
   544  	for _, a := range nonPersistentAliases {
   545  		if len(a) == 1 {
   546  			// pflag doesn't support short-only flags, so we have to register long one as well here
   547  			flags.StringArrayVarP(p, a, a, value, aliasesUsage)
   548  		} else {
   549  			flags.StringArrayVar(p, a, value, aliasesUsage)
   550  		}
   551  	}
   552  
   553  	persistentFlags := cmd.PersistentFlags()
   554  	persistentFlags.StringArrayVar(p, name, value, usage)
   555  	for _, a := range aliases {
   556  		if len(a) == 1 {
   557  			// pflag doesn't support short-only flags, so we have to register long one as well here
   558  			persistentFlags.StringArrayVarP(p, a, a, value, aliasesUsage)
   559  		} else {
   560  			persistentFlags.StringArrayVar(p, a, value, aliasesUsage)
   561  		}
   562  	}
   563  }
   564  
   565  func checkExperimental(feature string) func(cmd *cobra.Command, args []string) error {
   566  	return func(cmd *cobra.Command, args []string) error {
   567  		globalOptions, err := processRootCmdFlags(cmd)
   568  		if err != nil {
   569  			return err
   570  		}
   571  		if !globalOptions.Experimental {
   572  			return fmt.Errorf("%s is experimental feature, you should enable experimental config", feature)
   573  		}
   574  		return nil
   575  	}
   576  }
   577  
   578  // IsExactArgs returns an error if there is not the exact number of args
   579  func IsExactArgs(number int) cobra.PositionalArgs {
   580  	return func(cmd *cobra.Command, args []string) error {
   581  		if len(args) == number {
   582  			return nil
   583  		}
   584  		return fmt.Errorf(
   585  			"%q requires exactly %d %s.\nSee '%s --help'.\n\nUsage:  %s\n\n%s",
   586  			cmd.CommandPath(),
   587  			number,
   588  			"argument(s)",
   589  			cmd.CommandPath(),
   590  			cmd.UseLine(),
   591  			cmd.Short,
   592  		)
   593  	}
   594  }