github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/cli/cmd/usage.go (about)

     1  package cmd
     2  
     3  import (
     4  	"os"
     5  	"strconv"
     6  
     7  	"github.com/moby/term"
     8  	"github.com/spf13/cobra"
     9  	"github.com/spf13/pflag"
    10  	"k8s.io/cli-runtime/pkg/genericclioptions"
    11  
    12  	"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global"
    13  )
    14  
    15  var CLIHelpDocumentationURL = "https://www.telepresence.io" //nolint:gochecknoglobals // extension point
    16  
    17  const (
    18  	help = `Telepresence can connect to a cluster and route all outbound traffic from your
    19  workstation to that cluster so that software running locally can communicate
    20  as if it executed remotely, inside the cluster. This is achieved using the
    21  command:
    22  
    23  telepresence connect
    24  
    25  Telepresence can also intercept traffic intended for a specific service in a
    26  cluster and redirect it to your local workstation:
    27  
    28  telepresence intercept <name of service>
    29  
    30  Telepresence uses background processes to manage the cluster session. One of
    31  the processes runs with superuser privileges because it modifies the network.
    32  Unless the daemons are already started, an attempt will be made to start them.
    33  This will involve a call to sudo unless this command is run as root (not
    34  recommended) which in turn may result in a password prompt.`
    35  
    36  	usage = `Usage:{{if .Runnable}}
    37    {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
    38    {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
    39  
    40  Aliases:
    41    {{.NameAndAliases}}{{end}}{{if .HasExample}}
    42  
    43  Examples:
    44  {{.Example}}{{end}}{{if .HasAvailableSubCommands}}
    45  
    46  Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
    47    {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
    48  
    49  Flags:
    50  {{flags . | wrappedFlagUsages | trimTrailingWhitespaces}}{{end}}
    51  {{- if hasKubeFlags .}}
    52  
    53  Kubernetes flags:
    54  {{kubeFlags | wrappedFlagUsages | trimTrailingWhitespaces}}{{end}}
    55  
    56  Global flags:
    57  {{globalFlags . | wrappedFlagUsages | trimTrailingWhitespaces}}{{if .HasHelpSubCommands}}
    58  
    59  Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
    60    {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
    61  
    62  Use "{{.CommandPath}} [command] --help" for more information about a command.
    63  
    64  For complete documentation and quick-start guides, check out our website at {{ getDocumentationURL }}{{end}}
    65  `
    66  )
    67  
    68  func flagEqual(a, b *pflag.Flag) bool {
    69  	if a == b {
    70  		return true
    71  	}
    72  	if a == nil || b == nil {
    73  		return false
    74  	}
    75  	return a.Name == b.Name && a.Usage == b.Usage && a.Hidden == b.Hidden
    76  }
    77  
    78  func localFlags(cmd *cobra.Command, exclude ...*pflag.FlagSet) *pflag.FlagSet {
    79  	ngFlags := pflag.NewFlagSet("local", pflag.ContinueOnError)
    80  	cmd.Flags().VisitAll(func(flag *pflag.Flag) {
    81  		for _, ex := range exclude {
    82  			if flagEqual(flag, ex.Lookup(flag.Name)) {
    83  				return
    84  			}
    85  		}
    86  		ngFlags.AddFlag(flag)
    87  	})
    88  	return ngFlags
    89  }
    90  
    91  func kubeFlags() *pflag.FlagSet {
    92  	pflag.NewFlagSet("Kubernetes flags", 0)
    93  	kubeConfig := genericclioptions.NewConfigFlags(false)
    94  	kubeConfig.Namespace = nil // "connect", don't take --namespace
    95  	flags := pflag.NewFlagSet("Kubernetes flags", 0)
    96  	kubeConfig.AddFlags(flags)
    97  	return flags
    98  }
    99  
   100  func hasKubeFlags(cmd *cobra.Command) bool {
   101  	yep := true
   102  	flags := cmd.Flags()
   103  	kubeFlags().VisitAll(func(flag *pflag.Flag) {
   104  		if yep && !flagEqual(flag, flags.Lookup(flag.Name)) {
   105  			yep = false
   106  		}
   107  	})
   108  	return yep
   109  }
   110  
   111  func addUsageTemplate(cmd *cobra.Command) {
   112  	cobra.AddTemplateFunc("globalFlags", func(cmd *cobra.Command) *pflag.FlagSet { return global.Flags(hasKubeFlags(cmd)) })
   113  	cobra.AddTemplateFunc("flags", func(cmd *cobra.Command) *pflag.FlagSet {
   114  		return localFlags(cmd, kubeFlags(), global.Flags(hasKubeFlags(cmd)))
   115  	})
   116  	cobra.AddTemplateFunc("hasKubeFlags", hasKubeFlags)
   117  	cobra.AddTemplateFunc("kubeFlags", kubeFlags)
   118  	cobra.AddTemplateFunc("wrappedFlagUsages", func(flags *pflag.FlagSet) string {
   119  		// This is based off of what Docker does (github.com/docker/cli/cli/cobra.go), but is
   120  		// adjusted
   121  		//  1. to take a pflag.FlagSet instead of a cobra.interceptCmd, so that we can have flag groups, and
   122  		//  2. to correct for the ways that Docker upsets me.
   123  
   124  		// Obey COLUMNS if the shell or user sets it.  (Docker doesn't do this.)
   125  		cols, err := strconv.Atoi(os.Getenv("COLUMNS"))
   126  		if err != nil {
   127  			// Try to detect the size of the stdout file descriptor.  (Docker checks stdin, not stdout.)
   128  			if ws, err := term.GetWinsize(1); err != nil {
   129  				// If stdout is a terminal, but we were unable to get its size (I'm not sure how that can
   130  				// happen), then fall back to assuming 80.  If stdout isn't a terminal, then we leave cols
   131  				// as 0, meaning "don't wrap it".  (Docker wraps it even if stdout isn't a terminal.)
   132  				if term.IsTerminal(1) {
   133  					cols = 80
   134  				}
   135  			} else {
   136  				cols = int(ws.Width)
   137  			}
   138  		}
   139  		return flags.FlagUsagesWrapped(cols)
   140  	})
   141  	cobra.AddTemplateFunc("getDocumentationURL", func() string {
   142  		return CLIHelpDocumentationURL
   143  	})
   144  
   145  	// Set a usage template that is derived from the default but replaces the "Available Commands"
   146  	// section with the commandGroups() from the given command
   147  	cmd.SetUsageTemplate(usage)
   148  }