Refactor printers to support rendering as a Table

Return tables from the server.
parent f203e42c
......@@ -192,7 +192,7 @@ func Example_printReplicationControllerWithNamespace() {
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
err := f.PrintObject(cmd, mapper, ctrl, printers.GetNewTabWriter(os.Stdout))
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
......@@ -247,7 +247,7 @@ func Example_printMultiContainersReplicationControllerWithWide() {
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
err := f.PrintObject(cmd, mapper, ctrl, printers.GetNewTabWriter(os.Stdout))
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
......@@ -301,7 +301,7 @@ func Example_printReplicationController() {
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
err := f.PrintObject(cmd, mapper, ctrl, printers.GetNewTabWriter(os.Stdout))
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
......@@ -344,7 +344,7 @@ func Example_printPodWithWideFormat() {
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, pod, os.Stdout)
err := f.PrintObject(cmd, mapper, pod, printers.GetNewTabWriter(os.Stdout))
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
......@@ -390,7 +390,7 @@ func Example_printPodWithShowLabels() {
},
}
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, pod, os.Stdout)
err := f.PrintObject(cmd, mapper, pod, printers.GetNewTabWriter(os.Stdout))
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
......@@ -514,7 +514,7 @@ func Example_printPodHideTerminated() {
}
for _, pod := range filteredPodList {
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, pod, os.Stdout)
err := f.PrintObject(cmd, mapper, pod, printers.GetNewTabWriter(os.Stdout))
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
......@@ -542,7 +542,7 @@ func Example_printPodShowAll() {
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList()
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, podList, os.Stdout)
err := f.PrintObject(cmd, mapper, podList, printers.GetNewTabWriter(os.Stdout))
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
......@@ -616,9 +616,10 @@ func Example_printServiceWithNamespacesAndLabels() {
}
ld := strings.NewLineDelimiter(os.Stdout, "|")
defer ld.Flush()
out := printers.GetNewTabWriter(ld)
defer out.Flush()
mapper, _ := f.Object()
err := f.PrintObject(cmd, mapper, svc, ld)
err := f.PrintObject(cmd, mapper, svc, out)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
......
......@@ -28,12 +28,14 @@ go_library(
"//pkg/util/slice:go_default_library",
"//vendor/github.com/fatih/camelcase:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog: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/v1alpha1: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/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
],
)
......@@ -63,6 +65,7 @@ filegroup(
srcs = [
":package-srcs",
"//pkg/printers/internalversion:all-srcs",
"//pkg/printers/storage:all-srcs",
],
tags = ["automanaged"],
)
......
......@@ -40,6 +40,7 @@ go_test(
"//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/v1alpha1: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",
......@@ -65,6 +66,7 @@ go_library(
"//pkg/api/helper/qos:go_default_library",
"//pkg/api/ref:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/batch:go_default_library",
......@@ -94,8 +96,10 @@ go_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/v1alpha1: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/intstr:go_default_library",
......
......@@ -27,12 +27,15 @@ import (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/federation/apis/federation"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/events"
"k8s.io/kubernetes/pkg/api/helper"
apiv1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
......@@ -53,8 +56,6 @@ const loadBalancerWidth = 16
// NOTE: When adding a new resource type here, please update the list
// pkg/kubectl/cmd/get.go to reflect the new resource type.
var (
podColumns = []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"}
podWideColumns = []string{"IP", "NODE"}
podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"}
podDisruptionBudgetColumns = []string{"NAME", "MIN-AVAILABLE", "MAX-UNAVAILABLE", "ALLOWED-DISRUPTIONS", "AGE"}
replicationControllerColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "AGE"}
......@@ -105,27 +106,21 @@ var (
podPresetColumns = []string{"NAME", "AGE"}
)
func printPod(pod *api.Pod, w io.Writer, options printers.PrintOptions) error {
if err := printPodBase(pod, w, options); err != nil {
return err
}
return nil
}
func printPodList(podList *api.PodList, w io.Writer, options printers.PrintOptions) error {
for _, pod := range podList.Items {
if err := printPodBase(&pod, w, options); err != nil {
return err
}
}
return nil
}
// AddHandlers adds print handlers for default Kubernetes types dealing with internal versions.
func AddHandlers(h *printers.HumanReadablePrinter) {
h.Handler(podColumns, podWideColumns, printPodList)
h.Handler(podColumns, podWideColumns, printPod)
// TODO: handle errors from Handler
func AddHandlers(h printers.PrintHandler) {
podColumnDefinitions := []metav1alpha1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Ready", Type: "string", Description: "The aggregate readiness state of this pod for accepting traffic."},
{Name: "Status", Type: "string", Description: "The aggregate status of the containers in this pod."},
{Name: "Restarts", Type: "integer", Description: "The number of times the containers in this pod have been restarted."},
{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"]},
}
h.TableHandler(podColumnDefinitions, printPodList)
h.TableHandler(podColumnDefinitions, printPod)
h.Handler(podTemplateColumns, nil, printPodTemplate)
h.Handler(podTemplateColumns, nil, printPodTemplateList)
h.Handler(podDisruptionBudgetColumns, nil, printPodDisruptionBudget)
......@@ -247,10 +242,24 @@ func translateTimestamp(timestamp metav1.Time) string {
return printers.ShortHumanDuration(time.Now().Sub(timestamp.Time))
}
func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) error {
name := printers.FormatResourceName(options.Kind, pod.Name, options.WithKind)
namespace := pod.Namespace
var (
podSuccessConditions = []metav1alpha1.TableRowCondition{{Type: metav1alpha1.RowCompleted, Status: metav1alpha1.ConditionTrue, Reason: string(api.PodSucceeded), Message: "The pod has completed successfully."}}
podFailedConditions = []metav1alpha1.TableRowCondition{{Type: metav1alpha1.RowCompleted, Status: metav1alpha1.ConditionTrue, Reason: string(api.PodFailed), Message: "The pod failed."}}
)
func printPodList(podList *api.PodList, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) {
rows := make([]metav1alpha1.TableRow, 0, len(podList.Items))
for i := range podList.Items {
r, err := printPod(&podList.Items[i], options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}
func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1alpha1.TableRow, error) {
restarts := 0
totalContainers := len(pod.Spec.Containers)
readyContainers := 0
......@@ -260,6 +269,17 @@ func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) erro
reason = pod.Status.Reason
}
row := metav1alpha1.TableRow{
Object: runtime.RawExtension{Object: pod},
}
switch pod.Status.Phase {
case api.PodSucceeded:
row.Conditions = podSuccessConditions
case api.PodFailed:
row.Conditions = podFailedConditions
}
initializing := false
for i := range pod.Status.InitContainerStatuses {
container := pod.Status.InitContainerStatuses[i]
......@@ -316,21 +336,7 @@ func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) erro
reason = "Terminating"
}
if options.WithNamespace {
if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil {
return err
}
}
if _, err := fmt.Fprintf(w, "%s\t%d/%d\t%s\t%d\t%s",
name,
readyContainers,
totalContainers,
reason,
restarts,
translateTimestamp(pod.CreationTimestamp),
); err != nil {
return err
}
row.Cells = append(row.Cells, pod.Name, fmt.Sprintf("%d/%d", readyContainers, totalContainers), reason, restarts, translateTimestamp(pod.CreationTimestamp))
if options.Wide {
nodeName := pod.Spec.NodeName
......@@ -341,22 +347,10 @@ func printPodBase(pod *api.Pod, w io.Writer, options printers.PrintOptions) erro
if nodeName == "" {
nodeName = "<none>"
}
if _, err := fmt.Fprintf(w, "\t%s\t%s",
podIP,
nodeName,
); err != nil {
return err
}
row.Cells = append(row.Cells, podIP, nodeName)
}
if _, err := fmt.Fprint(w, printers.AppendLabels(pod.Labels, options.ColumnLabels)); err != nil {
return err
}
if _, err := fmt.Fprint(w, printers.AppendAllLabels(options.ShowLabels, pod.Labels)); err != nil {
return err
}
return nil
return []metav1alpha1.TableRow{row}, nil
}
func printPodTemplate(pod *api.PodTemplate, w io.Writer, options printers.PrintOptions) error {
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["storage.go"],
tags = ["automanaged"],
deps = [
"//pkg/printers:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
/*
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 storage
import (
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
"k8s.io/apimachinery/pkg/runtime"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/printers"
)
type TableConvertor struct {
printers.TablePrinter
}
func (c TableConvertor) ConvertToTable(ctx genericapirequest.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1alpha1.Table, error) {
return c.TablePrinter.PrintTable(obj, printers.PrintOptions{Wide: true})
}
......@@ -15,12 +15,14 @@ go_test(
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/registry/registrytest:go_default_library",
"//pkg/securitycontext:go_default_library",
"//vendor/golang.org/x/net/context:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1: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",
......@@ -49,6 +51,9 @@ go_library(
"//pkg/client/clientset_generated/internalclientset/typed/policy/internalversion:go_default_library",
"//pkg/client/retry:go_default_library",
"//pkg/kubelet/client:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//pkg/printers/storage:go_default_library",
"//pkg/registry/cachesize:go_default_library",
"//pkg/registry/core/pod:go_default_library",
"//pkg/registry/core/pod/rest:go_default_library",
......
......@@ -35,6 +35,9 @@ import (
"k8s.io/kubernetes/pkg/api/validation"
policyclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/policy/internalversion"
"k8s.io/kubernetes/pkg/kubelet/client"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/cachesize"
"k8s.io/kubernetes/pkg/registry/core/pod"
podrest "k8s.io/kubernetes/pkg/registry/core/pod/rest"
......@@ -61,6 +64,7 @@ type REST struct {
// NewStorage returns a RESTStorage object that will work against pods.
func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGetter, proxyTransport http.RoundTripper, podDisruptionBudgetClient policyclient.PodDisruptionBudgetsGetter) PodStorage {
store := &genericregistry.Store{
Copier: api.Scheme,
NewFunc: func() runtime.Object { return &api.Pod{} },
......@@ -73,6 +77,8 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGet
UpdateStrategy: pod.Strategy,
DeleteStrategy: pod.Strategy,
ReturnDeletedObject: true,
TableConvertor: printerstorage.TableConvertor{TablePrinter: printers.NewTablePrinter().With(printersinternal.AddHandlers)},
}
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: pod.GetAttrs, TriggerFunc: pod.NodeNameTriggerFunc}
if err := store.CompleteWithOptions(options); err != nil {
......
......@@ -19,11 +19,13 @@ package storage
import (
"strings"
"testing"
"time"
"golang.org/x/net/context"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
......@@ -35,6 +37,7 @@ import (
storeerr "k8s.io/apiserver/pkg/storage/errors"
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/securitycontext"
)
......@@ -396,6 +399,88 @@ func TestWatch(t *testing.T) {
)
}
func TestConvertToTableList(t *testing.T) {
storage, _, _, server := newStorage(t)
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
ctx := genericapirequest.NewDefaultContext()
columns := []metav1alpha1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Ready", Type: "string", Description: "The aggregate readiness state of this pod for accepting traffic."},
{Name: "Status", Type: "string", Description: "The aggregate status of the containers in this pod."},
{Name: "Restarts", Type: "integer", Description: "The number of times the containers in this pod have been restarted."},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
{Name: "IP", Type: "string", Priority: 1, Description: v1.PodStatus{}.SwaggerDoc()["podIP"]},
{Name: "Node", Type: "string", Priority: 1, Description: v1.PodSpec{}.SwaggerDoc()["nodeName"]},
}
pod1 := &api.Pod{
ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo", CreationTimestamp: metav1.NewTime(time.Now().Add(-370 * 24 * time.Hour))},
Spec: api.PodSpec{
Containers: []api.Container{
{Name: "ctr1"},
{Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 9376}}},
},
NodeName: "test-node",
},
Status: api.PodStatus{
PodIP: "10.1.2.3",
Phase: api.PodPending,
ContainerStatuses: []api.ContainerStatus{
{Name: "ctr1", State: api.ContainerState{Running: &api.ContainerStateRunning{}}, RestartCount: 10, Ready: true},
{Name: "ctr2", State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, RestartCount: 0},
},
},
}
testCases := []struct {
in runtime.Object
out *metav1alpha1.Table
err bool
}{
{
in: nil,
err: true,
},
{
in: &api.Pod{},
out: &metav1alpha1.Table{
ColumnDefinitions: columns,
Rows: []metav1alpha1.TableRow{
{Cells: []interface{}{"", "0/0", "", 0, "<unknown>", "<none>", "<none>"}, Object: runtime.RawExtension{Object: &api.Pod{}}},
},
},
},
{
in: pod1,
out: &metav1alpha1.Table{
ColumnDefinitions: columns,
Rows: []metav1alpha1.TableRow{
{Cells: []interface{}{"foo", "1/2", "Pending", 10, "1y", "10.1.2.3", "test-node"}, Object: runtime.RawExtension{Object: pod1}},
},
},
},
{
in: &api.PodList{},
out: &metav1alpha1.Table{ColumnDefinitions: columns},
},
}
for i, test := range testCases {
out, err := storage.ConvertToTable(ctx, test.in, nil)
if err != nil {
if test.err {
continue
}
t.Errorf("%d: error: %v", i, err)
continue
}
if !apiequality.Semantic.DeepEqual(test.out, out) {
t.Errorf("%d: mismatch: %s", i, diff.ObjectReflectDiff(test.out, out))
}
}
}
func TestEtcdCreate(t *testing.T) {
storage, bindingStorage, _, server := newStorage(t)
defer server.Terminate(t)
......
......@@ -11,6 +11,7 @@ load(
go_test(
name = "go_default_test",
srcs = [
"meta_test.go",
"multirestmapper_test.go",
"priority_test.go",
"restmapper_test.go",
......@@ -18,8 +19,12 @@ go_test(
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//vendor/github.com/google/gofuzz:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1: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/diff:go_default_library",
],
)
......
......@@ -114,6 +114,7 @@ func AsPartialObjectMetadata(m metav1.Object) *metav1alpha1.PartialObjectMetadat
OwnerReferences: m.GetOwnerReferences(),
Finalizers: m.GetFinalizers(),
ClusterName: m.GetClusterName(),
Initializers: m.GetInitializers(),
},
}
}
......
/*
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 meta
import (
"math/rand"
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
"k8s.io/apimachinery/pkg/util/diff"
fuzz "github.com/google/gofuzz"
)
func TestAsPartialObjectMetadata(t *testing.T) {
f := fuzz.New().NilChance(.5).NumElements(0, 1).RandSource(rand.NewSource(1))
for i := 0; i < 100; i++ {
m := &metav1.ObjectMeta{}
f.Fuzz(m)
partial := AsPartialObjectMetadata(m)
if !reflect.DeepEqual(&partial.ObjectMeta, m) {
t.Fatalf("incomplete partial object metadata: %s", diff.ObjectReflectDiff(&partial.ObjectMeta, m))
}
}
for i := 0; i < 100; i++ {
m := &metav1alpha1.PartialObjectMetadata{}
f.Fuzz(&m.ObjectMeta)
partial := AsPartialObjectMetadata(m)
if !reflect.DeepEqual(&partial.ObjectMeta, &m.ObjectMeta) {
t.Fatalf("incomplete partial object metadata: %s", diff.ObjectReflectDiff(&partial.ObjectMeta, &m.ObjectMeta))
}
}
}
......@@ -149,6 +149,9 @@ func (meta *ObjectMeta) GetFinalizers() []string { return m
func (meta *ObjectMeta) SetFinalizers(finalizers []string) { meta.Finalizers = finalizers }
func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference {
if meta.OwnerReferences == nil {
return nil
}
ret := make([]OwnerReference, len(meta.OwnerReferences))
for i := 0; i < len(meta.OwnerReferences); i++ {
ret[i].Kind = meta.OwnerReferences[i].Kind
......@@ -168,6 +171,10 @@ func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference {
}
func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) {
if references == nil {
meta.OwnerReferences = nil
return
}
newReferences := make([]OwnerReference, len(references))
for i := 0; i < len(references); i++ {
newReferences[i].Kind = references[i].Kind
......
......@@ -34,7 +34,7 @@ import (
// transformResponseObject takes an object loaded from storage and performs any necessary transformations.
// Will write the complete response object.
func transformResponseObject(ctx request.Context, scope RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, result runtime.Object) {
// TODO: use returned serializer
// TODO: fetch the media type much earlier in request processing and pass it into this method.
mediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
if err != nil {
status := responsewriters.ErrorToAPIStatus(err)
......@@ -169,7 +169,7 @@ func transformResponseObject(ctx request.Context, scope RequestScope, req *http.
}
}
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
responsewriters.WriteObject(ctx, statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
}
// errNotAcceptable indicates Accept negotiation has failed
......
......@@ -62,6 +62,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/api/validation/path:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1: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",
......
......@@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/api/validation/path"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
......@@ -155,6 +156,9 @@ type Store struct {
// ExportStrategy implements resource-specific behavior during export,
// optional. Exported objects are not decorated.
ExportStrategy rest.RESTExportStrategy
// TableConvertor is an optional interface for transforming items or lists
// of items into tabular output. If unset, the default will be used.
TableConvertor rest.TableConvertor
// Storage is the interface for the underlying storage for the resource.
Storage storage.Interface
......@@ -169,6 +173,7 @@ type Store struct {
// Note: the rest.StandardStorage interface aggregates the common REST verbs
var _ rest.StandardStorage = &Store{}
var _ rest.Exporter = &Store{}
var _ rest.TableConvertor = &Store{}
const OptimisticLockErrorMsg = "the object has been modified; please apply your changes to the latest version and try again"
......@@ -1230,3 +1235,10 @@ func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error {
return nil
}
func (e *Store) ConvertToTable(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.Table, error) {
if e.TableConvertor != nil {
return e.TableConvertor.ConvertToTable(ctx, object, tableOptions)
}
return rest.DefaultTableConvertor.ConvertToTable(ctx, object, tableOptions)
}
......@@ -31,6 +31,7 @@ go_library(
"export.go",
"meta.go",
"rest.go",
"table.go",
"update.go",
],
tags = ["automanaged"],
......@@ -42,6 +43,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1alpha1: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/uuid:go_default_library",
......
......@@ -118,7 +118,7 @@ type GetterWithOptions interface {
}
type TableConvertor interface {
ConvertToTableList(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.TableList, error)
ConvertToTable(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.Table, error)
}
// Deleter is an object that can delete a named RESTful resource.
......
......@@ -32,15 +32,15 @@ type defaultTableConvertor struct{}
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
func (defaultTableConvertor) ConvertToTableList(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.TableList, error) {
var table metav1alpha1.TableList
func (defaultTableConvertor) ConvertToTable(ctx genericapirequest.Context, object runtime.Object, tableOptions runtime.Object) (*metav1alpha1.Table, error) {
var table metav1alpha1.Table
fn := func(obj runtime.Object) error {
m, err := meta.Accessor(obj)
if err != nil {
// TODO: skip objects we don't recognize
return nil
}
table.Items = append(table.Items, metav1alpha1.TableListItem{
table.Rows = append(table.Rows, metav1alpha1.TableRow{
Cells: []interface{}{m.GetClusterName(), m.GetNamespace(), m.GetName(), m.GetCreationTimestamp().Time.UTC().Format(time.RFC3339)},
Object: runtime.RawExtension{Object: obj},
})
......@@ -56,7 +56,7 @@ func (defaultTableConvertor) ConvertToTableList(ctx genericapirequest.Context, o
return nil, err
}
}
table.Headers = []metav1alpha1.TableListHeader{
table.ColumnDefinitions = []metav1alpha1.TableColumnDefinition{
{Name: "Cluster Name", Type: "string", Description: swaggerMetadataDescriptions["clusterName"]},
{Name: "Namespace", Type: "string", Description: swaggerMetadataDescriptions["namespace"]},
{Name: "Name", Type: "string", Description: swaggerMetadataDescriptions["name"]},
......@@ -71,8 +71,8 @@ func (defaultTableConvertor) ConvertToTableList(ctx genericapirequest.Context, o
return &table, nil
}
func trimColumn(column int, table *metav1alpha1.TableList) bool {
for _, item := range table.Items {
func trimColumn(column int, table *metav1alpha1.Table) bool {
for _, item := range table.Rows {
switch t := item.Cells[column].(type) {
case string:
if len(t) > 0 {
......@@ -85,22 +85,22 @@ func trimColumn(column int, table *metav1alpha1.TableList) bool {
}
}
if column == 0 {
table.Headers = table.Headers[1:]
table.ColumnDefinitions = table.ColumnDefinitions[1:]
} else {
for j := column; j < len(table.Headers); j++ {
table.Headers[j] = table.Headers[j+1]
for j := column; j < len(table.ColumnDefinitions); j++ {
table.ColumnDefinitions[j] = table.ColumnDefinitions[j+1]
}
}
for i := range table.Items {
cells := table.Items[i].Cells
for i := range table.Rows {
cells := table.Rows[i].Cells
if column == 0 {
table.Items[i].Cells = cells[1:]
table.Rows[i].Cells = cells[1:]
continue
}
for j := column; j < len(cells); j++ {
cells[j] = cells[j+1]
}
table.Items[i].Cells = cells[:len(cells)-1]
table.Rows[i].Cells = cells[:len(cells)-1]
}
return true
}
......@@ -331,6 +331,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1alpha1",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment