Add experimental API support to kubectl

This commit is contained in:
Muhammed Uluyol
2015-08-10 13:08:34 -07:00
parent 01807c1fac
commit fab367230f
16 changed files with 194 additions and 63 deletions

View File

@@ -17,6 +17,7 @@ limitations under the License.
package util
import (
"errors"
"flag"
"fmt"
"io"
@@ -27,7 +28,6 @@ import (
"github.com/spf13/pflag"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/registered"
"k8s.io/kubernetes/pkg/api/validation"
@@ -57,6 +57,8 @@ type Factory struct {
Object func() (meta.RESTMapper, runtime.ObjectTyper)
// Returns a client for accessing Kubernetes resources or an error.
Client func() (*client.Client, error)
// Returns a client for accessing experimental Kubernetes resources or an error.
ExperimentalClient func() (*client.ExperimentalClient, error)
// Returns a client.Config for accessing the Kubernetes server.
ClientConfig func() (*client.Config, error)
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended
@@ -90,7 +92,7 @@ type Factory struct {
// 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.
func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
mapper := kubectl.ShortcutExpander{RESTMapper: latest.RESTMapper}
mapper := kubectl.ShortcutExpander{RESTMapper: api.RESTMapper}
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
flags.SetNormalizeFunc(util.WarnWordSepNormalizeFunc) // Warn for "_" flags
@@ -109,6 +111,25 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
clients := NewClientCache(clientConfig)
// Initialize the experimental client (if possible). Failing here is non-fatal, errors
// will be returned when an experimental client is explicitly requested.
var experimentalClient *client.ExperimentalClient
cfg, experimentalClientErr := clientConfig.ClientConfig()
if experimentalClientErr == nil {
experimentalClient, experimentalClientErr = client.NewExperimental(cfg)
}
noClientErr := errors.New("could not get client")
getBothClients := func(group string, version string) (client *client.Client, expClient *client.ExperimentalClient, err error) {
err = noClientErr
switch group {
case "api":
client, err = clients.ClientForVersion(version)
case "experimental":
expClient, err = experimentalClient, experimentalClientErr
}
return
}
return &Factory{
clients: clients,
flags: flags,
@@ -124,26 +145,46 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
Client: func() (*client.Client, error) {
return clients.ClientForVersion("")
},
ExperimentalClient: func() (*client.ExperimentalClient, error) {
return experimentalClient, experimentalClientErr
},
ClientConfig: func() (*client.Config, error) {
return clients.ClientConfigForVersion("")
},
RESTClient: func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
client, err := clients.ClientForVersion(mapping.APIVersion)
group, err := api.RESTMapper.GroupForResource(mapping.Resource)
if err != nil {
return nil, err
}
return client.RESTClient, nil
switch group {
case "api":
client, err := clients.ClientForVersion(mapping.APIVersion)
if err != nil {
return nil, err
}
return client.RESTClient, nil
case "experimental":
client, err := experimentalClient, experimentalClientErr
if err != nil {
return nil, err
}
return client.RESTClient, nil
}
return nil, fmt.Errorf("unable to get RESTClient for resource '%s'", mapping.Resource)
},
Describer: func(mapping *meta.RESTMapping) (kubectl.Describer, error) {
client, err := clients.ClientForVersion(mapping.APIVersion)
group, err := api.RESTMapper.GroupForResource(mapping.Resource)
if err != nil {
return nil, err
}
describer, ok := kubectl.DescriberFor(mapping.Kind, client)
if !ok {
return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind)
client, expClient, err := getBothClients(group, mapping.APIVersion)
if err != nil {
return nil, err
}
return describer, nil
if describer, ok := kubectl.DescriberFor(mapping.Kind, client, expClient); ok {
return describer, nil
}
return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind)
},
Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, wide bool, columnLabels []string) (kubectl.ResourcePrinter, error) {
return kubectl.NewHumanReadablePrinter(noHeaders, withNamespace, wide, columnLabels), nil
@@ -192,18 +233,26 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
return meta.NewAccessor().Labels(object)
},
Scaler: func(mapping *meta.RESTMapping) (kubectl.Scaler, error) {
client, err := clients.ClientForVersion(mapping.APIVersion)
group, err := api.RESTMapper.GroupForResource(mapping.Resource)
if err != nil {
return nil, err
}
client, _, err := getBothClients(group, mapping.APIVersion)
if err != nil {
return nil, err
}
return kubectl.ScalerFor(mapping.Kind, kubectl.NewScalerClient(client))
},
Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) {
client, err := clients.ClientForVersion(mapping.APIVersion)
group, err := api.RESTMapper.GroupForResource(mapping.Resource)
if err != nil {
return nil, err
}
return kubectl.ReaperFor(mapping.Kind, client)
client, expClient, err := getBothClients(group, mapping.APIVersion)
if err != nil {
return nil, err
}
return kubectl.ReaperFor(mapping.Kind, client, expClient)
},
Validator: func() (validation.Schema, error) {
if flags.Lookup("validate").Value.String() == "true" {
@@ -211,7 +260,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
if err != nil {
return nil, err
}
return &clientSwaggerSchema{client, api.Scheme}, nil
return &clientSwaggerSchema{client, experimentalClient, api.Scheme}, nil
}
return validation.NullSchema{}, nil
},
@@ -274,20 +323,14 @@ func getServicePorts(spec api.ServiceSpec) []string {
}
type clientSwaggerSchema struct {
c *client.Client
t runtime.ObjectTyper
c *client.Client
ec *client.ExperimentalClient
t runtime.ObjectTyper
}
func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
version, _, err := runtime.UnstructuredJSONScheme.DataVersionAndKind(data)
if err != nil {
return err
}
if ok := registered.IsRegisteredAPIVersion(version); !ok {
return fmt.Errorf("API version %q isn't supported, only supports API versions %q", version, registered.RegisteredVersions)
}
schemaData, err := c.c.RESTClient.Get().
AbsPath("/swaggerapi/api", version).
func getSchemaAndValidate(c *client.RESTClient, data []byte, group, version string) error {
schemaData, err := c.Get().
AbsPath("/swaggerapi", group, version).
Do().
Raw()
if err != nil {
@@ -300,6 +343,28 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
return schema.ValidateBytes(data)
}
func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
version, _, err := runtime.UnstructuredJSONScheme.DataVersionAndKind(data)
if err != nil {
return err
}
if ok := registered.IsRegisteredAPIVersion(version); !ok {
return fmt.Errorf("API version %q isn't supported, only supports API versions %q", version, registered.RegisteredVersions)
}
// First try stable api, if we can't validate using that, try experimental.
// If experimental fails, return error from stable api.
// TODO: Figure out which group to try once multiple group support is merged
// instead of trying everything.
err = getSchemaAndValidate(c.c.RESTClient, data, "api", version)
if err != nil && c.ec != nil {
errExp := getSchemaAndValidate(c.ec.RESTClient, data, "experimental", version)
if errExp == nil {
return nil
}
}
return err
}
// DefaultClientConfig creates a clientcmd.ClientConfig with the following hierarchy:
// 1. Use the kubeconfig builder. The number of merges and overrides here gets a little crazy. Stay with me.
// 1. Merge together the kubeconfig itself. This is done with the following hierarchy rules: