mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-04 23:17:50 +00:00
Add experimental API support to kubectl
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user