Merge pull request #12405 from uluyol/kubectlexp

Add experimental api support to kubectl
This commit is contained in:
CJ Cullen 2015-08-12 14:24:56 -07:00
commit 59da05efdf
17 changed files with 210 additions and 70 deletions

View File

@ -97,7 +97,7 @@ func init() {
"PodProxyOptions", "PodProxyOptions",
"Daemon") "Daemon")
mapper := api.NewDefaultRESTMapper(versions, InterfacesFor, importPrefix, ignoredKinds, rootScoped) mapper := api.NewDefaultRESTMapper("api", versions, InterfacesFor, importPrefix, ignoredKinds, rootScoped)
// setup aliases for groups of resources // setup aliases for groups of resources
mapper.AddResourceAlias("all", userResources...) mapper.AddResourceAlias("all", userResources...)
RESTMapper = mapper RESTMapper = mapper

View File

@ -33,11 +33,12 @@ func RegisterRESTMapper(m meta.RESTMapper) {
RESTMapper = append(RESTMapper.(meta.MultiRESTMapper), m) RESTMapper = append(RESTMapper.(meta.MultiRESTMapper), m)
} }
func NewDefaultRESTMapper(versions []string, interfacesFunc meta.VersionInterfacesFunc, importPathPrefix string, func NewDefaultRESTMapper(group string, versions []string, interfacesFunc meta.VersionInterfacesFunc,
ignoredKinds, rootScoped util.StringSet) *meta.DefaultRESTMapper { importPathPrefix string, ignoredKinds, rootScoped util.StringSet) *meta.DefaultRESTMapper {
mapper := meta.NewDefaultRESTMapper(versions, interfacesFunc) mapper := meta.NewDefaultRESTMapper(group, versions, interfacesFunc)
// enumerate all supported versions, get the kinds, and register with the mapper how to address our resources. // enumerate all supported versions, get the kinds, and register with the mapper how to address
// our resources.
for _, version := range versions { for _, version := range versions {
for kind, oType := range Scheme.KnownTypes(version) { for kind, oType := range Scheme.KnownTypes(version) {
// TODO: Remove import path prefix check. // TODO: Remove import path prefix check.

View File

@ -142,8 +142,18 @@ type RESTMapping struct {
// RESTMapper allows clients to map resources to kind, and map kind and version // RESTMapper allows clients to map resources to kind, and map kind and version
// to interfaces for manipulating those objects. It is primarily intended for // to interfaces for manipulating those objects. It is primarily intended for
// consumers of Kubernetes compatible REST APIs as defined in docs/api-conventions.md. // consumers of Kubernetes compatible REST APIs as defined in docs/api-conventions.md.
//
// The Kubernetes API provides versioned resources and object kinds which are scoped
// to API groups. In other words, kinds and resources should not be assumed to be
// unique across groups.
//
// TODO(caesarxuchao): Add proper multi-group support so that kinds & resources are
// scoped to groups. See http://issues.k8s.io/12413 and http://issues.k8s.io/10009.
type RESTMapper interface { type RESTMapper interface {
VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error)
// TODO(caesarxuchao): Remove GroupForResource when multi-group support is in (since
// group will be part of the version).
GroupForResource(resource string) (string, error)
RESTMapping(kind string, versions ...string) (*RESTMapping, error) RESTMapping(kind string, versions ...string) (*RESTMapping, error)
AliasesForResource(resource string) ([]string, bool) AliasesForResource(resource string) ([]string, bool)
ResourceSingularizer(resource string) (singular string, err error) ResourceSingularizer(resource string) (singular string, err error)

View File

@ -76,6 +76,7 @@ type DefaultRESTMapper struct {
mapping map[string]typeMeta mapping map[string]typeMeta
reverse map[typeMeta]string reverse map[typeMeta]string
scopes map[typeMeta]RESTScope scopes map[typeMeta]RESTScope
group string
versions []string versions []string
plurals map[string]string plurals map[string]string
singulars map[string]string singulars map[string]string
@ -88,10 +89,10 @@ type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, error)
// NewDefaultRESTMapper initializes a mapping between Kind and APIVersion // NewDefaultRESTMapper initializes a mapping between Kind and APIVersion
// to a resource name and back based on the objects in a runtime.Scheme // to a resource name and back based on the objects in a runtime.Scheme
// and the Kubernetes API conventions. Takes a priority list of the versions to // and the Kubernetes API conventions. Takes a group name, a priority list of the versions
// search when an object has no default version (set empty to return an error) // to search when an object has no default version (set empty to return an error),
// and a function that retrieves the correct codec and metadata for a given version. // and a function that retrieves the correct codec and metadata for a given version.
func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRESTMapper { func NewDefaultRESTMapper(group string, versions []string, f VersionInterfacesFunc) *DefaultRESTMapper {
mapping := make(map[string]typeMeta) mapping := make(map[string]typeMeta)
reverse := make(map[typeMeta]string) reverse := make(map[typeMeta]string)
scopes := make(map[typeMeta]RESTScope) scopes := make(map[typeMeta]RESTScope)
@ -103,6 +104,7 @@ func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRE
mapping: mapping, mapping: mapping,
reverse: reverse, reverse: reverse,
scopes: scopes, scopes: scopes,
group: group,
versions: versions, versions: versions,
plurals: plurals, plurals: plurals,
singulars: singulars, singulars: singulars,
@ -174,6 +176,13 @@ func (m *DefaultRESTMapper) VersionAndKindForResource(resource string) (defaultV
return meta.APIVersion, meta.Kind, nil return meta.APIVersion, meta.Kind, nil
} }
func (m *DefaultRESTMapper) GroupForResource(resource string) (string, error) {
if _, ok := m.mapping[strings.ToLower(resource)]; !ok {
return "", fmt.Errorf("no resource %q has been defined", resource)
}
return m.group, nil
}
// RESTMapping returns a struct representing the resource path and conversion interfaces a // RESTMapping returns a struct representing the resource path and conversion interfaces a
// RESTClient should use to operate on the provided kind in order of versions. If a version search // RESTClient should use to operate on the provided kind in order of versions. If a version search
// order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which // order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
@ -292,6 +301,18 @@ func (m MultiRESTMapper) VersionAndKindForResource(resource string) (defaultVers
return return
} }
// GroupForResource provides the Group mappings for the REST resources. This
// implementation supports multiple REST schemas and returns the first match.
func (m MultiRESTMapper) GroupForResource(resource string) (group string, err error) {
for _, t := range m {
group, err = t.GroupForResource(resource)
if err == nil {
return
}
}
return
}
// RESTMapping provides the REST mapping for the resource based on the resource // RESTMapping provides the REST mapping for the resource based on the resource
// kind and version. This implementation supports multiple REST schemas and // kind and version. This implementation supports multiple REST schemas and
// return the first match. // return the first match.

View File

@ -93,7 +93,7 @@ func TestRESTMapperVersionAndKindForResource(t *testing.T) {
{Resource: "internalObjects", MixedCase: true, Kind: "InternalObject", APIVersion: "test"}, {Resource: "internalObjects", MixedCase: true, Kind: "InternalObject", APIVersion: "test"},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
mapper := NewDefaultRESTMapper([]string{"test"}, fakeInterfaces) mapper := NewDefaultRESTMapper("tgroup", []string{"test"}, fakeInterfaces)
mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase) mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase)
v, k, err := mapper.VersionAndKindForResource(testCase.Resource) v, k, err := mapper.VersionAndKindForResource(testCase.Resource)
hasErr := err != nil hasErr := err != nil
@ -107,6 +107,33 @@ func TestRESTMapperVersionAndKindForResource(t *testing.T) {
} }
} }
func TestRESTMapperGroupForResource(t *testing.T) {
testCases := []struct {
Resource string
Kind, APIVersion, Group string
Err bool
}{
{Resource: "myObject", Kind: "MyObject", APIVersion: "test", Group: "testapi"},
{Resource: "myobject", Kind: "MyObject", APIVersion: "test", Group: "testapi2"},
{Resource: "myObje", Err: true, Kind: "MyObject", APIVersion: "test", Group: "testapi"},
{Resource: "myobje", Err: true, Kind: "MyObject", APIVersion: "test", Group: "testapi"},
}
for i, testCase := range testCases {
mapper := NewDefaultRESTMapper(testCase.Group, []string{"test"}, fakeInterfaces)
mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, false)
g, err := mapper.GroupForResource(testCase.Resource)
if testCase.Err {
if err == nil {
t.Errorf("%d: expected error", i)
}
} else if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
} else if g != testCase.Group {
t.Errorf("%d: expected group %q, got %q", i, testCase.Group, g)
}
}
}
func TestKindToResource(t *testing.T) { func TestKindToResource(t *testing.T) {
testCases := []struct { testCases := []struct {
Kind string Kind string
@ -159,7 +186,7 @@ func TestRESTMapperResourceSingularizer(t *testing.T) {
{Kind: "lowercases", APIVersion: "test", MixedCase: false, Plural: "lowercases", Singular: "lowercases"}, {Kind: "lowercases", APIVersion: "test", MixedCase: false, Plural: "lowercases", Singular: "lowercases"},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
mapper := NewDefaultRESTMapper([]string{"test"}, fakeInterfaces) mapper := NewDefaultRESTMapper("tgroup", []string{"test"}, fakeInterfaces)
// create singular/plural mapping // create singular/plural mapping
mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase) mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase)
singular, _ := mapper.ResourceSingularizer(testCase.Plural) singular, _ := mapper.ResourceSingularizer(testCase.Plural)
@ -198,7 +225,7 @@ func TestRESTMapperRESTMapping(t *testing.T) {
// TODO: add test for a resource that exists in one version but not another // TODO: add test for a resource that exists in one version but not another
} }
for i, testCase := range testCases { for i, testCase := range testCases {
mapper := NewDefaultRESTMapper(testCase.DefaultVersions, fakeInterfaces) mapper := NewDefaultRESTMapper("tgroup", testCase.DefaultVersions, fakeInterfaces)
mapper.Add(RESTScopeNamespace, "InternalObject", "test", testCase.MixedCase) mapper.Add(RESTScopeNamespace, "InternalObject", "test", testCase.MixedCase)
mapping, err := mapper.RESTMapping(testCase.Kind, testCase.APIVersions...) mapping, err := mapper.RESTMapping(testCase.Kind, testCase.APIVersions...)
hasErr := err != nil hasErr := err != nil
@ -225,7 +252,7 @@ func TestRESTMapperRESTMapping(t *testing.T) {
} }
func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) { func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) {
mapper := NewDefaultRESTMapper([]string{"test1", "test2"}, fakeInterfaces) mapper := NewDefaultRESTMapper("tgroup", []string{"test1", "test2"}, fakeInterfaces)
mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false) mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false)
mapper.Add(RESTScopeNamespace, "OtherObject", "test2", false) mapper.Add(RESTScopeNamespace, "OtherObject", "test2", false)
@ -278,7 +305,7 @@ func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) {
} }
func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) { func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) {
mapper := NewDefaultRESTMapper([]string{"test1", "test2"}, unmatchedVersionInterfaces) mapper := NewDefaultRESTMapper("tgroup", []string{"test1", "test2"}, unmatchedVersionInterfaces)
mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false) mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false)
_, err := mapper.RESTMapping("InternalObject", "test1") _, err := mapper.RESTMapping("InternalObject", "test1")
if err == nil { if err == nil {

View File

@ -88,7 +88,7 @@ func interfacesFor(version string) (*meta.VersionInterfaces, error) {
} }
func newMapper() *meta.DefaultRESTMapper { func newMapper() *meta.DefaultRESTMapper {
return meta.NewDefaultRESTMapper(versions, interfacesFor) return meta.NewDefaultRESTMapper("testgroup", versions, interfacesFor)
} }
func addTestTypes() { func addTestTypes() {

View File

@ -55,7 +55,7 @@ func init() {
ignoredKinds := util.NewStringSet() ignoredKinds := util.NewStringSet()
RESTMapper = api.NewDefaultRESTMapper(Versions, InterfacesFor, importPrefix, ignoredKinds, rootScoped) RESTMapper = api.NewDefaultRESTMapper("experimental", Versions, InterfacesFor, importPrefix, ignoredKinds, rootScoped)
api.RegisterRESTMapper(RESTMapper) api.RegisterRESTMapper(RESTMapper)
} }

View File

@ -17,12 +17,13 @@ limitations under the License.
package cmd package cmd
import ( import (
"fmt"
"io" "io"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/api"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
) )
@ -40,7 +41,7 @@ func NewCmdApiVersions(f *cmdutil.Factory, out io.Writer) *cobra.Command {
return cmd return cmd
} }
func RunApiVersions(f *cmdutil.Factory, out io.Writer) error { func RunApiVersions(f *cmdutil.Factory, w io.Writer) error {
if len(os.Args) > 1 && os.Args[1] == "apiversions" { if len(os.Args) > 1 && os.Args[1] == "apiversions" {
printDeprecationWarning("api-versions", "apiversions") printDeprecationWarning("api-versions", "apiversions")
} }
@ -50,6 +51,24 @@ func RunApiVersions(f *cmdutil.Factory, out io.Writer) error {
return err return err
} }
kubectl.GetApiVersions(out, client) apiVersions, err := client.ServerAPIVersions()
if err != nil {
fmt.Printf("Couldn't get available api versions from server: %v\n", err)
os.Exit(1)
}
var expAPIVersions *api.APIVersions
showExpVersions := false
expClient, err := f.ExperimentalClient()
if err == nil {
expAPIVersions, err = expClient.ServerAPIVersions()
showExpVersions = err == nil
}
fmt.Fprintf(w, "Available Server Api Versions: %#v\n", *apiVersions)
if showExpVersions {
fmt.Fprintf(w, "Available Server Experimental Api Versions: %#v\n", *expAPIVersions)
}
return nil return nil
} }

View File

@ -83,7 +83,7 @@ func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
codec := runtime.CodecFor(scheme, "unlikelyversion") codec := runtime.CodecFor(scheme, "unlikelyversion")
validVersion := testapi.Version() validVersion := testapi.Version()
mapper := meta.NewDefaultRESTMapper([]string{"unlikelyversion", validVersion}, func(version string) (*meta.VersionInterfaces, error) { mapper := meta.NewDefaultRESTMapper("apitest", []string{"unlikelyversion", validVersion}, func(version string) (*meta.VersionInterfaces, error) {
return &meta.VersionInterfaces{ return &meta.VersionInterfaces{
Codec: runtime.CodecFor(scheme, version), Codec: runtime.CodecFor(scheme, version),
ObjectConvertor: scheme, ObjectConvertor: scheme,

View File

@ -22,17 +22,17 @@ import (
"k8s.io/kubernetes/pkg/client/clientcmd" "k8s.io/kubernetes/pkg/client/clientcmd"
) )
func NewClientCache(loader clientcmd.ClientConfig) *clientCache { func NewClientCache(loader clientcmd.ClientConfig) *ClientCache {
return &clientCache{ return &ClientCache{
clients: make(map[string]*client.Client), clients: make(map[string]*client.Client),
configs: make(map[string]*client.Config), configs: make(map[string]*client.Config),
loader: loader, loader: loader,
} }
} }
// clientCache caches previously loaded clients for reuse, and ensures MatchServerVersion // ClientCache caches previously loaded clients for reuse, and ensures MatchServerVersion
// is invoked only once // is invoked only once
type clientCache struct { type ClientCache struct {
loader clientcmd.ClientConfig loader clientcmd.ClientConfig
clients map[string]*client.Client clients map[string]*client.Client
configs map[string]*client.Config configs map[string]*client.Config
@ -42,7 +42,7 @@ type clientCache struct {
} }
// ClientConfigForVersion returns the correct config for a server // ClientConfigForVersion returns the correct config for a server
func (c *clientCache) ClientConfigForVersion(version string) (*client.Config, error) { func (c *ClientCache) ClientConfigForVersion(version string) (*client.Config, error) {
if c.defaultConfig == nil { if c.defaultConfig == nil {
config, err := c.loader.ClientConfig() config, err := c.loader.ClientConfig()
if err != nil { if err != nil {
@ -73,7 +73,7 @@ func (c *clientCache) ClientConfigForVersion(version string) (*client.Config, er
// ClientForVersion initializes or reuses a client for the specified version, or returns an // ClientForVersion initializes or reuses a client for the specified version, or returns an
// error if that is not possible // error if that is not possible
func (c *clientCache) ClientForVersion(version string) (*client.Client, error) { func (c *ClientCache) ClientForVersion(version string) (*client.Client, error) {
if client, ok := c.clients[version]; ok { if client, ok := c.clients[version]; ok {
return client, nil return client, nil
} }

View File

@ -17,6 +17,7 @@ limitations under the License.
package util package util
import ( import (
"errors"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -27,7 +28,6 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/registered" "k8s.io/kubernetes/pkg/api/registered"
"k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/api/validation"
@ -49,7 +49,7 @@ const (
// TODO: pass the various interfaces on the factory directly into the command constructors (so the // TODO: pass the various interfaces on the factory directly into the command constructors (so the
// commands are decoupled from the factory). // commands are decoupled from the factory).
type Factory struct { type Factory struct {
clients *clientCache clients *ClientCache
flags *pflag.FlagSet flags *pflag.FlagSet
generators map[string]kubectl.Generator generators map[string]kubectl.Generator
@ -57,6 +57,8 @@ type Factory struct {
Object func() (meta.RESTMapper, runtime.ObjectTyper) Object func() (meta.RESTMapper, runtime.ObjectTyper)
// Returns a client for accessing Kubernetes resources or an error. // Returns a client for accessing Kubernetes resources or an error.
Client func() (*client.Client, 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. // Returns a client.Config for accessing the Kubernetes server.
ClientConfig func() (*client.Config, error) ClientConfig func() (*client.Config, error)
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended // 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 nil, then flags will be bound to a new clientcmd.ClientConfig.
// if optionalClientConfig is not nil, then this factory will make use of it. // if optionalClientConfig is not nil, then this factory will make use of it.
func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
mapper := kubectl.ShortcutExpander{RESTMapper: latest.RESTMapper} mapper := kubectl.ShortcutExpander{RESTMapper: api.RESTMapper}
flags := pflag.NewFlagSet("", pflag.ContinueOnError) flags := pflag.NewFlagSet("", pflag.ContinueOnError)
flags.SetNormalizeFunc(util.WarnWordSepNormalizeFunc) // Warn for "_" flags flags.SetNormalizeFunc(util.WarnWordSepNormalizeFunc) // Warn for "_" flags
@ -109,6 +111,25 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
clients := NewClientCache(clientConfig) 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{ return &Factory{
clients: clients, clients: clients,
flags: flags, flags: flags,
@ -124,26 +145,46 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
Client: func() (*client.Client, error) { Client: func() (*client.Client, error) {
return clients.ClientForVersion("") return clients.ClientForVersion("")
}, },
ExperimentalClient: func() (*client.ExperimentalClient, error) {
return experimentalClient, experimentalClientErr
},
ClientConfig: func() (*client.Config, error) { ClientConfig: func() (*client.Config, error) {
return clients.ClientConfigForVersion("") return clients.ClientConfigForVersion("")
}, },
RESTClient: func(mapping *meta.RESTMapping) (resource.RESTClient, error) { RESTClient: func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
client, err := clients.ClientForVersion(mapping.APIVersion) group, err := api.RESTMapper.GroupForResource(mapping.Resource)
if err != nil { if err != nil {
return nil, err 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) { Describer: func(mapping *meta.RESTMapping) (kubectl.Describer, error) {
client, err := clients.ClientForVersion(mapping.APIVersion) group, err := api.RESTMapper.GroupForResource(mapping.Resource)
if err != nil { if err != nil {
return nil, err return nil, err
} }
describer, ok := kubectl.DescriberFor(mapping.Kind, client) client, expClient, err := getBothClients(group, mapping.APIVersion)
if !ok { if err != nil {
return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind) 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) { Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, wide bool, columnLabels []string) (kubectl.ResourcePrinter, error) {
return kubectl.NewHumanReadablePrinter(noHeaders, withNamespace, wide, columnLabels), nil return kubectl.NewHumanReadablePrinter(noHeaders, withNamespace, wide, columnLabels), nil
@ -192,18 +233,26 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
return meta.NewAccessor().Labels(object) return meta.NewAccessor().Labels(object)
}, },
Scaler: func(mapping *meta.RESTMapping) (kubectl.Scaler, error) { 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 { if err != nil {
return nil, err return nil, err
} }
return kubectl.ScalerFor(mapping.Kind, kubectl.NewScalerClient(client)) return kubectl.ScalerFor(mapping.Kind, kubectl.NewScalerClient(client))
}, },
Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) {
client, err := clients.ClientForVersion(mapping.APIVersion) group, err := api.RESTMapper.GroupForResource(mapping.Resource)
if err != nil { if err != nil {
return nil, err 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) { Validator: func() (validation.Schema, error) {
if flags.Lookup("validate").Value.String() == "true" { if flags.Lookup("validate").Value.String() == "true" {
@ -211,7 +260,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &clientSwaggerSchema{client, api.Scheme}, nil return &clientSwaggerSchema{client, experimentalClient, api.Scheme}, nil
} }
return validation.NullSchema{}, nil return validation.NullSchema{}, nil
}, },
@ -274,20 +323,14 @@ func getServicePorts(spec api.ServiceSpec) []string {
} }
type clientSwaggerSchema struct { type clientSwaggerSchema struct {
c *client.Client c *client.Client
t runtime.ObjectTyper ec *client.ExperimentalClient
t runtime.ObjectTyper
} }
func (c *clientSwaggerSchema) ValidateBytes(data []byte) error { func getSchemaAndValidate(c *client.RESTClient, data []byte, group, version string) error {
version, _, err := runtime.UnstructuredJSONScheme.DataVersionAndKind(data) schemaData, err := c.Get().
if err != nil { AbsPath("/swaggerapi", group, version).
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).
Do(). Do().
Raw() Raw()
if err != nil { if err != nil {
@ -300,6 +343,28 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
return schema.ValidateBytes(data) 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: // 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. 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: // 1. Merge together the kubeconfig itself. This is done with the following hierarchy rules:

View File

@ -82,6 +82,10 @@ func describerMap(c *client.Client) map[string]Describer {
return m return m
} }
func expDescriberMap(c *client.ExperimentalClient) map[string]Describer {
return map[string]Describer{}
}
// List of all resource types we can describe // List of all resource types we can describe
func DescribableResources() []string { func DescribableResources() []string {
keys := make([]string, 0) keys := make([]string, 0)
@ -95,12 +99,15 @@ func DescribableResources() []string {
// Describer returns the default describe functions for each of the standard // Describer returns the default describe functions for each of the standard
// Kubernetes types. // Kubernetes types.
func DescriberFor(kind string, c *client.Client) (Describer, bool) { func DescriberFor(kind string, c *client.Client, ec *client.ExperimentalClient) (Describer, bool) {
f, ok := describerMap(c)[kind] var f Describer
if ok { var ok bool
return f, true if c != nil {
f, ok = describerMap(c)[kind]
} else if ec != nil {
f, ok = expDescriberMap(ec)[kind]
} }
return nil, false return f, ok
} }
// DefaultObjectDescriber can describe the default Kubernetes objects. // DefaultObjectDescriber can describe the default Kubernetes objects.

View File

@ -52,7 +52,7 @@ func IsNoSuchReaperError(err error) bool {
return ok return ok
} }
func ReaperFor(kind string, c client.Interface) (Reaper, error) { func ReaperFor(kind string, c client.Interface, ec *client.ExperimentalClient) (Reaper, error) {
switch kind { switch kind {
case "ReplicationController": case "ReplicationController":
return &ReplicationControllerReaper{c, Interval, Timeout}, nil return &ReplicationControllerReaper{c, Interval, Timeout}, nil

View File

@ -156,7 +156,7 @@ func TestSimpleStop(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
fake := test.fake fake := test.fake
reaper, err := ReaperFor(test.kind, fake) reaper, err := ReaperFor(test.kind, fake, nil)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v (%s)", err, test.test) t.Errorf("unexpected error: %v (%s)", err, test.test)
} }

View File

@ -40,13 +40,3 @@ func GetVersion(w io.Writer, kubeClient client.Interface) {
func GetClientVersion(w io.Writer) { func GetClientVersion(w io.Writer) {
fmt.Fprintf(w, "Client Version: %#v\n", version.Get()) fmt.Fprintf(w, "Client Version: %#v\n", version.Get())
} }
func GetApiVersions(w io.Writer, kubeClient client.Interface) {
apiVersions, err := kubeClient.ServerAPIVersions()
if err != nil {
fmt.Printf("Couldn't get available api versions from server: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(w, "Available Server Api Versions: %#v\n", *apiVersions)
}

View File

@ -89,7 +89,7 @@ func ServeImageOrFail(f *Framework, test string, image string) {
defer func() { defer func() {
// Resize the replication controller to zero to get rid of pods. // Resize the replication controller to zero to get rid of pods.
By("Cleaning up the replication controller") By("Cleaning up the replication controller")
rcReaper, err := kubectl.ReaperFor("ReplicationController", f.Client) rcReaper, err := kubectl.ReaperFor("ReplicationController", f.Client, nil)
if err != nil { if err != nil {
Logf("Failed to cleanup replication controller %v: %v.", controller.Name, err) Logf("Failed to cleanup replication controller %v: %v.", controller.Name, err)
} }

View File

@ -190,7 +190,7 @@ func RCFromManifest(fileName string) *api.ReplicationController {
// StopRC stops the rc via kubectl's stop library // StopRC stops the rc via kubectl's stop library
func StopRC(rc *api.ReplicationController, restClient *client.Client) error { func StopRC(rc *api.ReplicationController, restClient *client.Client) error {
reaper, err := kubectl.ReaperFor("ReplicationController", restClient) reaper, err := kubectl.ReaperFor("ReplicationController", restClient, nil)
if err != nil || reaper == nil { if err != nil || reaper == nil {
return err return err
} }