diff --git a/pkg/kubectl/BUILD b/pkg/kubectl/BUILD index 2acfc138e8d65..0480f464a1123 100644 --- a/pkg/kubectl/BUILD +++ b/pkg/kubectl/BUILD @@ -69,6 +69,7 @@ go_library( "//pkg/apis/batch/v2alpha1:go_default_library", "//pkg/apis/certificates:go_default_library", "//pkg/apis/extensions:go_default_library", + "//pkg/apis/policy:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/apis/storage/util:go_default_library", @@ -155,6 +156,7 @@ go_test( "//pkg/apimachinery/registered:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/extensions:go_default_library", + "//pkg/apis/policy:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 98281ad122ba8..5223e565bd241 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -40,6 +40,7 @@ import ( "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/storage" storageutil "k8s.io/kubernetes/pkg/apis/storage/util" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" @@ -120,6 +121,7 @@ func describerMap(c clientset.Interface) map[unversioned.GroupKind]Describer { apps.Kind("StatefulSet"): &StatefulSetDescriber{c}, certificates.Kind("CertificateSigningRequest"): &CertificateSigningRequestDescriber{c}, storage.Kind("StorageClass"): &StorageClassDescriber{c}, + policy.Kind("PodDisruptionBudget"): &PodDisruptionBudgetDescriber{c}, } return m @@ -2395,6 +2397,41 @@ func (s *StorageClassDescriber) Describe(namespace, name string, describerSettin }) } +type PodDisruptionBudgetDescriber struct { + clientset.Interface +} + +func (p *PodDisruptionBudgetDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) { + pdb, err := p.Policy().PodDisruptionBudgets(namespace).Get(name) + if err != nil { + return "", err + } + return tabbedString(func(out io.Writer) error { + fmt.Fprintf(out, "Name:\t%s\n", pdb.Name) + fmt.Fprintf(out, "Min available:\t%s\n", pdb.Spec.MinAvailable.String()) + if pdb.Spec.Selector != nil { + fmt.Fprintf(out, "Selector:\t%s\n", unversioned.FormatLabelSelector(pdb.Spec.Selector)) + } else { + fmt.Fprintf(out, "Selector:\t\n") + } + fmt.Fprintf(out, "Status:\n") + fmt.Fprintf(out, " Allowed disruptions:\t%d\n", pdb.Status.PodDisruptionsAllowed) + fmt.Fprintf(out, " Current:\t%d\n", pdb.Status.CurrentHealthy) + fmt.Fprintf(out, " Desired:\t%d\n", pdb.Status.DesiredHealthy) + fmt.Fprintf(out, " Total:\t%d\n", pdb.Status.ExpectedPods) + if describerSettings.ShowEvents { + events, err := p.Core().Events(namespace).Search(pdb) + if err != nil { + return err + } + if events != nil { + DescribeEvents(events, out) + } + } + return nil + }) +} + // newErrNoDescriber creates a new ErrNoDescriber with the names of the provided types. func newErrNoDescriber(types ...reflect.Type) error { names := make([]string, 0, len(types)) diff --git a/pkg/kubectl/describe_test.go b/pkg/kubectl/describe_test.go index b798cffaffbe3..9d7cca87d3b9d 100644 --- a/pkg/kubectl/describe_test.go +++ b/pkg/kubectl/describe_test.go @@ -31,9 +31,11 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/util/intstr" ) type describeClient struct { @@ -694,6 +696,30 @@ func TestDescribeStorageClass(t *testing.T) { } } +func TestDescribePodDisruptionBudget(t *testing.T) { + f := fake.NewSimpleClientset(&policy.PodDisruptionBudget{ + ObjectMeta: api.ObjectMeta{ + Namespace: "ns1", + Name: "pdb1", + CreationTimestamp: unversioned.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: policy.PodDisruptionBudgetSpec{ + MinAvailable: intstr.FromInt(22), + }, + Status: policy.PodDisruptionBudgetStatus{ + PodDisruptionsAllowed: 5, + }, + }) + s := PodDisruptionBudgetDescriber{f} + out, err := s.Describe("ns1", "pdb1", DescriberSettings{ShowEvents: true}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(out, "pdb1") { + t.Errorf("unexpected out: %s", out) + } +} + func TestDescribeEvents(t *testing.T) { events := &api.EventList{ diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index cb136f8d192fc..c294361b2736b 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -40,6 +40,7 @@ import ( "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/storage" storageutil "k8s.io/kubernetes/pkg/apis/storage/util" @@ -474,6 +475,7 @@ func (h *HumanReadablePrinter) AfterPrint(output io.Writer, res string) error { var ( podColumns = []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"} podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"} + podDisruptionBudgetColumns = []string{"NAME", "MIN-AVAILABLE", "ALLOWED-DISRUPTIONS", "AGE"} replicationControllerColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "AGE"} replicaSetColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "AGE"} jobColumns = []string{"NAME", "DESIRED", "SUCCESSFUL", "AGE"} @@ -536,6 +538,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() { h.Handler(podColumns, h.printPod) h.Handler(podTemplateColumns, printPodTemplate) h.Handler(podTemplateColumns, printPodTemplateList) + h.Handler(podDisruptionBudgetColumns, printPodDisruptionBudget) + h.Handler(podDisruptionBudgetColumns, printPodDisruptionBudgetList) h.Handler(replicationControllerColumns, printReplicationController) h.Handler(replicationControllerColumns, printReplicationControllerList) h.Handler(replicaSetColumns, printReplicaSet) @@ -828,6 +832,37 @@ func printPodTemplateList(podList *api.PodTemplateList, w io.Writer, options Pri return nil } +func printPodDisruptionBudget(pdb *policy.PodDisruptionBudget, w io.Writer, options PrintOptions) error { + // name, minavailable, selector + name := formatResourceName(options.Kind, pdb.Name, options.WithKind) + namespace := pdb.Namespace + + if options.WithNamespace { + if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil { + return err + } + } + if _, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", + name, + pdb.Spec.MinAvailable.String(), + pdb.Status.PodDisruptionsAllowed, + translateTimestamp(pdb.CreationTimestamp), + ); err != nil { + return err + } + + return nil +} + +func printPodDisruptionBudgetList(pdbList *policy.PodDisruptionBudgetList, w io.Writer, options PrintOptions) error { + for _, pdb := range pdbList.Items { + if err := printPodDisruptionBudget(&pdb, w, options); err != nil { + return err + } + } + return nil +} + // TODO(AdoHe): try to put wide output in a single method func printReplicationController(controller *api.ReplicationController, w io.Writer, options PrintOptions) error { name := formatResourceName(options.Kind, controller.Name, options.WithKind) diff --git a/pkg/kubectl/resource_printer_test.go b/pkg/kubectl/resource_printer_test.go index c18ead9e242f3..a368e965e06b8 100644 --- a/pkg/kubectl/resource_printer_test.go +++ b/pkg/kubectl/resource_printer_test.go @@ -33,6 +33,7 @@ import ( "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/policy" kubectltesting "k8s.io/kubernetes/pkg/kubectl/testing" "k8s.io/kubernetes/pkg/runtime" yamlserializer "k8s.io/kubernetes/pkg/runtime/serializer/yaml" @@ -1622,3 +1623,35 @@ func TestPrintService(t *testing.T) { buf.Reset() } } + +func TestPrintPodDisruptionBudget(t *testing.T) { + tests := []struct { + pdb policy.PodDisruptionBudget + expect string + }{ + { + policy.PodDisruptionBudget{ + ObjectMeta: api.ObjectMeta{ + Namespace: "ns1", + Name: "pdb1", + CreationTimestamp: unversioned.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: policy.PodDisruptionBudgetSpec{ + MinAvailable: intstr.FromInt(22), + }, + Status: policy.PodDisruptionBudgetStatus{ + PodDisruptionsAllowed: 5, + }, + }, + "pdb1\t22\t5\t0s\n", + }} + + buf := bytes.NewBuffer([]byte{}) + for _, test := range tests { + printPodDisruptionBudget(&test.pdb, buf, PrintOptions{false, false, false, false, true, false, false, "", []string{}}) + if buf.String() != test.expect { + t.Fatalf("Expected: %s, got: %s", test.expect, buf.String()) + } + buf.Reset() + } +}