Bumping k8s dependencies to 1.13

This commit is contained in:
Cheng Xing
2018-11-16 14:08:25 -08:00
parent 305407125c
commit b4c0b68ec7
8002 changed files with 884099 additions and 276228 deletions

View File

@@ -13,28 +13,23 @@ go_library(
"customcolumn_flags.go",
"humanreadable.go",
"interface.go",
"jsonpath.go",
"jsonpath_flags.go",
"kube_template_flags.go",
"tabwriter.go",
"template.go",
"template_flags.go",
],
importpath = "k8s.io/kubernetes/pkg/printers",
deps = [
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/printers:go_default_library",
"//staging/src/k8s.io/client-go/util/jsonpath:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
],
)
@@ -61,18 +56,15 @@ go_test(
"customcolumn_flags_test.go",
"customcolumn_test.go",
"humanreadable_test.go",
"jsonpath_flags_test.go",
"template_flags_test.go",
"template_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
],
)

View File

@@ -28,14 +28,14 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/client-go/util/jsonpath"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
)
var jsonRegexp = regexp.MustCompile("^\\{\\.?([^{}]+)\\}$|^\\.?([^{}]+)$")
// RelaxedJSONPathExpression attempts to be flexible with JSONPath expressions, it accepts:
// * metadata.name (no leading '.' or curly brances '{...}'
// * metadata.name (no leading '.' or curly braces '{...}'
// * {metadata.name} (no leading '.')
// * .metadata.name (no curly braces '{...}')
// * {.metadata.name} (complete expression)

View File

@@ -23,7 +23,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)

View File

@@ -26,7 +26,7 @@ import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func TestPrinterSupportsExpectedCustomColumnFormats(t *testing.T) {

View File

@@ -286,8 +286,8 @@ bar
},
},
obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
expectedOutput: `NAME API_VERSION
foo baz
expectedOutput: `NAME API_VERSION
foo baz
`,
},
{
@@ -306,8 +306,8 @@ foo baz
},
},
obj: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
expectedOutput: `NAME API_VERSION NOT_FOUND
foo baz <none>
expectedOutput: `NAME API_VERSION NOT_FOUND
foo baz <none>
`,
},
}
@@ -349,9 +349,9 @@ func TestIndividualPrintObjOnExistingTabWriter(t *testing.T) {
{ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{"label1": "foo", "label2": "foo"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{"label1": "bar", "label2": "bar"}}},
}
expectedOutput := `NAME LONG COLUMN NAME LABEL 2
foo foo foo
bar bar bar
expectedOutput := `NAME LONG COLUMN NAME LABEL 2
foo foo foo
bar bar bar
`
buffer := &bytes.Buffer{}

View File

@@ -114,17 +114,25 @@ func (h *HumanReadablePrinter) EnsurePrintHeaders() {
// See ValidatePrintHandlerFunc for required method signature.
func (h *HumanReadablePrinter) Handler(columns, columnsWithWide []string, printFunc interface{}) error {
var columnDefinitions []metav1beta1.TableColumnDefinition
for _, column := range columns {
for i, column := range columns {
format := ""
if i == 0 && strings.EqualFold(column, "name") {
format = "name"
}
columnDefinitions = append(columnDefinitions, metav1beta1.TableColumnDefinition{
Name: column,
Type: "string",
Name: column,
Description: column,
Type: "string",
Format: format,
})
}
for _, column := range columnsWithWide {
columnDefinitions = append(columnDefinitions, metav1beta1.TableColumnDefinition{
Name: column,
Type: "string",
Priority: 1,
Name: column,
Description: column,
Type: "string",
Priority: 1,
})
}
@@ -363,6 +371,11 @@ func PrintTable(table *metav1beta1.Table, output io.Writer, options PrintOptions
for _, row := range table.Rows {
first := true
for i, cell := range row.Cells {
if i >= len(table.ColumnDefinitions) {
// https://issue.k8s.io/66379
// don't panic in case of bad output from the server, with more cells than column definitions
break
}
column := table.ColumnDefinitions[i]
if !options.Wide && column.Priority != 0 {
continue
@@ -631,6 +644,13 @@ func (h *HumanReadablePrinter) legacyPrinterToTable(obj runtime.Object, handler
args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(buf), reflect.ValueOf(options)}
if meta.IsListType(obj) {
listInterface, ok := obj.(metav1.ListInterface)
if ok {
table.ListMeta.SelfLink = listInterface.GetSelfLink()
table.ListMeta.ResourceVersion = listInterface.GetResourceVersion()
table.ListMeta.Continue = listInterface.GetContinue()
}
// TODO: this uses more memory than it has to, as we refactor printers we should remove the need
// for this.
args[0] = reflect.ValueOf(obj)

View File

@@ -20,6 +20,7 @@ go_test(
"//pkg/apis/apps:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/coordination:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/networking:go_default_library",
@@ -27,25 +28,25 @@ go_test(
"//pkg/apis/storage:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
"//pkg/kubectl/genericclioptions/printers:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/util/pointer:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/yaml:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//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/genericclioptions/printers:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/yaml:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)
@@ -65,6 +66,7 @@ go_library(
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/coordination:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/core/helper/qos:go_default_library",
@@ -84,35 +86,36 @@ go_library(
"//pkg/registry/rbac/validation:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/util/slice:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",
"//staging/src/k8s.io/api/autoscaling/v2beta1:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_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/coordination/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1beta1:go_default_library",
"//staging/src/k8s.io/api/storage/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/duration:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//vendor/github.com/fatih/camelcase:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/autoscaling/v2beta1:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
"//vendor/k8s.io/api/storage/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/duration:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)

View File

@@ -44,6 +44,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/dynamic"
@@ -275,19 +276,22 @@ func printUnstructuredContent(w PrefixWriter, level int, content map[string]inte
func smartLabelFor(field string) string {
commonAcronyms := []string{"API", "URL", "UID", "OSB", "GUID"}
splitted := camelcase.Split(field)
for i := 0; i < len(splitted); i++ {
part := splitted[i]
parts := camelcase.Split(field)
result := make([]string, 0, len(parts))
for _, part := range parts {
if part == "_" {
continue
}
if slice.ContainsString(commonAcronyms, strings.ToUpper(part), nil) {
part = strings.ToUpper(part)
} else {
part = strings.Title(part)
}
splitted[i] = part
result = append(result, part)
}
return strings.Join(splitted, " ")
return strings.Join(result, " ")
}
// DefaultObjectDescriber can describe the default Kubernetes objects.
@@ -639,7 +643,7 @@ func describePod(pod *api.Pod, events *api.EventList) (string, error) {
printLabelsMultiline(w, "Labels", pod.Labels)
printAnnotationsMultiline(w, "Annotations", pod.Annotations)
if pod.DeletionTimestamp != nil {
w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestamp(*pod.DeletionTimestamp))
w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestampUntil(*pod.DeletionTimestamp))
w.Write(LEVEL_0, "Termination Grace Period:\t%ds\n", *pod.DeletionGracePeriodSeconds)
} else {
w.Write(LEVEL_0, "Status:\t%s\n", string(pod.Status.Phase))
@@ -1125,6 +1129,48 @@ func printCSIPersistentVolumeSource(csi *api.CSIPersistentVolumeSource, w Prefix
" VolumeHandle:\t%v\n"+
" ReadOnly:\t%v\n",
csi.Driver, csi.VolumeHandle, csi.ReadOnly)
printCSIPersistentVolumeAttributesMultiline(w, "VolumeAttributes", csi.VolumeAttributes)
}
func printCSIPersistentVolumeAttributesMultiline(w PrefixWriter, title string, annotations map[string]string) {
printCSIPersistentVolumeAttributesMultilineIndent(w, "", title, "\t", annotations, sets.NewString())
}
func printCSIPersistentVolumeAttributesMultilineIndent(w PrefixWriter, initialIndent, title, innerIndent string, attributes map[string]string, skip sets.String) {
w.Write(LEVEL_2, "%s%s:%s", initialIndent, title, innerIndent)
if len(attributes) == 0 {
w.WriteLine("<none>")
return
}
// to print labels in the sorted order
keys := make([]string, 0, len(attributes))
for key := range attributes {
if skip.Has(key) {
continue
}
keys = append(keys, key)
}
if len(attributes) == 0 {
w.WriteLine("<none>")
return
}
sort.Strings(keys)
for i, key := range keys {
if i != 0 {
w.Write(LEVEL_2, initialIndent)
w.Write(LEVEL_2, innerIndent)
}
line := fmt.Sprintf("%s=%s", key, attributes[key])
if len(line) > maxAnnotationLen {
w.Write(LEVEL_2, "%s...\n", line[:maxAnnotationLen])
} else {
w.Write(LEVEL_2, "%s\n", line)
}
i++
}
}
type PersistentVolumeDescriber struct {
@@ -1193,12 +1239,12 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) (
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", pv.Name)
printLabelsMultiline(w, "Labels", pv.Labels)
printAnnotationsMultiline(w, "Annotations", pv.Annotations)
printLabelsMultiline(w, "Labels", pv.ObjectMeta.Labels)
printAnnotationsMultiline(w, "Annotations", pv.ObjectMeta.Annotations)
w.Write(LEVEL_0, "Finalizers:\t%v\n", pv.ObjectMeta.Finalizers)
w.Write(LEVEL_0, "StorageClass:\t%s\n", helper.GetPersistentVolumeClass(pv))
if pv.ObjectMeta.DeletionTimestamp != nil {
w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestamp(*pv.ObjectMeta.DeletionTimestamp))
w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestampUntil(*pv.ObjectMeta.DeletionTimestamp))
} else {
w.Write(LEVEL_0, "Status:\t%v\n", pv.Status.Phase)
}
@@ -1287,19 +1333,59 @@ func (d *PersistentVolumeClaimDescriber) Describe(namespace, name string, descri
return "", err
}
pc := d.Core().Pods(namespace)
mountPods, err := getMountPods(pc, pvc.Name)
if err != nil {
return "", err
}
events, _ := d.Core().Events(namespace).Search(legacyscheme.Scheme, pvc)
return describePersistentVolumeClaim(pvc, events)
return describePersistentVolumeClaim(pvc, events, mountPods)
}
func describePersistentVolumeClaim(pvc *api.PersistentVolumeClaim, events *api.EventList) (string, error) {
func getMountPods(c coreclient.PodInterface, pvcName string) ([]api.Pod, error) {
nsPods, err := c.List(metav1.ListOptions{})
if err != nil {
return []api.Pod{}, err
}
var pods []api.Pod
for _, pod := range nsPods.Items {
pvcs := getPvcs(pod.Spec.Volumes)
for _, pvc := range pvcs {
if pvc.PersistentVolumeClaim.ClaimName == pvcName {
pods = append(pods, pod)
}
}
}
return pods, nil
}
func getPvcs(volumes []api.Volume) []api.Volume {
var pvcs []api.Volume
for _, volume := range volumes {
if volume.VolumeSource.PersistentVolumeClaim != nil {
pvcs = append(pvcs, volume)
}
}
return pvcs
}
func describePersistentVolumeClaim(pvc *api.PersistentVolumeClaim, events *api.EventList, mountPods []api.Pod) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", pvc.Name)
w.Write(LEVEL_0, "Namespace:\t%s\n", pvc.Namespace)
w.Write(LEVEL_0, "StorageClass:\t%s\n", helper.GetPersistentVolumeClaimClass(pvc))
if pvc.ObjectMeta.DeletionTimestamp != nil {
w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestamp(*pvc.ObjectMeta.DeletionTimestamp))
w.Write(LEVEL_0, "Status:\tTerminating (lasts %s)\n", translateTimestampUntil(*pvc.ObjectMeta.DeletionTimestamp))
} else {
w.Write(LEVEL_0, "Status:\t%v\n", pvc.Status.Phase)
}
@@ -1338,6 +1424,8 @@ func describePersistentVolumeClaim(pvc *api.PersistentVolumeClaim, events *api.E
DescribeEvents(events, w)
}
printPodsMultiline(w, "Mounted By", mountPods)
return nil
})
}
@@ -1423,13 +1511,17 @@ func describeContainerCommand(container api.Container, w PrefixWriter) {
if len(container.Command) > 0 {
w.Write(LEVEL_2, "Command:\n")
for _, c := range container.Command {
w.Write(LEVEL_3, "%s\n", c)
for _, s := range strings.Split(c, "\n") {
w.Write(LEVEL_3, "%s\n", s)
}
}
}
if len(container.Args) > 0 {
w.Write(LEVEL_2, "Args:\n")
for _, arg := range container.Args {
w.Write(LEVEL_3, "%s\n", arg)
for _, s := range strings.Split(arg, "\n") {
w.Write(LEVEL_3, "%s\n", s)
}
}
}
}
@@ -1512,7 +1604,13 @@ func describeContainerEnvVars(container api.Container, resolverFn EnvVarResolver
for _, e := range container.Env {
if e.ValueFrom == nil {
w.Write(LEVEL_3, "%s:\t%s\n", e.Name, e.Value)
for i, s := range strings.Split(e.Value, "\n") {
if i == 0 {
w.Write(LEVEL_3, "%s:\t%s\n", e.Name, s)
} else {
w.Write(LEVEL_3, "\t%s\n", s)
}
}
continue
}
@@ -1598,7 +1696,12 @@ type EnvVarResolverFunc func(e api.EnvVar) string
// EnvValueFrom is exported for use by describers in other packages
func EnvValueRetriever(pod *api.Pod) EnvVarResolverFunc {
return func(e api.EnvVar) string {
internalFieldPath, _, err := legacyscheme.Scheme.ConvertFieldLabel(e.ValueFrom.FieldRef.APIVersion, "Pod", e.ValueFrom.FieldRef.FieldPath, "")
gv, err := schema.ParseGroupVersion(e.ValueFrom.FieldRef.APIVersion)
if err != nil {
return ""
}
gvk := gv.WithKind("Pod")
internalFieldPath, _, err := legacyscheme.Scheme.ConvertFieldLabel(gvk, e.ValueFrom.FieldRef.FieldPath, "")
if err != nil {
return "" // pod validation should catch this on create
}
@@ -1852,6 +1955,12 @@ func describeJob(job *batch.Job, events *api.EventList) (string, error) {
if job.Status.StartTime != nil {
w.Write(LEVEL_0, "Start Time:\t%s\n", job.Status.StartTime.Time.Format(time.RFC1123Z))
}
if job.Status.CompletionTime != nil {
w.Write(LEVEL_0, "Completed At:\t%s\n", job.Status.CompletionTime.Time.Format(time.RFC1123Z))
}
if job.Status.StartTime != nil && job.Status.CompletionTime != nil {
w.Write(LEVEL_0, "Duration:\t%s\n", duration.HumanDuration(job.Status.CompletionTime.Sub(job.Status.StartTime.Time)))
}
if job.Spec.ActiveDeadlineSeconds != nil {
w.Write(LEVEL_0, "Active Deadline Seconds:\t%ds\n", *job.Spec.ActiveDeadlineSeconds)
}
@@ -2905,50 +3014,50 @@ func describeHorizontalPodAutoscaler(hpa *autoscaling.HorizontalPodAutoscaler, e
for i, metric := range hpa.Spec.Metrics {
switch metric.Type {
case autoscaling.ExternalMetricSourceType:
if metric.External.TargetAverageValue != nil {
if metric.External.Target.AverageValue != nil {
current := "<unknown>"
if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].External != nil &&
hpa.Status.CurrentMetrics[i].External.CurrentAverageValue != nil {
current = hpa.Status.CurrentMetrics[i].External.CurrentAverageValue.String()
&hpa.Status.CurrentMetrics[i].External.Current.AverageValue != nil {
current = hpa.Status.CurrentMetrics[i].External.Current.AverageValue.String()
}
w.Write(LEVEL_1, "%q (target average value):\t%s / %s\n", metric.External.MetricName, current, metric.External.TargetAverageValue.String())
w.Write(LEVEL_1, "%q (target average value):\t%s / %s\n", metric.External.Metric.Name, current, metric.External.Target.AverageValue.String())
} else {
current := "<unknown>"
if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].External != nil {
current = hpa.Status.CurrentMetrics[i].External.CurrentValue.String()
current = hpa.Status.CurrentMetrics[i].External.Current.Value.String()
}
w.Write(LEVEL_1, "%q (target value):\t%s / %s\n", metric.External.MetricName, current, metric.External.TargetValue.String())
w.Write(LEVEL_1, "%q (target value):\t%s / %s\n", metric.External.Metric.Name, current, metric.External.Target.Value.String())
}
case autoscaling.PodsMetricSourceType:
current := "<unknown>"
if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Pods != nil {
current = hpa.Status.CurrentMetrics[i].Pods.CurrentAverageValue.String()
current = hpa.Status.CurrentMetrics[i].Pods.Current.AverageValue.String()
}
w.Write(LEVEL_1, "%q on pods:\t%s / %s\n", metric.Pods.MetricName, current, metric.Pods.TargetAverageValue.String())
w.Write(LEVEL_1, "%q on pods:\t%s / %s\n", metric.Pods.Metric.Name, current, metric.Pods.Target.AverageValue.String())
case autoscaling.ObjectMetricSourceType:
current := "<unknown>"
if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Object != nil {
current = hpa.Status.CurrentMetrics[i].Object.CurrentValue.String()
current = hpa.Status.CurrentMetrics[i].Object.Current.Value.String()
}
w.Write(LEVEL_1, "%q on %s/%s:\t%s / %s\n", metric.Object.MetricName, metric.Object.Target.Kind, metric.Object.Target.Name, current, metric.Object.TargetValue.String())
w.Write(LEVEL_1, "%q on %s/%s:\t%s / %s\n", metric.Object.Metric.Name, metric.Object.DescribedObject.Kind, metric.Object.DescribedObject.Name, current, metric.Object.Target.Value.String())
case autoscaling.ResourceMetricSourceType:
w.Write(LEVEL_1, "resource %s on pods", string(metric.Resource.Name))
if metric.Resource.TargetAverageValue != nil {
if metric.Resource.Target.AverageValue != nil {
current := "<unknown>"
if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Resource != nil {
current = hpa.Status.CurrentMetrics[i].Resource.CurrentAverageValue.String()
current = hpa.Status.CurrentMetrics[i].Resource.Current.AverageValue.String()
}
w.Write(LEVEL_0, ":\t%s / %s\n", current, metric.Resource.TargetAverageValue.String())
w.Write(LEVEL_0, ":\t%s / %s\n", current, metric.Resource.Target.AverageValue.String())
} else {
current := "<unknown>"
if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Resource != nil && hpa.Status.CurrentMetrics[i].Resource.CurrentAverageUtilization != nil {
current = fmt.Sprintf("%d%% (%s)", *hpa.Status.CurrentMetrics[i].Resource.CurrentAverageUtilization, hpa.Status.CurrentMetrics[i].Resource.CurrentAverageValue.String())
if len(hpa.Status.CurrentMetrics) > i && hpa.Status.CurrentMetrics[i].Resource != nil && hpa.Status.CurrentMetrics[i].Resource.Current.AverageUtilization != nil {
current = fmt.Sprintf("%d%% (%s)", *hpa.Status.CurrentMetrics[i].Resource.Current.AverageUtilization, hpa.Status.CurrentMetrics[i].Resource.Current.AverageValue.String())
}
target := "<auto>"
if metric.Resource.TargetAverageUtilization != nil {
target = fmt.Sprintf("%d%%", *metric.Resource.TargetAverageUtilization)
if metric.Resource.Target.AverageUtilization != nil {
target = fmt.Sprintf("%d%%", *metric.Resource.Target.AverageUtilization)
}
w.Write(LEVEL_1, "(as a percentage of request):\t%s / %s\n", current, target)
}
@@ -3073,9 +3182,9 @@ func DescribeEvents(el *api.EventList, w PrefixWriter) {
for _, e := range el.Items {
var interval string
if e.Count > 1 {
interval = fmt.Sprintf("%s (x%d over %s)", translateTimestamp(e.LastTimestamp), e.Count, translateTimestamp(e.FirstTimestamp))
interval = fmt.Sprintf("%s (x%d over %s)", translateTimestampSince(e.LastTimestamp), e.Count, translateTimestampSince(e.FirstTimestamp))
} else {
interval = translateTimestamp(e.FirstTimestamp)
interval = translateTimestampSince(e.FirstTimestamp)
}
w.Write(LEVEL_1, "%v\t%v\t%s\t%v\t%v\n",
e.Type,
@@ -3407,6 +3516,9 @@ func describeStorageClass(sc *storage.StorageClass, events *api.EventList) (stri
if sc.VolumeBindingMode != nil {
w.Write(LEVEL_0, "VolumeBindingMode:\t%s\n", *sc.VolumeBindingMode)
}
if sc.AllowedTopologies != nil {
printAllowedTopologies(w, sc.AllowedTopologies)
}
if events != nil {
DescribeEvents(events, w)
}
@@ -3415,6 +3527,38 @@ func describeStorageClass(sc *storage.StorageClass, events *api.EventList) (stri
})
}
func printAllowedTopologies(w PrefixWriter, topologies []api.TopologySelectorTerm) {
w.Write(LEVEL_0, "AllowedTopologies:\t")
if len(topologies) == 0 {
w.WriteLine("<none>")
return
}
w.WriteLine("")
for i, term := range topologies {
printTopologySelectorTermsMultilineWithIndent(w, LEVEL_1, fmt.Sprintf("Term %d", i), "\t", term.MatchLabelExpressions)
}
}
func printTopologySelectorTermsMultilineWithIndent(w PrefixWriter, indentLevel int, title, innerIndent string, reqs []api.TopologySelectorLabelRequirement) {
w.Write(indentLevel, "%s:%s", title, innerIndent)
if len(reqs) == 0 {
w.WriteLine("<none>")
return
}
for i, req := range reqs {
if i != 0 {
w.Write(indentLevel, "%s", innerIndent)
}
exprStr := fmt.Sprintf("%s %s", req.Key, "in")
if len(req.Values) > 0 {
exprStr = fmt.Sprintf("%s [%s]", exprStr, strings.Join(req.Values, ", "))
}
w.Write(LEVEL_0, "%s\n", exprStr)
}
}
type PodDisruptionBudgetDescriber struct {
clientset.Interface
}
@@ -3530,6 +3674,12 @@ func describePodSecurityPolicy(psp *policy.PodSecurityPolicy) (string, error) {
if len(psp.Spec.AllowedFlexVolumes) > 0 {
w.Write(LEVEL_1, "Allowed FlexVolume Types:\t%s\n", flexVolumesToString(psp.Spec.AllowedFlexVolumes))
}
if len(psp.Spec.AllowedUnsafeSysctls) > 0 {
w.Write(LEVEL_1, "Allowed Unsafe Sysctls:\t%s\n", sysctlsToString(psp.Spec.AllowedUnsafeSysctls))
}
if len(psp.Spec.ForbiddenSysctls) > 0 {
w.Write(LEVEL_1, "Forbidden Sysctls:\t%s\n", sysctlsToString(psp.Spec.ForbiddenSysctls))
}
w.Write(LEVEL_1, "Allow Host Network:\t%t\n", psp.Spec.HostNetwork)
w.Write(LEVEL_1, "Allow Host Ports:\t%s\n", hostPortRangeToString(psp.Spec.HostPorts))
w.Write(LEVEL_1, "Allow Host PID:\t%t\n", psp.Spec.HostPID)
@@ -3589,6 +3739,10 @@ func flexVolumesToString(flexVolumes []policy.AllowedFlexVolume) string {
return stringOrDefaultValue(strings.Join(volumes, ","), "<all>")
}
func sysctlsToString(sysctls []string) string {
return stringOrNone(strings.Join(sysctls, ","))
}
func hostPortRangeToString(ranges []policy.HostPortRange) string {
formattedString := ""
if ranges != nil {
@@ -3847,6 +4001,37 @@ func printTaintsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerI
}
}
// printPodsMultiline prints multiple pods with a proper alignment.
func printPodsMultiline(w PrefixWriter, title string, pods []api.Pod) {
printPodsMultilineWithIndent(w, "", title, "\t", pods)
}
// printPodsMultilineWithIndent prints multiple pods with a user-defined alignment.
func printPodsMultilineWithIndent(w PrefixWriter, initialIndent, title, innerIndent string, pods []api.Pod) {
w.Write(LEVEL_0, "%s%s:%s", initialIndent, title, innerIndent)
if pods == nil || len(pods) == 0 {
w.WriteLine("<none>")
return
}
// to print pods in the sorted order
sort.Slice(pods, func(i, j int) bool {
cmpKey := func(pod api.Pod) string {
return pod.Name
}
return cmpKey(pods[i]) < cmpKey(pods[j])
})
for i, pod := range pods {
if i != 0 {
w.Write(LEVEL_0, "%s", initialIndent)
w.Write(LEVEL_0, "%s", innerIndent)
}
w.Write(LEVEL_0, "%s\n", pod.Name)
}
}
// printPodTolerationsMultiline prints multiple tolerations with a proper alignment.
func printPodTolerationsMultiline(w PrefixWriter, title string, tolerations []api.Toleration) {
printTolerationsMultilineWithIndent(w, "", title, "\t", tolerations)
@@ -3970,7 +4155,7 @@ func (list SortableVolumeDevices) Less(i, j int) bool {
return list[i].DevicePath < list[j].DevicePath
}
var maxAnnotationLen = 200
var maxAnnotationLen = 140
// printAnnotationsMultilineWithFilter prints filtered multiple annotations with a proper alignment.
func printAnnotationsMultilineWithFilter(w PrefixWriter, title string, annotations map[string]string, skip sets.String) {
@@ -4006,18 +4191,27 @@ func printAnnotationsMultilineWithIndent(w PrefixWriter, initialIndent, title, i
return
}
sort.Strings(keys)
indent := initialIndent + innerIndent
for i, key := range keys {
if i != 0 {
w.Write(LEVEL_0, initialIndent)
w.Write(LEVEL_0, innerIndent)
w.Write(LEVEL_0, indent)
}
line := fmt.Sprintf("%s=%s", key, annotations[key])
if len(line) > maxAnnotationLen {
w.Write(LEVEL_0, "%s...\n", line[:maxAnnotationLen])
value := strings.TrimSuffix(annotations[key], "\n")
if (len(value)+len(key)+2) > maxAnnotationLen || strings.Contains(value, "\n") {
w.Write(LEVEL_0, "%s:\n", key)
for _, s := range strings.Split(value, "\n") {
w.Write(LEVEL_0, "%s %s\n", indent, shorten(s, maxAnnotationLen-2))
}
} else {
w.Write(LEVEL_0, "%s\n", line)
w.Write(LEVEL_0, "%s: %s\n", key, value)
}
i++
}
}
func shorten(s string, maxLength int) string {
if len(s) > maxLength {
return s[:maxLength] + "..."
}
return s
}

View File

@@ -43,7 +43,7 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/printers"
utilpointer "k8s.io/kubernetes/pkg/util/pointer"
utilpointer "k8s.io/utils/pointer"
)
type describeClient struct {
@@ -54,10 +54,14 @@ type describeClient struct {
}
func TestDescribePod(t *testing.T) {
deletionTimestamp := metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0)}
gracePeriod := int64(1234)
fake := fake.NewSimpleClientset(&api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
Name: "bar",
Namespace: "foo",
DeletionTimestamp: &deletionTimestamp,
DeletionGracePeriodSeconds: &gracePeriod,
},
})
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
@@ -69,6 +73,9 @@ func TestDescribePod(t *testing.T) {
if !strings.Contains(out, "bar") || !strings.Contains(out, "Status:") {
t.Errorf("unexpected out: %s", out)
}
if !strings.Contains(out, "Terminating (lasts 10y)") || !strings.Contains(out, "1234s") {
t.Errorf("unexpected out: %s", out)
}
}
func TestDescribePodNode(t *testing.T) {
@@ -458,14 +465,12 @@ func VerifyDatesInOrder(
func TestDescribeContainers(t *testing.T) {
trueVal := true
testCases := []struct {
name string
container api.Container
status api.ContainerStatus
expectedElements []string
}{
// Running state.
{
name: "test1",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@@ -481,7 +486,6 @@ func TestDescribeContainers(t *testing.T) {
},
// Waiting state.
{
name: "test2",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@@ -497,7 +501,6 @@ func TestDescribeContainers(t *testing.T) {
},
// Terminated state.
{
name: "test3",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@@ -516,7 +519,6 @@ func TestDescribeContainers(t *testing.T) {
},
// Last Terminated
{
name: "test4",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@@ -540,7 +542,6 @@ func TestDescribeContainers(t *testing.T) {
},
// No state defaults to waiting.
{
name: "test5",
container: api.Container{Name: "test", Image: "image"},
status: api.ContainerStatus{
Name: "test",
@@ -551,7 +552,6 @@ func TestDescribeContainers(t *testing.T) {
},
// Env
{
name: "test6",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{ConfigMapRef: &api.ConfigMapEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}},
status: api.ContainerStatus{
Name: "test",
@@ -561,7 +561,6 @@ func TestDescribeContainers(t *testing.T) {
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: false"},
},
{
name: "test7",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{Prefix: "p_", ConfigMapRef: &api.ConfigMapEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}},
status: api.ContainerStatus{
Name: "test",
@@ -571,7 +570,6 @@ func TestDescribeContainers(t *testing.T) {
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap with prefix 'p_'\tOptional: false"},
},
{
name: "test8",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{ConfigMapRef: &api.ConfigMapEnvSource{Optional: &trueVal, LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}},
status: api.ContainerStatus{
Name: "test",
@@ -581,7 +579,6 @@ func TestDescribeContainers(t *testing.T) {
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: true"},
},
{
name: "test9",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{SecretRef: &api.SecretEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}, Optional: &trueVal}}}},
status: api.ContainerStatus{
Name: "test",
@@ -591,7 +588,6 @@ func TestDescribeContainers(t *testing.T) {
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tSecret\tOptional: true"},
},
{
name: "test10",
container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{Prefix: "p_", SecretRef: &api.SecretEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}},
status: api.ContainerStatus{
Name: "test",
@@ -602,7 +598,6 @@ func TestDescribeContainers(t *testing.T) {
},
// Command
{
name: "test11",
container: api.Container{Name: "test", Image: "image", Command: []string{"sleep", "1000"}},
status: api.ContainerStatus{
Name: "test",
@@ -611,9 +606,18 @@ func TestDescribeContainers(t *testing.T) {
},
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "sleep", "1000"},
},
// Command with newline
{
container: api.Container{Name: "test", Image: "image", Command: []string{"sleep", "1000\n2000"}},
status: api.ContainerStatus{
Name: "test",
Ready: true,
RestartCount: 7,
},
expectedElements: []string{"1000\n 2000"},
},
// Args
{
name: "test12",
container: api.Container{Name: "test", Image: "image", Args: []string{"time", "1000"}},
status: api.ContainerStatus{
Name: "test",
@@ -622,9 +626,18 @@ func TestDescribeContainers(t *testing.T) {
},
expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "time", "1000"},
},
// Args with newline
{
container: api.Container{Name: "test", Image: "image", Args: []string{"time", "1000\n2000"}},
status: api.ContainerStatus{
Name: "test",
Ready: true,
RestartCount: 7,
},
expectedElements: []string{"1000\n 2000"},
},
// Using limits.
{
name: "test13",
container: api.Container{
Name: "test",
Image: "image",
@@ -645,7 +658,6 @@ func TestDescribeContainers(t *testing.T) {
},
// Using requests.
{
name: "test14",
container: api.Container{
Name: "test",
Image: "image",
@@ -661,7 +673,6 @@ func TestDescribeContainers(t *testing.T) {
},
// volumeMounts read/write
{
name: "test15",
container: api.Container{
Name: "test",
Image: "image",
@@ -676,7 +687,6 @@ func TestDescribeContainers(t *testing.T) {
},
// volumeMounts readonly
{
name: "test16",
container: api.Container{
Name: "test",
Image: "image",
@@ -693,7 +703,6 @@ func TestDescribeContainers(t *testing.T) {
// volumeDevices
{
name: "test17",
container: api.Container{
Name: "test",
Image: "image",
@@ -709,7 +718,7 @@ func TestDescribeContainers(t *testing.T) {
}
for i, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
out := new(bytes.Buffer)
pod := api.Pod{
Spec: api.PodSpec{
@@ -892,6 +901,7 @@ func TestGetPodsTotalRequests(t *testing.T) {
func TestPersistentVolumeDescriber(t *testing.T) {
block := api.PersistentVolumeBlock
file := api.PersistentVolumeFilesystem
deletionTimestamp := metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0)}
testCases := []struct {
name string
plugin string
@@ -1137,6 +1147,96 @@ func TestPersistentVolumeDescriber(t *testing.T) {
"foo in [val1, val2]",
"foo exists"},
},
{
name: "test15",
plugin: "local",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
DeletionTimestamp: &deletionTimestamp,
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
Local: &api.LocalVolumeSource{},
},
},
},
expectedElements: []string{"Terminating (lasts 10y)"},
},
{
name: "test16",
plugin: "local",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
GenerateName: "test-GenerateName",
UID: "test-UID",
CreationTimestamp: metav1.Time{Time: time.Now()},
DeletionTimestamp: &metav1.Time{Time: time.Now()},
DeletionGracePeriodSeconds: new(int64),
Labels: map[string]string{"label1": "label1", "label2": "label2", "label3": "label3"},
Annotations: map[string]string{"annotation1": "annotation1", "annotation2": "annotation2", "annotation3": "annotation3"},
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
Local: &api.LocalVolumeSource{},
},
NodeAffinity: &api.VolumeNodeAffinity{
Required: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: []api.NodeSelectorRequirement{
{
Key: "foo",
Operator: "In",
Values: []string{"val1", "val2"},
},
{
Key: "foo",
Operator: "Exists",
},
},
},
},
},
},
},
},
expectedElements: []string{"Node Affinity", "Required Terms", "Term 0",
"foo in [val1, val2]",
"foo exists"},
},
{
name: "test17",
plugin: "local",
pv: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
GenerateName: "test-GenerateName",
UID: "test-UID",
CreationTimestamp: metav1.Time{Time: time.Now()},
DeletionTimestamp: &metav1.Time{Time: time.Now()},
DeletionGracePeriodSeconds: new(int64),
Labels: map[string]string{"label1": "label1", "label2": "label2", "label3": "label3"},
Annotations: map[string]string{"annotation1": "annotation1", "annotation2": "annotation2", "annotation3": "annotation3"},
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
CSI: &api.CSIPersistentVolumeSource{
Driver: "drive",
VolumeHandle: "handler",
ReadOnly: true,
VolumeAttributes: map[string]string{
"Attribute1": "Value1",
"Attribute2": "Value2",
"Attribute3": "Value3",
},
},
},
},
},
expectedElements: []string{"Driver", "VolumeHandle", "ReadOnly", "VolumeAttributes"},
},
}
for _, test := range testCases {
@@ -1169,6 +1269,7 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) {
file := api.PersistentVolumeFilesystem
goldClassName := "gold"
now := time.Now()
deletionTimestamp := metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0)}
testCases := []struct {
name string
pvc *api.PersistentVolumeClaim
@@ -1316,6 +1417,22 @@ func TestPersistentVolumeClaimDescriber(t *testing.T) {
},
expectedElements: []string{"Conditions", "Message", "User request resize"},
},
{
name: "deletion-timestamp",
pvc: &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
DeletionTimestamp: &deletionTimestamp,
},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume10",
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{},
},
expectedElements: []string{"Terminating (lasts 10y)"},
},
}
for _, test := range testCases {
@@ -1390,6 +1507,32 @@ func TestDescribeStorageClass(t *testing.T) {
},
ReclaimPolicy: &reclaimPolicy,
VolumeBindingMode: &bindingMode,
AllowedTopologies: []api.TopologySelectorTerm{
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "failure-domain.beta.kubernetes.io/zone",
Values: []string{"zone1"},
},
{
Key: "kubernetes.io/hostname",
Values: []string{"node1"},
},
},
},
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "failure-domain.beta.kubernetes.io/zone",
Values: []string{"zone2"},
},
{
Key: "kubernetes.io/hostname",
Values: []string{"node2"},
},
},
},
},
})
s := StorageClassDescriber{f}
out, err := s.Describe("", "foo", printers.DescriberSettings{ShowEvents: true})
@@ -1403,7 +1546,13 @@ func TestDescribeStorageClass(t *testing.T) {
!strings.Contains(out, "value1") ||
!strings.Contains(out, "value2") ||
!strings.Contains(out, "Retain") ||
!strings.Contains(out, "bindingmode") {
!strings.Contains(out, "bindingmode") ||
!strings.Contains(out, "failure-domain.beta.kubernetes.io/zone") ||
!strings.Contains(out, "zone1") ||
!strings.Contains(out, "kubernetes.io/hostname") ||
!strings.Contains(out, "node1") ||
!strings.Contains(out, "zone2") ||
!strings.Contains(out, "node2") {
t.Errorf("unexpected out: %s", out)
}
}
@@ -1440,6 +1589,10 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
minReplicasVal := int32(2)
targetUtilizationVal := int32(80)
currentUtilizationVal := int32(50)
metricLabelSelector, err := metav1.ParseToLabelSelector("label=value")
if err != nil {
t.Errorf("unable to parse label selector: %v", err)
}
tests := []struct {
name string
hpa autoscaling.HorizontalPodAutoscaler
@@ -1474,13 +1627,14 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@@ -1505,13 +1659,14 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@@ -1523,13 +1678,13 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricStatus{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
MetricName: "some-external-metric",
CurrentAverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
@@ -1550,13 +1705,14 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@@ -1581,13 +1737,14 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@@ -1599,13 +1756,13 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricStatus{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Current: autoscaling.MetricValueStatus{
Value: resource.NewMilliQuantity(50, resource.DecimalSI),
},
MetricName: "some-external-metric",
CurrentValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
@@ -1626,8 +1783,13 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "some-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -1652,8 +1814,13 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "some-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -1665,8 +1832,12 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricStatus{
MetricName: "some-pods-metric",
CurrentAverageValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@@ -1687,12 +1858,17 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ObjectMetricSourceType,
Object: &autoscaling.ObjectMetricSource{
Target: autoscaling.CrossVersionObjectReference{
DescribedObject: autoscaling.CrossVersionObjectReference{
Name: "some-service",
Kind: "Service",
},
MetricName: "some-service-metric",
TargetValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -1717,12 +1893,17 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ObjectMetricSourceType,
Object: &autoscaling.ObjectMetricSource{
Target: autoscaling.CrossVersionObjectReference{
DescribedObject: autoscaling.CrossVersionObjectReference{
Name: "some-service",
Kind: "Service",
},
MetricName: "some-service-metric",
TargetValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -1734,12 +1915,16 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ObjectMetricSourceType,
Object: &autoscaling.ObjectMetricStatus{
Target: autoscaling.CrossVersionObjectReference{
DescribedObject: autoscaling.CrossVersionObjectReference{
Name: "some-service",
Kind: "Service",
},
MetricName: "some-service-metric",
CurrentValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
},
Current: autoscaling.MetricValueStatus{
Value: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@@ -1760,8 +1945,11 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
Name: api.ResourceCPU,
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -1786,8 +1974,11 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
Name: api.ResourceCPU,
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -1799,8 +1990,10 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricStatus{
Name: api.ResourceCPU,
CurrentAverageValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Name: api.ResourceCPU,
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@@ -1822,7 +2015,10 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageUtilization: &targetUtilizationVal,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
},
@@ -1848,7 +2044,10 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageUtilization: &targetUtilizationVal,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
},
@@ -1861,8 +2060,10 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricStatus{
Name: api.ResourceCPU,
CurrentAverageUtilization: &currentUtilizationVal,
CurrentAverageValue: *resource.NewMilliQuantity(40, resource.DecimalSI),
Current: autoscaling.MetricValueStatus{
AverageUtilization: &currentUtilizationVal,
AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI),
},
},
},
},
@@ -1883,22 +2084,35 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "some-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageUtilization: &targetUtilizationVal,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "other-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(400, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "other-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(400, resource.DecimalSI),
},
},
},
},
@@ -1910,16 +2124,22 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricStatus{
MetricName: "some-pods-metric",
CurrentAverageValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricStatus{
Name: api.ResourceCPU,
CurrentAverageUtilization: &currentUtilizationVal,
CurrentAverageValue: *resource.NewMilliQuantity(40, resource.DecimalSI),
Current: autoscaling.MetricValueStatus{
AverageUtilization: &currentUtilizationVal,
AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI),
},
},
},
},
@@ -2101,22 +2321,31 @@ func TestDescribeEvents(t *testing.T) {
}
func TestPrintLabelsMultiline(t *testing.T) {
var maxLenAnnotationStr string = "MaxLenAnnotation=Multicast addressing can be used in the link layer (Layer 2 in the OSI model), such as Ethernet multicast, and at the internet layer (Layer 3 for OSI) for Internet Protocol Version 4 "
key := "MaxLenAnnotation"
value := strings.Repeat("a", maxAnnotationLen-len(key)-2)
testCases := []struct {
annotations map[string]string
expectPrint string
}{
{
annotations: map[string]string{"col1": "asd", "COL2": "zxc"},
expectPrint: "Annotations:\tCOL2=zxc\n\tcol1=asd\n",
expectPrint: "Annotations:\tCOL2: zxc\n\tcol1: asd\n",
},
{
annotations: map[string]string{"MaxLenAnnotation": maxLenAnnotationStr[17:]},
expectPrint: "Annotations:\t" + maxLenAnnotationStr + "\n",
annotations: map[string]string{"MaxLenAnnotation": value},
expectPrint: fmt.Sprintf("Annotations:\t%s: %s\n", key, value),
},
{
annotations: map[string]string{"MaxLenAnnotation": maxLenAnnotationStr[17:] + "1"},
expectPrint: "Annotations:\t" + maxLenAnnotationStr + "...\n",
annotations: map[string]string{"MaxLenAnnotation": value + "1"},
expectPrint: fmt.Sprintf("Annotations:\t%s:\n\t %s\n", key, value+"1"),
},
{
annotations: map[string]string{"MaxLenAnnotation": value + value},
expectPrint: fmt.Sprintf("Annotations:\t%s:\n\t %s\n", key, strings.Repeat("a", maxAnnotationLen-2)+"..."),
},
{
annotations: map[string]string{"key": "value\nwith\nnewlines\n"},
expectPrint: "Annotations:\tkey:\n\t value\n\t with\n\t newlines\n",
},
{
annotations: map[string]string{},
@@ -2124,13 +2353,13 @@ func TestPrintLabelsMultiline(t *testing.T) {
},
}
for i, testCase := range testCases {
t.Run(testCase.expectPrint, func(t *testing.T) {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
out := new(bytes.Buffer)
writer := NewPrefixWriter(out)
printAnnotationsMultiline(writer, "Annotations", testCase.annotations)
output := out.String()
if output != testCase.expectPrint {
t.Errorf("Test case %d: expected to find %q in output: %q", i, testCase.expectPrint, output)
t.Errorf("Test case %d: expected to match:\n%q\nin output:\n%q", i, testCase.expectPrint, output)
}
})
}
@@ -2228,6 +2457,8 @@ func TestDescribePodSecurityPolicy(t *testing.T) {
"Required Drop Capabilities:\\s*<none>",
"Allowed Capabilities:\\s*<none>",
"Allowed Volume Types:\\s*<none>",
"Allowed Unsafe Sysctls:\\s*kernel\\.\\*,net\\.ipv4.ip_local_port_range",
"Forbidden Sysctls:\\s*net\\.ipv4\\.ip_default_ttl",
"Allow Host Network:\\s*false",
"Allow Host Ports:\\s*<none>",
"Allow Host PID:\\s*false",
@@ -2248,6 +2479,8 @@ func TestDescribePodSecurityPolicy(t *testing.T) {
Name: "mypsp",
},
Spec: policy.PodSecurityPolicySpec{
AllowedUnsafeSysctls: []string{"kernel.*", "net.ipv4.ip_local_port_range"},
ForbiddenSysctls: []string{"net.ipv4.ip_default_ttl"},
SELinux: policy.SELinuxStrategyOptions{
Rule: policy.SELinuxStrategyRunAsAny,
},

View File

@@ -21,7 +21,6 @@ import (
"fmt"
"io"
"net"
"sort"
"strconv"
"strings"
"time"
@@ -31,6 +30,7 @@ import (
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
coordinationv1beta1 "k8s.io/api/coordination/v1beta1"
apiv1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
@@ -43,11 +43,11 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api/events"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/pkg/apis/coordination"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/extensions"
@@ -82,6 +82,7 @@ func AddHandlers(h printers.PrintHandler) {
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
{Name: "IP", Type: "string", Priority: 1, Description: apiv1.PodStatus{}.SwaggerDoc()["podIP"]},
{Name: "Node", Type: "string", Priority: 1, Description: apiv1.PodSpec{}.SwaggerDoc()["nodeName"]},
{Name: "Nominated Node", Type: "string", Priority: 1, Description: apiv1.PodStatus{}.SwaggerDoc()["nominatedNodeName"]},
}
h.TableHandler(podColumnDefinitions, printPodList)
h.TableHandler(podColumnDefinitions, printPod)
@@ -149,8 +150,8 @@ func AddHandlers(h printers.PrintHandler) {
jobColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Desired", Type: "integer", Description: batchv1.JobSpec{}.SwaggerDoc()["completions"]},
{Name: "Successful", Type: "integer", Description: batchv1.JobStatus{}.SwaggerDoc()["succeeded"]},
{Name: "Completions", Type: "string", Description: batchv1.JobStatus{}.SwaggerDoc()["succeeded"]},
{Name: "Duration", Type: "string", Description: "Time required to complete the job."},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
{Name: "Containers", Type: "string", Priority: 1, Description: "Names of each container in the template."},
{Name: "Images", Type: "string", Priority: 1, Description: "Images referenced by each container in the template."},
@@ -233,15 +234,15 @@ func AddHandlers(h printers.PrintHandler) {
eventColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Last Seen", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["lastTimestamp"]},
{Name: "First Seen", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["firstTimestamp"]},
{Name: "Count", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["count"]},
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Kind", Type: "string", Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["kind"]},
{Name: "Subobject", Type: "string", Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["fieldPath"]},
{Name: "Type", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["type"]},
{Name: "Reason", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["reason"]},
{Name: "Source", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["source"]},
{Name: "Kind", Type: "string", Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["kind"]},
{Name: "Source", Type: "string", Priority: 1, Description: apiv1.Event{}.SwaggerDoc()["source"]},
{Name: "Message", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["message"]},
{Name: "Subobject", Type: "string", Priority: 1, Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["fieldPath"]},
{Name: "First Seen", Type: "string", Priority: 1, Description: apiv1.Event{}.SwaggerDoc()["firstTimestamp"]},
{Name: "Count", Type: "string", Priority: 1, Description: apiv1.Event{}.SwaggerDoc()["count"]},
{Name: "Name", Type: "string", Priority: 1, Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
}
h.TableHandler(eventColumnDefinitions, printEvent)
h.TableHandler(eventColumnDefinitions, printEventList)
@@ -393,6 +394,14 @@ func AddHandlers(h printers.PrintHandler) {
h.TableHandler(certificateSigningRequestColumnDefinitions, printCertificateSigningRequest)
h.TableHandler(certificateSigningRequestColumnDefinitions, printCertificateSigningRequestList)
leaseColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Holder", Type: "string", Description: coordinationv1beta1.LeaseSpec{}.SwaggerDoc()["holderIdentity"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}
h.TableHandler(leaseColumnDefinitions, printLease)
h.TableHandler(leaseColumnDefinitions, printLeaseList)
storageClassColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Provisioner", Type: "string", Description: storagev1.StorageClass{}.SwaggerDoc()["provisioner"]},
@@ -457,7 +466,7 @@ func printObjectMeta(obj runtime.Object, options printers.PrintOptions) ([]metav
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, m.GetName(), translateTimestamp(m.GetCreationTimestamp()))
row.Cells = append(row.Cells, m.GetName(), translateTimestampSince(m.GetCreationTimestamp()))
rows = append(rows, row)
return rows, nil
}
@@ -473,19 +482,33 @@ func formatEndpoints(endpoints *api.Endpoints, ports sets.String) string {
count := 0
for i := range endpoints.Subsets {
ss := &endpoints.Subsets[i]
for i := range ss.Ports {
port := &ss.Ports[i]
if ports == nil || ports.Has(port.Name) {
for i := range ss.Addresses {
if len(list) == max {
more = true
if len(ss.Ports) == 0 {
// It's possible to have headless services with no ports.
for i := range ss.Addresses {
if len(list) == max {
more = true
}
if !more {
list = append(list, ss.Addresses[i].IP)
}
count++
}
} else {
// "Normal" services with ports defined.
for i := range ss.Ports {
port := &ss.Ports[i]
if ports == nil || ports.Has(port.Name) {
for i := range ss.Addresses {
if len(list) == max {
more = true
}
addr := &ss.Addresses[i]
if !more {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port.Port)))
list = append(list, hostPort)
}
count++
}
addr := &ss.Addresses[i]
if !more {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port.Port)))
list = append(list, hostPort)
}
count++
}
}
}
@@ -497,14 +520,24 @@ func formatEndpoints(endpoints *api.Endpoints, ports sets.String) string {
return ret
}
// translateTimestamp returns the elapsed time since timestamp in
// translateTimestampSince returns the elapsed time since timestamp in
// human-readable approximation.
func translateTimestamp(timestamp metav1.Time) string {
func translateTimestampSince(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}
return duration.ShortHumanDuration(time.Since(timestamp.Time))
return duration.HumanDuration(time.Since(timestamp.Time))
}
// translateTimestampUntil returns the elapsed time until timestamp in
// human-readable approximation.
func translateTimestampUntil(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}
return duration.HumanDuration(time.Until(timestamp.Time))
}
var (
@@ -608,10 +641,11 @@ func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1beta1.TableR
reason = "Terminating"
}
row.Cells = append(row.Cells, pod.Name, fmt.Sprintf("%d/%d", readyContainers, totalContainers), reason, int64(restarts), translateTimestamp(pod.CreationTimestamp))
row.Cells = append(row.Cells, pod.Name, fmt.Sprintf("%d/%d", readyContainers, totalContainers), reason, int64(restarts), translateTimestampSince(pod.CreationTimestamp))
if options.Wide {
nodeName := pod.Spec.NodeName
nominatedNodeName := pod.Status.NominatedNodeName
podIP := pod.Status.PodIP
if podIP == "" {
podIP = "<none>"
@@ -619,10 +653,10 @@ func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1beta1.TableR
if nodeName == "" {
nodeName = "<none>"
}
row.Cells = append(row.Cells, podIP, nodeName)
if len(pod.Status.NominatedNodeName) > 0 {
row.Cells = append(row.Cells, pod.Status.NominatedNodeName)
if nominatedNodeName == "" {
nominatedNodeName = "<none>"
}
row.Cells = append(row.Cells, podIP, nodeName, nominatedNodeName)
}
return []metav1beta1.TableRow{row}, nil
@@ -668,7 +702,7 @@ func printPodDisruptionBudget(obj *policy.PodDisruptionBudget, options printers.
maxUnavailable = "N/A"
}
row.Cells = append(row.Cells, obj.Name, minAvailable, maxUnavailable, int64(obj.Status.PodDisruptionsAllowed), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, minAvailable, maxUnavailable, int64(obj.Status.PodDisruptionsAllowed), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -694,7 +728,7 @@ func printReplicationController(obj *api.ReplicationController, options printers
currentReplicas := obj.Status.Replicas
readyReplicas := obj.Status.ReadyReplicas
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, labels.FormatLabels(obj.Spec.Selector))
@@ -723,7 +757,7 @@ func printReplicaSet(obj *extensions.ReplicaSet, options printers.PrintOptions)
currentReplicas := obj.Status.Replicas
readyReplicas := obj.Status.ReadyReplicas
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), int64(readyReplicas), translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, metav1.FormatLabelSelector(obj.Spec.Selector))
@@ -750,12 +784,28 @@ func printJob(obj *batch.Job, options printers.PrintOptions) ([]metav1beta1.Tabl
var completions string
if obj.Spec.Completions != nil {
completions = strconv.Itoa(int(*obj.Spec.Completions))
completions = fmt.Sprintf("%d/%d", obj.Status.Succeeded, *obj.Spec.Completions)
} else {
completions = "<none>"
parallelism := int32(0)
if obj.Spec.Parallelism != nil {
parallelism = *obj.Spec.Parallelism
}
if parallelism > 1 {
completions = fmt.Sprintf("%d/1 of %d", obj.Status.Succeeded, parallelism)
} else {
completions = fmt.Sprintf("%d/1", obj.Status.Succeeded)
}
}
var jobDuration string
switch {
case obj.Status.StartTime == nil:
case obj.Status.CompletionTime == nil:
jobDuration = duration.HumanDuration(time.Now().Sub(obj.Status.StartTime.Time))
default:
jobDuration = duration.HumanDuration(obj.Status.CompletionTime.Sub(obj.Status.StartTime.Time))
}
row.Cells = append(row.Cells, obj.Name, completions, int64(obj.Status.Succeeded), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, completions, jobDuration, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, metav1.FormatLabelSelector(obj.Spec.Selector))
@@ -782,10 +832,10 @@ func printCronJob(obj *batch.CronJob, options printers.PrintOptions) ([]metav1be
lastScheduleTime := "<none>"
if obj.Status.LastScheduleTime != nil {
lastScheduleTime = translateTimestamp(*obj.Status.LastScheduleTime)
lastScheduleTime = translateTimestampSince(*obj.Status.LastScheduleTime)
}
row.Cells = append(row.Cells, obj.Name, obj.Spec.Schedule, printBoolPtr(obj.Spec.Suspend), int64(len(obj.Status.Active)), lastScheduleTime, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, obj.Spec.Schedule, printBoolPtr(obj.Spec.Suspend), int64(len(obj.Status.Active)), lastScheduleTime, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.JobTemplate.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, metav1.FormatLabelSelector(obj.Spec.JobTemplate.Spec.Selector))
@@ -884,7 +934,7 @@ func printService(obj *api.Service, options printers.PrintOptions) ([]metav1beta
svcPorts = "<none>"
}
row.Cells = append(row.Cells, obj.Name, string(svcType), internalIP, externalIP, svcPorts, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, string(svcType), internalIP, externalIP, svcPorts, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
row.Cells = append(row.Cells, labels.FormatLabels(obj.Spec.Selector))
}
@@ -948,7 +998,7 @@ func printIngress(obj *extensions.Ingress, options printers.PrintOptions) ([]met
hosts := formatHosts(obj.Spec.Rules)
address := loadBalancerStatusStringer(obj.Status.LoadBalancer, options.Wide)
ports := formatPorts(obj.Spec.TLS)
createTime := translateTimestamp(obj.CreationTimestamp)
createTime := translateTimestampSince(obj.CreationTimestamp)
row.Cells = append(row.Cells, obj.Name, hosts, address, ports, createTime)
return []metav1beta1.TableRow{row}, nil
}
@@ -971,7 +1021,7 @@ func printStatefulSet(obj *apps.StatefulSet, options printers.PrintOptions) ([]m
}
desiredReplicas := obj.Spec.Replicas
currentReplicas := obj.Status.Replicas
createTime := translateTimestamp(obj.CreationTimestamp)
createTime := translateTimestampSince(obj.CreationTimestamp)
row.Cells = append(row.Cells, obj.Name, int64(desiredReplicas), int64(currentReplicas), createTime)
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
@@ -1003,7 +1053,7 @@ func printDaemonSet(obj *extensions.DaemonSet, options printers.PrintOptions) ([
numberUpdated := obj.Status.UpdatedNumberScheduled
numberAvailable := obj.Status.NumberAvailable
row.Cells = append(row.Cells, obj.Name, int64(desiredScheduled), int64(currentScheduled), int64(numberReady), int64(numberUpdated), int64(numberAvailable), labels.FormatLabels(obj.Spec.Template.Spec.NodeSelector), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(desiredScheduled), int64(currentScheduled), int64(numberReady), int64(numberUpdated), int64(numberAvailable), labels.FormatLabels(obj.Spec.Template.Spec.NodeSelector), translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
names, images := layoutContainerCells(obj.Spec.Template.Spec.Containers)
row.Cells = append(row.Cells, names, images, metav1.FormatLabelSelector(obj.Spec.Selector))
@@ -1027,7 +1077,7 @@ func printEndpoints(obj *api.Endpoints, options printers.PrintOptions) ([]metav1
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, formatEndpoints(obj, nil), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, formatEndpoints(obj, nil), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1047,7 +1097,7 @@ func printNamespace(obj *api.Namespace, options printers.PrintOptions) ([]metav1
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, string(obj.Status.Phase), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, string(obj.Status.Phase), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1067,7 +1117,7 @@ func printSecret(obj *api.Secret, options printers.PrintOptions) ([]metav1beta1.
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, string(obj.Type), int64(len(obj.Data)), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, string(obj.Type), int64(len(obj.Data)), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1087,7 +1137,7 @@ func printServiceAccount(obj *api.ServiceAccount, options printers.PrintOptions)
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Secrets)), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Secrets)), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1136,7 +1186,7 @@ func printNode(obj *api.Node, options printers.PrintOptions) ([]metav1beta1.Tabl
roles = "<none>"
}
row.Cells = append(row.Cells, obj.Name, strings.Join(status, ","), roles, translateTimestamp(obj.CreationTimestamp), obj.Status.NodeInfo.KubeletVersion)
row.Cells = append(row.Cells, obj.Name, strings.Join(status, ","), roles, translateTimestampSince(obj.CreationTimestamp), obj.Status.NodeInfo.KubeletVersion)
if options.Wide {
osImage, kernelVersion, crVersion := obj.Status.NodeInfo.OSImage, obj.Status.NodeInfo.KernelVersion, obj.Status.NodeInfo.ContainerRuntimeVersion
if osImage == "" {
@@ -1234,7 +1284,7 @@ func printPersistentVolume(obj *api.PersistentVolume, options printers.PrintOpti
row.Cells = append(row.Cells, obj.Name, aSize, modesStr, reclaimPolicyStr,
string(phase), claimRefUID, helper.GetPersistentVolumeClass(obj),
obj.Status.Reason,
translateTimestamp(obj.CreationTimestamp))
translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1269,7 +1319,7 @@ func printPersistentVolumeClaim(obj *api.PersistentVolumeClaim, options printers
capacity = storage.String()
}
row.Cells = append(row.Cells, obj.Name, string(phase), obj.Spec.VolumeName, capacity, accessModes, helper.GetPersistentVolumeClaimClass(obj), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, string(phase), obj.Spec.VolumeName, capacity, accessModes, helper.GetPersistentVolumeClaimClass(obj), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1290,25 +1340,42 @@ func printEvent(obj *api.Event, options printers.PrintOptions) ([]metav1beta1.Ta
Object: runtime.RawExtension{Object: obj},
}
// While watching event, we should print absolute time.
var FirstTimestamp, LastTimestamp string
var firstTimestamp, lastTimestamp string
if options.AbsoluteTimestamps {
FirstTimestamp = obj.FirstTimestamp.String()
LastTimestamp = obj.LastTimestamp.String()
firstTimestamp = obj.FirstTimestamp.String()
lastTimestamp = obj.LastTimestamp.String()
} else {
FirstTimestamp = translateTimestamp(obj.FirstTimestamp)
LastTimestamp = translateTimestamp(obj.LastTimestamp)
firstTimestamp = translateTimestampSince(obj.FirstTimestamp)
lastTimestamp = translateTimestampSince(obj.LastTimestamp)
}
if options.Wide {
row.Cells = append(row.Cells,
lastTimestamp,
obj.Type,
obj.Reason,
obj.InvolvedObject.Kind,
formatEventSource(obj.Source),
strings.TrimSpace(obj.Message),
obj.InvolvedObject.FieldPath,
firstTimestamp,
int64(obj.Count),
obj.Name,
)
} else {
row.Cells = append(row.Cells,
lastTimestamp,
obj.Type,
obj.Reason,
obj.InvolvedObject.Kind,
strings.TrimSpace(obj.Message),
)
}
row.Cells = append(row.Cells, LastTimestamp, FirstTimestamp,
int64(obj.Count), obj.Name, obj.InvolvedObject.Kind,
obj.InvolvedObject.FieldPath, obj.Type, obj.Reason,
formatEventSource(obj.Source), obj.Message)
return []metav1beta1.TableRow{row}, nil
}
// Sorts and prints the EventList in a human-friendly format.
func printEventList(list *api.EventList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
sort.Sort(events.SortableEvents(list.Items))
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printEvent(&list.Items[i], options)
@@ -1325,7 +1392,7 @@ func printRoleBinding(obj *rbac.RoleBinding, options printers.PrintOptions) ([]m
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
roleRef := fmt.Sprintf("%s/%s", obj.RoleRef.Kind, obj.RoleRef.Name)
users, groups, sas, _ := rbac.SubjectsStrings(obj.Subjects)
@@ -1352,7 +1419,7 @@ func printClusterRoleBinding(obj *rbac.ClusterRoleBinding, options printers.Prin
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, translateTimestampSince(obj.CreationTimestamp))
if options.Wide {
roleRef := fmt.Sprintf("%s/%s", obj.RoleRef.Kind, obj.RoleRef.Name)
users, groups, sas, _ := rbac.SubjectsStrings(obj.Subjects)
@@ -1382,7 +1449,7 @@ func printCertificateSigningRequest(obj *certificates.CertificateSigningRequest,
if err != nil {
return nil, err
}
row.Cells = append(row.Cells, obj.Name, translateTimestamp(obj.CreationTimestamp), obj.Spec.Username, status)
row.Cells = append(row.Cells, obj.Name, translateTimestampSince(obj.CreationTimestamp), obj.Spec.Username, status)
return []metav1beta1.TableRow{row}, nil
}
@@ -1475,7 +1542,7 @@ func printDeployment(obj *extensions.Deployment, options printers.PrintOptions)
currentReplicas := obj.Status.Replicas
updatedReplicas := obj.Status.UpdatedReplicas
availableReplicas := obj.Status.AvailableReplicas
age := translateTimestamp(obj.CreationTimestamp)
age := translateTimestampSince(obj.CreationTimestamp)
containers := obj.Spec.Template.Spec.Containers
selector, err := metav1.LabelSelectorAsSelector(obj.Spec.Selector)
if err != nil {
@@ -1513,47 +1580,47 @@ func formatHPAMetrics(specs []autoscaling.MetricSpec, statuses []autoscaling.Met
for i, spec := range specs {
switch spec.Type {
case autoscaling.ExternalMetricSourceType:
if spec.External.TargetAverageValue != nil {
if spec.External.Target.AverageValue != nil {
current := "<unknown>"
if len(statuses) > i && statuses[i].External != nil && statuses[i].External.CurrentAverageValue != nil {
current = statuses[i].External.CurrentAverageValue.String()
if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.Current.AverageValue != nil {
current = statuses[i].External.Current.AverageValue.String()
}
list = append(list, fmt.Sprintf("%s/%s (avg)", current, spec.External.TargetAverageValue.String()))
list = append(list, fmt.Sprintf("%s/%s (avg)", current, spec.External.Target.AverageValue.String()))
} else {
current := "<unknown>"
if len(statuses) > i && statuses[i].External != nil {
current = statuses[i].External.CurrentValue.String()
current = statuses[i].External.Current.Value.String()
}
list = append(list, fmt.Sprintf("%s/%s", current, spec.External.TargetValue.String()))
list = append(list, fmt.Sprintf("%s/%s", current, spec.External.Target.Value.String()))
}
case autoscaling.PodsMetricSourceType:
current := "<unknown>"
if len(statuses) > i && statuses[i].Pods != nil {
current = statuses[i].Pods.CurrentAverageValue.String()
current = statuses[i].Pods.Current.AverageValue.String()
}
list = append(list, fmt.Sprintf("%s/%s", current, spec.Pods.TargetAverageValue.String()))
list = append(list, fmt.Sprintf("%s/%s", current, spec.Pods.Target.AverageValue.String()))
case autoscaling.ObjectMetricSourceType:
current := "<unknown>"
if len(statuses) > i && statuses[i].Object != nil {
current = statuses[i].Object.CurrentValue.String()
current = statuses[i].Object.Current.Value.String()
}
list = append(list, fmt.Sprintf("%s/%s", current, spec.Object.TargetValue.String()))
list = append(list, fmt.Sprintf("%s/%s", current, spec.Object.Target.Value.String()))
case autoscaling.ResourceMetricSourceType:
if spec.Resource.TargetAverageValue != nil {
if spec.Resource.Target.AverageValue != nil {
current := "<unknown>"
if len(statuses) > i && statuses[i].Resource != nil {
current = statuses[i].Resource.CurrentAverageValue.String()
current = statuses[i].Resource.Current.AverageValue.String()
}
list = append(list, fmt.Sprintf("%s/%s", current, spec.Resource.TargetAverageValue.String()))
list = append(list, fmt.Sprintf("%s/%s", current, spec.Resource.Target.AverageValue.String()))
} else {
current := "<unknown>"
if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.CurrentAverageUtilization != nil {
current = fmt.Sprintf("%d%%", *statuses[i].Resource.CurrentAverageUtilization)
if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.Current.AverageUtilization != nil {
current = fmt.Sprintf("%d%%", *statuses[i].Resource.Current.AverageUtilization)
}
target := "<auto>"
if spec.Resource.TargetAverageUtilization != nil {
target = fmt.Sprintf("%d%%", *spec.Resource.TargetAverageUtilization)
if spec.Resource.Target.AverageUtilization != nil {
target = fmt.Sprintf("%d%%", *spec.Resource.Target.AverageUtilization)
}
list = append(list, fmt.Sprintf("%s/%s", current, target))
}
@@ -1591,7 +1658,7 @@ func printHorizontalPodAutoscaler(obj *autoscaling.HorizontalPodAutoscaler, opti
}
maxPods := obj.Spec.MaxReplicas
currentReplicas := obj.Status.CurrentReplicas
row.Cells = append(row.Cells, obj.Name, reference, metrics, minPods, int64(maxPods), int64(currentReplicas), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, reference, metrics, minPods, int64(maxPods), int64(currentReplicas), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1611,7 +1678,7 @@ func printConfigMap(obj *api.ConfigMap, options printers.PrintOptions) ([]metav1
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Data)), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, int64(len(obj.Data)), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1664,7 +1731,7 @@ func printNetworkPolicy(obj *networking.NetworkPolicy, options printers.PrintOpt
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells = append(row.Cells, obj.Name, metav1.FormatLabelSelector(&obj.Spec.PodSelector), translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, obj.Name, metav1.FormatLabelSelector(&obj.Spec.PodSelector), translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1690,7 +1757,7 @@ func printStorageClass(obj *storage.StorageClass, options printers.PrintOptions)
name += " (default)"
}
provtype := obj.Provisioner
row.Cells = append(row.Cells, name, provtype, translateTimestamp(obj.CreationTimestamp))
row.Cells = append(row.Cells, name, provtype, translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
@@ -1707,6 +1774,31 @@ func printStorageClassList(list *storage.StorageClassList, options printers.Prin
return rows, nil
}
func printLease(obj *coordination.Lease, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
var holderIdentity string
if obj.Spec.HolderIdentity != nil {
holderIdentity = *obj.Spec.HolderIdentity
}
row.Cells = append(row.Cells, obj.Name, holderIdentity, translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
func printLeaseList(list *coordination.LeaseList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printLease(&list.Items[i], options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}
func printStatus(obj *metav1.Status, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
@@ -1776,7 +1868,7 @@ func printControllerRevision(obj *apps.ControllerRevision, options printers.Prin
controllerName = printers.FormatResourceName(gvk.GroupKind(), controllerRef.Name, withKind)
}
revision := obj.Revision
age := translateTimestamp(obj.CreationTimestamp)
age := translateTimestampSince(obj.CreationTimestamp)
row.Cells = append(row.Cells, obj.Name, controllerName, revision, age)
return []metav1beta1.TableRow{row}, nil
}

View File

@@ -41,17 +41,18 @@ import (
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
genericprinters "k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/coordination"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/apis/storage"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
genericprinters "k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
"k8s.io/kubernetes/pkg/printers"
)
@@ -337,7 +338,7 @@ func TestUnknownTypePrinting(t *testing.T) {
func TestTemplatePanic(t *testing.T) {
tmpl := `{{and ((index .currentState.info "foo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}`
printer, err := printers.NewGoTemplatePrinter([]byte(tmpl))
printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
@@ -502,7 +503,7 @@ func TestTemplateStrings(t *testing.T) {
}
// The point of this test is to verify that the below template works.
tmpl := `{{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "foo") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}`
printer, err := printers.NewGoTemplatePrinter([]byte(tmpl))
printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
@@ -534,17 +535,17 @@ func TestPrinters(t *testing.T) {
jsonpathPrinter printers.ResourcePrinter
)
templatePrinter, err = printers.NewGoTemplatePrinter([]byte("{{.name}}"))
templatePrinter, err = genericprinters.NewGoTemplatePrinter([]byte("{{.name}}"))
if err != nil {
t.Fatal(err)
}
templatePrinter2, err = printers.NewGoTemplatePrinter([]byte("{{len .items}}"))
templatePrinter2, err = genericprinters.NewGoTemplatePrinter([]byte("{{len .items}}"))
if err != nil {
t.Fatal(err)
}
jsonpathPrinter, err = printers.NewJSONPathPrinter("{.metadata.name}")
jsonpathPrinter, err = genericprinters.NewJSONPathPrinter("{.metadata.name}")
if err != nil {
t.Fatal(err)
}
@@ -1151,6 +1152,10 @@ func TestPrintHumanReadableService(t *testing.T) {
Port: 8000,
Protocol: "TCP",
},
{
Port: 7777,
Protocol: "SCTP",
},
},
},
},
@@ -1685,7 +1690,7 @@ func TestPrintPodwide(t *testing.T) {
},
},
},
[]metav1beta1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", int64(6), "<unknown>", "<none>", "<none>"}}},
[]metav1beta1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", int64(6), "<unknown>", "<none>", "<none>", "<none>"}}},
},
}
@@ -1918,18 +1923,43 @@ type stringTestList []struct {
name, got, exp string
}
func TestTranslateTimestamp(t *testing.T) {
func TestTranslateTimestampSince(t *testing.T) {
tl := stringTestList{
{"a while from now", translateTimestamp(metav1.Time{Time: time.Now().Add(2.1e9)}), "<invalid>"},
{"almost now", translateTimestamp(metav1.Time{Time: time.Now().Add(1.9e9)}), "0s"},
{"now", translateTimestamp(metav1.Time{Time: time.Now()}), "0s"},
{"unknown", translateTimestamp(metav1.Time{}), "<unknown>"},
{"30 seconds ago", translateTimestamp(metav1.Time{Time: time.Now().Add(-3e10)}), "30s"},
{"5 minutes ago", translateTimestamp(metav1.Time{Time: time.Now().Add(-3e11)}), "5m"},
{"an hour ago", translateTimestamp(metav1.Time{Time: time.Now().Add(-6e12)}), "1h"},
{"2 days ago", translateTimestamp(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}), "2d"},
{"months ago", translateTimestamp(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -90)}), "90d"},
{"10 years ago", translateTimestamp(metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}), "10y"},
{"a while from now", translateTimestampSince(metav1.Time{Time: time.Now().Add(2.1e9)}), "<invalid>"},
{"almost now", translateTimestampSince(metav1.Time{Time: time.Now().Add(1.9e9)}), "0s"},
{"now", translateTimestampSince(metav1.Time{Time: time.Now()}), "0s"},
{"unknown", translateTimestampSince(metav1.Time{}), "<unknown>"},
{"30 seconds ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e10)}), "30s"},
{"5 minutes ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e11)}), "5m"},
{"an hour ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-6e12)}), "100m"},
{"2 days ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}), "2d"},
{"months ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -90)}), "90d"},
{"10 years ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}), "10y"},
}
for _, test := range tl {
if test.got != test.exp {
t.Errorf("On %v, expected '%v', but got '%v'",
test.name, test.exp, test.got)
}
}
}
func TestTranslateTimestampUntil(t *testing.T) {
// Since this method compares the time with time.Now() internally,
// small buffers of 0.1 seconds are added on comparing times to consider method call overhead.
// Otherwise, the output strings become shorter than expected.
const buf = 1e8
tl := stringTestList{
{"a while ago", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-2.1e9)}), "<invalid>"},
{"almost now", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-1.9e9)}), "0s"},
{"now", translateTimestampUntil(metav1.Time{Time: time.Now()}), "0s"},
{"unknown", translateTimestampUntil(metav1.Time{}), "<unknown>"},
{"in 30 seconds", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e10 + buf)}), "30s"},
{"in 5 minutes", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e11 + buf)}), "5m"},
{"in an hour", translateTimestampUntil(metav1.Time{Time: time.Now().Add(6e12 + buf)}), "100m"},
{"in 2 days", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 2).Add(buf)}), "2d"},
{"in months", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 90).Add(buf)}), "90d"},
{"in 10 years", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0).Add(buf)}), "10y"},
}
for _, test := range tl {
if test.got != test.exp {
@@ -2054,6 +2084,7 @@ func TestPrintDaemonSet(t *testing.T) {
}
func TestPrintJob(t *testing.T) {
now := time.Now()
completions := int32(2)
tests := []struct {
job batch.Job
@@ -2072,7 +2103,7 @@ func TestPrintJob(t *testing.T) {
Succeeded: 1,
},
},
"job1\t2\t1\t0s\n",
"job1\t1/2\t\t0s\n",
},
{
batch.Job{
@@ -2087,7 +2118,40 @@ func TestPrintJob(t *testing.T) {
Succeeded: 0,
},
},
"job2\t<none>\t0\t10y\n",
"job2\t0/1\t\t10y\n",
},
{
batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job3",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: batch.JobSpec{
Completions: nil,
},
Status: batch.JobStatus{
Succeeded: 0,
StartTime: &metav1.Time{Time: now.Add(time.Minute)},
CompletionTime: &metav1.Time{Time: now.Add(31 * time.Minute)},
},
},
"job3\t0/1\t30m\t10y\n",
},
{
batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job4",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: batch.JobSpec{
Completions: nil,
},
Status: batch.JobStatus{
Succeeded: 0,
StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)},
},
},
"job4\t0/1\t20m\t10y\n",
},
}
@@ -2112,6 +2176,10 @@ func TestPrintHPA(t *testing.T) {
minReplicasVal := int32(2)
targetUtilizationVal := int32(80)
currentUtilizationVal := int32(50)
metricLabelSelector, err := metav1.ParseToLabelSelector("label=value")
if err != nil {
t.Errorf("unable to parse label selector: %v", err)
}
tests := []struct {
hpa autoscaling.HorizontalPodAutoscaler
expected string
@@ -2149,13 +2217,14 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@@ -2182,13 +2251,14 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@@ -2200,13 +2270,13 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricStatus{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
MetricName: "some-external-metric",
CurrentAverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
@@ -2229,13 +2299,14 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-service-metric",
TargetValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@@ -2262,13 +2333,14 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricSource{
MetricSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label": "value",
},
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
Selector: metricLabelSelector,
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
MetricName: "some-external-metric",
TargetValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
@@ -2280,8 +2352,12 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ExternalMetricSourceType,
External: &autoscaling.ExternalMetricStatus{
MetricName: "some-external-metric",
CurrentValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-external-metric",
},
Current: autoscaling.MetricValueStatus{
Value: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@@ -2304,8 +2380,13 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "some-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -2332,8 +2413,13 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "some-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -2345,8 +2431,12 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricStatus{
MetricName: "some-pods-metric",
CurrentAverageValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@@ -2369,12 +2459,17 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ObjectMetricSourceType,
Object: &autoscaling.ObjectMetricSource{
Target: autoscaling.CrossVersionObjectReference{
DescribedObject: autoscaling.CrossVersionObjectReference{
Name: "some-service",
Kind: "Service",
},
MetricName: "some-service-metric",
TargetValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -2401,12 +2496,17 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ObjectMetricSourceType,
Object: &autoscaling.ObjectMetricSource{
Target: autoscaling.CrossVersionObjectReference{
DescribedObject: autoscaling.CrossVersionObjectReference{
Name: "some-service",
Kind: "Service",
},
MetricName: "some-service-metric",
TargetValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.ValueMetricType,
Value: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -2418,12 +2518,16 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ObjectMetricSourceType,
Object: &autoscaling.ObjectMetricStatus{
Target: autoscaling.CrossVersionObjectReference{
DescribedObject: autoscaling.CrossVersionObjectReference{
Name: "some-service",
Kind: "Service",
},
MetricName: "some-service-metric",
CurrentValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-service-metric",
},
Current: autoscaling.MetricValueStatus{
Value: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@@ -2446,8 +2550,11 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
Name: api.ResourceCPU,
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -2474,8 +2581,11 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
Name: api.ResourceCPU,
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
},
@@ -2487,8 +2597,10 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricStatus{
Name: api.ResourceCPU,
CurrentAverageValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Name: api.ResourceCPU,
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
},
@@ -2512,7 +2624,10 @@ func TestPrintHPA(t *testing.T) {
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageUtilization: &targetUtilizationVal,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
},
@@ -2540,7 +2655,10 @@ func TestPrintHPA(t *testing.T) {
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageUtilization: &targetUtilizationVal,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
},
@@ -2553,8 +2671,10 @@ func TestPrintHPA(t *testing.T) {
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricStatus{
Name: api.ResourceCPU,
CurrentAverageUtilization: &currentUtilizationVal,
CurrentAverageValue: *resource.NewMilliQuantity(40, resource.DecimalSI),
Current: autoscaling.MetricValueStatus{
AverageUtilization: &currentUtilizationVal,
AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI),
},
},
},
},
@@ -2577,22 +2697,35 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "some-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(100, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
},
},
},
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
TargetAverageUtilization: &targetUtilizationVal,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricSource{
MetricName: "other-pods-metric",
TargetAverageValue: *resource.NewMilliQuantity(400, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "other-pods-metric",
},
Target: autoscaling.MetricTarget{
Type: autoscaling.AverageValueMetricType,
AverageValue: resource.NewMilliQuantity(400, resource.DecimalSI),
},
},
},
},
@@ -2604,16 +2737,22 @@ func TestPrintHPA(t *testing.T) {
{
Type: autoscaling.PodsMetricSourceType,
Pods: &autoscaling.PodsMetricStatus{
MetricName: "some-pods-metric",
CurrentAverageValue: *resource.NewMilliQuantity(50, resource.DecimalSI),
Metric: autoscaling.MetricIdentifier{
Name: "some-pods-metric",
},
Current: autoscaling.MetricValueStatus{
AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
},
},
},
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricStatus{
Name: api.ResourceCPU,
CurrentAverageUtilization: &currentUtilizationVal,
CurrentAverageValue: *resource.NewMilliQuantity(40, resource.DecimalSI),
Current: autoscaling.MetricValueStatus{
AverageUtilization: &currentUtilizationVal,
AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI),
},
},
},
},
@@ -3299,6 +3438,55 @@ func TestPrintStorageClass(t *testing.T) {
}
}
func TestPrintLease(t *testing.T) {
holder1 := "holder1"
holder2 := "holder2"
tests := []struct {
sc coordination.Lease
expect string
}{
{
coordination.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: "lease1",
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Spec: coordination.LeaseSpec{
HolderIdentity: &holder1,
},
},
"lease1\tholder1\t0s\n",
},
{
coordination.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: "lease2",
CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
},
Spec: coordination.LeaseSpec{
HolderIdentity: &holder2,
},
},
"lease2\tholder2\t5m\n",
},
}
buf := bytes.NewBuffer([]byte{})
for _, test := range tests {
table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.sc, printers.PrintOptions{})
if err != nil {
t.Fatal(err)
}
if err := printers.PrintTable(table, buf, printers.PrintOptions{NoHeaders: true}); err != nil {
t.Fatal(err)
}
if buf.String() != test.expect {
t.Fatalf("Expected: %s, got: %s", test.expect, buf.String())
}
buf.Reset()
}
}
func verifyTable(t *testing.T, table *metav1beta1.Table) {
var panicErr interface{}
func() {

View File

@@ -1,159 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"encoding/json"
"fmt"
"io"
"reflect"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/util/jsonpath"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
)
// exists returns true if it would be possible to call the index function
// with these arguments.
//
// TODO: how to document this for users?
//
// index returns the result of indexing its first argument by the following
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
// indexed item must be a map, slice, or array.
func exists(item interface{}, indices ...interface{}) bool {
v := reflect.ValueOf(item)
for _, i := range indices {
index := reflect.ValueOf(i)
var isNil bool
if v, isNil = indirect(v); isNil {
return false
}
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
var x int64
switch index.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x = index.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
x = int64(index.Uint())
default:
return false
}
if x < 0 || x >= int64(v.Len()) {
return false
}
v = v.Index(int(x))
case reflect.Map:
if !index.IsValid() {
index = reflect.Zero(v.Type().Key())
}
if !index.Type().AssignableTo(v.Type().Key()) {
return false
}
if x := v.MapIndex(index); x.IsValid() {
v = x
} else {
v = reflect.Zero(v.Type().Elem())
}
default:
return false
}
}
if _, isNil := indirect(v); isNil {
return false
}
return true
}
// stolen from text/template
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
// We indirect through pointers and empty interfaces (only) because
// non-empty interfaces have methods we might need.
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}
// JSONPathPrinter is an implementation of ResourcePrinter which formats data with jsonpath expression.
type JSONPathPrinter struct {
rawTemplate string
*jsonpath.JSONPath
}
func NewJSONPathPrinter(tmpl string) (*JSONPathPrinter, error) {
j := jsonpath.New("out")
if err := j.Parse(tmpl); err != nil {
return nil, err
}
return &JSONPathPrinter{
rawTemplate: tmpl,
JSONPath: j,
}, nil
}
// PrintObj formats the obj with the JSONPath Template.
func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
// we use reflect.Indirect here in order to obtain the actual value from a pointer.
// we need an actual value in order to retrieve the package path for an object.
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
return fmt.Errorf(printers.InternalObjectPrinterErr)
}
var queryObj interface{} = obj
if meta.IsListType(obj) {
data, err := json.Marshal(obj)
if err != nil {
return err
}
queryObj = map[string]interface{}{}
if err := json.Unmarshal(data, &queryObj); err != nil {
return err
}
}
if unknown, ok := obj.(*runtime.Unknown); ok {
data, err := json.Marshal(unknown)
if err != nil {
return err
}
queryObj = map[string]interface{}{}
if err := json.Unmarshal(data, &queryObj); err != nil {
return err
}
}
if unstructured, ok := obj.(runtime.Unstructured); ok {
queryObj = unstructured.UnstructuredContent()
}
if err := j.JSONPath.Execute(w, queryObj); err != nil {
fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n", err)
fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n", j.rawTemplate)
fmt.Fprintf(w, "\tobject given to jsonpath engine was:\n\t\t%#v\n\n", queryObj)
return fmt.Errorf("error executing jsonpath %q: %v\n", j.rawTemplate, err)
}
return nil
}

View File

@@ -1,128 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"fmt"
"io/ioutil"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
// templates are logically optional for specifying a format.
// this allows a user to specify a template format value
// as --output=jsonpath=
var jsonFormats = map[string]bool{
"jsonpath": true,
"jsonpath-file": true,
}
// JSONPathPrintFlags provides default flags necessary for template printing.
// Given the following flag values, a printer can be requested that knows
// how to handle printing based on these values.
type JSONPathPrintFlags struct {
// indicates if it is OK to ignore missing keys for rendering
// an output template.
AllowMissingKeys *bool
TemplateArgument *string
}
func (f *JSONPathPrintFlags) AllowedFormats() []string {
formats := make([]string, 0, len(jsonFormats))
for format := range jsonFormats {
formats = append(formats, format)
}
return formats
}
// ToPrinter receives an templateFormat and returns a printer capable of
// handling --template format printing.
// Returns false if the specified templateFormat does not match a template format.
func (f *JSONPathPrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) {
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
return nil, genericclioptions.NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
}
templateValue := ""
if f.TemplateArgument == nil || len(*f.TemplateArgument) == 0 {
for format := range jsonFormats {
format = format + "="
if strings.HasPrefix(templateFormat, format) {
templateValue = templateFormat[len(format):]
templateFormat = format[:len(format)-1]
break
}
}
} else {
templateValue = *f.TemplateArgument
}
if _, supportedFormat := jsonFormats[templateFormat]; !supportedFormat {
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()}
}
if len(templateValue) == 0 {
return nil, fmt.Errorf("template format specified but no template given")
}
if templateFormat == "jsonpath-file" {
data, err := ioutil.ReadFile(templateValue)
if err != nil {
return nil, fmt.Errorf("error reading --template %s, %v\n", templateValue, err)
}
templateValue = string(data)
}
p, err := NewJSONPathPrinter(templateValue)
if err != nil {
return nil, fmt.Errorf("error parsing jsonpath %s, %v\n", templateValue, err)
}
allowMissingKeys := true
if f.AllowMissingKeys != nil {
allowMissingKeys = *f.AllowMissingKeys
}
p.AllowMissingKeys(allowMissingKeys)
return p, nil
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to template printing to it
func (f *JSONPathPrintFlags) AddFlags(c *cobra.Command) {
if f.TemplateArgument != nil {
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when --output=jsonpath, --output=jsonpath-file.")
c.MarkFlagFilename("template")
}
if f.AllowMissingKeys != nil {
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
}
}
// NewJSONPathPrintFlags returns flags associated with
// --template printing, with default values set.
func NewJSONPathPrintFlags(templateValue string, allowMissingKeys bool) *JSONPathPrintFlags {
return &JSONPathPrintFlags{
TemplateArgument: &templateValue,
AllowMissingKeys: &allowMissingKeys,
}
}

View File

@@ -1,212 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestPrinterSupportsExpectedJSONPathFormats(t *testing.T) {
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
jsonpathFile, err := ioutil.TempFile("", "printers_jsonpath_flags")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer func(tempFile *os.File) {
tempFile.Close()
os.Remove(tempFile.Name())
}(jsonpathFile)
fmt.Fprintf(jsonpathFile, "{ .metadata.name }\n")
testCases := []struct {
name string
outputFormat string
templateArg string
expectedError string
expectedParseError string
expectedOutput string
expectNoMatch bool
}{
{
name: "valid output format also containing the jsonpath argument succeeds",
outputFormat: "jsonpath={ .metadata.name }",
expectedOutput: "foo",
},
{
name: "valid output format and no --template argument results in an error",
outputFormat: "jsonpath",
expectedError: "template format specified but no template given",
},
{
name: "valid output format and --template argument succeeds",
outputFormat: "jsonpath",
templateArg: "{ .metadata.name }",
expectedOutput: "foo",
},
{
name: "jsonpath template file should match, and successfully return correct value",
outputFormat: "jsonpath-file",
templateArg: jsonpathFile.Name(),
expectedOutput: "foo",
},
{
name: "valid output format and invalid --template argument results in a parsing from the printer",
outputFormat: "jsonpath",
templateArg: "{invalid}",
expectedParseError: "unrecognized identifier invalid",
},
{
name: "no printer is matched on an invalid outputFormat",
outputFormat: "invalid",
expectNoMatch: true,
},
{
name: "jsonpath printer should not match on any other format supported by another printer",
outputFormat: "go-template",
expectNoMatch: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
templateArg := &tc.templateArg
if len(tc.templateArg) == 0 {
templateArg = nil
}
printFlags := JSONPathPrintFlags{
TemplateArgument: templateArg,
}
p, err := printFlags.ToPrinter(tc.outputFormat)
if tc.expectNoMatch {
if !genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
}
return
}
if genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected to match template printer for output format %q", tc.outputFormat)
}
if len(tc.expectedError) > 0 {
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
if len(tc.expectedParseError) > 0 {
if err == nil || !strings.Contains(err.Error(), tc.expectedParseError) {
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !strings.Contains(out.String(), tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}
func TestJSONPathPrinterDefaultsAllowMissingKeysToTrue(t *testing.T) {
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
allowMissingKeys := false
testCases := []struct {
name string
templateArg string
expectedOutput string
expectedError string
allowMissingKeys *bool
}{
{
name: "existing field does not error and returns expected value",
templateArg: "{ .metadata.name }",
expectedOutput: "foo",
allowMissingKeys: &allowMissingKeys,
},
{
name: "missing field does not error and returns an empty string since missing keys are allowed by default",
templateArg: "{ .metadata.missing }",
expectedOutput: "",
allowMissingKeys: nil,
},
{
name: "missing field returns expected error if field is missing and allowMissingKeys is explicitly set to false",
templateArg: "{ .metadata.missing }",
expectedError: "error executing jsonpath",
allowMissingKeys: &allowMissingKeys,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
printFlags := JSONPathPrintFlags{
TemplateArgument: &tc.templateArg,
AllowMissingKeys: tc.allowMissingKeys,
}
outputFormat := "jsonpath"
p, err := printFlags.ToPrinter(outputFormat)
if genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected to match template printer for output format %q", outputFormat)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
if len(tc.expectedError) > 0 {
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(out.String()) != len(tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}

View File

@@ -1,78 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
// KubeTemplatePrintFlags composes print flags that provide both a JSONPath and a go-template printer.
// This is necessary if dealing with cases that require support both both printers, since both sets of flags
// require overlapping flags.
type KubeTemplatePrintFlags struct {
*GoTemplatePrintFlags
*JSONPathPrintFlags
AllowMissingKeys *bool
TemplateArgument *string
}
func (f *KubeTemplatePrintFlags) AllowedFormats() []string {
return append(f.GoTemplatePrintFlags.AllowedFormats(), f.JSONPathPrintFlags.AllowedFormats()...)
}
func (f *KubeTemplatePrintFlags) ToPrinter(outputFormat string) (ResourcePrinter, error) {
if p, err := f.JSONPathPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return p, err
}
return f.GoTemplatePrintFlags.ToPrinter(outputFormat)
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to template printing to it
func (f *KubeTemplatePrintFlags) AddFlags(c *cobra.Command) {
if f.TemplateArgument != nil {
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
c.MarkFlagFilename("template")
}
if f.AllowMissingKeys != nil {
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
}
}
// NewKubeTemplatePrintFlags returns flags associated with
// --template printing, with default values set.
func NewKubeTemplatePrintFlags() *KubeTemplatePrintFlags {
allowMissingKeysPtr := true
templateArgPtr := ""
return &KubeTemplatePrintFlags{
GoTemplatePrintFlags: &GoTemplatePrintFlags{
TemplateArgument: &templateArgPtr,
AllowMissingKeys: &allowMissingKeysPtr,
},
JSONPathPrintFlags: &JSONPathPrintFlags{
TemplateArgument: &templateArgPtr,
AllowMissingKeys: &allowMissingKeysPtr,
},
TemplateArgument: &templateArgPtr,
AllowMissingKeys: &allowMissingKeysPtr,
}
}

View File

@@ -11,8 +11,8 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/printers/storage",
deps = [
"//pkg/printers:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
],
)

View File

@@ -22,7 +22,7 @@ import (
)
const (
tabwriterMinWidth = 10
tabwriterMinWidth = 6
tabwriterWidth = 4
tabwriterPadding = 3
tabwriterPadChar = ' '

View File

@@ -1,119 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"reflect"
"text/template"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
)
// GoTemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
type GoTemplatePrinter struct {
rawTemplate string
template *template.Template
}
func NewGoTemplatePrinter(tmpl []byte) (*GoTemplatePrinter, error) {
t, err := template.New("output").
Funcs(template.FuncMap{
"exists": exists,
"base64decode": base64decode,
}).
Parse(string(tmpl))
if err != nil {
return nil, err
}
return &GoTemplatePrinter{
rawTemplate: string(tmpl),
template: t,
}, nil
}
// AllowMissingKeys tells the template engine if missing keys are allowed.
func (p *GoTemplatePrinter) AllowMissingKeys(allow bool) {
if allow {
p.template.Option("missingkey=default")
} else {
p.template.Option("missingkey=error")
}
}
// PrintObj formats the obj with the Go Template.
func (p *GoTemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
return fmt.Errorf(printers.InternalObjectPrinterErr)
}
var data []byte
var err error
data, err = json.Marshal(obj)
if err != nil {
return err
}
out := map[string]interface{}{}
if err := json.Unmarshal(data, &out); err != nil {
return err
}
if err = p.safeExecute(w, out); err != nil {
// It is way easier to debug this stuff when it shows up in
// stdout instead of just stdin. So in addition to returning
// a nice error, also print useful stuff with the writer.
fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n", err)
fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n", p.rawTemplate)
fmt.Fprintf(w, "\traw data was:\n\t\t%v\n", string(data))
fmt.Fprintf(w, "\tobject given to template engine was:\n\t\t%+v\n\n", out)
return fmt.Errorf("error executing template %q: %v", p.rawTemplate, err)
}
return nil
}
// safeExecute tries to execute the template, but catches panics and returns an error
// should the template engine panic.
func (p *GoTemplatePrinter) safeExecute(w io.Writer, obj interface{}) error {
var panicErr error
// Sorry for the double anonymous function. There's probably a clever way
// to do this that has the defer'd func setting the value to be returned, but
// that would be even less obvious.
retErr := func() error {
defer func() {
if x := recover(); x != nil {
panicErr = fmt.Errorf("caught panic: %+v", x)
}
}()
return p.template.Execute(w, obj)
}()
if panicErr != nil {
return panicErr
}
return retErr
}
func base64decode(v string) (string, error) {
data, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return "", fmt.Errorf("base64 decode failed: %v", err)
}
return string(data), nil
}

View File

@@ -1,133 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"fmt"
"io/ioutil"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
// templates are logically optional for specifying a format.
// this allows a user to specify a template format value
// as --output=go-template=
var templateFormats = map[string]bool{
"template": true,
"go-template": true,
"go-template-file": true,
"templatefile": true,
}
// GoTemplatePrintFlags provides default flags necessary for template printing.
// Given the following flag values, a printer can be requested that knows
// how to handle printing based on these values.
type GoTemplatePrintFlags struct {
// indicates if it is OK to ignore missing keys for rendering
// an output template.
AllowMissingKeys *bool
TemplateArgument *string
}
func (f *GoTemplatePrintFlags) AllowedFormats() []string {
formats := make([]string, 0, len(templateFormats))
for format := range templateFormats {
formats = append(formats, format)
}
return formats
}
// ToPrinter receives an templateFormat and returns a printer capable of
// handling --template format printing.
// Returns false if the specified templateFormat does not match a template format.
func (f *GoTemplatePrintFlags) ToPrinter(templateFormat string) (ResourcePrinter, error) {
if (f.TemplateArgument == nil || len(*f.TemplateArgument) == 0) && len(templateFormat) == 0 {
return nil, genericclioptions.NoCompatiblePrinterError{Options: f, OutputFormat: &templateFormat}
}
templateValue := ""
if f.TemplateArgument == nil || len(*f.TemplateArgument) == 0 {
for format := range templateFormats {
format = format + "="
if strings.HasPrefix(templateFormat, format) {
templateValue = templateFormat[len(format):]
templateFormat = format[:len(format)-1]
break
}
}
} else {
templateValue = *f.TemplateArgument
}
if _, supportedFormat := templateFormats[templateFormat]; !supportedFormat {
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()}
}
if len(templateValue) == 0 {
return nil, fmt.Errorf("template format specified but no template given")
}
if templateFormat == "templatefile" || templateFormat == "go-template-file" {
data, err := ioutil.ReadFile(templateValue)
if err != nil {
return nil, fmt.Errorf("error reading --template %s, %v\n", templateValue, err)
}
templateValue = string(data)
}
p, err := NewGoTemplatePrinter([]byte(templateValue))
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", templateValue, err)
}
allowMissingKeys := true
if f.AllowMissingKeys != nil {
allowMissingKeys = *f.AllowMissingKeys
}
p.AllowMissingKeys(allowMissingKeys)
return p, nil
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to template printing to it
func (f *GoTemplatePrintFlags) AddFlags(c *cobra.Command) {
if f.TemplateArgument != nil {
c.Flags().StringVar(f.TemplateArgument, "template", *f.TemplateArgument, "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
c.MarkFlagFilename("template")
}
if f.AllowMissingKeys != nil {
c.Flags().BoolVar(f.AllowMissingKeys, "allow-missing-template-keys", *f.AllowMissingKeys, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
}
}
// NewGoTemplatePrintFlags returns flags associated with
// --template printing, with default values set.
func NewGoTemplatePrintFlags() *GoTemplatePrintFlags {
allowMissingKeysPtr := true
templateValuePtr := ""
return &GoTemplatePrintFlags{
TemplateArgument: &templateValuePtr,
AllowMissingKeys: &allowMissingKeysPtr,
}
}

View File

@@ -1,206 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
func TestPrinterSupportsExpectedTemplateFormats(t *testing.T) {
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
templateFile, err := ioutil.TempFile("", "printers_jsonpath_flags")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer func(tempFile *os.File) {
tempFile.Close()
os.Remove(tempFile.Name())
}(templateFile)
fmt.Fprintf(templateFile, "{{ .metadata.name }}")
testCases := []struct {
name string
outputFormat string
templateArg string
expectedError string
expectedParseError string
expectedOutput string
expectNoMatch bool
}{
{
name: "valid output format also containing the template argument succeeds",
outputFormat: "go-template={{ .metadata.name }}",
expectedOutput: "foo",
},
{
name: "valid output format and no template argument results in an error",
outputFormat: "template",
expectedError: "template format specified but no template given",
},
{
name: "valid output format and template argument succeeds",
outputFormat: "go-template",
templateArg: "{{ .metadata.name }}",
expectedOutput: "foo",
},
{
name: "Go-template file should match, and successfully return correct value",
outputFormat: "go-template-file",
templateArg: templateFile.Name(),
expectedOutput: "foo",
},
{
name: "valid output format and invalid template argument results in the templateArg contents as the output",
outputFormat: "go-template",
templateArg: "invalid",
expectedOutput: "invalid",
},
{
name: "no printer is matched on an invalid outputFormat",
outputFormat: "invalid",
expectNoMatch: true,
},
{
name: "go-template printer should not match on any other format supported by another printer",
outputFormat: "jsonpath",
expectNoMatch: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
templateArg := &tc.templateArg
if len(tc.templateArg) == 0 {
templateArg = nil
}
printFlags := GoTemplatePrintFlags{
TemplateArgument: templateArg,
}
p, err := printFlags.ToPrinter(tc.outputFormat)
if tc.expectNoMatch {
if !genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected no printer matches for output format %q", tc.outputFormat)
}
return
}
if genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected to match template printer for output format %q", tc.outputFormat)
}
if len(tc.expectedError) > 0 {
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(out.String()) != len(tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}
func TestTemplatePrinterDefaultsAllowMissingKeysToTrue(t *testing.T) {
testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
allowMissingKeys := false
testCases := []struct {
name string
templateArg string
expectedOutput string
expectedError string
allowMissingKeys *bool
}{
{
name: "existing field does not error and returns expected value",
templateArg: "{{ .metadata.name }}",
expectedOutput: "foo",
allowMissingKeys: &allowMissingKeys,
},
{
name: "missing field does not error and returns no value since missing keys are allowed by default",
templateArg: "{{ .metadata.missing }}",
expectedOutput: "<no value>",
allowMissingKeys: nil,
},
{
name: "missing field returns expected error if field is missing and allowMissingKeys is explicitly set to false",
templateArg: "{{ .metadata.missing }}",
expectedError: "error executing template",
allowMissingKeys: &allowMissingKeys,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
printFlags := GoTemplatePrintFlags{
TemplateArgument: &tc.templateArg,
AllowMissingKeys: tc.allowMissingKeys,
}
outputFormat := "template"
p, err := printFlags.ToPrinter(outputFormat)
if genericclioptions.IsNoCompatiblePrinterError(err) {
t.Fatalf("expected to match template printer for output format %q", outputFormat)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
if len(tc.expectedError) > 0 {
if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
t.Errorf("expecting error %q, got %v", tc.expectedError, err)
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(out.String()) != len(tc.expectedOutput) {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
}
})
}
}

View File

@@ -1,102 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printers
import (
"bytes"
"strings"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestTemplate(t *testing.T) {
testCase := []struct {
name string
template string
obj runtime.Object
expectOut string
expectErr func(error) (string, bool)
}{
{
name: "support base64 decoding of secret data",
template: "{{ .data.username | base64decode }}",
obj: &v1.Secret{
Data: map[string][]byte{
"username": []byte("hunter"),
},
},
expectOut: "hunter",
},
{
name: "test error path for base64 decoding",
template: "{{ .data.username | base64decode }}",
obj: &badlyMarshaledSecret{},
expectErr: func(err error) (string, bool) {
matched := strings.Contains(err.Error(), "base64 decode")
return "a base64 decode error", matched
},
},
}
for _, test := range testCase {
t.Run(test.name, func(t *testing.T) {
buffer := &bytes.Buffer{}
p, err := NewGoTemplatePrinter([]byte(test.template))
if err != nil {
if test.expectErr == nil {
t.Errorf("[%s]expected success but got:\n %v\n", test.name, err)
return
}
if expected, ok := test.expectErr(err); !ok {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, expected, err)
}
return
}
err = p.PrintObj(test.obj, buffer)
if err != nil {
if test.expectErr == nil {
t.Errorf("[%s]expected success but got:\n %v\n", test.name, err)
return
}
if expected, ok := test.expectErr(err); !ok {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, expected, err)
}
return
}
if test.expectErr != nil {
t.Errorf("[%s]expect:\n error\n but got:\n no error\n", test.name)
return
}
if test.expectOut != buffer.String() {
t.Errorf("[%s]expect:\n %v\n but got:\n %v\n", test.name, test.expectOut, buffer.String())
}
})
}
}
type badlyMarshaledSecret struct {
v1.Secret
}
func (a badlyMarshaledSecret) MarshalJSON() ([]byte, error) {
return []byte(`{"apiVersion":"v1","data":{"username":"--THIS IS NOT BASE64--"},"kind":"Secret"}`), nil
}