github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cli.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package cmd 21 22 import ( 23 "fmt" 24 "os" 25 "strings" 26 27 "github.com/spf13/cobra" 28 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 29 "k8s.io/cli-runtime/pkg/genericiooptions" 30 cliflag "k8s.io/component-base/cli/flag" 31 "k8s.io/klog/v2" 32 kccmd "k8s.io/kubectl/pkg/cmd" 33 kcplugin "k8s.io/kubectl/pkg/cmd/plugin" 34 cmdutil "k8s.io/kubectl/pkg/cmd/util" 35 utilcomp "k8s.io/kubectl/pkg/util/completion" 36 "k8s.io/kubectl/pkg/util/templates" 37 38 "github.com/1aal/kubeblocks/pkg/cli/cmd/addon" 39 "github.com/1aal/kubeblocks/pkg/cli/cmd/alert" 40 "github.com/1aal/kubeblocks/pkg/cli/cmd/auth" 41 "github.com/1aal/kubeblocks/pkg/cli/cmd/backuprepo" 42 "github.com/1aal/kubeblocks/pkg/cli/cmd/bench" 43 "github.com/1aal/kubeblocks/pkg/cli/cmd/builder" 44 "github.com/1aal/kubeblocks/pkg/cli/cmd/class" 45 "github.com/1aal/kubeblocks/pkg/cli/cmd/cluster" 46 "github.com/1aal/kubeblocks/pkg/cli/cmd/clusterdefinition" 47 "github.com/1aal/kubeblocks/pkg/cli/cmd/clusterversion" 48 "github.com/1aal/kubeblocks/pkg/cli/cmd/dashboard" 49 "github.com/1aal/kubeblocks/pkg/cli/cmd/dataprotection" 50 "github.com/1aal/kubeblocks/pkg/cli/cmd/fault" 51 infras "github.com/1aal/kubeblocks/pkg/cli/cmd/infrastructure" 52 "github.com/1aal/kubeblocks/pkg/cli/cmd/kubeblocks" 53 "github.com/1aal/kubeblocks/pkg/cli/cmd/migration" 54 "github.com/1aal/kubeblocks/pkg/cli/cmd/options" 55 "github.com/1aal/kubeblocks/pkg/cli/cmd/playground" 56 "github.com/1aal/kubeblocks/pkg/cli/cmd/plugin" 57 "github.com/1aal/kubeblocks/pkg/cli/cmd/report" 58 "github.com/1aal/kubeblocks/pkg/cli/cmd/version" 59 "github.com/1aal/kubeblocks/pkg/cli/types" 60 "github.com/1aal/kubeblocks/pkg/cli/util" 61 viper "github.com/1aal/kubeblocks/pkg/viperx" 62 ) 63 64 const ( 65 cliName = "kbcli" 66 ) 67 68 // TODO: add more commands 69 var cloudCmds = map[string]bool{ 70 "org": true, 71 "logout": true, 72 "context": true, 73 } 74 75 func init() { 76 if _, err := util.GetCliHomeDir(); err != nil { 77 fmt.Println("Failed to create kbcli home dir:", err) 78 } 79 80 // replace the kubectl plugin filename prefixes with ours 81 kcplugin.ValidPluginFilenamePrefixes = plugin.ValidPluginFilenamePrefixes 82 83 // put the download directory of the plugin into the PATH 84 if err := util.AddDirToPath(fmt.Sprintf("%s/.%s/plugins/bin", os.Getenv("HOME"), cliName)); err != nil { 85 fmt.Println("Failed to add kbcli bin dir to PATH:", err) 86 } 87 88 // when the kubernetes cluster is not ready, the runtime will output the error 89 // message like "couldn't get resource list for", we ignore it 90 utilruntime.ErrorHandlers[0] = func(err error) { 91 if klog.V(2).Enabled() { 92 klog.ErrorDepth(2, err) 93 } 94 } 95 } 96 97 func NewDefaultCliCmd() *cobra.Command { 98 cmd := NewCliCmd() 99 100 pluginHandler := kccmd.NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes) 101 102 if len(os.Args) > 1 { 103 cmdPathPieces := os.Args[1:] 104 105 // only look for suitable extension executables if 106 // the specified command does not exist 107 if _, _, err := cmd.Find(cmdPathPieces); err != nil { 108 var cmdName string 109 for _, arg := range cmdPathPieces { 110 if !strings.HasPrefix(arg, "-") { 111 cmdName = arg 112 break 113 } 114 } 115 116 switch cmdName { 117 case "help", cobra.ShellCompRequestCmd, cobra.ShellCompNoDescRequestCmd: 118 // Don't search for a plugin 119 default: 120 if err := kccmd.HandlePluginCommand(pluginHandler, cmdPathPieces, true); err != nil { 121 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 122 os.Exit(1) 123 } 124 } 125 } 126 } 127 128 return cmd 129 } 130 131 func NewCliCmd() *cobra.Command { 132 cmd := &cobra.Command{ 133 Use: cliName, 134 Short: "KubeBlocks CLI.", 135 Long: ` 136 ============================================= 137 __ __ _______ ______ __ ______ 138 | \ / \ \ / \| \ | \ 139 | ▓▓ / ▓▓ ▓▓▓▓▓▓▓\ ▓▓▓▓▓▓\ ▓▓ \▓▓▓▓▓▓ 140 | ▓▓/ ▓▓| ▓▓__/ ▓▓ ▓▓ \▓▓ ▓▓ | ▓▓ 141 | ▓▓ ▓▓ | ▓▓ ▓▓ ▓▓ | ▓▓ | ▓▓ 142 | ▓▓▓▓▓\ | ▓▓▓▓▓▓▓\ ▓▓ __| ▓▓ | ▓▓ 143 | ▓▓ \▓▓\| ▓▓__/ ▓▓ ▓▓__/ \ ▓▓_____ _| ▓▓_ 144 | ▓▓ \▓▓\ ▓▓ ▓▓\▓▓ ▓▓ ▓▓ \ ▓▓ \ 145 \▓▓ \▓▓\▓▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓▓▓▓▓▓▓\▓▓▓▓▓▓ 146 147 ============================================= 148 A Command Line Interface for KubeBlocks`, 149 150 Run: func(cmd *cobra.Command, args []string) { 151 _ = cmd.Help() 152 }, 153 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 154 if cmd.Name() == cobra.ShellCompRequestCmd { 155 kcplugin.SetupPluginCompletion(cmd, args) 156 } 157 158 commandPath := cmd.CommandPath() 159 parts := strings.Split(commandPath, " ") 160 if len(parts) < 2 { 161 return nil 162 } 163 subCommand := parts[1] 164 if cloudCmds[subCommand] && !auth.IsLoggedIn() { 165 return fmt.Errorf("use 'kbcli login' to login first") 166 } 167 return nil 168 }, 169 } 170 171 // Start from this point we get warnings on flags that contain "_" separators 172 // when adding them with hyphen instead of the original name. 173 cmd.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc) 174 175 flags := cmd.PersistentFlags() 176 177 // add kubernetes flags like kubectl 178 kubeConfigFlags := util.NewConfigFlagNoWarnings() 179 kubeConfigFlags.AddFlags(flags) 180 matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags) 181 matchVersionKubeConfigFlags.AddFlags(flags) 182 183 // add klog flags 184 util.AddKlogFlags(flags) 185 186 f := cmdutil.NewFactory(matchVersionKubeConfigFlags) 187 ioStreams := genericiooptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} 188 189 // Add subcommands 190 cmd.AddCommand( 191 auth.NewLogin(ioStreams), 192 auth.NewLogout(ioStreams), 193 // organization.NewOrganizationCmd(ioStreams), 194 // context.NewContextCmd(ioStreams), 195 playground.NewPlaygroundCmd(ioStreams), 196 kubeblocks.NewKubeBlocksCmd(f, ioStreams), 197 bench.NewBenchCmd(f, ioStreams), 198 options.NewCmdOptions(ioStreams.Out), 199 version.NewVersionCmd(f), 200 dashboard.NewDashboardCmd(f, ioStreams), 201 clusterversion.NewClusterVersionCmd(f, ioStreams), 202 clusterdefinition.NewClusterDefinitionCmd(f, ioStreams), 203 class.NewClassCommand(f, ioStreams), 204 alert.NewAlertCmd(f, ioStreams), 205 addon.NewAddonCmd(f, ioStreams), 206 migration.NewMigrationCmd(f, ioStreams), 207 plugin.NewPluginCmd(ioStreams), 208 fault.NewFaultCmd(f, ioStreams), 209 builder.NewBuilderCmd(f, ioStreams), 210 report.NewReportCmd(f, ioStreams), 211 infras.NewInfraCmd(ioStreams), 212 backuprepo.NewBackupRepoCmd(f, ioStreams), 213 dataprotection.NewDataProtectionCmd(f, ioStreams), 214 ) 215 216 filters := []string{"options"} 217 templates.ActsAsRootCommand(cmd, filters, []templates.CommandGroup{}...) 218 219 helpFunc := cmd.HelpFunc() 220 usageFunc := cmd.UsageFunc() 221 222 // clusterCmd sets its own usage and help function and its subcommand will inherit it, 223 // so we need to set its subcommand's usage and help function back to the root command 224 clusterCmd := cluster.NewClusterCmd(f, ioStreams) 225 registerUsageAndHelpFuncForSubCommand(clusterCmd, helpFunc, usageFunc) 226 cmd.AddCommand(clusterCmd) 227 228 utilcomp.SetFactoryForCompletion(f) 229 registerCompletionFuncForGlobalFlags(cmd, f) 230 231 cobra.OnInitialize(initConfig) 232 return cmd 233 } 234 235 // initConfig reads in config file and ENV variables if set. 236 func initConfig() { 237 viper.SetConfigName("config") 238 viper.SetConfigType("yaml") 239 viper.AddConfigPath(fmt.Sprintf("/etc/%s/", cliName)) 240 viper.AddConfigPath(fmt.Sprintf("$HOME/.%s/", cliName)) 241 viper.AutomaticEnv() // read in environment variables that match 242 viper.SetEnvPrefix(cliName) 243 244 viper.SetDefault(types.CfgKeyClusterDefaultStorageSize, "20Gi") 245 viper.SetDefault(types.CfgKeyClusterDefaultReplicas, 1) 246 viper.SetDefault(types.CfgKeyClusterDefaultCPU, "1000m") 247 viper.SetDefault(types.CfgKeyClusterDefaultMemory, "1Gi") 248 249 viper.SetDefault(types.CfgKeyHelmRepoURL, "") 250 251 // If a config file is found, read it in. 252 if err := viper.ReadInConfig(); err == nil { 253 fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) 254 } 255 } 256 257 func registerCompletionFuncForGlobalFlags(cmd *cobra.Command, f cmdutil.Factory) { 258 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( 259 "namespace", 260 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 261 return utilcomp.CompGetResource(f, "namespace", toComplete), cobra.ShellCompDirectiveNoFileComp 262 })) 263 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( 264 "context", 265 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 266 return utilcomp.ListContextsInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp 267 })) 268 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( 269 "cluster", 270 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 271 return utilcomp.ListClustersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp 272 })) 273 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( 274 "user", 275 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 276 return utilcomp.ListUsersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp 277 })) 278 } 279 280 func registerUsageAndHelpFuncForSubCommand(cmd *cobra.Command, helpFunc func(*cobra.Command, []string), usageFunc func(command *cobra.Command) error) { 281 for _, subCmd := range cmd.Commands() { 282 subCmd.SetHelpFunc(helpFunc) 283 subCmd.SetUsageFunc(usageFunc) 284 } 285 }