Skip to content

Commit

Permalink
Merge pull request karmada-io#5392 from hulizhe/karmadactldescribe
Browse files Browse the repository at this point in the history
enhance karmadactl describe command
  • Loading branch information
karmada-bot authored Aug 19, 2024
2 parents 51b2e10 + 6eae2b9 commit c757122
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 59 deletions.
52 changes: 38 additions & 14 deletions pkg/karmadactl/describe/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (

var (
describeLong = templates.LongDesc(`
Show details of a specific resource or group of resources in a member cluster.
Show details of a specific resource or group of resources in Karmada control plane or a member cluster.
Print a detailed description of the selected resources, including related
resources such as events or controllers. You may select a single object by name,
Expand All @@ -43,21 +43,24 @@ var (
prefixed with NAME_PREFIX.`)

describeExample = templates.Examples(`
# Describe a deployment in Karmada control plane
%[1]s describe deployment/nginx
# Describe a pod in cluster(member1)
%[1]s describe pods/nginx -C=member1
%[1]s describe pods/nginx --operation-scope=members --cluster=member1
# Describe all pods in cluster(member1)
%[1]s describe pods -C=member1
%[1]s describe pods --operation-scope=members --cluster=member1
# Describe a pod identified by type and name in "pod.json" in cluster(member1)
%[1]s describe -f pod.json -C=member1
%[1]s describe -f pod.json --operation-scope=members --cluster=member1
# Describe pods by label name=myLabel in cluster(member1)
%[1]s describe po -l name=myLabel -C=member1
%[1]s describe po -l name=myLabel --operation-scope=members --cluster=member1
# Describe all pods managed by the 'frontend' replication controller in cluster(member1)
# (rc-created pods get the name of the rc as a prefix in the pod name)
%[1]s describe pods frontend -C=member1`)
%[1]s describe pods frontend --operation-scope=members --cluster=member1`)
)

// NewCmdDescribe new describe command.
Expand All @@ -66,8 +69,8 @@ func NewCmdDescribe(f util.Factory, parentCommand string, streams genericiooptio
kubedescribeFlags := kubectldescribe.NewDescribeFlags(f, streams)

cmd := &cobra.Command{
Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME) (-C CLUSTER)",
Short: "Show details of a specific resource or group of resources in a cluster",
Use: "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME) (--operation-scope=SCOPE --cluster=CLUSTER)",
Short: "Show details of a specific resource or group of resources in Karmada control plane or a member cluster",
Long: fmt.Sprintf(describeLong, parentCommand),
SilenceUsage: true,
DisableFlagsInUseLine: true,
Expand All @@ -76,6 +79,9 @@ func NewCmdDescribe(f util.Factory, parentCommand string, streams genericiooptio
if err := o.Complete(f, args, kubedescribeFlags, parentCommand); err != nil {
return err
}
if err := o.Validate(); err != nil {
return err
}
if err := o.Run(); err != nil {
return err
}
Expand All @@ -90,8 +96,10 @@ func NewCmdDescribe(f util.Factory, parentCommand string, streams genericiooptio
kubedescribeFlags.AddFlags(cmd)

options.AddKubeConfigFlags(flags)
o.OperationScope = options.KarmadaControlPlane
flags.Var(&o.OperationScope, "operation-scope", "Used to control the operation scope of the command. The optional values are karmada and members. Defaults to karmada.")
flags.StringVarP(options.DefaultConfigFlags.Namespace, "namespace", "n", *options.DefaultConfigFlags.Namespace, "If present, the namespace scope for this CLI request")
flags.StringVarP(&o.Cluster, "cluster", "C", "", "Specify a member cluster")
flags.StringVarP(&o.Cluster, "cluster", "C", "", "Used to specify a target member cluster and only takes effect when the command's operation scope is members, for example: --operation-scope=members --cluster=member1")

return cmd
}
Expand All @@ -101,23 +109,39 @@ type CommandDescribeOptions struct {
// flags specific to describe
KubectlDescribeOptions *kubectldescribe.DescribeOptions
Cluster string
OperationScope options.OperationScope
}

// Complete ensures that options are valid and marshals them if necessary
func (o *CommandDescribeOptions) Complete(f util.Factory, args []string, describeFlag *kubectldescribe.DescribeFlags, parentCommand string) error {
if len(o.Cluster) == 0 {
return fmt.Errorf("must specify a cluster")
if o.OperationScope == options.KarmadaControlPlane {
describeFlag.Factory = f
}
if o.OperationScope == options.Members && len(o.Cluster) != 0 {
memberFactory, err := f.FactoryForMemberCluster(o.Cluster)
if err != nil {
return err
}
describeFlag.Factory = memberFactory
}

memberFactory, err := f.FactoryForMemberCluster(o.Cluster)
var err error
o.KubectlDescribeOptions, err = describeFlag.ToOptions(parentCommand, args)
if err != nil {
return err
}
describeFlag.Factory = memberFactory
o.KubectlDescribeOptions, err = describeFlag.ToOptions(parentCommand, args)
return nil
}

// Validate checks if the parameters are valid
func (o *CommandDescribeOptions) Validate() error {
err := options.VerifyOperationScopeFlags(o.OperationScope, options.KarmadaControlPlane, options.Members)
if err != nil {
return err
}
if o.OperationScope == options.Members && len(o.Cluster) == 0 {
return fmt.Errorf("must specify a member cluster")
}
return nil
}

Expand Down
85 changes: 40 additions & 45 deletions test/e2e/karmadactl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -963,67 +963,62 @@ var _ = ginkgo.Describe("Karmadactl get testing", func() {
})

var _ = ginkgo.Describe("Karmadactl describe testing", func() {
var member1 string
var member1Client kubernetes.Interface

ginkgo.BeforeEach(func() {
member1 = framework.ClusterNames()[0]
member1Client = framework.GetClusterClient(member1)
})

ginkgo.Context("Test karmadactl describe for existing resource", func() {
var namespace, podName string
var (
ns *corev1.Namespace
pod *corev1.Pod
)
var deployment *appsv1.Deployment
var propagationPolicy *policyv1alpha1.PropagationPolicy

ginkgo.BeforeEach(func() {
namespace = fmt.Sprintf("karmadatest-%s", rand.String(RandomStrLength))
podName = podNamePrefix + rand.String(RandomStrLength)
pod = helper.NewPod(namespace, podName)
ns = helper.NewNamespace(namespace)
// Create the namespace and pod in the member cluster.
_, err := member1Client.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())

_, err = member1Client.CoreV1().Pods(namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
deployment = helper.NewDeployment(testNamespace, deploymentNamePrefix+rand.String(RandomStrLength))
propagationPolicy = helper.NewPropagationPolicy(deployment.Namespace, ppNamePrefix+rand.String(RandomStrLength), []policyv1alpha1.ResourceSelector{
{
APIVersion: deployment.APIVersion,
Kind: deployment.Kind,
Name: deployment.Name,
},
}, policyv1alpha1.Placement{
ClusterAffinity: &policyv1alpha1.ClusterAffinity{ClusterNames: framework.ClusterNames()},
})
})

ginkgo.AfterEach(func() {
err := member1Client.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{})
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
ginkgo.BeforeEach(func() {
framework.CreateDeployment(kubeClient, deployment)
framework.CreatePropagationPolicy(karmadaClient, propagationPolicy)

ginkgo.DeferCleanup(func() {
framework.RemoveDeployment(kubeClient, deployment.Namespace, deployment.Name)
framework.RemovePropagationPolicy(karmadaClient, propagationPolicy.Namespace, propagationPolicy.Name)
})
})

ginkgo.It("should describe the existing pod successfully", func() {
cmd := framework.NewKarmadactlCommand(kubeconfig, karmadaContext, karmadactlPath, namespace, karmadactlTimeout, "describe", "pods", podName, "-C", member1)
output, err := cmd.ExecOrDie()
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(strings.Contains(output, podName)).Should(gomega.BeTrue())
framework.WaitDeploymentPresentOnClustersFitWith(framework.ClusterNames(), deployment.Namespace, deployment.Name,
func(*appsv1.Deployment) bool {
return true
})
ginkgo.By("should describe resources in Karmada control plane", func() {
cmd := framework.NewKarmadactlCommand(kubeconfig, karmadaContext, karmadactlPath, testNamespace, karmadactlTimeout, "describe", "deployments", deployment.Name)
output, err := cmd.ExecOrDie()
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(strings.Contains(output, deployment.Name)).Should(gomega.BeTrue())
})
ginkgo.By("should describe resources in member1", func() {
cmd := framework.NewKarmadactlCommand(kubeconfig, karmadaContext, karmadactlPath, testNamespace, karmadactlTimeout, "describe", "deployments", deployment.Name, "--operation-scope", "members", "--cluster", framework.ClusterNames()[0])
output, err := cmd.ExecOrDie()
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(strings.Contains(output, deployment.Name)).Should(gomega.BeTrue())
})
})
})

ginkgo.Context("Test karmadactl describe for non-existing resource", func() {
var namespace, podName string
var ns *corev1.Namespace
var podName string

ginkgo.BeforeEach(func() {
namespace = fmt.Sprintf("karmadatest-%s", rand.String(RandomStrLength))
podName = podNamePrefix + rand.String(RandomStrLength)
ns = helper.NewNamespace(namespace)
// Create the namespace in the member cluster.
_, err := member1Client.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
})

ginkgo.AfterEach(func() {
err := member1Client.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{})
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
})

ginkgo.It("should return not found error for non-existing pod", func() {
cmd := framework.NewKarmadactlCommand(kubeconfig, karmadaContext, karmadactlPath, namespace, karmadactlTimeout, "describe", "pods", podName, "-C", member1)
cmd := framework.NewKarmadactlCommand(kubeconfig, karmadaContext, karmadactlPath, testNamespace, karmadactlTimeout, "describe", "pods", podName)
_, err := cmd.ExecOrDie()
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(strings.Contains(err.Error(), fmt.Sprintf("pods \"%s\" not found", podName))).Should(gomega.BeTrue())
Expand All @@ -1039,7 +1034,7 @@ var _ = ginkgo.Describe("Karmadactl describe testing", func() {
})

ginkgo.It("should return not found error for non-existing namespace", func() {
cmd := framework.NewKarmadactlCommand(kubeconfig, karmadaContext, karmadactlPath, namespace, karmadactlTimeout, "describe", "pods", podName, "-C", member1)
cmd := framework.NewKarmadactlCommand(kubeconfig, karmadaContext, karmadactlPath, namespace, karmadactlTimeout, "describe", "pods", podName)
_, err := cmd.ExecOrDie()
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(strings.Contains(err.Error(), fmt.Sprintf("namespaces \"%s\" not found", namespace))).Should(gomega.BeTrue())
Expand All @@ -1054,7 +1049,7 @@ var _ = ginkgo.Describe("Karmadactl describe testing", func() {
})

ginkgo.It("should return error for invalid resource type", func() {
cmd := framework.NewKarmadactlCommand(kubeconfig, karmadaContext, karmadactlPath, namespace, karmadactlTimeout, "describe", "invalidresource", "invalidname", "-C", member1)
cmd := framework.NewKarmadactlCommand(kubeconfig, karmadaContext, karmadactlPath, namespace, karmadactlTimeout, "describe", "invalidresource", "invalidname")
_, err := cmd.ExecOrDie()
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(strings.Contains(err.Error(), "the server doesn't have a resource type \"invalidresource\"")).Should(gomega.BeTrue())
Expand Down

0 comments on commit c757122

Please sign in to comment.