use kubectl describe to implement karmadactl describe

Signed-off-by: wlp1153468871 <luping.wei@daocloud.io>
This commit is contained in:
wlp1153468871 2022-07-26 22:08:46 +08:00
parent b860ba56c7
commit f1db971b44
3 changed files with 287 additions and 178 deletions

View File

@ -2,15 +2,11 @@ package karmadactl
import (
"fmt"
"strings"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
kubectldescribe "k8s.io/kubectl/pkg/cmd/describe"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/describe"
"k8s.io/kubectl/pkg/util/templates"
@ -39,16 +35,16 @@ var (
// NewCmdDescribe new describe command.
func NewCmdDescribe(karmadaConfig KarmadaConfig, parentCommand string, streams genericclioptions.IOStreams) *cobra.Command {
o := &CommandDescribeOptions{
FilenameOptions: &resource.FilenameOptions{},
DescriberSettings: &describe.DescriberSettings{
ShowEvents: true,
ChunkSize: cmdutil.DefaultChunkSize,
o := &DescribeOptions{
KubectlDescribeOptions: &kubectldescribe.DescribeOptions{
FilenameOptions: &resource.FilenameOptions{},
DescriberSettings: &describe.DescriberSettings{
ShowEvents: true,
ChunkSize: cmdutil.DefaultChunkSize,
},
CmdParent: parentCommand,
IOStreams: streams,
},
CmdParent: parentCommand,
IOStreams: streams,
}
cmd := &cobra.Command{
@ -57,7 +53,7 @@ func NewCmdDescribe(karmadaConfig KarmadaConfig, parentCommand string, streams g
SilenceUsage: true,
Example: fmt.Sprintf(describeExample, parentCommand),
RunE: func(cmd *cobra.Command, args []string) error {
if err := o.Complete(karmadaConfig, args); err != nil {
if err := o.Complete(karmadaConfig, cmd, args); err != nil {
return err
}
if err := o.Run(); err != nil {
@ -70,45 +66,29 @@ func NewCmdDescribe(karmadaConfig KarmadaConfig, parentCommand string, streams g
o.GlobalCommandOptions.AddFlags(cmd.Flags())
usage := "containing the resource to describe"
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, usage)
cmdutil.AddFilenameOptionFlags(cmd, o.KubectlDescribeOptions.FilenameOptions, usage)
cmd.Flags().StringVarP(&o.Cluster, "cluster", "C", "", "Specify a member cluster")
cmd.Flags().StringVarP(&o.Namespace, "namespace", "n", o.Namespace, "If present, the namespace scope for this CLI request")
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
cmd.Flags().BoolVar(&o.DescriberSettings.ShowEvents, "show-events", o.DescriberSettings.ShowEvents, "If true, display events related to the described object.")
cmdutil.AddChunkSizeFlag(cmd, &o.DescriberSettings.ChunkSize)
cmd.Flags().StringVarP(&o.KubectlDescribeOptions.Selector, "selector", "l", o.KubectlDescribeOptions.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
cmd.Flags().BoolVarP(&o.KubectlDescribeOptions.AllNamespaces, "all-namespaces", "A", o.KubectlDescribeOptions.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
cmd.Flags().BoolVar(&o.KubectlDescribeOptions.DescriberSettings.ShowEvents, "show-events", o.KubectlDescribeOptions.DescriberSettings.ShowEvents, "If true, display events related to the described object.")
cmdutil.AddChunkSizeFlag(cmd, &o.KubectlDescribeOptions.DescriberSettings.ChunkSize)
return cmd
}
// CommandDescribeOptions contains the input to the describe command.
type CommandDescribeOptions struct {
// DescribeOptions contains the input to the describe command.
type DescribeOptions struct {
// global flags
options.GlobalCommandOptions
Cluster string
CmdParent string
Selector string
Namespace string
Describer func(*meta.RESTMapping) (describe.ResourceDescriber, error)
NewBuilder func() *resource.Builder
BuilderArgs []string
EnforceNamespace bool
AllNamespaces bool
DescriberSettings *describe.DescriberSettings
FilenameOptions *resource.FilenameOptions
genericclioptions.IOStreams
// flags specific to describe
KubectlDescribeOptions *kubectldescribe.DescribeOptions
Namespace string
Cluster string
}
// Complete ensures that options are valid and marshals them if necessary
func (o *CommandDescribeOptions) Complete(karmadaConfig KarmadaConfig, args []string) error {
var err error
func (o *DescribeOptions) Complete(karmadaConfig KarmadaConfig, cmd *cobra.Command, args []string) error {
if len(o.Cluster) == 0 {
return fmt.Errorf("must specify a cluster")
}
@ -118,146 +98,15 @@ func (o *CommandDescribeOptions) Complete(karmadaConfig KarmadaConfig, args []st
return fmt.Errorf("failed to get control plane rest config. context: %s, kube-config: %s, error: %v",
o.KarmadaContext, o.KubeConfig, err)
}
clusterInfo, err := getClusterInfo(karmadaRestConfig, o.Cluster, o.KubeConfig, o.KarmadaContext)
if err != nil {
return err
}
f := getFactory(o.Cluster, clusterInfo, "")
namespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
if o.Namespace == "" {
o.Namespace = namespace
}
o.EnforceNamespace = enforceNamespace
if o.AllNamespaces {
o.EnforceNamespace = false
}
if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) {
return fmt.Errorf("must specify the type of resource to describe. %s", cmdutil.SuggestAPIResources(o.CmdParent))
}
o.BuilderArgs = args
o.Describer = func(mapping *meta.RESTMapping) (describe.ResourceDescriber, error) {
return describe.DescriberFn(f, mapping)
}
o.NewBuilder = f.NewBuilder
return nil
f := getFactory(o.Cluster, clusterInfo, o.Namespace)
return o.KubectlDescribeOptions.Complete(f, cmd, args)
}
// Run describe information of resources
func (o *CommandDescribeOptions) Run() error {
r := o.NewBuilder().
Unstructured().
ContinueOnError().
NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
FilenameParam(o.EnforceNamespace, o.FilenameOptions).
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(true, o.BuilderArgs...).
RequestChunksOf(o.DescriberSettings.ChunkSize).
Flatten().
Do()
err := r.Err()
if err != nil {
return err
}
allErrs := []error{}
infos, err := r.Infos()
if err != nil {
if apierrors.IsNotFound(err) && len(o.BuilderArgs) == 2 {
return o.describeMatchingResources(err, o.BuilderArgs[0], o.BuilderArgs[1])
}
allErrs = append(allErrs, err)
}
errs := sets.NewString()
first := true
for _, info := range infos {
mapping := info.ResourceMapping()
describer, err := o.Describer(mapping)
if err != nil {
if errs.Has(err.Error()) {
continue
}
allErrs = append(allErrs, err)
errs.Insert(err.Error())
continue
}
s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings)
if err != nil {
if errs.Has(err.Error()) {
continue
}
allErrs = append(allErrs, err)
errs.Insert(err.Error())
continue
}
if first {
first = false
fmt.Fprint(o.Out, s)
} else {
fmt.Fprintf(o.Out, "\n\n%s", s)
}
}
if len(infos) == 0 && len(allErrs) == 0 {
// if we wrote no output, and had no errors, be sure we output something.
if o.AllNamespaces {
fmt.Fprintln(o.ErrOut, "No resources found")
} else {
fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace)
}
}
return utilerrors.NewAggregate(allErrs)
}
func (o *CommandDescribeOptions) describeMatchingResources(originalError error, resource, prefix string) error {
r := o.NewBuilder().
Unstructured().
NamespaceParam(o.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(true, resource).
SingleResourceType().
RequestChunksOf(o.DescriberSettings.ChunkSize).
Flatten().
Do()
mapping, err := r.ResourceMapping()
if err != nil {
return err
}
describer, err := o.Describer(mapping)
if err != nil {
return err
}
infos, err := r.Infos()
if err != nil {
return err
}
isFound := false
for ix := range infos {
info := infos[ix]
if strings.HasPrefix(info.Name, prefix) {
isFound = true
s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings)
if err != nil {
return err
}
fmt.Fprintf(o.Out, "%s\n", s)
}
}
if !isFound {
return originalError
}
return nil
func (o *DescribeOptions) Run() error {
return o.KubectlDescribeOptions.Run()
}

259
vendor/k8s.io/kubectl/pkg/cmd/describe/describe.go generated vendored Normal file
View File

@ -0,0 +1,259 @@
/*
Copyright 2014 The Kubernetes 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 describe
import (
"fmt"
"strings"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/describe"
"k8s.io/kubectl/pkg/util/completion"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
)
var (
describeLong = templates.LongDesc(i18n.T(`
Show details of a specific resource or group of resources.
Print a detailed description of the selected resources, including related resources such
as events or controllers. You may select a single object by name, all objects of that
type, provide a name prefix, or label selector. For example:
$ kubectl describe TYPE NAME_PREFIX
will first check for an exact match on TYPE and NAME_PREFIX. If no such resource
exists, it will output details for every resource that has a name prefixed with NAME_PREFIX.`))
describeExample = templates.Examples(i18n.T(`
# Describe a node
kubectl describe nodes kubernetes-node-emt8.c.myproject.internal
# Describe a pod
kubectl describe pods/nginx
# Describe a pod identified by type and name in "pod.json"
kubectl describe -f pod.json
# Describe all pods
kubectl describe pods
# Describe pods by label name=myLabel
kubectl describe po -l name=myLabel
# Describe all pods managed by the 'frontend' replication controller
# (rc-created pods get the name of the rc as a prefix in the pod name)
kubectl describe pods frontend`))
)
type DescribeOptions struct {
CmdParent string
Selector string
Namespace string
Describer func(*meta.RESTMapping) (describe.ResourceDescriber, error)
NewBuilder func() *resource.Builder
BuilderArgs []string
EnforceNamespace bool
AllNamespaces bool
DescriberSettings *describe.DescriberSettings
FilenameOptions *resource.FilenameOptions
genericclioptions.IOStreams
}
func NewCmdDescribe(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := &DescribeOptions{
FilenameOptions: &resource.FilenameOptions{},
DescriberSettings: &describe.DescriberSettings{
ShowEvents: true,
ChunkSize: cmdutil.DefaultChunkSize,
},
CmdParent: parent,
IOStreams: streams,
}
cmd := &cobra.Command{
Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)",
DisableFlagsInUseLine: true,
Short: i18n.T("Show details of a specific resource or group of resources"),
Long: describeLong + "\n\n" + cmdutil.SuggestAPIResources(parent),
Example: describeExample,
ValidArgsFunction: completion.ResourceTypeAndNameCompletionFunc(f),
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Run())
},
}
usage := "containing the resource to describe"
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, usage)
cmdutil.AddLabelSelectorFlagVar(cmd, &o.Selector)
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
cmd.Flags().BoolVar(&o.DescriberSettings.ShowEvents, "show-events", o.DescriberSettings.ShowEvents, "If true, display events related to the described object.")
cmdutil.AddChunkSizeFlag(cmd, &o.DescriberSettings.ChunkSize)
return cmd
}
func (o *DescribeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
if o.AllNamespaces {
o.EnforceNamespace = false
}
if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) {
return fmt.Errorf("You must specify the type of resource to describe. %s\n", cmdutil.SuggestAPIResources(o.CmdParent))
}
o.BuilderArgs = args
o.Describer = func(mapping *meta.RESTMapping) (describe.ResourceDescriber, error) {
return describe.DescriberFn(f, mapping)
}
o.NewBuilder = f.NewBuilder
return nil
}
func (o *DescribeOptions) Validate(args []string) error {
return nil
}
func (o *DescribeOptions) Run() error {
r := o.NewBuilder().
Unstructured().
ContinueOnError().
NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
FilenameParam(o.EnforceNamespace, o.FilenameOptions).
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(true, o.BuilderArgs...).
RequestChunksOf(o.DescriberSettings.ChunkSize).
Flatten().
Do()
err := r.Err()
if err != nil {
return err
}
allErrs := []error{}
infos, err := r.Infos()
if err != nil {
if apierrors.IsNotFound(err) && len(o.BuilderArgs) == 2 {
return o.DescribeMatchingResources(err, o.BuilderArgs[0], o.BuilderArgs[1])
}
allErrs = append(allErrs, err)
}
errs := sets.NewString()
first := true
for _, info := range infos {
mapping := info.ResourceMapping()
describer, err := o.Describer(mapping)
if err != nil {
if errs.Has(err.Error()) {
continue
}
allErrs = append(allErrs, err)
errs.Insert(err.Error())
continue
}
s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings)
if err != nil {
if errs.Has(err.Error()) {
continue
}
allErrs = append(allErrs, err)
errs.Insert(err.Error())
continue
}
if first {
first = false
fmt.Fprint(o.Out, s)
} else {
fmt.Fprintf(o.Out, "\n\n%s", s)
}
}
if len(infos) == 0 && len(allErrs) == 0 {
// if we wrote no output, and had no errors, be sure we output something.
if o.AllNamespaces {
fmt.Fprintln(o.ErrOut, "No resources found")
} else {
fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace)
}
}
return utilerrors.NewAggregate(allErrs)
}
func (o *DescribeOptions) DescribeMatchingResources(originalError error, resource, prefix string) error {
r := o.NewBuilder().
Unstructured().
NamespaceParam(o.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(true, resource).
SingleResourceType().
RequestChunksOf(o.DescriberSettings.ChunkSize).
Flatten().
Do()
mapping, err := r.ResourceMapping()
if err != nil {
return err
}
describer, err := o.Describer(mapping)
if err != nil {
return err
}
infos, err := r.Infos()
if err != nil {
return err
}
isFound := false
for ix := range infos {
info := infos[ix]
if strings.HasPrefix(info.Name, prefix) {
isFound = true
s, err := describer.Describe(info.Namespace, info.Name, *o.DescriberSettings)
if err != nil {
return err
}
fmt.Fprintf(o.Out, "%s\n", s)
}
}
if !isFound {
return originalError
}
return nil
}

1
vendor/modules.txt vendored
View File

@ -1414,6 +1414,7 @@ k8s.io/kubectl/pkg/apps
k8s.io/kubectl/pkg/cmd/apiresources
k8s.io/kubectl/pkg/cmd/apply
k8s.io/kubectl/pkg/cmd/delete
k8s.io/kubectl/pkg/cmd/describe
k8s.io/kubectl/pkg/cmd/exec
k8s.io/kubectl/pkg/cmd/get
k8s.io/kubectl/pkg/cmd/logs