Commit 61673c4b authored by deads2k's avatar deads2k

make kubectl get generic with respect to objects

parent 7fe9bd4c
......@@ -26,6 +26,14 @@ import (
// IsListType returns true if the provided Object has a slice called Items
func IsListType(obj runtime.Object) bool {
// if we're a runtime.Unstructured, check to see if we have an `items` key
// This is a list type for recognition, but other Items type methods will fail on it
// and give you errors.
if unstructured, ok := obj.(*runtime.Unstructured); ok {
_, ok := unstructured.Object["items"]
return ok
}
_, err := GetItemsPtr(obj)
return err == nil
}
......@@ -39,6 +47,7 @@ func GetItemsPtr(list runtime.Object) (interface{}, error) {
if err != nil {
return nil, err
}
items := v.FieldByName("Items")
if !items.IsValid() {
return nil, fmt.Errorf("no Items field in %#v", list)
......@@ -117,6 +126,13 @@ func SetList(list runtime.Object, objects []runtime.Object) error {
slice := reflect.MakeSlice(items.Type(), len(objects), len(objects))
for i := range objects {
dest := slice.Index(i)
// check to see if you're directly assignable
if reflect.TypeOf(objects[i]).AssignableTo(dest.Type()) {
dest.Set(reflect.ValueOf(objects[i]))
continue
}
src, err := conversion.EnforcePtr(objects[i])
if err != nil {
return err
......
......@@ -229,6 +229,27 @@ func TestSetListToRuntimeObjectArray(t *testing.T) {
}
}
func TestSetListToMatchingType(t *testing.T) {
pl := &runtime.UnstructuredList{}
list := []runtime.Object{
&runtime.Unstructured{Object: map[string]interface{}{"foo": 1}},
&runtime.Unstructured{Object: map[string]interface{}{"foo": 2}},
&runtime.Unstructured{Object: map[string]interface{}{"foo": 3}},
}
err := meta.SetList(pl, list)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := len(list), len(pl.Items); e != a {
t.Fatalf("Expected %v, got %v", e, a)
}
for i := range list {
if e, a := list[i], pl.Items[i]; e != a {
t.Fatalf("%d: unmatched: %s", i, diff.ObjectDiff(e, a))
}
}
}
func TestSetExtractListRoundTrip(t *testing.T) {
fuzzer := fuzz.New().NilChance(0).NumElements(1, 5)
for i := 0; i < 5; i++ {
......
......@@ -182,11 +182,9 @@ go_test(
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/runtime:go_default_library",
"//pkg/runtime/serializer:go_default_library",
"//pkg/runtime/serializer/json:go_default_library",
"//pkg/runtime/serializer/streaming:go_default_library",
"//pkg/types:go_default_library",
"//pkg/util/diff:go_default_library",
"//pkg/util/intstr:go_default_library",
"//pkg/util/strings:go_default_library",
"//pkg/util/term:go_default_library",
......
......@@ -28,16 +28,12 @@ import (
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer"
)
var unstructuredSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{
MediaType: "application/json",
EncodesAsText: true,
Serializer: runtime.UnstructuredJSONScheme})
var unstructuredSerializer = dynamic.ContentConfig().NegotiatedSerializer
func TestDeleteObjectByTuple(t *testing.T) {
_, _, rc := testData()
......
......@@ -149,7 +149,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
selector := cmdutil.GetFlagString(cmd, "selector")
allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces")
showKind := cmdutil.GetFlagBool(cmd, "show-kind")
mapper, typer := f.Object()
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
printAll := false
filterFuncs := f.DefaultResourceFilterFunc()
filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces)
......@@ -196,7 +199,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
// handle watch separately since we cannot watch multiple resource types
isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only")
if isWatch || isWatchOnly {
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme).
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, &options.FilenameOptions).
SelectorParam(selector).
......@@ -281,7 +284,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
return nil
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme).
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, &options.FilenameOptions).
SelectorParam(selector).
......@@ -302,18 +305,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
}
if generic {
clientConfig, err := f.ClientConfig()
if err != nil {
return err
}
// the outermost object will be converted to the output-version, but inner
// objects can use their mappings
version, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion)
if err != nil {
return err
}
// we flattened the data from the builder, so we have individual items, but now we'd like to either:
// 1. if there is more than one item, combine them all into a single list
// 2. if there is a single item and that item is a list, leave it as its specific list
// 3. if there is a single item and it is not a a list, leave it as a single item
var errs []error
singular := false
infos, err := r.IntoSingular(&singular).Infos()
......@@ -332,9 +327,22 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
res = infos[0].ResourceMapping().Resource
}
obj, err := resource.AsVersionedObject(infos, !singular, version, f.JSONEncoder())
if err != nil {
return err
var obj runtime.Object
if singular {
obj = infos[0].Object
} else {
// we have more than one item, so coerce all items into a list
list := &runtime.UnstructuredList{
Object: map[string]interface{}{
"kind": "List",
"apiVersion": "v1",
"metadata": map[string]interface{}{},
},
}
for _, info := range infos {
list.Items = append(list.Items, info.Object.(*runtime.Unstructured))
}
obj = list
}
isList := meta.IsListType(obj)
......@@ -343,11 +351,24 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
if err != nil {
return err
}
filteredObj, err := cmdutil.ObjectListToVersionedObject(items, version)
if err != nil {
return err
// take the filtered items and create a new list for display
list := &runtime.UnstructuredList{
Object: map[string]interface{}{
"kind": "List",
"apiVersion": "v1",
"metadata": map[string]interface{}{},
},
}
if listMeta, err := meta.ListAccessor(obj); err == nil {
list.Object["selfLink"] = listMeta.GetSelfLink()
list.Object["resourceVersion"] = listMeta.GetResourceVersion()
}
if err := printer.PrintObj(filteredObj, out); err != nil {
for _, item := range items {
list.Items = append(list.Items, item.(*runtime.Unstructured))
}
if err := printer.PrintObj(list, out); err != nil {
errs = append(errs, err)
}
......@@ -390,24 +411,6 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
}
var sorter *kubectl.RuntimeSort
if len(sorting) > 0 && len(objs) > 1 {
clientConfig, err := f.ClientConfig()
if err != nil {
return err
}
version, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion)
if err != nil {
return err
}
for ix := range infos {
objs[ix], err = infos[ix].Mapping.ConvertToVersion(infos[ix].Object, version)
if err != nil {
allErrs = append(allErrs, err)
continue
}
}
// TODO: questionable
if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil {
return err
......
......@@ -182,7 +182,11 @@ func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
}
func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) {
return nil, nil, nil
groupResources := testDynamicResources()
mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured)
typer := discovery.NewUnstructuredObjectTyper(groupResources)
return cmdutil.NewShortcutExpander(mapper, nil), typer, nil
}
func (f *FakeFactory) Decoder(bool) runtime.Decoder {
......@@ -379,7 +383,7 @@ func (f *fakeMixedFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTC
}
func NewMixedFactory(apiClient resource.RESTClient) (cmdutil.Factory, *TestFactory, runtime.Codec) {
f, t, c, _ := NewTestFactory()
f, t, c, _ := NewAPIFactory()
return &fakeMixedFactory{
Factory: f,
tf: t,
......@@ -545,6 +549,9 @@ func testDynamicResources() []*discovery.APIGroupResources {
{Name: "pods", Namespaced: true, Kind: "Pod"},
{Name: "services", Namespaced: true, Kind: "Service"},
{Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"},
{Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"},
{Name: "nodes", Namespaced: false, Kind: "Node"},
{Name: "type", Namespaced: false, Kind: "Type"},
},
},
},
......
......@@ -29,12 +29,10 @@ go_library(
"//pkg/api/service:go_default_library",
"//pkg/api/unversioned:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/apimachinery:go_default_library",
"//pkg/apimachinery/registered:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/restclient:go_default_library",
......
......@@ -33,23 +33,19 @@ import (
"time"
"github.com/emicklei/go-restful/swagger"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/kubernetes/federation/apis/federation"
"k8s.io/kubernetes/pkg/api"
apierrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/service"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apimachinery"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/client/restclient"
......@@ -339,21 +335,10 @@ func (f *factory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
mapper := registered.RESTMapper()
discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg)
if err == nil {
// register third party resources with the api machinery groups. This probably should be done, but
// its consistent with old code, so we'll start with it.
if err := registerThirdPartyResources(discoveryClient); err != nil {
glog.V(1).Infof("Unable to register third party resources: %v", err)
}
// ThirdPartyResourceData is special. It's not discoverable, but needed for thirdparty resource listing
// TODO eliminate this once we're truly generic.
thirdPartyResourceDataMapper := meta.NewDefaultRESTMapper([]unversioned.GroupVersion{extensionsv1beta1.SchemeGroupVersion}, registered.InterfacesFor)
thirdPartyResourceDataMapper.Add(extensionsv1beta1.SchemeGroupVersion.WithKind("ThirdPartyResourceData"), meta.RESTScopeNamespace)
mapper = meta.FirstHitRESTMapper{
MultiRESTMapper: meta.MultiRESTMapper{
discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, registered.InterfacesFor),
thirdPartyResourceDataMapper, // needed for TPR printing
registered.RESTMapper(), // hardcoded fall back
registered.RESTMapper(), // hardcoded fall back
},
}
}
......@@ -1331,54 +1316,3 @@ func (f *factory) SuggestedPodTemplateResources() []unversioned.GroupResource {
{Resource: "replicaset"},
}
}
// registerThirdPartyResources inspects the discovery endpoint to find thirdpartyresources in the discovery doc
// and then registers them with the apimachinery code. I think this is done so that scheme/codec stuff works,
// but I really don't know. Feels like this code should go away once kubectl is completely generic for generic
// CRUD
func registerThirdPartyResources(discoveryClient discovery.DiscoveryInterface) error {
var versions []unversioned.GroupVersion
var gvks []unversioned.GroupVersionKind
var err error
retries := 3
for i := 0; i < retries; i++ {
versions, gvks, err = GetThirdPartyGroupVersions(discoveryClient)
// Retry if we got a NotFound error, because user may delete
// a thirdparty group when the GetThirdPartyGroupVersions is
// running.
if err == nil || !apierrors.IsNotFound(err) {
break
}
}
if err != nil {
return err
}
groupsMap := map[string][]unversioned.GroupVersion{}
for _, version := range versions {
groupsMap[version.Group] = append(groupsMap[version.Group], version)
}
for group, versionList := range groupsMap {
preferredExternalVersion := versionList[0]
thirdPartyMapper, err := kubectl.NewThirdPartyResourceMapper(versionList, getGroupVersionKinds(gvks, group))
if err != nil {
return err
}
accessor := meta.NewAccessor()
groupMeta := apimachinery.GroupMeta{
GroupVersion: preferredExternalVersion,
GroupVersions: versionList,
RESTMapper: thirdPartyMapper,
SelfLinker: runtime.SelfLinker(accessor),
InterfacesFor: makeInterfacesFor(versionList),
}
if err := registered.RegisterGroup(groupMeta); err != nil {
return err
}
registered.AddThirdPartyAPIGroupVersions(versionList...)
}
return nil
}
......@@ -34,7 +34,6 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
......@@ -551,49 +550,6 @@ func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool {
return GetRecordFlag(cmd) || (ContainsChangeCause(info) && !cmd.Flags().Changed("record"))
}
// GetThirdPartyGroupVersions returns the thirdparty "group/versions"s and
// resources supported by the server. A user may delete a thirdparty resource
// when this function is running, so this function may return a "NotFound" error
// due to the race.
func GetThirdPartyGroupVersions(discovery discovery.DiscoveryInterface) ([]unversioned.GroupVersion, []unversioned.GroupVersionKind, error) {
result := []unversioned.GroupVersion{}
gvks := []unversioned.GroupVersionKind{}
groupList, err := discovery.ServerGroups()
if err != nil {
// On forbidden or not found, just return empty lists.
if kerrors.IsForbidden(err) || kerrors.IsNotFound(err) {
return result, gvks, nil
}
return nil, nil, err
}
for ix := range groupList.Groups {
group := &groupList.Groups[ix]
for jx := range group.Versions {
gv, err2 := unversioned.ParseGroupVersion(group.Versions[jx].GroupVersion)
if err2 != nil {
return nil, nil, err
}
// Skip GroupVersionKinds that have been statically registered.
if registered.IsRegisteredVersion(gv) {
continue
}
result = append(result, gv)
resourceList, err := discovery.ServerResourcesForGroupVersion(group.Versions[jx].GroupVersion)
if err != nil {
return nil, nil, err
}
for kx := range resourceList.APIResources {
gvks = append(gvks, gv.WithKind(resourceList.APIResources[kx].Kind))
}
}
}
return result, gvks, nil
}
func AddInclude3rdPartyFlags(cmd *cobra.Command) {
cmd.Flags().Bool("include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]")
cmd.Flags().MarkDeprecated("include-extended-apis", "No longer required.")
......
......@@ -206,9 +206,18 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso
}
}
}
for ix := range parsers {
parser := parsers[ix]
values, err := parser.FindResults(reflect.ValueOf(obj).Elem().Interface())
var values [][]reflect.Value
var err error
if unstructured, ok := obj.(*runtime.Unstructured); ok {
values, err = parser.FindResults(unstructured.Object)
} else {
values, err = parser.FindResults(reflect.ValueOf(obj).Elem().Interface())
}
if err != nil {
return err
}
......
......@@ -48,28 +48,6 @@ func makeImageList(spec *api.PodSpec) string {
return strings.Join(listOfImages(spec), ",")
}
func NewThirdPartyResourceMapper(gvs []unversioned.GroupVersion, gvks []unversioned.GroupVersionKind) (meta.RESTMapper, error) {
mapper := meta.NewDefaultRESTMapper(gvs, func(gv unversioned.GroupVersion) (*meta.VersionInterfaces, error) {
for ix := range gvs {
if gvs[ix].Group == gv.Group && gvs[ix].Version == gv.Version {
return &meta.VersionInterfaces{
ObjectConvertor: api.Scheme,
MetadataAccessor: meta.NewAccessor(),
}, nil
}
}
groupVersions := make([]string, 0, len(gvs))
for ix := range gvs {
groupVersions = append(groupVersions, gvs[ix].String())
}
return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", gv.String(), strings.Join(groupVersions, ", "))
})
for ix := range gvks {
mapper.Add(gvks[ix], meta.RESTScopeNamespace)
}
return mapper, nil
}
// OutputVersionMapper is a RESTMapper that will prefer mappings that
// correspond to a preferred output version (if feasible)
type OutputVersionMapper struct {
......
......@@ -241,8 +241,6 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
return nil
}
// TODO: this is wrong, runtime.Unknown and runtime.Unstructured are not handled properly here.
name := "<unknown>"
if acc, err := meta.Accessor(obj); err == nil {
if n := acc.GetName(); len(n) > 0 {
......@@ -250,12 +248,22 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
}
}
if gvks, _, err := p.Typer.ObjectKinds(obj); err == nil {
if kind := obj.GetObjectKind().GroupVersionKind(); len(kind.Kind) == 0 {
// this is the old code. It's unnecessary on decoded external objects, but on internal objects
// you may have to do it. Tests are definitely calling it with internals and I'm not sure who else
// is
if gvks, _, err := p.Typer.ObjectKinds(obj); err == nil {
// TODO: this is wrong, it assumes that meta knows about all Kinds - should take a RESTMapper
_, resource := meta.KindToResource(gvks[0])
fmt.Fprintf(w, "%s/%s\n", resource.Resource, name)
} else {
fmt.Fprintf(w, "<unknown>/%s\n", name)
}
} else {
// TODO: this is wrong, it assumes that meta knows about all Kinds - should take a RESTMapper
_, resource := meta.KindToResource(gvks[0])
_, resource := meta.KindToResource(kind)
fmt.Fprintf(w, "%s/%s\n", resource.Resource, name)
} else {
fmt.Fprintf(w, "<unknown>/%s\n", name)
}
return nil
......@@ -2251,6 +2259,18 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
w = GetNewTabWriter(output)
defer w.Flush()
}
// check if the object is unstructured. If so, let's attempt to convert it to a type we can understand before
// trying to print, since the printers are keyed by type. This is extremely expensive.
switch obj.(type) {
case *runtime.Unstructured, *runtime.Unknown:
if objBytes, err := runtime.Encode(api.Codecs.LegacyCodec(), obj); err == nil {
if decodedObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), objBytes); err == nil {
obj = decodedObj
}
}
}
t := reflect.TypeOf(obj)
if handler := h.handlerMap[t]; handler != nil {
if !h.options.NoHeaders && t != h.lastType {
......@@ -2271,9 +2291,78 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
}
return resultValue.Interface().(error)
}
// we don't recognize this type, but we can still attempt to print some reasonable information about.
unstructured, ok := obj.(*runtime.Unstructured)
if !ok {
return fmt.Errorf("error: unknown type %#v", obj)
}
if _, err := meta.Accessor(obj); err == nil {
if !h.options.NoHeaders && t != h.lastType {
headers := []string{"NAME", "KIND"}
headers = append(headers, formatLabelHeaders(h.options.ColumnLabels)...)
// LABELS is always the last column.
headers = append(headers, formatShowLabelsHeader(h.options.ShowLabels, t)...)
if h.options.WithNamespace {
headers = append(withNamespacePrefixColumns, headers...)
}
h.printHeader(headers, w)
h.lastType = t
}
// if the error isn't nil, report the "I don't recognize this" error
if err := printUnstructured(unstructured, w, h.options); err != nil {
return err
}
return nil
}
// we failed all reasonable printing efforts, report failure
return fmt.Errorf("error: unknown type %#v", obj)
}
func printUnstructured(unstructured *runtime.Unstructured, w io.Writer, options PrintOptions) error {
metadata, err := meta.Accessor(unstructured)
if err != nil {
return err
}
if options.WithNamespace {
if _, err := fmt.Fprintf(w, "%s\t", metadata.GetNamespace()); err != nil {
return err
}
}
kind := "<missing>"
if objKind, ok := unstructured.Object["kind"]; ok {
if str, ok := objKind.(string); ok {
kind = str
}
}
if objAPIVersion, ok := unstructured.Object["apiVersion"]; ok {
if str, ok := objAPIVersion.(string); ok {
version, err := unversioned.ParseGroupVersion(str)
if err != nil {
return err
}
kind = kind + "." + version.Version + "." + version.Group
}
}
name := formatResourceName(options.Kind, metadata.GetName(), options.WithKind)
if _, err := fmt.Fprintf(w, "%s\t%s", name, kind); err != nil {
return err
}
if _, err := fmt.Fprint(w, AppendLabels(metadata.GetLabels(), options.ColumnLabels)); err != nil {
return err
}
if _, err := fmt.Fprint(w, AppendAllLabels(options.ShowLabels, metadata.GetLabels())); err != nil {
return err
}
return nil
}
// TemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
type TemplatePrinter struct {
rawTemplate string
......@@ -2299,10 +2388,18 @@ func (p *TemplatePrinter) AfterPrint(w io.Writer, res string) error {
// PrintObj formats the obj with the Go Template.
func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
data, err := json.Marshal(obj)
var data []byte
var err error
if unstructured, ok := obj.(*runtime.Unstructured); ok {
data, err = json.Marshal(unstructured.Object)
} else {
data, err = json.Marshal(obj)
}
if err != nil {
return err
}
out := map[string]interface{}{}
if err := json.Unmarshal(data, &out); err != nil {
return err
......@@ -2462,6 +2559,20 @@ func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
}
}
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.Object
}
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)
......
......@@ -109,7 +109,13 @@ func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput stri
}
}
values, err := parser.FindResults(reflect.ValueOf(objs[0]).Elem().Interface())
var values [][]reflect.Value
if unstructured, ok := objs[0].(*runtime.Unstructured); ok {
values, err = parser.FindResults(unstructured.Object)
} else {
values, err = parser.FindResults(reflect.ValueOf(objs[0]).Elem().Interface())
}
if err != nil {
return nil, err
}
......@@ -182,6 +188,66 @@ func isLess(i, j reflect.Value) (bool, error) {
}
}
return true, nil
case reflect.Interface:
switch itype := i.Interface().(type) {
case uint8:
if jtype, ok := j.Interface().(uint8); ok {
return itype < jtype, nil
}
case uint16:
if jtype, ok := j.Interface().(uint16); ok {
return itype < jtype, nil
}
case uint32:
if jtype, ok := j.Interface().(uint32); ok {
return itype < jtype, nil
}
case uint64:
if jtype, ok := j.Interface().(uint64); ok {
return itype < jtype, nil
}
case int8:
if jtype, ok := j.Interface().(int8); ok {
return itype < jtype, nil
}
case int16:
if jtype, ok := j.Interface().(int16); ok {
return itype < jtype, nil
}
case int32:
if jtype, ok := j.Interface().(int32); ok {
return itype < jtype, nil
}
case int64:
if jtype, ok := j.Interface().(int64); ok {
return itype < jtype, nil
}
case uint:
if jtype, ok := j.Interface().(uint); ok {
return itype < jtype, nil
}
case int:
if jtype, ok := j.Interface().(int); ok {
return itype < jtype, nil
}
case float32:
if jtype, ok := j.Interface().(float32); ok {
return itype < jtype, nil
}
case float64:
if jtype, ok := j.Interface().(float64); ok {
return itype < jtype, nil
}
case string:
if jtype, ok := j.Interface().(string); ok {
return itype < jtype, nil
}
default:
return false, fmt.Errorf("unsortable type: %T", itype)
}
return false, fmt.Errorf("unsortable interface: %v", i.Kind())
default:
return false, fmt.Errorf("unsortable type: %v", i.Kind())
}
......@@ -194,11 +260,24 @@ func (r *RuntimeSort) Less(i, j int) bool {
parser := jsonpath.New("sorting")
parser.Parse(r.field)
iValues, err := parser.FindResults(reflect.ValueOf(iObj).Elem().Interface())
var iValues [][]reflect.Value
var jValues [][]reflect.Value
var err error
if unstructured, ok := iObj.(*runtime.Unstructured); ok {
iValues, err = parser.FindResults(unstructured.Object)
} else {
iValues, err = parser.FindResults(reflect.ValueOf(iObj).Elem().Interface())
}
if err != nil {
glog.Fatalf("Failed to get i values for %#v using %s (%#v)", iObj, r.field, err)
}
jValues, err := parser.FindResults(reflect.ValueOf(jObj).Elem().Interface())
if unstructured, ok := jObj.(*runtime.Unstructured); ok {
jValues, err = parser.FindResults(unstructured.Object)
} else {
jValues, err = parser.FindResults(reflect.ValueOf(jObj).Elem().Interface())
}
if err != nil {
glog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err)
}
......
......@@ -551,25 +551,25 @@ var _ = framework.KubeDescribe("Kubectl client", func() {
By("checking the result")
forEachReplicationController(c, ns, "app", "redis", validateReplicationControllerConfiguration)
})
It("should reuse nodePort when apply to an existing SVC", func() {
It("should reuse port when apply to an existing SVC", func() {
serviceJson := readTestFileOrDie(redisServiceFilename)
nsFlag := fmt.Sprintf("--namespace=%v", ns)
By("creating Redis SVC")
framework.RunKubectlOrDieInput(string(serviceJson[:]), "create", "-f", "-", nsFlag)
By("getting the original nodePort")
originalNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].nodePort}")
By("getting the original port")
originalNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].port}")
By("applying the same configuration")
framework.RunKubectlOrDieInput(string(serviceJson[:]), "apply", "-f", "-", nsFlag)
By("getting the nodePort after applying configuration")
currentNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].nodePort}")
By("getting the port after applying configuration")
currentNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].port}")
By("checking the result")
if originalNodePort != currentNodePort {
framework.Failf("nodePort should keep the same")
framework.Failf("port should keep the same")
}
})
})
......
......@@ -181,7 +181,7 @@ Kubectl alpha client Kubectl run ScheduledJob should create a ScheduledJob,karga
Kubectl client Guestbook application should create and stop a working application,pwittrock,0
Kubectl client Kubectl api-versions should check if v1 is in available api versions,pwittrock,0
Kubectl client Kubectl apply should apply a new configuration to an existing RC,pwittrock,0
Kubectl client Kubectl apply should reuse nodePort when apply to an existing SVC,pwittrock,0
Kubectl client Kubectl apply should reuse port when apply to an existing SVC,deads2k,0
Kubectl client Kubectl cluster-info should check if Kubernetes master services is included in cluster-info,pwittrock,0
Kubectl client Kubectl create quota should create a quota with scopes,jdef,1
Kubectl client Kubectl create quota should create a quota without scopes,xiang90,1
......
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