diff --git a/pkg/karmadactl/config/config.go b/pkg/karmadactl/config/config.go new file mode 100644 index 000000000000..672394f30231 --- /dev/null +++ b/pkg/karmadactl/config/config.go @@ -0,0 +1,52 @@ +/* +Copyright 2023 The Karmada Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "fmt" + "path" + + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/tools/clientcmd" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +// NewCmdConfig creates a command object for the "config" action, and adds all child commands to it. +func NewCmdConfig(parentCommand string, pathOptions *clientcmd.PathOptions, streams genericclioptions.IOStreams) *cobra.Command { + if len(pathOptions.ExplicitFileFlag) == 0 { + pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag + } + + cmd := &cobra.Command{ + Use: "config SUBCOMMAND", + DisableFlagsInUseLine: true, + Short: i18n.T("Modify kubeconfig files"), + Long: fmt.Sprintf(templates.LongDesc(i18n.T(` + Modify kubeconfig files using subcommands like "%[1]s config set current-context my-context". + The loading order follows these rules: + 1. If the --`)+pathOptions.ExplicitFileFlag+i18n.T(` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place. + 2. If $`)+pathOptions.EnvVar+i18n.T(` environment variable is set, then it is used as a list of paths (normal path delimiting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list. + 3. Otherwise, `)+path.Join("${HOME}", pathOptions.GlobalFileSubpath)+i18n.T(` is used and no merging takes place.`)), parentCommand), + Run: cmdutil.DefaultSubCommandRun(streams.ErrOut), + } + + // file paths are common to all sub commands + cmd.PersistentFlags().StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file") + + cmd.AddCommand(NewCmdConfigGetContexts(parentCommand, streams, pathOptions)) + return cmd +} diff --git a/pkg/karmadactl/config/config_printer.go b/pkg/karmadactl/config/config_printer.go new file mode 100644 index 000000000000..20c5aaa7e22b --- /dev/null +++ b/pkg/karmadactl/config/config_printer.go @@ -0,0 +1,80 @@ +/* +Copyright 2023 The Karmada Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "fmt" + "io" + "sort" + "strings" + + "k8s.io/cli-runtime/pkg/printers" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +var ( + getContextColumns = []string{"CURRENT", "NAME", "CLUSTER", "AUTHINFO", "NAMESPACE"} +) + +type ConfigCmdPrinter struct { + out io.Writer +} + +func NewConfigCmdPrinter(out io.Writer) *ConfigCmdPrinter { + return &ConfigCmdPrinter{out: out} +} + +func (p *ConfigCmdPrinter) printGetContexts(names []string, config *clientcmdapi.Config, showHeaders, nameOnly bool) error { + w := printers.GetNewTabWriter(p.out) + defer w.Flush() + + if showHeaders { + err := printContextHeaders(w, nameOnly) + if err != nil { + return err + } + } + + sort.Strings(names) + for _, name := range names { + err := printContext(name, config.Contexts[name], w, nameOnly, config.CurrentContext == name) + if err != nil { + return err + } + } + + return nil +} + +func printContextHeaders(out io.Writer, nameOnly bool) error { + columnNames := getContextColumns + if nameOnly { + columnNames = columnNames[:1] + } + _, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t")) + return err +} + +func printContext(name string, context *clientcmdapi.Context, w io.Writer, nameOnly, current bool) error { + if nameOnly { + _, err := fmt.Fprintf(w, "%s\n", name) + return err + } + prefix := " " + if current { + prefix = "*" + } + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", prefix, name, context.Cluster, context.AuthInfo, context.Namespace) + return err +} diff --git a/pkg/karmadactl/config/get_contexts.go b/pkg/karmadactl/config/get_contexts.go new file mode 100644 index 000000000000..884ddebb334b --- /dev/null +++ b/pkg/karmadactl/config/get_contexts.go @@ -0,0 +1,133 @@ +/* +Copyright 2023 The Karmada Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "fmt" + + "github.com/spf13/cobra" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/tools/clientcmd" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +// contains the assignable options from the args. +type getContextsOptions struct { + configAccess clientcmd.ConfigAccess + nameOnly bool + showHeaders bool + contextNames []string + + outputFormat string + noHeaders bool + print *ConfigCmdPrinter + + genericclioptions.IOStreams +} + +var ( + getContextsLong = templates.LongDesc(i18n.T(`Display one or many contexts from the kubeconfig file.`)) + + getContextsExample = templates.Examples(` + # List all the contexts in your kubeconfig file + %[1]s config get-contexts + + # Describe one context in your kubeconfig file + %[1]s config get-contexts my-context`) +) + +// NewCmdConfigGetContexts creates a command object for the "get-contexts" action, which +// retrieves one or more contexts from a kubeconfig. +func NewCmdConfigGetContexts(parentCommand string, streams genericclioptions.IOStreams, configAccess clientcmd.ConfigAccess) *cobra.Command { + options := &getContextsOptions{ + configAccess: configAccess, + IOStreams: streams, + } + + cmd := &cobra.Command{ + Use: "get-contexts [(-o|--output=)name)]", + DisableFlagsInUseLine: true, + Short: i18n.T("Describe one or many contexts"), + Long: getContextsLong, + Example: fmt.Sprintf(getContextsExample, parentCommand), + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(options.complete(cmd, args)) + cmdutil.CheckErr(options.runGetContexts()) + }, + } + + cmd.Flags().BoolVar(&options.noHeaders, "no-headers", options.noHeaders, "When using the default or custom-column output format, don't print headers (default print headers).") + cmd.Flags().StringVarP(&options.outputFormat, "output", "o", options.outputFormat, `Output format. One of: (name).`) + return cmd +} + +// assigns getContextsOptions from the args. +func (o *getContextsOptions) complete(cmd *cobra.Command, args []string) error { + supportedOutputTypes := sets.NewString("", "name") + if !supportedOutputTypes.Has(o.outputFormat) { + return fmt.Errorf("--output %v is not available in kubectl config get-contexts; resetting to default output format", o.outputFormat) + } + + o.contextNames = args + o.nameOnly = false + if o.outputFormat == "name" { + o.nameOnly = true + } + o.showHeaders = true + if cmdutil.GetFlagBool(cmd, "no-headers") || o.nameOnly { + o.showHeaders = false + } + + o.print = NewConfigCmdPrinter(o.Out) + + return nil +} + +// implements all the necessary functionality for context retrieval. +func (o *getContextsOptions) runGetContexts() error { + config, err := o.configAccess.GetStartingConfig() + if err != nil { + return err + } + + // Build a list of context names to print, and warn if any requested contexts are not found. + // Do this before printing the headers, so it doesn't look ugly. + var allErrs []error + var toPrint []string + if len(o.contextNames) == 0 { + for name := range config.Contexts { + toPrint = append(toPrint, name) + } + } else { + for _, name := range o.contextNames { + _, ok := config.Contexts[name] + if ok { + toPrint = append(toPrint, name) + } else { + allErrs = append(allErrs, fmt.Errorf("context %s not found", name)) + } + } + } + + err = o.print.printGetContexts(toPrint, config, o.showHeaders, o.nameOnly) + if err != nil { + allErrs = append(allErrs, err) + } + + return utilerrors.NewAggregate(allErrs) +} diff --git a/pkg/karmadactl/karmadactl.go b/pkg/karmadactl/karmadactl.go index 653ad010f0b0..e1a57efd04e7 100644 --- a/pkg/karmadactl/karmadactl.go +++ b/pkg/karmadactl/karmadactl.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/tools/clientcmd" apiserverflag "k8s.io/component-base/cli/flag" "k8s.io/klog/v2" "k8s.io/kubectl/pkg/util/templates" @@ -31,6 +32,7 @@ import ( "github.com/karmada-io/karmada/pkg/karmadactl/addons" "github.com/karmada-io/karmada/pkg/karmadactl/apply" "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit" + "github.com/karmada-io/karmada/pkg/karmadactl/config" "github.com/karmada-io/karmada/pkg/karmadactl/cordon" "github.com/karmada-io/karmada/pkg/karmadactl/deinit" "github.com/karmada-io/karmada/pkg/karmadactl/describe" @@ -133,6 +135,7 @@ func NewKarmadaCtlCommand(cmdUse, parentCommand string) *cobra.Command { rootCmd.AddCommand(sharedcommand.NewCmdVersion(parentCommand)) rootCmd.AddCommand(options.NewCmdOptions(parentCommand, ioStreams.Out)) + rootCmd.AddCommand(config.NewCmdConfig(parentCommand, clientcmd.NewDefaultPathOptions(), ioStreams)) templates.ActsAsRootCommand(rootCmd, filters, groups...)