istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/cmd/root.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	"github.com/spf13/cobra"
    24  	"github.com/spf13/viper"
    25  
    26  	"istio.io/istio/istioctl/pkg/admin"
    27  	"istio.io/istio/istioctl/pkg/analyze"
    28  	"istio.io/istio/istioctl/pkg/authz"
    29  	"istio.io/istio/istioctl/pkg/checkinject"
    30  	"istio.io/istio/istioctl/pkg/cli"
    31  	"istio.io/istio/istioctl/pkg/completion"
    32  	"istio.io/istio/istioctl/pkg/config"
    33  	"istio.io/istio/istioctl/pkg/dashboard"
    34  	"istio.io/istio/istioctl/pkg/describe"
    35  	"istio.io/istio/istioctl/pkg/injector"
    36  	"istio.io/istio/istioctl/pkg/internaldebug"
    37  	"istio.io/istio/istioctl/pkg/kubeinject"
    38  	"istio.io/istio/istioctl/pkg/metrics"
    39  	"istio.io/istio/istioctl/pkg/multicluster"
    40  	"istio.io/istio/istioctl/pkg/precheck"
    41  	"istio.io/istio/istioctl/pkg/proxyconfig"
    42  	"istio.io/istio/istioctl/pkg/proxystatus"
    43  	"istio.io/istio/istioctl/pkg/root"
    44  	"istio.io/istio/istioctl/pkg/tag"
    45  	"istio.io/istio/istioctl/pkg/util"
    46  	"istio.io/istio/istioctl/pkg/validate"
    47  	"istio.io/istio/istioctl/pkg/version"
    48  	"istio.io/istio/istioctl/pkg/wait"
    49  	"istio.io/istio/istioctl/pkg/waypoint"
    50  	"istio.io/istio/istioctl/pkg/workload"
    51  	"istio.io/istio/istioctl/pkg/ztunnelconfig"
    52  	"istio.io/istio/operator/cmd/mesh"
    53  	"istio.io/istio/pkg/cmd"
    54  	"istio.io/istio/pkg/collateral"
    55  	"istio.io/istio/pkg/config/constants"
    56  	"istio.io/istio/pkg/log"
    57  	"istio.io/istio/tools/bug-report/pkg/bugreport"
    58  )
    59  
    60  const (
    61  	// Location to read istioctl defaults from
    62  	defaultIstioctlConfig = "$HOME/.istioctl/config.yaml"
    63  )
    64  
    65  const (
    66  	FlagCharts = "charts"
    67  )
    68  
    69  // ConfigAndEnvProcessing uses spf13/viper for overriding CLI parameters
    70  func ConfigAndEnvProcessing() error {
    71  	configPath := filepath.Dir(root.IstioConfig)
    72  	baseName := filepath.Base(root.IstioConfig)
    73  	configType := filepath.Ext(root.IstioConfig)
    74  	configName := baseName[0 : len(baseName)-len(configType)]
    75  	if configType != "" {
    76  		configType = configType[1:]
    77  	}
    78  
    79  	// Allow users to override some variables through $HOME/.istioctl/config.yaml
    80  	// and environment variables.
    81  	viper.SetEnvPrefix("ISTIOCTL")
    82  	viper.AutomaticEnv()
    83  	viper.AllowEmptyEnv(true) // So we can say ISTIOCTL_CERT_DIR="" to suppress certs
    84  	viper.SetConfigName(configName)
    85  	viper.SetConfigType(configType)
    86  	viper.AddConfigPath(configPath)
    87  	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
    88  	err := viper.ReadInConfig()
    89  	// Ignore errors reading the configuration unless the file is explicitly customized
    90  	if root.IstioConfig != defaultIstioctlConfig {
    91  		return err
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  func init() {
    98  	viper.SetDefault("istioNamespace", constants.IstioSystemNamespace)
    99  	viper.SetDefault("xds-port", 15012)
   100  }
   101  
   102  // GetRootCmd returns the root of the cobra command-tree.
   103  func GetRootCmd(args []string) *cobra.Command {
   104  	rootCmd := &cobra.Command{
   105  		Use:               "istioctl",
   106  		Short:             "Istio control interface.",
   107  		SilenceUsage:      true,
   108  		DisableAutoGenTag: true,
   109  		PersistentPreRunE: ConfigureLogging,
   110  		Long: `Istio configuration command line utility for service operators to
   111  debug and diagnose their Istio mesh.
   112  `,
   113  	}
   114  
   115  	rootCmd.SetArgs(args)
   116  
   117  	flags := rootCmd.PersistentFlags()
   118  	rootOptions := cli.AddRootFlags(flags)
   119  
   120  	ctx := cli.NewCLIContext(rootOptions)
   121  
   122  	_ = rootCmd.RegisterFlagCompletionFunc(cli.FlagIstioNamespace, func(
   123  		cmd *cobra.Command, args []string, toComplete string,
   124  	) ([]string, cobra.ShellCompDirective) {
   125  		return completion.ValidNamespaceArgs(cmd, ctx, args, toComplete)
   126  	})
   127  	_ = rootCmd.RegisterFlagCompletionFunc(cli.FlagNamespace, func(
   128  		cmd *cobra.Command, args []string, toComplete string,
   129  	) ([]string, cobra.ShellCompDirective) {
   130  		return completion.ValidNamespaceArgs(cmd, ctx, args, toComplete)
   131  	})
   132  
   133  	// Attach the Istio logging options to the command.
   134  	root.LoggingOptions.AttachCobraFlags(rootCmd)
   135  	hiddenFlags := []string{
   136  		"log_as_json", "log_rotate", "log_rotate_max_age", "log_rotate_max_backups",
   137  		"log_rotate_max_size", "log_stacktrace_level", "log_target", "log_caller", "log_output_level",
   138  	}
   139  	for _, opt := range hiddenFlags {
   140  		_ = rootCmd.PersistentFlags().MarkHidden(opt)
   141  	}
   142  
   143  	cmd.AddFlags(rootCmd)
   144  
   145  	kubeInjectCmd := kubeinject.InjectCommand(ctx)
   146  	hideInheritedFlags(kubeInjectCmd, cli.FlagNamespace)
   147  	rootCmd.AddCommand(kubeInjectCmd)
   148  
   149  	experimentalCmd := &cobra.Command{
   150  		Use:     "experimental",
   151  		Aliases: []string{"x", "exp"},
   152  		Short:   "Experimental commands that may be modified or deprecated",
   153  	}
   154  
   155  	xdsBasedTroubleshooting := []*cobra.Command{
   156  		// TODO(hanxiaop): I think experimental version still has issues, so we keep the old version for now.
   157  		version.XdsVersionCommand(ctx),
   158  		// TODO(hanxiaop): this is kept for some releases in case someone is using it.
   159  		proxystatus.XdsStatusCommand(ctx),
   160  	}
   161  	troubleshootingCommands := []*cobra.Command{
   162  		version.NewVersionCommand(ctx),
   163  		proxystatus.StableXdsStatusCommand(ctx),
   164  	}
   165  	var debugCmdAttachmentPoint *cobra.Command
   166  	if viper.GetBool("PREFER-EXPERIMENTAL") {
   167  		legacyCmd := &cobra.Command{
   168  			Use:   "legacy",
   169  			Short: "Legacy command variants",
   170  		}
   171  		rootCmd.AddCommand(legacyCmd)
   172  		for _, c := range xdsBasedTroubleshooting {
   173  			rootCmd.AddCommand(c)
   174  		}
   175  		debugCmdAttachmentPoint = legacyCmd
   176  	} else {
   177  		debugCmdAttachmentPoint = rootCmd
   178  	}
   179  	for _, c := range xdsBasedTroubleshooting {
   180  		experimentalCmd.AddCommand(c)
   181  	}
   182  	for _, c := range troubleshootingCommands {
   183  		debugCmdAttachmentPoint.AddCommand(c)
   184  	}
   185  
   186  	rootCmd.AddCommand(experimentalCmd)
   187  	rootCmd.AddCommand(proxyconfig.ProxyConfig(ctx))
   188  	rootCmd.AddCommand(admin.Cmd(ctx))
   189  	experimentalCmd.AddCommand(injector.Cmd(ctx))
   190  
   191  	rootCmd.AddCommand(mesh.NewVerifyCommand(ctx))
   192  	rootCmd.AddCommand(mesh.UninstallCmd(ctx))
   193  
   194  	experimentalCmd.AddCommand(authz.AuthZ(ctx))
   195  	rootCmd.AddCommand(seeExperimentalCmd("authz"))
   196  	experimentalCmd.AddCommand(metrics.Cmd(ctx))
   197  	experimentalCmd.AddCommand(describe.Cmd(ctx))
   198  	experimentalCmd.AddCommand(wait.Cmd(ctx))
   199  	experimentalCmd.AddCommand(config.Cmd())
   200  	experimentalCmd.AddCommand(workload.Cmd(ctx))
   201  	experimentalCmd.AddCommand(internaldebug.DebugCommand(ctx))
   202  	experimentalCmd.AddCommand(precheck.Cmd(ctx))
   203  	experimentalCmd.AddCommand(proxyconfig.StatsConfigCmd(ctx))
   204  	experimentalCmd.AddCommand(checkinject.Cmd(ctx))
   205  	experimentalCmd.AddCommand(waypoint.Cmd(ctx))
   206  	experimentalCmd.AddCommand(ztunnelconfig.ZtunnelConfig(ctx))
   207  
   208  	analyzeCmd := analyze.Analyze(ctx)
   209  	hideInheritedFlags(analyzeCmd, cli.FlagIstioNamespace)
   210  	rootCmd.AddCommand(analyzeCmd)
   211  
   212  	dashboardCmd := dashboard.Dashboard(ctx)
   213  	hideInheritedFlags(dashboardCmd, cli.FlagNamespace, cli.FlagIstioNamespace)
   214  	rootCmd.AddCommand(dashboardCmd)
   215  
   216  	manifestCmd := mesh.ManifestCmd(ctx)
   217  	hideInheritedFlags(manifestCmd, cli.FlagNamespace, cli.FlagIstioNamespace, FlagCharts)
   218  	rootCmd.AddCommand(manifestCmd)
   219  
   220  	operatorCmd := mesh.OperatorCmd(ctx)
   221  	hideInheritedFlags(operatorCmd, cli.FlagNamespace, cli.FlagIstioNamespace, FlagCharts)
   222  	rootCmd.AddCommand(operatorCmd)
   223  
   224  	installCmd := mesh.InstallCmd(ctx)
   225  	hideInheritedFlags(installCmd, cli.FlagNamespace, cli.FlagIstioNamespace, FlagCharts)
   226  	rootCmd.AddCommand(installCmd)
   227  
   228  	profileCmd := mesh.ProfileCmd(ctx)
   229  	hideInheritedFlags(profileCmd, cli.FlagNamespace, cli.FlagIstioNamespace, FlagCharts)
   230  	rootCmd.AddCommand(profileCmd)
   231  
   232  	upgradeCmd := mesh.UpgradeCmd(ctx)
   233  	hideInheritedFlags(upgradeCmd, cli.FlagNamespace, cli.FlagIstioNamespace, FlagCharts)
   234  	rootCmd.AddCommand(upgradeCmd)
   235  
   236  	bugReportCmd := bugreport.Cmd(ctx, root.LoggingOptions)
   237  	hideInheritedFlags(bugReportCmd, cli.FlagNamespace, cli.FlagIstioNamespace)
   238  	rootCmd.AddCommand(bugReportCmd)
   239  
   240  	tagCmd := tag.TagCommand(ctx)
   241  	hideInheritedFlags(tag.TagCommand(ctx), cli.FlagNamespace, cli.FlagIstioNamespace, FlagCharts)
   242  	rootCmd.AddCommand(tagCmd)
   243  
   244  	// leave the multicluster commands in x for backwards compat
   245  	rootCmd.AddCommand(multicluster.NewCreateRemoteSecretCommand(ctx))
   246  	rootCmd.AddCommand(proxyconfig.ClustersCommand(ctx))
   247  
   248  	rootCmd.AddCommand(collateral.CobraCommand(rootCmd, collateral.Metadata{
   249  		Title:   "Istio Control",
   250  		Section: "istioctl CLI",
   251  		Manual:  "Istio Control",
   252  	}))
   253  
   254  	validateCmd := validate.NewValidateCommand(ctx)
   255  	hideInheritedFlags(validateCmd, "kubeconfig")
   256  	rootCmd.AddCommand(validateCmd)
   257  
   258  	rootCmd.AddCommand(optionsCommand(rootCmd))
   259  
   260  	// BFS applies the flag error function to all subcommands
   261  	seenCommands := make(map[*cobra.Command]bool)
   262  	var commandStack []*cobra.Command
   263  
   264  	commandStack = append(commandStack, rootCmd)
   265  
   266  	for len(commandStack) > 0 {
   267  		n := len(commandStack) - 1
   268  		curCmd := commandStack[n]
   269  		commandStack = commandStack[:n]
   270  		seenCommands[curCmd] = true
   271  		for _, command := range curCmd.Commands() {
   272  			if !seenCommands[command] {
   273  				commandStack = append(commandStack, command)
   274  			}
   275  		}
   276  		curCmd.SetFlagErrorFunc(func(_ *cobra.Command, e error) error {
   277  			return util.CommandParseError{Err: e}
   278  		})
   279  	}
   280  
   281  	return rootCmd
   282  }
   283  
   284  func hideInheritedFlags(orig *cobra.Command, hidden ...string) {
   285  	orig.SetHelpFunc(func(cmd *cobra.Command, args []string) {
   286  		for _, hidden := range hidden {
   287  			_ = cmd.Flags().MarkHidden(hidden) // nolint: errcheck
   288  		}
   289  
   290  		orig.SetHelpFunc(nil)
   291  		orig.HelpFunc()(cmd, args)
   292  	})
   293  }
   294  
   295  func ConfigureLogging(_ *cobra.Command, _ []string) error {
   296  	return log.Configure(root.LoggingOptions)
   297  }
   298  
   299  // seeExperimentalCmd is used for commands that have been around for a release but not graduated from
   300  // Other alternative
   301  // for graduatedCmd see https://github.com/istio/istio/pull/26408
   302  // for softGraduatedCmd see https://github.com/istio/istio/pull/26563
   303  func seeExperimentalCmd(name string) *cobra.Command {
   304  	msg := fmt.Sprintf("(%s is experimental. Use `istioctl experimental %s`)", name, name)
   305  	return &cobra.Command{
   306  		Use:   name,
   307  		Short: msg,
   308  		RunE: func(_ *cobra.Command, _ []string) error {
   309  			return errors.New(msg)
   310  		},
   311  	}
   312  }