Commit 61673c4b authored by deads2k's avatar deads2k

make kubectl get generic with respect to objects

parent 7fe9bd4c
...@@ -26,6 +26,14 @@ import ( ...@@ -26,6 +26,14 @@ import (
// IsListType returns true if the provided Object has a slice called Items // IsListType returns true if the provided Object has a slice called Items
func IsListType(obj runtime.Object) bool { 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) _, err := GetItemsPtr(obj)
return err == nil return err == nil
} }
...@@ -39,6 +47,7 @@ func GetItemsPtr(list runtime.Object) (interface{}, error) { ...@@ -39,6 +47,7 @@ func GetItemsPtr(list runtime.Object) (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
items := v.FieldByName("Items") items := v.FieldByName("Items")
if !items.IsValid() { if !items.IsValid() {
return nil, fmt.Errorf("no Items field in %#v", list) return nil, fmt.Errorf("no Items field in %#v", list)
...@@ -117,6 +126,13 @@ func SetList(list runtime.Object, objects []runtime.Object) error { ...@@ -117,6 +126,13 @@ func SetList(list runtime.Object, objects []runtime.Object) error {
slice := reflect.MakeSlice(items.Type(), len(objects), len(objects)) slice := reflect.MakeSlice(items.Type(), len(objects), len(objects))
for i := range objects { for i := range objects {
dest := slice.Index(i) 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]) src, err := conversion.EnforcePtr(objects[i])
if err != nil { if err != nil {
return err return err
......
...@@ -229,6 +229,27 @@ func TestSetListToRuntimeObjectArray(t *testing.T) { ...@@ -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) { func TestSetExtractListRoundTrip(t *testing.T) {
fuzzer := fuzz.New().NilChance(0).NumElements(1, 5) fuzzer := fuzz.New().NilChance(0).NumElements(1, 5)
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
......
...@@ -182,11 +182,9 @@ go_test( ...@@ -182,11 +182,9 @@ go_test(
"//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/resource:go_default_library",
"//pkg/runtime:go_default_library", "//pkg/runtime:go_default_library",
"//pkg/runtime/serializer:go_default_library",
"//pkg/runtime/serializer/json:go_default_library", "//pkg/runtime/serializer/json:go_default_library",
"//pkg/runtime/serializer/streaming:go_default_library", "//pkg/runtime/serializer/streaming:go_default_library",
"//pkg/types:go_default_library", "//pkg/types:go_default_library",
"//pkg/util/diff:go_default_library",
"//pkg/util/intstr:go_default_library", "//pkg/util/intstr:go_default_library",
"//pkg/util/strings:go_default_library", "//pkg/util/strings:go_default_library",
"//pkg/util/term:go_default_library", "//pkg/util/term:go_default_library",
......
...@@ -28,16 +28,12 @@ import ( ...@@ -28,16 +28,12 @@ import (
"k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake" "k8s.io/kubernetes/pkg/client/restclient/fake"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer"
) )
var unstructuredSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{ var unstructuredSerializer = dynamic.ContentConfig().NegotiatedSerializer
MediaType: "application/json",
EncodesAsText: true,
Serializer: runtime.UnstructuredJSONScheme})
func TestDeleteObjectByTuple(t *testing.T) { func TestDeleteObjectByTuple(t *testing.T) {
_, _, rc := testData() _, _, rc := testData()
......
...@@ -149,7 +149,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -149,7 +149,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
selector := cmdutil.GetFlagString(cmd, "selector") selector := cmdutil.GetFlagString(cmd, "selector")
allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces") allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces")
showKind := cmdutil.GetFlagBool(cmd, "show-kind") showKind := cmdutil.GetFlagBool(cmd, "show-kind")
mapper, typer := f.Object() mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
printAll := false printAll := false
filterFuncs := f.DefaultResourceFilterFunc() filterFuncs := f.DefaultResourceFilterFunc()
filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces) filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces)
...@@ -196,7 +199,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -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 // handle watch separately since we cannot watch multiple resource types
isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only") isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only")
if isWatch || isWatchOnly { 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). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, &options.FilenameOptions). FilenameParam(enforceNamespace, &options.FilenameOptions).
SelectorParam(selector). SelectorParam(selector).
...@@ -281,7 +284,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -281,7 +284,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
return nil 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). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, &options.FilenameOptions). FilenameParam(enforceNamespace, &options.FilenameOptions).
SelectorParam(selector). SelectorParam(selector).
...@@ -302,18 +305,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -302,18 +305,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
} }
if generic { if generic {
clientConfig, err := f.ClientConfig() // we flattened the data from the builder, so we have individual items, but now we'd like to either:
if err != nil { // 1. if there is more than one item, combine them all into a single list
return err // 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
// 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
}
var errs []error var errs []error
singular := false singular := false
infos, err := r.IntoSingular(&singular).Infos() infos, err := r.IntoSingular(&singular).Infos()
...@@ -332,9 +327,22 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -332,9 +327,22 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
res = infos[0].ResourceMapping().Resource res = infos[0].ResourceMapping().Resource
} }
obj, err := resource.AsVersionedObject(infos, !singular, version, f.JSONEncoder()) var obj runtime.Object
if err != nil { if singular {
return err 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) isList := meta.IsListType(obj)
...@@ -343,11 +351,24 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -343,11 +351,24 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
if err != nil { if err != nil {
return err return err
} }
filteredObj, err := cmdutil.ObjectListToVersionedObject(items, version)
if err != nil { // take the filtered items and create a new list for display
return err 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) errs = append(errs, err)
} }
...@@ -390,24 +411,6 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -390,24 +411,6 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
} }
var sorter *kubectl.RuntimeSort var sorter *kubectl.RuntimeSort
if len(sorting) > 0 && len(objs) > 1 { 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 // TODO: questionable
if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil { if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil {
return err return err
......
...@@ -182,7 +182,11 @@ func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) { ...@@ -182,7 +182,11 @@ func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
} }
func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) { 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 { func (f *FakeFactory) Decoder(bool) runtime.Decoder {
...@@ -379,7 +383,7 @@ func (f *fakeMixedFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTC ...@@ -379,7 +383,7 @@ func (f *fakeMixedFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTC
} }
func NewMixedFactory(apiClient resource.RESTClient) (cmdutil.Factory, *TestFactory, runtime.Codec) { func NewMixedFactory(apiClient resource.RESTClient) (cmdutil.Factory, *TestFactory, runtime.Codec) {
f, t, c, _ := NewTestFactory() f, t, c, _ := NewAPIFactory()
return &fakeMixedFactory{ return &fakeMixedFactory{
Factory: f, Factory: f,
tf: t, tf: t,
...@@ -545,6 +549,9 @@ func testDynamicResources() []*discovery.APIGroupResources { ...@@ -545,6 +549,9 @@ func testDynamicResources() []*discovery.APIGroupResources {
{Name: "pods", Namespaced: true, Kind: "Pod"}, {Name: "pods", Namespaced: true, Kind: "Pod"},
{Name: "services", Namespaced: true, Kind: "Service"}, {Name: "services", Namespaced: true, Kind: "Service"},
{Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"}, {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( ...@@ -29,12 +29,10 @@ go_library(
"//pkg/api/service:go_default_library", "//pkg/api/service:go_default_library",
"//pkg/api/unversioned:go_default_library", "//pkg/api/unversioned:go_default_library",
"//pkg/api/validation:go_default_library", "//pkg/api/validation:go_default_library",
"//pkg/apimachinery:go_default_library",
"//pkg/apimachinery/registered:go_default_library", "//pkg/apimachinery/registered:go_default_library",
"//pkg/apis/apps:go_default_library", "//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library", "//pkg/apis/batch:go_default_library",
"//pkg/apis/extensions: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:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/restclient:go_default_library", "//pkg/client/restclient:go_default_library",
......
...@@ -33,23 +33,19 @@ import ( ...@@ -33,23 +33,19 @@ import (
"time" "time"
"github.com/emicklei/go-restful/swagger" "github.com/emicklei/go-restful/swagger"
"github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/kubernetes/federation/apis/federation" "k8s.io/kubernetes/federation/apis/federation"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
apierrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/service" "k8s.io/kubernetes/pkg/api/service"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apimachinery"
"k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/restclient"
...@@ -339,21 +335,10 @@ func (f *factory) Object() (meta.RESTMapper, runtime.ObjectTyper) { ...@@ -339,21 +335,10 @@ func (f *factory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
mapper := registered.RESTMapper() mapper := registered.RESTMapper()
discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg) discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg)
if err == nil { 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{ mapper = meta.FirstHitRESTMapper{
MultiRESTMapper: meta.MultiRESTMapper{ MultiRESTMapper: meta.MultiRESTMapper{
discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, registered.InterfacesFor), 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 { ...@@ -1331,54 +1316,3 @@ func (f *factory) SuggestedPodTemplateResources() []unversioned.GroupResource {
{Resource: "replicaset"}, {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 ( ...@@ -34,7 +34,6 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
...@@ -551,49 +550,6 @@ func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool { ...@@ -551,49 +550,6 @@ func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool {
return GetRecordFlag(cmd) || (ContainsChangeCause(info) && !cmd.Flags().Changed("record")) 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) { 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().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.") cmd.Flags().MarkDeprecated("include-extended-apis", "No longer required.")
......
...@@ -206,9 +206,18 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso ...@@ -206,9 +206,18 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso
} }
} }
} }
for ix := range parsers { for ix := range parsers {
parser := parsers[ix] 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 { if err != nil {
return err return err
} }
......
...@@ -48,28 +48,6 @@ func makeImageList(spec *api.PodSpec) string { ...@@ -48,28 +48,6 @@ func makeImageList(spec *api.PodSpec) string {
return strings.Join(listOfImages(spec), ",") 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 // OutputVersionMapper is a RESTMapper that will prefer mappings that
// correspond to a preferred output version (if feasible) // correspond to a preferred output version (if feasible)
type OutputVersionMapper struct { type OutputVersionMapper struct {
......
...@@ -241,8 +241,6 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error { ...@@ -241,8 +241,6 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
return nil return nil
} }
// TODO: this is wrong, runtime.Unknown and runtime.Unstructured are not handled properly here.
name := "<unknown>" name := "<unknown>"
if acc, err := meta.Accessor(obj); err == nil { if acc, err := meta.Accessor(obj); err == nil {
if n := acc.GetName(); len(n) > 0 { if n := acc.GetName(); len(n) > 0 {
...@@ -250,12 +248,22 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error { ...@@ -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 // 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) fmt.Fprintf(w, "%s/%s\n", resource.Resource, name)
} else {
fmt.Fprintf(w, "<unknown>/%s\n", name)
} }
return nil return nil
...@@ -2251,6 +2259,18 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er ...@@ -2251,6 +2259,18 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
w = GetNewTabWriter(output) w = GetNewTabWriter(output)
defer w.Flush() 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) t := reflect.TypeOf(obj)
if handler := h.handlerMap[t]; handler != nil { if handler := h.handlerMap[t]; handler != nil {
if !h.options.NoHeaders && t != h.lastType { if !h.options.NoHeaders && t != h.lastType {
...@@ -2271,9 +2291,78 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er ...@@ -2271,9 +2291,78 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
} }
return resultValue.Interface().(error) 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) 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. // TemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template.
type TemplatePrinter struct { type TemplatePrinter struct {
rawTemplate string rawTemplate string
...@@ -2299,10 +2388,18 @@ func (p *TemplatePrinter) AfterPrint(w io.Writer, res string) error { ...@@ -2299,10 +2388,18 @@ func (p *TemplatePrinter) AfterPrint(w io.Writer, res string) error {
// PrintObj formats the obj with the Go Template. // PrintObj formats the obj with the Go Template.
func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error { 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 { if err != nil {
return err return err
} }
out := map[string]interface{}{} out := map[string]interface{}{}
if err := json.Unmarshal(data, &out); err != nil { if err := json.Unmarshal(data, &out); err != nil {
return err return err
...@@ -2462,6 +2559,20 @@ func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error { ...@@ -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 { 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, "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, "\ttemplate was:\n\t\t%v\n", j.rawTemplate)
......
...@@ -109,7 +109,13 @@ func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput stri ...@@ -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 { if err != nil {
return nil, err return nil, err
} }
...@@ -182,6 +188,66 @@ func isLess(i, j reflect.Value) (bool, error) { ...@@ -182,6 +188,66 @@ func isLess(i, j reflect.Value) (bool, error) {
} }
} }
return true, nil 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: default:
return false, fmt.Errorf("unsortable type: %v", i.Kind()) return false, fmt.Errorf("unsortable type: %v", i.Kind())
} }
...@@ -194,11 +260,24 @@ func (r *RuntimeSort) Less(i, j int) bool { ...@@ -194,11 +260,24 @@ func (r *RuntimeSort) Less(i, j int) bool {
parser := jsonpath.New("sorting") parser := jsonpath.New("sorting")
parser.Parse(r.field) 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 { if err != nil {
glog.Fatalf("Failed to get i values for %#v using %s (%#v)", iObj, r.field, err) 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 { if err != nil {
glog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err) 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() { ...@@ -551,25 +551,25 @@ var _ = framework.KubeDescribe("Kubectl client", func() {
By("checking the result") By("checking the result")
forEachReplicationController(c, ns, "app", "redis", validateReplicationControllerConfiguration) 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) serviceJson := readTestFileOrDie(redisServiceFilename)
nsFlag := fmt.Sprintf("--namespace=%v", ns) nsFlag := fmt.Sprintf("--namespace=%v", ns)
By("creating Redis SVC") By("creating Redis SVC")
framework.RunKubectlOrDieInput(string(serviceJson[:]), "create", "-f", "-", nsFlag) framework.RunKubectlOrDieInput(string(serviceJson[:]), "create", "-f", "-", nsFlag)
By("getting the original nodePort") By("getting the original port")
originalNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].nodePort}") originalNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].port}")
By("applying the same configuration") By("applying the same configuration")
framework.RunKubectlOrDieInput(string(serviceJson[:]), "apply", "-f", "-", nsFlag) framework.RunKubectlOrDieInput(string(serviceJson[:]), "apply", "-f", "-", nsFlag)
By("getting the nodePort after applying configuration") By("getting the port after applying configuration")
currentNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].nodePort}") currentNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].port}")
By("checking the result") By("checking the result")
if originalNodePort != currentNodePort { 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 ...@@ -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 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 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 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 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 with scopes,jdef,1
Kubectl client Kubectl create quota should create a quota without scopes,xiang90,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