Add third party support to kubectl

This commit is contained in:
Brendan Burns
2016-03-09 17:27:19 -08:00
parent 84a6c4588a
commit be6c5b332b
115 changed files with 697 additions and 148 deletions

View File

@@ -39,6 +39,7 @@ import (
"k8s.io/kubernetes/pkg/api/meta"
"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/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
@@ -51,6 +52,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/json"
utilflag "k8s.io/kubernetes/pkg/util/flag"
@@ -69,8 +71,9 @@ type Factory struct {
clients *ClientCache
flags *pflag.FlagSet
// Returns interfaces for dealing with arbitrary runtime.Objects.
Object func() (meta.RESTMapper, runtime.ObjectTyper)
// Returns interfaces for dealing with arbitrary runtime.Objects. If thirdPartyDiscovery is true, performs API calls
// to discovery dynamic API objects registered by third parties.
Object func(thirdPartyDiscovery bool) (meta.RESTMapper, runtime.ObjectTyper)
// Returns interfaces for decoding objects - if toInternal is set, decoded objects will be converted
// into their internal form (if possible). Eventually the internal form will be removed as an option,
// and only versioned objects will be returned.
@@ -179,6 +182,31 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator {
return generators[cmdName]
}
func getGroupVersionKinds(gvks []unversioned.GroupVersionKind, group string) []unversioned.GroupVersionKind {
result := []unversioned.GroupVersionKind{}
for ix := range gvks {
if gvks[ix].Group == group {
result = append(result, gvks[ix])
}
}
return result
}
func makeInterfacesFor(versionList []unversioned.GroupVersion) func(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) {
accessor := meta.NewAccessor()
return func(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) {
for ix := range versionList {
if versionList[ix].String() == version.String() {
return &meta.VersionInterfaces{
ObjectConvertor: thirdpartyresourcedata.NewThirdPartyObjectConverter(api.Scheme),
MetadataAccessor: accessor,
}, nil
}
}
return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, versionList)
}
}
// NewFactory creates a factory with the default Kubernetes resources defined
// if optionalClientConfig is nil, then flags will be bound to a new clientcmd.ClientConfig.
// if optionalClientConfig is not nil, then this factory will make use of it.
@@ -199,17 +227,61 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
clients: clients,
flags: flags,
Object: func() (meta.RESTMapper, runtime.ObjectTyper) {
// If discoverDynamicAPIs is true, make API calls to the discovery service to find APIs that
// have been dynamically added to the apiserver
Object: func(discoverDynamicAPIs bool) (meta.RESTMapper, runtime.ObjectTyper) {
cfg, err := clientConfig.ClientConfig()
CheckErr(err)
cmdApiVersion := unversioned.GroupVersion{}
if cfg.GroupVersion != nil {
cmdApiVersion = *cfg.GroupVersion
}
if discoverDynamicAPIs {
client, err := clients.ClientForVersion(&unversioned.GroupVersion{Version: "v1"})
CheckErr(err)
versions, gvks, err := GetThirdPartyGroupVersions(client.Discovery())
CheckErr(err)
if len(versions) > 0 {
priorityMapper, ok := mapper.RESTMapper.(meta.PriorityRESTMapper)
if !ok {
CheckErr(fmt.Errorf("expected PriorityMapper, saw: %v", mapper.RESTMapper))
return nil, nil
}
multiMapper, ok := priorityMapper.Delegate.(meta.MultiRESTMapper)
if !ok {
CheckErr(fmt.Errorf("unexpected type: %v", mapper.RESTMapper))
return nil, nil
}
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))
CheckErr(err)
accessor := meta.NewAccessor()
groupMeta := apimachinery.GroupMeta{
GroupVersion: preferredExternalVersion,
GroupVersions: versionList,
RESTMapper: thirdPartyMapper,
SelfLinker: runtime.SelfLinker(accessor),
InterfacesFor: makeInterfacesFor(versionList),
}
CheckErr(registered.RegisterGroup(groupMeta))
registered.AddThirdPartyAPIGroupVersions(versionList...)
multiMapper = append(meta.MultiRESTMapper{thirdPartyMapper}, multiMapper...)
}
priorityMapper.Delegate = multiMapper
// Re-assign to the RESTMapper here because priorityMapper is actually a copy, so if we
// don't re-assign, the above assignement won't actually update mapper.RESTMapper
mapper.RESTMapper = priorityMapper
}
}
outputRESTMapper := kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersions: []unversioned.GroupVersion{cmdApiVersion}}
// eventually this should allow me choose a group priority based on the order of the discovery doc, for now hardcode a given order
priorityRESTMapper := meta.PriorityRESTMapper{
Delegate: outputRESTMapper,
ResourcePriority: []unversioned.GroupVersionResource{
@@ -223,7 +295,6 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
{Group: metrics.GroupName, Version: meta.AnyVersion, Kind: meta.AnyKind},
},
}
return priorityRESTMapper, api.Scheme
},
Client: func() (*client.Client, error) {
@@ -233,22 +304,39 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
return clients.ClientConfigForVersion(nil)
},
ClientForMapping: func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
gvk := mapping.GroupVersionKind
mappingVersion := mapping.GroupVersionKind.GroupVersion()
client, err := clients.ClientForVersion(&mappingVersion)
c, err := clients.ClientForVersion(&mappingVersion)
if err != nil {
return nil, err
}
switch mapping.GroupVersionKind.Group {
switch gvk.Group {
case api.GroupName:
return client.RESTClient, nil
return c.RESTClient, nil
case autoscaling.GroupName:
return client.AutoscalingClient.RESTClient, nil
return c.AutoscalingClient.RESTClient, nil
case batch.GroupName:
return client.BatchClient.RESTClient, nil
return c.BatchClient.RESTClient, nil
case extensions.GroupName:
return client.ExtensionsClient.RESTClient, nil
return c.ExtensionsClient.RESTClient, nil
case api.SchemeGroupVersion.Group:
return c.RESTClient, nil
case extensions.SchemeGroupVersion.Group:
return c.ExtensionsClient.RESTClient, nil
default:
if !registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
return nil, fmt.Errorf("unknown api group/version: %s", gvk.String())
}
cfg, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
gv := gvk.GroupVersion()
cfg.GroupVersion = &gv
cfg.APIPath = "/apis"
cfg.Codec = thirdpartyresourcedata.NewCodec(c.ExtensionsClient.RESTClient.Codec(), gvk.Kind)
return restclient.RESTClientFor(cfg)
}
return nil, fmt.Errorf("unable to get RESTClient for resource '%s'", mapping.Resource)
},
Describer: func(mapping *meta.RESTMapping) (kubectl.Describer, error) {
mappingVersion := mapping.GroupVersionKind.GroupVersion()
@@ -810,6 +898,10 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
}
return getSchemaAndValidate(c.c.BatchClient.RESTClient, data, "apis/", gvk.GroupVersion().String(), c.cacheDir)
}
if registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
// Don't attempt to validate third party objects
return nil
}
if gvk.Group == extensions.GroupName {
if c.c.ExtensionsClient == nil {
return errors.New("unable to validate: no experimental client")
@@ -874,8 +966,7 @@ func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
}
// PrintObject prints an api object given command line flags to modify the output format
func (f *Factory) PrintObject(cmd *cobra.Command, obj runtime.Object, out io.Writer) error {
mapper, _ := f.Object()
func (f *Factory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error {
gvk, err := api.Scheme.ObjectKind(obj)
if err != nil {
return err
@@ -936,8 +1027,8 @@ func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMappin
}
// One stop shopping for a Builder
func (f *Factory) NewBuilder() *resource.Builder {
mapper, typer := f.Object()
func (f *Factory) NewBuilder(thirdPartyDiscovery bool) *resource.Builder {
mapper, typer := f.Object(thirdPartyDiscovery)
return resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true))
}