Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance karmadactl describe command #5392

Merged
merged 2 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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