Make expandResourceShortcuts part of RESTMapper on client

pull/6/head
Clayton Coleman 2014-12-28 00:27:52 -05:00
parent a1ee782df5
commit 8a4f225941
14 changed files with 185 additions and 96 deletions

View File

@ -19,11 +19,15 @@ package main
import ( import (
"os" "os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
"github.com/golang/glog"
) )
func main() { func main() {
clientBuilder := clientcmd.NewInteractiveClientConfig(clientcmd.Config{}, "", &clientcmd.ConfigOverrides{}, os.Stdin) cmd := cmd.NewFactory().NewKubectlCommand(os.Stdout)
cmd.NewFactory(clientBuilder).Run(os.Stdout) if err := cmd.Execute(); err != nil {
glog.Errorf("error: %v", err)
os.Exit(1)
}
} }

View File

@ -33,6 +33,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
) )
const ( const (
@ -42,55 +43,108 @@ const (
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types // Factory provides abstractions that allow the Kubectl command to be extended across multiple types
// of resources and different API sets. // of resources and different API sets.
type Factory struct { type Factory struct {
ClientConfig clientcmd.ClientConfig clients *clientCache
Mapper meta.RESTMapper flags *pflag.FlagSet
Typer runtime.ObjectTyper
Client func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) Mapper meta.RESTMapper
Describer func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error) Typer runtime.ObjectTyper
Printer func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error)
Validator func(*cobra.Command) (validation.Schema, error) // Returns a client for accessing Kubernetes resources or an error.
Client func(cmd *cobra.Command) (*client.Client, error)
// Returns a client.Config for accessing the Kubernetes server.
ClientConfig func(cmd *cobra.Command) (*client.Config, error)
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended
// for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer.
RESTClient func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error)
// Returns a Describer for displaying the specified RESTMapping type or an error.
Describer func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error)
// Returns a Printer for formatting objects of the given type or an error.
Printer func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error)
// Returns a schema that can validate objects stored on disk.
Validator func(*cobra.Command) (validation.Schema, error)
} }
// NewFactory creates a factory with the default Kubernetes resources defined // NewFactory creates a factory with the default Kubernetes resources defined
func NewFactory(clientConfig clientcmd.ClientConfig) *Factory { func NewFactory() *Factory {
ret := &Factory{ mapper := kubectl.ShortcutExpander{latest.RESTMapper}
ClientConfig: clientConfig,
Mapper: latest.RESTMapper, flags := pflag.NewFlagSet("", pflag.ContinueOnError)
Typer: api.Scheme, clientConfig := DefaultClientConfig(flags)
Printer: func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) { clients := &clientCache{
return kubectl.NewHumanReadablePrinter(noHeaders), nil clients: make(map[string]*client.Client),
}, loader: clientConfig,
} }
ret.Validator = func(cmd *cobra.Command) (validation.Schema, error) { return &Factory{
if GetFlagBool(cmd, "validate") { clients: clients,
client, err := getClient(ret.ClientConfig, GetFlagBool(cmd, FlagMatchBinaryVersion)) flags: flags,
Mapper: mapper,
Typer: api.Scheme,
Client: func(cmd *cobra.Command) (*client.Client, error) {
return clients.ClientForVersion("")
},
ClientConfig: func(cmd *cobra.Command) (*client.Config, error) {
return clients.ClientConfigForVersion("")
},
RESTClient: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) {
client, err := clients.ClientForVersion(mapping.APIVersion)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &clientSwaggerSchema{client, api.Scheme}, nil return client.RESTClient, nil
} else { },
Describer: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error) {
client, err := clients.ClientForVersion(mapping.APIVersion)
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)
}
return describer, nil
},
Printer: func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) {
return kubectl.NewHumanReadablePrinter(noHeaders), nil
},
Validator: func(cmd *cobra.Command) (validation.Schema, error) {
if GetFlagBool(cmd, "validate") {
client, err := clients.ClientForVersion("")
if err != nil {
return nil, err
}
return &clientSwaggerSchema{client, api.Scheme}, nil
}
return validation.NullSchema{}, nil return validation.NullSchema{}, nil
} },
} }
ret.Client = func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) {
return getClient(ret.ClientConfig, GetFlagBool(cmd, FlagMatchBinaryVersion))
}
ret.Describer = func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error) {
client, err := getClient(ret.ClientConfig, GetFlagBool(cmd, FlagMatchBinaryVersion))
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)
}
return describer, nil
}
return ret
} }
func (f *Factory) Run(out io.Writer) { // BindFlags adds any flags that are common to all kubectl sub commands.
func (f *Factory) BindFlags(flags *pflag.FlagSet) {
// any flags defined by external projects (not part of pflags)
util.AddAllFlagsToPFlagSet(flags)
if f.flags != nil {
f.flags.VisitAll(func(flag *pflag.Flag) {
flags.AddFlag(flag)
})
}
// Globally persistent flags across all subcommands.
// TODO Change flag names to consts to allow safer lookup from subcommands.
// TODO Add a verbose flag that turns on glog logging. Probably need a way
// to do that automatically for every subcommand.
flags.BoolVar(&f.clients.matchVersion, FlagMatchBinaryVersion, false, "Require server version to match client version")
flags.String("ns-path", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace info file that holds the namespace context to use for CLI requests.")
flags.StringP("namespace", "n", "", "If present, the namespace scope for this CLI request.")
flags.Bool("validate", false, "If true, use a schema to validate the input before sending it")
}
// NewKubectlCommand creates the `kubectl` command and its nested children.
func (f *Factory) NewKubectlCommand(out io.Writer) *cobra.Command {
// Parent command to which all subcommands are added. // Parent command to which all subcommands are added.
cmds := &cobra.Command{ cmds := &cobra.Command{
Use: "kubectl", Use: "kubectl",
@ -101,15 +155,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
Run: runHelp, Run: runHelp,
} }
util.AddAllFlagsToPFlagSet(cmds.PersistentFlags()) f.BindFlags(cmds.PersistentFlags())
f.ClientConfig = getClientConfig(cmds)
// Globally persistent flags across all subcommands.
// TODO Change flag names to consts to allow safer lookup from subcommands.
cmds.PersistentFlags().Bool(FlagMatchBinaryVersion, false, "Require server version to match client version")
cmds.PersistentFlags().String("ns-path", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace info file that holds the namespace context to use for CLI requests.")
cmds.PersistentFlags().StringP("namespace", "n", "", "If present, the namespace scope for this CLI request.")
cmds.PersistentFlags().Bool("validate", false, "If true, use a schema to validate the input before sending it")
cmds.AddCommand(f.NewCmdVersion(out)) cmds.AddCommand(f.NewCmdVersion(out))
cmds.AddCommand(f.NewCmdProxy(out)) cmds.AddCommand(f.NewCmdProxy(out))
@ -125,12 +171,10 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
cmds.AddCommand(f.NewCmdLog(out)) cmds.AddCommand(f.NewCmdLog(out))
cmds.AddCommand(f.NewCmdRollingUpdate(out)) cmds.AddCommand(f.NewCmdRollingUpdate(out))
if err := cmds.Execute(); err != nil { return cmds
os.Exit(1)
}
} }
// getClientBuilder creates a clientcmd.ClientConfig that has a hierarchy like this: // 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 and merge rules: // 1. Merge together the kubeconfig itself. This is done with the following hierarchy and merge rules:
// 1. CommandLineLocation - this parsed from the command line, so it must be late bound // 1. CommandLineLocation - this parsed from the command line, so it must be late bound
@ -162,13 +206,13 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
// 2. If the command line does not specify one, and the auth info has conflicting techniques, fail. // 2. If the command line does not specify one, and the auth info has conflicting techniques, fail.
// 3. If the command line specifies one and the auth info specifies another, honor the command line technique. // 3. If the command line specifies one and the auth info specifies another, honor the command line technique.
// 2. Use default values and potentially prompt for auth information // 2. Use default values and potentially prompt for auth information
func getClientConfig(cmd *cobra.Command) clientcmd.ClientConfig { func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
loadingRules := clientcmd.NewClientConfigLoadingRules() loadingRules := clientcmd.NewClientConfigLoadingRules()
loadingRules.EnvVarPath = os.Getenv(clientcmd.RecommendedConfigPathEnvVar) loadingRules.EnvVarPath = os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
cmd.PersistentFlags().StringVar(&loadingRules.CommandLinePath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") flags.StringVar(&loadingRules.CommandLinePath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.")
overrides := &clientcmd.ConfigOverrides{} overrides := &clientcmd.ConfigOverrides{}
overrides.BindFlags(cmd.PersistentFlags(), clientcmd.RecommendedConfigOverrideFlags("")) overrides.BindFlags(flags, clientcmd.RecommendedConfigOverrideFlags(""))
clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin)
return clientConfig return clientConfig
@ -246,18 +290,55 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
return schema.ValidateBytes(data) return schema.ValidateBytes(data)
} }
// TODO Need to only run server version match once per client host creation // clientCache caches previously loaded clients for reuse, and ensures MatchServerVersion
func getClient(clientConfig clientcmd.ClientConfig, matchServerVersion bool) (*client.Client, error) { // is invoked only once
config, err := clientConfig.ClientConfig() type clientCache struct {
loader clientcmd.ClientConfig
clients map[string]*client.Client
defaultConfig *client.Config
matchVersion bool
}
// ClientConfigForVersion returns the correct config for a server
func (c *clientCache) ClientConfigForVersion(version string) (*client.Config, error) {
if c.defaultConfig == nil {
config, err := c.loader.ClientConfig()
if err != nil {
return nil, err
}
c.defaultConfig = config
if c.matchVersion {
if err := client.MatchesServerVersion(config); err != nil {
return nil, err
}
}
}
// TODO: remove when SetKubernetesDefaults gets added
if len(version) == 0 {
version = c.defaultConfig.Version
}
// TODO: have a better config copy method
config := *c.defaultConfig
// TODO: call new client.SetKubernetesDefaults method
// instead of doing this
config.Version = version
return &config, nil
}
// ClientForVersion initializes or reuses a client for the specified version, or returns an
// error if that is not possible
func (c *clientCache) ClientForVersion(version string) (*client.Client, error) {
config, err := c.ClientConfigForVersion(version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if matchServerVersion { if client, ok := c.clients[config.Version]; ok {
err := client.MatchesServerVersion(config) return client, nil
if err != nil {
return nil, err
}
} }
client, err := client.New(config) client, err := client.New(config)
@ -265,5 +346,6 @@ func getClient(clientConfig clientcmd.ClientConfig, matchServerVersion bool) (*c
return nil, err return nil, err
} }
c.clients[config.Version] = client
return client, nil return client, nil
} }

View File

@ -100,7 +100,7 @@ func NewTestFactory() (*Factory, *testFactory, runtime.Codec) {
return &Factory{ return &Factory{
Mapper: mapper, Mapper: mapper,
Typer: scheme, Typer: scheme,
Client: func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error) { RESTClient: func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error) {
return t.Client, t.Err return t.Client, t.Err
}, },
Describer: func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error) { Describer: func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error) {

View File

@ -46,7 +46,7 @@ Examples:
schema, err := f.Validator(cmd) schema, err := f.Validator(cmd)
checkErr(err) checkErr(err)
mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema) mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema)
client, err := f.Client(cmd, mapping) client, err := f.RESTClient(cmd, mapping)
checkErr(err) checkErr(err)
// use the default namespace if not specified, or check for conflict with the file's namespace // use the default namespace if not specified, or check for conflict with the file's namespace

View File

@ -79,7 +79,7 @@ Examples:
<creates all resources listed in config.json>`, <creates all resources listed in config.json>`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
clientFunc := func(mapper *meta.RESTMapping) (config.RESTClientPoster, error) { clientFunc := func(mapper *meta.RESTMapping) (config.RESTClientPoster, error) {
client, err := f.Client(cmd, mapper) client, err := f.RESTClient(cmd, mapper)
checkErr(err) checkErr(err)
return client, nil return client, nil
} }

View File

@ -59,7 +59,7 @@ Examples:
checkErr(err) checkErr(err)
selector := GetFlagString(cmd, "selector") selector := GetFlagString(cmd, "selector")
found := 0 found := 0
ResourcesFromArgsOrFile(cmd, args, filename, selector, f.Typer, f.Mapper, f.Client, schema).Visit(func(r *resource.Info) error { ResourcesFromArgsOrFile(cmd, args, filename, selector, f.Typer, f.Mapper, f.RESTClient, schema).Visit(func(r *resource.Info) error {
found++ found++
if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil { if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
return err return err

View File

@ -56,7 +56,7 @@ Examples:
labelSelector, err := labels.ParseSelector(selector) labelSelector, err := labels.ParseSelector(selector)
checkErr(err) checkErr(err)
client, err := f.Client(cmd, mapping) client, err := f.RESTClient(cmd, mapping)
checkErr(err) checkErr(err)
outputFormat := GetFlagString(cmd, "output") outputFormat := GetFlagString(cmd, "output")

View File

@ -21,8 +21,6 @@ import (
"strconv" "strconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
) )
func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command { func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
@ -46,9 +44,7 @@ Examples:
} }
namespace := GetKubeNamespace(cmd) namespace := GetKubeNamespace(cmd)
config, err := f.ClientConfig.ClientConfig() client, err := f.Client(cmd)
checkErr(err)
client, err := client.New(config)
checkErr(err) checkErr(err)
podID := args[0] podID := args[0]

View File

@ -33,7 +33,7 @@ func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command {
port := GetFlagInt(cmd, "port") port := GetFlagInt(cmd, "port")
glog.Infof("Starting to serve on localhost:%d", port) glog.Infof("Starting to serve on localhost:%d", port)
clientConfig, err := f.ClientConfig.ClientConfig() clientConfig, err := f.ClientConfig(cmd)
checkErr(err) checkErr(err)
server, err := kubectl.NewProxyServer(GetFlagString(cmd, "www"), clientConfig, port) server, err := kubectl.NewProxyServer(GetFlagString(cmd, "www"), clientConfig, port)

View File

@ -63,7 +63,7 @@ func ResourcesFromArgsOrFile(
} }
types := SplitResourceArgument(args[0]) types := SplitResourceArgument(args[0])
for _, arg := range types { for _, arg := range types {
resourceName := kubectl.ExpandResourceShortcut(arg) resourceName := arg
if len(resourceName) == 0 { if len(resourceName) == 0 {
usageError(cmd, "Unknown resource %s", resourceName) usageError(cmd, "Unknown resource %s", resourceName)
} }
@ -91,7 +91,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
} }
if len(args) == 2 { if len(args) == 2 {
resource := kubectl.ExpandResourceShortcut(args[0]) resource := args[0]
namespace = GetKubeNamespace(cmd) namespace = GetKubeNamespace(cmd)
name = args[1] name = args[1]
if len(name) == 0 || len(resource) == 0 { if len(name) == 0 || len(resource) == 0 {
@ -129,7 +129,7 @@ func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper)
usageError(cmd, "Must provide resource and name command line params") usageError(cmd, "Must provide resource and name command line params")
} }
resource := kubectl.ExpandResourceShortcut(args[0]) resource := args[0]
namespace = GetKubeNamespace(cmd) namespace = GetKubeNamespace(cmd)
name = args[1] name = args[1]
if len(name) == 0 || len(resource) == 0 { if len(name) == 0 || len(resource) == 0 {
@ -152,7 +152,7 @@ func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTM
usageError(cmd, "Must provide resource or a resource and name as command line params") usageError(cmd, "Must provide resource or a resource and name as command line params")
} }
resource := kubectl.ExpandResourceShortcut(args[0]) resource := args[0]
if len(resource) == 0 { if len(resource) == 0 {
usageError(cmd, "Must provide resource or a resource and name as command line params") usageError(cmd, "Must provide resource or a resource and name as command line params")
} }

View File

@ -21,7 +21,6 @@ import (
"io" "io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -69,9 +68,7 @@ $ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f -
err = CompareNamespaceFromFile(cmd, namespace) err = CompareNamespaceFromFile(cmd, namespace)
checkErr(err) checkErr(err)
config, err := f.ClientConfig.ClientConfig() client, err := f.Client(cmd)
checkErr(err)
client, err := client.New(config)
checkErr(err) checkErr(err)
obj, err := mapping.Codec.Decode(data) obj, err := mapping.Codec.Decode(data)

View File

@ -46,7 +46,7 @@ Examples:
schema, err := f.Validator(cmd) schema, err := f.Validator(cmd)
checkErr(err) checkErr(err)
mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema) mapping, namespace, name, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema)
client, err := f.Client(cmd, mapping) client, err := f.RESTClient(cmd, mapping)
checkErr(err) checkErr(err)
err = CompareNamespaceFromFile(cmd, namespace) err = CompareNamespaceFromFile(cmd, namespace)

View File

@ -21,7 +21,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
) )
@ -32,14 +31,13 @@ func (f *Factory) NewCmdVersion(out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if GetFlagBool(cmd, "client") { if GetFlagBool(cmd, "client") {
kubectl.GetClientVersion(out) kubectl.GetClientVersion(out)
} else { return
config, err := f.ClientConfig.ClientConfig()
checkErr(err)
client, err := client.New(config)
checkErr(err)
kubectl.GetVersion(out, client)
} }
client, err := f.Client(cmd)
checkErr(err)
kubectl.GetVersion(out, client)
}, },
} }
cmd.Flags().BoolP("client", "c", false, "Client version only (no server required)") cmd.Flags().BoolP("client", "c", false, "Client version only (no server required)")

View File

@ -26,6 +26,7 @@ import (
"strings" "strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@ -111,12 +112,23 @@ func makeImageList(spec *api.PodSpec) string {
return strings.Join(listOfImages(spec), ",") return strings.Join(listOfImages(spec), ",")
} }
// ExpandResourceShortcut will return the expanded version of resource // ShortcutExpander is a RESTMapper that can be used for Kubernetes
// resources.
type ShortcutExpander struct {
meta.RESTMapper
}
// VersionAndKindForResource implements meta.RESTMapper. It expands the resource first, then invokes the wrapped
// mapper.
func (e ShortcutExpander) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
resource = expandResourceShortcut(resource)
return e.RESTMapper.VersionAndKindForResource(resource)
}
// expandResourceShortcut will return the expanded version of resource
// (something that a pkg/api/meta.RESTMapper can understand), if it is // (something that a pkg/api/meta.RESTMapper can understand), if it is
// indeed a shortcut. Otherwise, will return resource unmodified. // indeed a shortcut. Otherwise, will return resource unmodified.
// TODO: Combine with RESTMapper stuff to provide a general solution func expandResourceShortcut(resource string) string {
// to this problem.
func ExpandResourceShortcut(resource string) string {
shortForms := map[string]string{ shortForms := map[string]string{
"po": "pods", "po": "pods",
"rc": "replicationcontrollers", "rc": "replicationcontrollers",