diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index 5c6793ad604..e8ea8e8d917 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -21,6 +21,7 @@ go_test( "//pkg/apis/batch:go_default_library", "//pkg/apis/coordination:go_default_library", "//pkg/apis/core:go_default_library", + "//pkg/apis/discovery:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/node:go_default_library", "//pkg/apis/policy:go_default_library", @@ -40,6 +41,7 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/printers:go_default_library", + "//vendor/k8s.io/utils/pointer:go_default_library", "//vendor/sigs.k8s.io/yaml:go_default_library", ], ) diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index e504ab483c5..fa479169816 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -472,9 +472,10 @@ func AddHandlers(h printers.PrintHandler) { endpointSliceColumnDefinitions := []metav1beta1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, - {Name: "Ports", Type: "string", Description: discoveryv1alpha1.EndpointSlice{}.SwaggerDoc()["ports"]}, {Name: "AddressType", Type: "string", Description: discoveryv1alpha1.EndpointSlice{}.SwaggerDoc()["addressType"]}, + {Name: "Ports", Type: "string", Description: discoveryv1alpha1.EndpointSlice{}.SwaggerDoc()["ports"]}, {Name: "Endpoints", Type: "string", Description: discoveryv1alpha1.EndpointSlice{}.SwaggerDoc()["endpoints"]}, + {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, } h.TableHandler(endpointSliceColumnDefinitions, printEndpointSlice) h.TableHandler(endpointSliceColumnDefinitions, printEndpointSliceList) @@ -529,6 +530,57 @@ func formatEndpoints(endpoints *api.Endpoints, ports sets.String) string { return ret } +func formatDiscoveryPorts(ports []discovery.EndpointPort) string { + list := []string{} + max := 3 + more := false + count := 0 + for _, port := range ports { + if len(list) < max { + portNum := "*" + if port.Port != nil { + portNum = strconv.Itoa(int(*port.Port)) + } else if port.Name != nil { + portNum = *port.Name + } + list = append(list, portNum) + } else if len(list) == max { + more = true + } + count++ + } + return listWithMoreString(list, more, count, max) +} + +func formatDiscoveryEndpoints(endpoints []discovery.Endpoint) string { + list := []string{} + max := 3 + more := false + count := 0 + for _, endpoint := range endpoints { + for _, address := range endpoint.Addresses { + if len(list) < max { + list = append(list, address) + } else if len(list) == max { + more = true + } + count++ + } + } + return listWithMoreString(list, more, count, max) +} + +func listWithMoreString(list []string, more bool, count, max int) string { + ret := strings.Join(list, ",") + if more { + return fmt.Sprintf("%s + %d more...", ret, count-max) + } + if ret == "" { + ret = "" + } + return ret +} + // translateTimestampSince returns the elapsed time since timestamp in // human-readable approximation. func translateTimestampSince(timestamp metav1.Time) string { @@ -1118,7 +1170,11 @@ func printEndpointSlice(obj *discovery.EndpointSlice, options printers.GenerateO row := metav1beta1.TableRow{ Object: runtime.RawExtension{Object: obj}, } - row.Cells = append(row.Cells, obj.Name, obj.Ports, obj.AddressType, obj.Endpoints) + addressType := "" + if obj.AddressType != nil { + addressType = string(*obj.AddressType) + } + row.Cells = append(row.Cells, obj.Name, addressType, formatDiscoveryPorts(obj.Ports), formatDiscoveryEndpoints(obj.Endpoints), translateTimestampSince(obj.CreationTimestamp)) return []metav1beta1.TableRow{row}, nil } diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 0b79c4b649d..3f50f8c9a0c 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -49,12 +49,14 @@ import ( "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/coordination" api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/discovery" "k8s.io/kubernetes/pkg/apis/networking" nodeapi "k8s.io/kubernetes/pkg/apis/node" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/printers" + utilpointer "k8s.io/utils/pointer" ) var testData = TestStruct{ @@ -3867,6 +3869,106 @@ func TestPrintRuntimeClass(t *testing.T) { } } +func TestPrintEndpointSlice(t *testing.T) { + ipAddressType := discovery.AddressTypeIP + tcpProtocol := api.ProtocolTCP + + tests := []struct { + endpointSlice discovery.EndpointSlice + expect string + }{ + { + discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abcslice.123", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + AddressType: &ipAddressType, + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Port: utilpointer.Int32Ptr(80), + Protocol: &tcpProtocol, + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, + }}, + }, + "abcslice.123 IP 80 10.1.2.3,2001:db8::1234:5678 0s\n", + }, { + discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "longerslicename.123", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + AddressType: &ipAddressType, + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Port: utilpointer.Int32Ptr(80), + Protocol: &tcpProtocol, + }, { + Name: utilpointer.StringPtr("https"), + Port: utilpointer.Int32Ptr(443), + Protocol: &tcpProtocol, + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, + }, { + Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"}, + }}, + }, + "longerslicename.123 IP 80,443 10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more... 5m\n", + }, { + discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multiportslice.123", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + AddressType: &ipAddressType, + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Port: utilpointer.Int32Ptr(80), + Protocol: &tcpProtocol, + }, { + Name: utilpointer.StringPtr("https"), + Port: utilpointer.Int32Ptr(443), + Protocol: &tcpProtocol, + }, { + Name: utilpointer.StringPtr("extra1"), + Port: utilpointer.Int32Ptr(3000), + Protocol: &tcpProtocol, + }, { + Name: utilpointer.StringPtr("extra2"), + Port: utilpointer.Int32Ptr(3001), + Protocol: &tcpProtocol, + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, + }, { + Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"}, + }}, + }, + "multiportslice.123 IP 80,443,3000 + 1 more... 10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more... 5m\n", + }, + } + + buf := bytes.NewBuffer([]byte{}) + for _, test := range tests { + table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.endpointSlice, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + verifyTable(t, table) + printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) + if err := printer.PrintObj(table, buf); err != nil { + t.Fatal(err) + } + if buf.String() != test.expect { + t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + } + buf.Reset() + } +} + func verifyTable(t *testing.T, table *metav1beta1.Table) { var panicErr interface{} func() { diff --git a/staging/src/k8s.io/kubectl/pkg/describe/versioned/BUILD b/staging/src/k8s.io/kubectl/pkg/describe/versioned/BUILD index 85a57b56730..e47978fca41 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/versioned/BUILD +++ b/staging/src/k8s.io/kubectl/pkg/describe/versioned/BUILD @@ -14,6 +14,7 @@ go_library( "//staging/src/k8s.io/api/batch/v1beta1:go_default_library", "//staging/src/k8s.io/api/certificates/v1beta1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/api/discovery/v1alpha1:go_default_library", "//staging/src/k8s.io/api/extensions/v1beta1:go_default_library", "//staging/src/k8s.io/api/networking/v1:go_default_library", "//staging/src/k8s.io/api/networking/v1beta1:go_default_library", @@ -63,6 +64,7 @@ go_test( "//staging/src/k8s.io/api/autoscaling/v1:go_default_library", "//staging/src/k8s.io/api/autoscaling/v2beta2:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/api/discovery/v1alpha1:go_default_library", "//staging/src/k8s.io/api/networking/v1:go_default_library", "//staging/src/k8s.io/api/policy/v1beta1:go_default_library", "//staging/src/k8s.io/api/storage/v1:go_default_library", diff --git a/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe.go b/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe.go index a5c78c0da00..f78cfaaee22 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe.go @@ -40,6 +40,7 @@ import ( batchv1beta1 "k8s.io/api/batch/v1beta1" certificatesv1beta1 "k8s.io/api/certificates/v1beta1" corev1 "k8s.io/api/core/v1" + discoveryv1alpha1 "k8s.io/api/discovery/v1alpha1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" networkingv1beta1 "k8s.io/api/networking/v1beta1" @@ -169,6 +170,7 @@ func describerMap(clientConfig *rest.Config) (map[schema.GroupKind]describe.Desc {Group: corev1.GroupName, Kind: "Endpoints"}: &EndpointsDescriber{c}, {Group: corev1.GroupName, Kind: "ConfigMap"}: &ConfigMapDescriber{c}, {Group: corev1.GroupName, Kind: "PriorityClass"}: &PriorityClassDescriber{c}, + {Group: discoveryv1alpha1.GroupName, Kind: "EndpointSlice"}: &EndpointSliceDescriber{c}, {Group: extensionsv1beta1.GroupName, Kind: "ReplicaSet"}: &ReplicaSetDescriber{c}, {Group: extensionsv1beta1.GroupName, Kind: "NetworkPolicy"}: &NetworkPolicyDescriber{c}, {Group: extensionsv1beta1.GroupName, Kind: "PodSecurityPolicy"}: &PodSecurityPolicyDescriber{c}, @@ -2601,6 +2603,103 @@ func describeEndpoints(ep *corev1.Endpoints, events *corev1.EventList) (string, }) } +// EndpointSliceDescriber generates information about an EndpointSlice. +type EndpointSliceDescriber struct { + clientset.Interface +} + +func (d *EndpointSliceDescriber) Describe(namespace, name string, describerSettings describe.DescriberSettings) (string, error) { + c := d.DiscoveryV1alpha1().EndpointSlices(namespace) + + eps, err := c.Get(name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + var events *corev1.EventList + if describerSettings.ShowEvents { + events, _ = d.CoreV1().Events(namespace).Search(scheme.Scheme, eps) + } + + return describeEndpointSlice(eps, events) +} + +func describeEndpointSlice(eps *discoveryv1alpha1.EndpointSlice, events *corev1.EventList) (string, error) { + return tabbedString(func(out io.Writer) error { + w := NewPrefixWriter(out) + w.Write(LEVEL_0, "Name:\t%s\n", eps.Name) + w.Write(LEVEL_0, "Namespace:\t%s\n", eps.Namespace) + printLabelsMultiline(w, "Labels", eps.Labels) + printAnnotationsMultiline(w, "Annotations", eps.Annotations) + + addressType := "" + if eps.AddressType != nil { + addressType = string(*eps.AddressType) + } + w.Write(LEVEL_0, "AddressType:\t%s\n", addressType) + + if len(eps.Ports) == 0 { + w.Write(LEVEL_0, "Ports: \n") + } else { + w.Write(LEVEL_0, "Ports:\n") + w.Write(LEVEL_1, "Name\tPort\tProtocol\n") + w.Write(LEVEL_1, "----\t----\t--------\n") + for _, port := range eps.Ports { + portName := "" + if port.Name != nil && len(*port.Name) > 0 { + portName = *port.Name + } + + portNum := "" + if port.Port != nil { + portNum = strconv.Itoa(int(*port.Port)) + } + + w.Write(LEVEL_1, "%s\t%s\t%s\n", portName, portNum, *port.Protocol) + } + } + + if len(eps.Endpoints) == 0 { + w.Write(LEVEL_0, "Endpoints: \n") + } else { + w.Write(LEVEL_0, "Endpoints:\n") + for i := range eps.Endpoints { + endpoint := &eps.Endpoints[i] + + addressesString := strings.Join(endpoint.Addresses, ",") + if len(addressesString) == 0 { + addressesString = "" + } + w.Write(LEVEL_1, "- Addresses:\t%s\n", addressesString) + + w.Write(LEVEL_2, "Conditions:\n") + readyText := "" + if endpoint.Conditions.Ready != nil { + readyText = strconv.FormatBool(*endpoint.Conditions.Ready) + } + w.Write(LEVEL_3, "Ready:\t%s\n", readyText) + + hostnameText := "" + if endpoint.Hostname != nil { + hostnameText = *endpoint.Hostname + } + w.Write(LEVEL_2, "Hostname:\t%s\n", hostnameText) + + if endpoint.TargetRef != nil { + w.Write(LEVEL_2, "TargetRef:\t%s/%s\n", endpoint.TargetRef.Kind, endpoint.TargetRef.Name) + } + + printLabelsMultilineWithIndent(w, " ", "Topology", "\t", endpoint.Topology, sets.NewString()) + } + } + + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + // ServiceAccountDescriber generates information about a service. type ServiceAccountDescriber struct { clientset.Interface diff --git a/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe_test.go b/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe_test.go index 1fe3b944f0a..3edb92b469c 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe_test.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe_test.go @@ -29,6 +29,7 @@ import ( autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" corev1 "k8s.io/api/core/v1" + discoveryv1alpha1 "k8s.io/api/discovery/v1alpha1" networkingv1 "k8s.io/api/networking/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" storagev1 "k8s.io/api/storage/v1" @@ -2535,6 +2536,14 @@ func TestDescribeEvents(t *testing.T) { }, }, events), }, + "EndpointSliceDescriber": &EndpointSliceDescriber{ + fake.NewSimpleClientset(&discoveryv1alpha1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: "foo", + }, + }, events), + }, // TODO(jchaloup): add tests for: // - IngressDescriber // - JobDescriber @@ -3193,6 +3202,83 @@ func TestDescribeStatefulSet(t *testing.T) { } } +func TestDescribeEndpointSlice(t *testing.T) { + addressTypeIP := discoveryv1alpha1.AddressTypeIP + protocolTCP := corev1.ProtocolTCP + port80 := int32(80) + + fake := fake.NewSimpleClientset(&discoveryv1alpha1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo.123", + Namespace: "bar", + }, + AddressType: &addressTypeIP, + Endpoints: []discoveryv1alpha1.Endpoint{ + { + Addresses: []string{"1.2.3.4", "1.2.3.5"}, + Conditions: discoveryv1alpha1.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, + TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "test-123"}, + Topology: map[string]string{ + "topology.kubernetes.io/zone": "us-central1-a", + "topology.kubernetes.io/region": "us-central1", + }, + }, { + Addresses: []string{"1.2.3.6", "1.2.3.7"}, + Conditions: discoveryv1alpha1.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, + TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "test-124"}, + Topology: map[string]string{ + "topology.kubernetes.io/zone": "us-central1-b", + "topology.kubernetes.io/region": "us-central1", + }, + }, + }, + Ports: []discoveryv1alpha1.EndpointPort{ + { + Protocol: &protocolTCP, + Port: &port80, + }, + }, + }) + + c := &describeClient{T: t, Namespace: "foo", Interface: fake} + d := EndpointSliceDescriber{c} + out, err := d.Describe("bar", "foo.123", describe.DescriberSettings{ShowEvents: true}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + expectedOut := `Name: foo.123 +Namespace: bar +Labels: +Annotations: +AddressType: IP +Ports: + Name Port Protocol + ---- ---- -------- + 80 TCP +Endpoints: + - Addresses: 1.2.3.4,1.2.3.5 + Conditions: + Ready: true + Hostname: + TargetRef: Pod/test-123 + Topology: topology.kubernetes.io/region=us-central1 + topology.kubernetes.io/zone=us-central1-a + - Addresses: 1.2.3.6,1.2.3.7 + Conditions: + Ready: true + Hostname: + TargetRef: Pod/test-124 + Topology: topology.kubernetes.io/region=us-central1 + topology.kubernetes.io/zone=us-central1-b +Events: ` + "\n" + + if out != expectedOut { + t.Logf(out) + t.Errorf("expected : %q\n but got output:\n %q", expectedOut, out) + } +} + // boolPtr returns a pointer to a bool func boolPtr(b bool) *bool { o := b