diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index 18d27440a35..bb7385482d2 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -37,6 +37,7 @@ import ( discoveryv1beta1 "k8s.io/api/discovery/v1beta1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" flowcontrolv1beta2 "k8s.io/api/flowcontrol/v1beta2" + networkingv1alpha1 "k8s.io/api/networking/v1alpha1" policyv1beta1 "k8s.io/api/policy/v1beta1" rbacv1beta1 "k8s.io/api/rbac/v1beta1" schedulingv1 "k8s.io/api/scheduling/v1" @@ -591,6 +592,18 @@ func AddHandlers(h printers.PrintHandler) { {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, } h.TableHandler(scaleColumnDefinitions, printScale) + + clusterCIDRColumnDefinitions := []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, + {Name: "PerNodeHostBits", Type: "string", Description: networkingv1alpha1.ClusterCIDRSpec{}.SwaggerDoc()["perNodeHostBits"]}, + {Name: "IPv4", Type: "string", Description: networkingv1alpha1.ClusterCIDRSpec{}.SwaggerDoc()["ipv4"]}, + {Name: "IPv6", Type: "string", Description: networkingv1alpha1.ClusterCIDRSpec{}.SwaggerDoc()["ipv6"]}, + {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, + {Name: "NodeSelector", Type: "string", Priority: 1, Description: networkingv1alpha1.ClusterCIDRSpec{}.SwaggerDoc()["nodeSelector"]}, + } + + h.TableHandler(clusterCIDRColumnDefinitions, printClusterCIDR) + h.TableHandler(clusterCIDRColumnDefinitions, printClusterCIDRList) } // Pass ports=nil for all ports. @@ -2624,6 +2637,57 @@ func printPriorityLevelConfigurationList(list *flowcontrol.PriorityLevelConfigur return rows, nil } +func printClusterCIDR(obj *networking.ClusterCIDR, options printers.GenerateOptions) ([]metav1.TableRow, error) { + row := metav1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + ipv4 := "" + ipv6 := "" + + if obj.Spec.IPv4 != "" { + ipv4 = obj.Spec.IPv4 + } + if obj.Spec.IPv6 != "" { + ipv6 = obj.Spec.IPv6 + } + + row.Cells = append(row.Cells, obj.Name, fmt.Sprint(obj.Spec.PerNodeHostBits), ipv4, ipv6, translateTimestampSince(obj.CreationTimestamp)) + if options.Wide { + nodeSelector := "" + if obj.Spec.NodeSelector != nil { + allTerms := make([]string, 0) + for _, term := range obj.Spec.NodeSelector.NodeSelectorTerms { + if len(term.MatchExpressions) > 0 { + matchExpressions := fmt.Sprintf("MatchExpressions: %v", term.MatchExpressions) + allTerms = append(allTerms, matchExpressions) + } + + if len(term.MatchFields) > 0 { + matchFields := fmt.Sprintf("MatchFields: %v", term.MatchFields) + allTerms = append(allTerms, matchFields) + } + } + nodeSelector = strings.Join(allTerms, ",") + } + + row.Cells = append(row.Cells, nodeSelector) + } + + return []metav1.TableRow{row}, nil +} + +func printClusterCIDRList(list *networking.ClusterCIDRList, options printers.GenerateOptions) ([]metav1.TableRow, error) { + rows := make([]metav1.TableRow, 0, len(list.Items)) + for i := range list.Items { + r, err := printClusterCIDR(&list.Items[i], options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + } + return rows, nil +} + func printScale(obj *autoscaling.Scale, options printers.GenerateOptions) ([]metav1.TableRow, error) { row := metav1.TableRow{ Object: runtime.RawExtension{Object: obj}, diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 0301a0d27b8..d21da9bff15 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -6184,3 +6184,277 @@ func TestTableRowDeepCopyShouldNotPanic(t *testing.T) { }) } } + +func TestPrintClusterCIDR(t *testing.T) { + ipv4CIDR := "10.1.0.0/16" + perNodeHostBits := int32(8) + ipv6CIDR := "fd00:1:1::/64" + + tests := []struct { + ccc networking.ClusterCIDR + options printers.GenerateOptions + expected []metav1.TableRow + }{ + { + // Test name, IPv4 only with no node selector. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test1"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv4: ipv4CIDR, + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age. + expected: []metav1.TableRow{{Cells: []interface{}{"test1", "8", ipv4CIDR, "", ""}}}, + }, + { + // Test name, IPv4 only with node selector, Not wide. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test2"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv4: ipv4CIDR, + // Does NOT get printed. + NodeSelector: makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"}), + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age. + expected: []metav1.TableRow{{Cells: []interface{}{"test2", "8", ipv4CIDR, "", ""}}}, + }, + { + // Test name, IPv4 only with no node selector, wide. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test3"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv4: ipv4CIDR, + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age, NodeSelector . + expected: []metav1.TableRow{{Cells: []interface{}{"test3", "8", ipv4CIDR, "", "", ""}}}, + }, + { + // Test name, IPv4 only with node selector, wide. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test4"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv4: ipv4CIDR, + NodeSelector: makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"}), + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age, NodeSelector . + expected: []metav1.TableRow{{Cells: []interface{}{"test4", "8", ipv4CIDR, "", "", "MatchExpressions: [{foo In [bar]}]"}}}, + }, + { + // Test name, IPv6 only with no node selector. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test5"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv6: ipv6CIDR, + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age + expected: []metav1.TableRow{{Cells: []interface{}{"test5", "8", "", ipv6CIDR, ""}}}, + }, + { + // Test name, IPv6 only with node selector, Not wide. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test6"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv6: ipv6CIDR, + // Does NOT get printed. + NodeSelector: makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"}), + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age. + expected: []metav1.TableRow{{Cells: []interface{}{"test6", "8", "", ipv6CIDR, ""}}}, + }, + { + // Test name, IPv6 only with no node selector, wide. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test7"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv6: ipv6CIDR, + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age, NodeSelector . + expected: []metav1.TableRow{{Cells: []interface{}{"test7", "8", "", ipv6CIDR, "", ""}}}, + }, + { + // Test name, IPv6 only with node selector, wide. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test8"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv6: ipv6CIDR, + NodeSelector: makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"}), + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age, NodeSelector . + expected: []metav1.TableRow{{Cells: []interface{}{"test8", "8", "", ipv6CIDR, "", "MatchExpressions: [{foo In [bar]}]"}}}, + }, + { + // Test name, DualStack with no node selector. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test9"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv4: ipv4CIDR, + IPv6: ipv6CIDR, + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age. + expected: []metav1.TableRow{{Cells: []interface{}{"test9", "8", ipv4CIDR, ipv6CIDR, ""}}}, + }, + { + // Test name,DualStack with node selector, Not wide. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test10"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv4: ipv4CIDR, + IPv6: ipv6CIDR, + // Does NOT get printed. + NodeSelector: makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"}), + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age. + expected: []metav1.TableRow{{Cells: []interface{}{"test10", "8", ipv4CIDR, ipv6CIDR, ""}}}, + }, + { + // Test name, DualStack with no node selector, wide. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test11"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv4: ipv4CIDR, + IPv6: ipv6CIDR, + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age, NodeSelector. + expected: []metav1.TableRow{{Cells: []interface{}{"test11", "8", ipv4CIDR, ipv6CIDR, "", ""}}}, + }, + { + // Test name, DualStack with node selector, wide. + ccc: networking.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{Name: "test12"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: perNodeHostBits, + IPv4: ipv4CIDR, + IPv6: ipv6CIDR, + NodeSelector: makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"}), + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age, NodeSelector . + expected: []metav1.TableRow{{Cells: []interface{}{"test12", "8", ipv4CIDR, ipv6CIDR, "", "MatchExpressions: [{foo In [bar]}]"}}}, + }, + } + + for i, test := range tests { + rows, err := printClusterCIDR(&test.ccc, test.options) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + +func makeNodeSelector(key string, op api.NodeSelectorOperator, values []string) *api.NodeSelector { + return &api.NodeSelector{ + NodeSelectorTerms: []api.NodeSelectorTerm{ + { + MatchExpressions: []api.NodeSelectorRequirement{ + { + Key: key, + Operator: op, + Values: values, + }, + }, + }, + }, + } +} + +func TestPrintClusterCIDRList(t *testing.T) { + + cccList := networking.ClusterCIDRList{ + Items: []networking.ClusterCIDR{ + { + ObjectMeta: metav1.ObjectMeta{Name: "ccc1"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: int32(8), + IPv4: "10.1.0.0/16", + IPv6: "fd00:1:1::/64", + NodeSelector: makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"}), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "ccc2"}, + Spec: networking.ClusterCIDRSpec{ + PerNodeHostBits: int32(8), + IPv4: "10.2.0.0/16", + IPv6: "fd00:2:1::/64", + NodeSelector: makeNodeSelector("foo", api.NodeSelectorOpIn, []string{"bar"}), + }, + }, + }, + } + + tests := []struct { + options printers.GenerateOptions + expected []metav1.TableRow + }{ + { + // Test name, DualStack with node selector, wide. + options: printers.GenerateOptions{Wide: false}, + expected: []metav1.TableRow{ + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age. + {Cells: []interface{}{"ccc1", "8", "10.1.0.0/16", "fd00:1:1::/64", ""}}, + {Cells: []interface{}{"ccc2", "8", "10.2.0.0/16", "fd00:2:1::/64", ""}}, + }, + }, + { + // Test name, DualStack with node selector, wide. + options: printers.GenerateOptions{Wide: true}, + expected: []metav1.TableRow{ + // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age, NodeSelector. + {Cells: []interface{}{"ccc1", "8", "10.1.0.0/16", "fd00:1:1::/64", "", "MatchExpressions: [{foo In [bar]}]"}}, + {Cells: []interface{}{"ccc2", "8", "10.2.0.0/16", "fd00:2:1::/64", "", "MatchExpressions: [{foo In [bar]}]"}}, + }, + }, + } + + for _, test := range tests { + rows, err := printClusterCIDRList(&cccList, test.options) + if err != nil { + t.Fatalf("Error printing service list: %#v", err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("mismatch: %s", diff.ObjectReflectDiff(test.expected, rows)) + } + } +} diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe.go b/staging/src/k8s.io/kubectl/pkg/describe/describe.go index a1efdd0af7b..484ed09f67c 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/describe.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/describe.go @@ -33,7 +33,6 @@ import ( "unicode" "github.com/fatih/camelcase" - appsv1 "k8s.io/api/apps/v1" autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" @@ -46,6 +45,7 @@ import ( discoveryv1beta1 "k8s.io/api/discovery/v1beta1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" + networkingv1alpha1 "k8s.io/api/networking/v1alpha1" networkingv1beta1 "k8s.io/api/networking/v1beta1" policyv1 "k8s.io/api/policy/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" @@ -213,6 +213,7 @@ func describerMap(clientConfig *rest.Config) (map[schema.GroupKind]ResourceDescr {Group: networkingv1beta1.GroupName, Kind: "IngressClass"}: &IngressClassDescriber{c}, {Group: networkingv1.GroupName, Kind: "Ingress"}: &IngressDescriber{c}, {Group: networkingv1.GroupName, Kind: "IngressClass"}: &IngressClassDescriber{c}, + {Group: networkingv1alpha1.GroupName, Kind: "ClusterCIDR"}: &ClusterCIDRDescriber{c}, {Group: batchv1.GroupName, Kind: "Job"}: &JobDescriber{c}, {Group: batchv1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c}, {Group: batchv1beta1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c}, @@ -2853,6 +2854,63 @@ func (i *IngressClassDescriber) describeIngressClassV1(ic *networkingv1.IngressC }) } +// ClusterCIDRDescriber generates information about a ClusterCIDR. +type ClusterCIDRDescriber struct { + client clientset.Interface +} + +func (c *ClusterCIDRDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) { + var events *corev1.EventList + + ccV1alpha1, err := c.client.NetworkingV1alpha1().ClusterCIDRs().Get(context.TODO(), name, metav1.GetOptions{}) + if err == nil { + if describerSettings.ShowEvents { + events, _ = searchEvents(c.client.CoreV1(), ccV1alpha1, describerSettings.ChunkSize) + } + return c.describeClusterCIDRV1alpha1(ccV1alpha1, events) + } + return "", err +} + +func (c *ClusterCIDRDescriber) describeClusterCIDRV1alpha1(cc *networkingv1alpha1.ClusterCIDR, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%v\n", cc.Name) + printLabelsMultiline(w, "Labels", cc.Labels) + printAnnotationsMultiline(w, "Annotations", cc.Annotations) + + w.Write(LEVEL_0, "NodeSelector:\n") + if cc.Spec.NodeSelector != nil { + w.Write(LEVEL_1, "NodeSelector Terms:") + if len(cc.Spec.NodeSelector.NodeSelectorTerms) == 0 { + w.WriteLine("") + } else { + w.WriteLine("") + for i, term := range cc.Spec.NodeSelector.NodeSelectorTerms { + printNodeSelectorTermsMultilineWithIndent(w, LEVEL_2, fmt.Sprintf("Term %v", i), "\t", term.MatchExpressions) + } + } + } + + if cc.Spec.PerNodeHostBits != 0 { + w.Write(LEVEL_0, "PerNodeHostBits:\t%s\n", fmt.Sprint(cc.Spec.PerNodeHostBits)) + } + + if cc.Spec.IPv4 != "" { + w.Write(LEVEL_0, "IPv4:\t%s\n", cc.Spec.IPv4) + } + + if cc.Spec.IPv6 != "" { + w.Write(LEVEL_0, "IPv6:\t%s\n", cc.Spec.IPv6) + } + + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + // ServiceDescriber generates information about a service. type ServiceDescriber struct { clientset.Interface diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go index fe6b06c2895..5225ec2db13 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go @@ -25,6 +25,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" appsv1 "k8s.io/api/apps/v1" autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" @@ -34,6 +35,7 @@ import ( discoveryv1 "k8s.io/api/discovery/v1" discoveryv1beta1 "k8s.io/api/discovery/v1beta1" networkingv1 "k8s.io/api/networking/v1" + networkingv1alpha1 "k8s.io/api/networking/v1alpha1" networkingv1beta1 "k8s.io/api/networking/v1beta1" policyv1 "k8s.io/api/policy/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" @@ -5371,6 +5373,64 @@ Events: ` + "\n", } } +func TestDescribeClusterCIDR(t *testing.T) { + + testcases := map[string]struct { + input *fake.Clientset + output string + }{ + "ClusterCIDR v1alpha1": { + input: fake.NewSimpleClientset(&networkingv1alpha1.ClusterCIDR{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo.123", + }, + Spec: networkingv1alpha1.ClusterCIDRSpec{ + PerNodeHostBits: int32(8), + IPv4: "10.1.0.0/16", + IPv6: "fd00:1:1::/64", + NodeSelector: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "foo", + Operator: "In", + Values: []string{"bar"}}, + }, + }, + }, + }, + }, + }), + + output: `Name: foo.123 +Labels: +Annotations: +NodeSelector: + NodeSelector Terms: + Term 0: foo in [bar] +PerNodeHostBits: 8 +IPv4: 10.1.0.0/16 +IPv6: fd00:1:1::/64 +Events: ` + "\n", + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + c := &describeClient{T: t, Namespace: "foo", Interface: tc.input} + d := ClusterCIDRDescriber{c} + out, err := d.Describe("bar", "foo.123", DescriberSettings{ShowEvents: true}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if out != tc.output { + t.Errorf("expected :\n%s\nbut got output:\n%s diff:\n%s", tc.output, out, cmp.Diff(tc.output, out)) + } + }) + } +} + func TestControllerRef(t *testing.T) { var replicas int32 = 1 f := fake.NewSimpleClientset(