From 6344cf3c3a9f8097c75a10fda01c429b3210f29d Mon Sep 17 00:00:00 2001 From: deads2k Date: Tue, 7 Apr 2015 14:21:25 -0400 Subject: [PATCH] refactor to move kubectl.cmd.Factory to kubect/cmd/util --- cmd/gendocs/gen_kubectl_docs.go | 3 +- cmd/genman/gen_kubectl_man.go | 3 +- cmd/kubectl/kubectl.go | 3 +- pkg/kubectl/cmd/apiversions.go | 8 +- pkg/kubectl/cmd/clusterinfo.go | 8 +- pkg/kubectl/cmd/cmd.go | 461 +-------------------- pkg/kubectl/cmd/cmd_test.go | 13 +- pkg/kubectl/cmd/config/config.go | 3 +- pkg/kubectl/cmd/config/config_test.go | 4 +- pkg/kubectl/cmd/create.go | 4 +- pkg/kubectl/cmd/create_test.go | 8 +- pkg/kubectl/cmd/delete.go | 4 +- pkg/kubectl/cmd/delete_test.go | 12 +- pkg/kubectl/cmd/describe.go | 10 +- pkg/kubectl/cmd/describe_test.go | 2 +- pkg/kubectl/cmd/exec.go | 20 +- pkg/kubectl/cmd/expose.go | 36 +- pkg/kubectl/cmd/get.go | 18 +- pkg/kubectl/cmd/get_test.go | 22 +- pkg/kubectl/cmd/label.go | 26 +- pkg/kubectl/cmd/label_test.go | 4 +- pkg/kubectl/cmd/log.go | 14 +- pkg/kubectl/cmd/portforward.go | 14 +- pkg/kubectl/cmd/proxy.go | 16 +- pkg/kubectl/cmd/resize.go | 18 +- pkg/kubectl/cmd/rollingupdate.go | 26 +- pkg/kubectl/cmd/run.go | 22 +- pkg/kubectl/cmd/stop.go | 2 +- pkg/kubectl/cmd/update.go | 6 +- pkg/kubectl/cmd/update_test.go | 6 +- pkg/kubectl/cmd/util/clientcache.go | 76 ++++ pkg/kubectl/cmd/util/factory.go | 391 +++++++++++++++++ pkg/kubectl/cmd/{ => util}/factory_test.go | 2 +- pkg/kubectl/cmd/version.go | 10 +- 34 files changed, 664 insertions(+), 611 deletions(-) create mode 100644 pkg/kubectl/cmd/util/clientcache.go create mode 100644 pkg/kubectl/cmd/util/factory.go rename pkg/kubectl/cmd/{ => util}/factory_test.go (98%) diff --git a/cmd/gendocs/gen_kubectl_docs.go b/cmd/gendocs/gen_kubectl_docs.go index a0fee8d2b8f..64b3e545db6 100644 --- a/cmd/gendocs/gen_kubectl_docs.go +++ b/cmd/gendocs/gen_kubectl_docs.go @@ -25,6 +25,7 @@ import ( "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/spf13/cobra" ) @@ -139,7 +140,7 @@ func main() { // regardless of where we run. os.Setenv("HOME", "/home/username") //TODO os.Stdin should really be something like ioutil.Discard, but a Reader - kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) genMarkdown(kubectl, "", docsDir) for _, c := range kubectl.Commands() { genMarkdown(c, "kubectl", docsDir) diff --git a/cmd/genman/gen_kubectl_man.go b/cmd/genman/gen_kubectl_man.go index 5f06a651ff7..80a844100b9 100644 --- a/cmd/genman/gen_kubectl_man.go +++ b/cmd/genman/gen_kubectl_man.go @@ -25,6 +25,7 @@ import ( "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/cpuguy83/go-md2man/mangen" "github.com/russross/blackfriday" "github.com/spf13/cobra" @@ -63,7 +64,7 @@ func main() { // regardless of where we run. os.Setenv("HOME", "/home/username") //TODO os.Stdin should really be something like ioutil.Discard, but a Reader - kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) genMarkdown(kubectl, "", docsDir) for _, c := range kubectl.Commands() { genMarkdown(c, "kubectl", docsDir) diff --git a/cmd/kubectl/kubectl.go b/cmd/kubectl/kubectl.go index 2b0a920e322..58b1a2e5b4e 100644 --- a/cmd/kubectl/kubectl.go +++ b/cmd/kubectl/kubectl.go @@ -21,11 +21,12 @@ import ( "runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" ) func main() { runtime.GOMAXPROCS(runtime.NumCPU()) - cmd := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr) + cmd := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr) if err := cmd.Execute(); err != nil { os.Exit(1) } diff --git a/pkg/kubectl/cmd/apiversions.go b/pkg/kubectl/cmd/apiversions.go index 45c8b2254cc..037a12a95df 100644 --- a/pkg/kubectl/cmd/apiversions.go +++ b/pkg/kubectl/cmd/apiversions.go @@ -23,10 +23,10 @@ import ( "github.com/spf13/cobra" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" ) -func (f *Factory) NewCmdApiVersions(out io.Writer) *cobra.Command { +func NewCmdApiVersions(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "api-versions", // apiversions is deprecated. @@ -34,13 +34,13 @@ func (f *Factory) NewCmdApiVersions(out io.Writer) *cobra.Command { Short: "Print available API versions.", Run: func(cmd *cobra.Command, args []string) { err := RunApiVersions(f, out) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } return cmd } -func RunApiVersions(f *Factory, out io.Writer) error { +func RunApiVersions(f *cmdutil.Factory, out io.Writer) error { if os.Args[1] == "apiversions" { printDeprecationWarning("api-versions", "apiversions") } diff --git a/pkg/kubectl/cmd/clusterinfo.go b/pkg/kubectl/cmd/clusterinfo.go index 0765b105eeb..7a686ea1bd2 100644 --- a/pkg/kubectl/cmd/clusterinfo.go +++ b/pkg/kubectl/cmd/clusterinfo.go @@ -23,14 +23,14 @@ import ( "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/daviddengcn/go-colortext" "github.com/spf13/cobra" ) -func (f *Factory) NewCmdClusterInfo(out io.Writer) *cobra.Command { +func NewCmdClusterInfo(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "cluster-info", // clusterinfo is deprecated. @@ -39,13 +39,13 @@ func (f *Factory) NewCmdClusterInfo(out io.Writer) *cobra.Command { Long: "Display addresses of the master and services with label kubernetes.io/cluster-service=true", Run: func(cmd *cobra.Command, args []string) { err := RunClusterInfo(f, out, cmd) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } return cmd } -func RunClusterInfo(factory *Factory, out io.Writer, cmd *cobra.Command) error { +func RunClusterInfo(factory *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { if os.Args[1] == "clusterinfo" { printDeprecationWarning("cluster-info", "clusterinfo") } diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index b0ce41a53cb..fee7c5ec361 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -17,254 +17,17 @@ limitations under the License. package cmd import ( - "fmt" "io" - "os" - "strconv" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" - "github.com/GoogleCloudPlatform/kubernetes/pkg/client" - "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" cmdconfig "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/config" cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/golang/glog" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) -const ( - FlagMatchBinaryVersion = "match-server-version" -) - -// Factory provides abstractions that allow the Kubectl command to be extended across multiple types -// of resources and different API sets. -// TODO: make the functions interfaces -// TODO: pass the various interfaces on the factory directly into the command constructors (so the -// commands are decoupled from the factory). -type Factory struct { - clients *clientCache - flags *pflag.FlagSet - - // Returns interfaces for dealing with arbitrary runtime.Objects. - Object func() (meta.RESTMapper, runtime.ObjectTyper) - // Returns a client for accessing Kubernetes resources or an error. - Client func() (*client.Client, 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 - // for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer. - RESTClient func(mapping *meta.RESTMapping) (resource.RESTClient, error) - // Returns a Describer for displaying the specified RESTMapping type or an error. - Describer func(mapping *meta.RESTMapping) (kubectl.Describer, error) - // Returns a Printer for formatting objects of the given type or an error. - Printer func(mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) - // Returns a Resizer for changing the size of the specified RESTMapping type or an error - Resizer func(mapping *meta.RESTMapping) (kubectl.Resizer, error) - // Returns a Reaper for gracefully shutting down resources. - Reaper func(mapping *meta.RESTMapping) (kubectl.Reaper, error) - // PodSelectorForResource returns the pod selector associated with the provided resource name - // or an error. - PodSelectorForResource func(mapping *meta.RESTMapping, namespace, name string) (string, error) - // PortForResource returns the ports associated with the provided resource name or an error - PortsForResource func(mapping *meta.RESTMapping, namespace, name string) ([]string, error) - // Returns a schema that can validate objects stored on disk. - Validator func() (validation.Schema, error) - // Returns the default namespace to use in cases where no other namespace is specified - DefaultNamespace func() (string, error) -} - -func getPorts(spec api.PodSpec) []string { - result := []string{} - for _, container := range spec.Containers { - for _, port := range container.Ports { - result = append(result, strconv.Itoa(port.ContainerPort)) - } - } - return result -} - -// 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. -func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { - mapper := kubectl.ShortcutExpander{latest.RESTMapper} - - flags := pflag.NewFlagSet("", pflag.ContinueOnError) - - clientConfig := optionalClientConfig - if optionalClientConfig == nil { - clientConfig = DefaultClientConfig(flags) - } - - clients := &clientCache{ - clients: make(map[string]*client.Client), - loader: clientConfig, - } - - return &Factory{ - clients: clients, - flags: flags, - - Object: func() (meta.RESTMapper, runtime.ObjectTyper) { - cfg, err := clientConfig.ClientConfig() - cmdutil.CheckErr(err) - cmdApiVersion := cfg.Version - - return kubectl.OutputVersionMapper{mapper, cmdApiVersion}, api.Scheme - }, - Client: func() (*client.Client, error) { - return clients.ClientForVersion("") - }, - ClientConfig: func() (*client.Config, error) { - return clients.ClientConfigForVersion("") - }, - RESTClient: func(mapping *meta.RESTMapping) (resource.RESTClient, error) { - client, err := clients.ClientForVersion(mapping.APIVersion) - if err != nil { - return nil, err - } - return client.RESTClient, nil - }, - Describer: func(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(mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) { - return kubectl.NewHumanReadablePrinter(noHeaders), nil - }, - PodSelectorForResource: func(mapping *meta.RESTMapping, namespace, name string) (string, error) { - // TODO: replace with a swagger schema based approach (identify pod selector via schema introspection) - client, err := clients.ClientForVersion("") - if err != nil { - return "", err - } - switch mapping.Kind { - case "ReplicationController": - rc, err := client.ReplicationControllers(namespace).Get(name) - if err != nil { - return "", err - } - return kubectl.MakeLabels(rc.Spec.Selector), nil - case "Pod": - rc, err := client.Pods(namespace).Get(name) - if err != nil { - return "", err - } - if len(rc.Labels) == 0 { - return "", fmt.Errorf("the pod has no labels and cannot be exposed") - } - return kubectl.MakeLabels(rc.Labels), nil - case "Service": - rc, err := client.ReplicationControllers(namespace).Get(name) - if err != nil { - return "", err - } - if rc.Spec.Selector == nil { - return "", fmt.Errorf("the service has no pod selector set") - } - return kubectl.MakeLabels(rc.Spec.Selector), nil - default: - return "", fmt.Errorf("it is not possible to get a pod selector from %s", mapping.Kind) - } - }, - PortsForResource: func(mapping *meta.RESTMapping, namespace, name string) ([]string, error) { - // TODO: replace with a swagger schema based approach (identify pod selector via schema introspection) - client, err := clients.ClientForVersion("") - if err != nil { - return nil, err - } - switch mapping.Kind { - case "ReplicationController": - rc, err := client.ReplicationControllers(namespace).Get(name) - if err != nil { - return nil, err - } - return getPorts(rc.Spec.Template.Spec), nil - case "Pod": - pod, err := client.Pods(namespace).Get(name) - if err != nil { - return nil, err - } - return getPorts(pod.Spec), nil - default: - return nil, fmt.Errorf("it is not possible to get ports from %s", mapping.Kind) - } - }, - Resizer: func(mapping *meta.RESTMapping) (kubectl.Resizer, error) { - client, err := clients.ClientForVersion(mapping.APIVersion) - if err != nil { - return nil, err - } - return kubectl.ResizerFor(mapping.Kind, client) - }, - Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { - client, err := clients.ClientForVersion(mapping.APIVersion) - if err != nil { - return nil, err - } - return kubectl.ReaperFor(mapping.Kind, client) - }, - Validator: func() (validation.Schema, error) { - if flags.Lookup("validate").Value.String() == "true" { - client, err := clients.ClientForVersion("") - if err != nil { - return nil, err - } - return &clientSwaggerSchema{client, api.Scheme}, nil - } - return validation.NullSchema{}, nil - }, - DefaultNamespace: func() (string, error) { - return clientConfig.Namespace() - }, - } -} - -// 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) - - // This is necessary as github.com/spf13/cobra doesn't support "global" - // pflags currently. See https://github.com/spf13/cobra/issues/44. - util.AddPFlagSetToPFlagSet(pflag.CommandLine, flags) - - // Hack for global access to validation flag. - // TODO: Refactor out after configuration flag overhaul. - if f.flags.Lookup("validate") == nil { - f.flags.Bool("validate", false, "If true, use a schema to validate the input before sending it") - } - - 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") -} - // NewKubectlCommand creates the `kubectl` command and its nested children. -func (f *Factory) NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { +func NewKubectlCommand(f *cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command { // Parent command to which all subcommands are added. cmds := &cobra.Command{ Use: "kubectl", @@ -277,223 +40,39 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, f.BindFlags(cmds.PersistentFlags()) - cmds.AddCommand(f.NewCmdGet(out)) - cmds.AddCommand(f.NewCmdDescribe(out)) - cmds.AddCommand(f.NewCmdCreate(out)) - cmds.AddCommand(f.NewCmdUpdate(out)) - cmds.AddCommand(f.NewCmdDelete(out)) + cmds.AddCommand(NewCmdGet(f, out)) + cmds.AddCommand(NewCmdDescribe(f, out)) + cmds.AddCommand(NewCmdCreate(f, out)) + cmds.AddCommand(NewCmdUpdate(f, out)) + cmds.AddCommand(NewCmdDelete(f, out)) cmds.AddCommand(NewCmdNamespace(out)) - cmds.AddCommand(f.NewCmdLog(out)) - cmds.AddCommand(f.NewCmdRollingUpdate(out)) - cmds.AddCommand(f.NewCmdResize(out)) + cmds.AddCommand(NewCmdLog(f, out)) + cmds.AddCommand(NewCmdRollingUpdate(f, out)) + cmds.AddCommand(NewCmdResize(f, out)) - cmds.AddCommand(f.NewCmdExec(in, out, err)) - cmds.AddCommand(f.NewCmdPortForward()) - cmds.AddCommand(f.NewCmdProxy(out)) + cmds.AddCommand(NewCmdExec(f, in, out, err)) + cmds.AddCommand(NewCmdPortForward(f)) + cmds.AddCommand(NewCmdProxy(f, out)) - cmds.AddCommand(f.NewCmdRunContainer(out)) - cmds.AddCommand(f.NewCmdStop(out)) - cmds.AddCommand(f.NewCmdExposeService(out)) + cmds.AddCommand(NewCmdRunContainer(f, out)) + cmds.AddCommand(NewCmdStop(f, out)) + cmds.AddCommand(NewCmdExposeService(f, out)) - cmds.AddCommand(f.NewCmdLabel(out)) + cmds.AddCommand(NewCmdLabel(f, out)) - cmds.AddCommand(cmdconfig.NewCmdConfig(out)) - cmds.AddCommand(f.NewCmdClusterInfo(out)) - cmds.AddCommand(f.NewCmdApiVersions(out)) - cmds.AddCommand(f.NewCmdVersion(out)) + cmds.AddCommand(cmdconfig.NewCmdConfig(f, out)) + cmds.AddCommand(NewCmdClusterInfo(f, out)) + cmds.AddCommand(NewCmdApiVersions(f, out)) + cmds.AddCommand(NewCmdVersion(f, out)) return cmds } -// 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() - _, kind, err := api.Scheme.ObjectVersionAndKind(obj) - if err != nil { - return err - } - - mapping, err := mapper.RESTMapping(kind) - if err != nil { - return err - } - - printer, err := f.PrinterForMapping(cmd, mapping) - if err != nil { - return err - } - return printer.PrintObj(obj, out) -} - -// PrinterForMapping returns a printer suitable for displaying the provided resource type. -// Requires that printer flags have been added to cmd (see AddPrinterFlags). -func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.ResourcePrinter, error) { - printer, ok, err := cmdutil.PrinterForCommand(cmd) - if err != nil { - return nil, err - } - if ok { - clientConfig, err := f.ClientConfig() - if err != nil { - return nil, err - } - defaultVersion := clientConfig.Version - - version := cmdutil.OutputVersion(cmd, defaultVersion) - if len(version) == 0 { - version = mapping.APIVersion - } - if len(version) == 0 { - return nil, fmt.Errorf("you must specify an output-version when using this output format") - } - printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, version) - } else { - printer, err = f.Printer(mapping, cmdutil.GetFlagBool(cmd, "no-headers")) - if err != nil { - return nil, err - } - } - return printer, nil -} - -// ClientMapperForCommand returns a ClientMapper for the factory. -func (f *Factory) ClientMapperForCommand() resource.ClientMapper { - return resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) { - return f.RESTClient(mapping) - }) -} - -// 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 and merge rules: -// 1. CommandLineLocation - this parsed from the command line, so it must be late bound -// 2. EnvVarLocation -// 3. CurrentDirectoryLocation -// 4. HomeDirectoryLocation -// Empty filenames are ignored. Files with non-deserializable content produced errors. -// The first file to set a particular value or map key wins and the value or map key is never changed. -// This means that the first file to set CurrentContext will have its context preserved. It also means -// that if two files specify a "red-user", only values from the first file's red-user are used. Even -// non-conflicting entries from the second file's "red-user" are discarded. -// 2. Determine the context to use based on the first hit in this chain -// 1. command line argument - again, parsed from the command line, so it must be late bound -// 2. CurrentContext from the merged kubeconfig file -// 3. Empty is allowed at this stage -// 3. Determine the cluster info and auth info to use. At this point, we may or may not have a context. They -// are built based on the first hit in this chain. (run it twice, once for auth, once for cluster) -// 1. command line argument -// 2. If context is present, then use the context value -// 3. Empty is allowed -// 4. Determine the actual cluster info to use. At this point, we may or may not have a cluster info. Build -// each piece of the cluster info based on the chain: -// 1. command line argument -// 2. If cluster info is present and a value for the attribute is present, use it. -// 3. If you don't have a server location, bail. -// 5. Auth info is build using the same rules as cluster info, EXCEPT that you can only have one authentication -// technique per auth info. The following conditions result in an error: -// 1. If there are two conflicting techniques specified from the command line, 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. -// 2. Use default values and potentially prompt for auth information -func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig { - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - flags.StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") - - overrides := &clientcmd.ConfigOverrides{} - flagNames := clientcmd.RecommendedConfigOverrideFlags("") - // short flagnames are disabled by default. These are here for compatibility with existing scripts - flagNames.AuthOverrideFlags.AuthPathShort = "a" - flagNames.ClusterOverrideFlags.APIServerShort = "s" - - clientcmd.BindOverrideFlags(overrides, flags, flagNames) - clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) - - return clientConfig -} - func runHelp(cmd *cobra.Command, args []string) { cmd.Help() } -type clientSwaggerSchema struct { - c *client.Client - t runtime.ObjectTyper -} - -func (c *clientSwaggerSchema) ValidateBytes(data []byte) error { - version, _, err := c.t.DataVersionAndKind(data) - if err != nil { - return err - } - schemaData, err := c.c.RESTClient.Get(). - AbsPath("/swaggerapi/api", version). - Do(). - Raw() - if err != nil { - return err - } - schema, err := validation.NewSwaggerSchemaFromBytes(schemaData) - if err != nil { - return err - } - return schema.ValidateBytes(data) -} - -// clientCache caches previously loaded clients for reuse, and ensures MatchServerVersion -// is invoked only once -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: have a better config copy method - config := *c.defaultConfig - if len(version) != 0 { - config.Version = version - } - client.SetKubernetesDefaults(&config) - - 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 { - return nil, err - } - - if client, ok := c.clients[config.Version]; ok { - return client, nil - } - - client, err := client.New(config) - if err != nil { - return nil, err - } - - c.clients[config.Version] = client - return client, nil -} - func printDeprecationWarning(command, alias string) { glog.Warningf("%s is DEPRECATED and will be removed in a future version. Use %s instead.", alias, command) } diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 2ba27cb40a5..3a767b0d684 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -30,6 +30,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) @@ -109,14 +110,14 @@ type testFactory struct { Err error } -func NewTestFactory() (*Factory, *testFactory, runtime.Codec) { +func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { scheme, mapper, codec := newExternalScheme() t := &testFactory{ Validator: validation.NullSchema{}, Mapper: mapper, Typer: scheme, } - return &Factory{ + return &cmdutil.Factory{ Object: func() (meta.RESTMapper, runtime.ObjectTyper) { return t.Mapper, t.Typer }, @@ -141,11 +142,11 @@ func NewTestFactory() (*Factory, *testFactory, runtime.Codec) { }, t, codec } -func NewAPIFactory() (*Factory, *testFactory, runtime.Codec) { +func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { t := &testFactory{ Validator: validation.NullSchema{}, } - return &Factory{ + return &cmdutil.Factory{ Object: func() (meta.RESTMapper, runtime.ObjectTyper) { return latest.RESTMapper, api.Scheme }, @@ -180,7 +181,7 @@ func stringBody(body string) io.ReadCloser { // Verify that resource.RESTClients constructed from a factory respect mapping.APIVersion func TestClientVersions(t *testing.T) { - f := NewFactory(nil) + f := cmdutil.NewFactory(nil) versions := []string{ "v1beta1", @@ -209,7 +210,7 @@ func ExamplePrintReplicationController() { Codec: codec, Client: nil, } - cmd := f.NewCmdRunContainer(os.Stdout) + cmd := NewCmdRunContainer(f, os.Stdout) ctrl := &api.ReplicationController{ ObjectMeta: api.ObjectMeta{ Name: "foo", diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index 27bf1dbdb24..88f12a7b942 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -27,6 +27,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" ) type pathOptions struct { @@ -36,7 +37,7 @@ type pathOptions struct { specifiedFile string } -func NewCmdConfig(out io.Writer) *cobra.Command { +func NewCmdConfig(f *cmdutil.Factory, out io.Writer) *cobra.Command { pathOptions := &pathOptions{} cmd := &cobra.Command{ diff --git a/pkg/kubectl/cmd/config/config_test.go b/pkg/kubectl/cmd/config/config_test.go index a528ae8aa27..1ca9f50e88c 100644 --- a/pkg/kubectl/cmd/config/config_test.go +++ b/pkg/kubectl/cmd/config/config_test.go @@ -26,6 +26,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -618,8 +619,9 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config) (strin argsToUse = append(argsToUse, args...) buf := bytes.NewBuffer([]byte{}) + f := cmdutil.NewFactory(nil) - cmd := NewCmdConfig(buf) + cmd := NewCmdConfig(f, buf) cmd.SetArgs(argsToUse) cmd.Execute() diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index 54917b2f5dc..b0e731d1870 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -38,7 +38,7 @@ $ kubectl create -f pod.json $ cat pod.json | kubectl create -f -` ) -func (f *Factory) NewCmdCreate(out io.Writer) *cobra.Command { +func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command { var filenames util.StringList cmd := &cobra.Command{ Use: "create -f FILENAME", @@ -61,7 +61,7 @@ func ValidateArgs(cmd *cobra.Command, args []string) error { return nil } -func RunCreate(f *Factory, out io.Writer, filenames util.StringList) error { +func RunCreate(f *cmdutil.Factory, out io.Writer, filenames util.StringList) error { schema, err := f.Validator() if err != nil { return err diff --git a/pkg/kubectl/cmd/create_test.go b/pkg/kubectl/cmd/create_test.go index aa9fdb0b7d6..0d154c7997b 100644 --- a/pkg/kubectl/cmd/create_test.go +++ b/pkg/kubectl/cmd/create_test.go @@ -28,7 +28,7 @@ func TestExtraArgsFail(t *testing.T) { buf := bytes.NewBuffer([]byte{}) f, _, _ := NewAPIFactory() - c := f.NewCmdCreate(buf) + c := NewCmdCreate(f, buf) if ValidateArgs(c, []string{"rc"}) == nil { t.Errorf("unexpected non-error") } @@ -55,7 +55,7 @@ func TestCreateObject(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdCreate(buf) + cmd := NewCmdCreate(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.json") cmd.Run(cmd, []string{}) @@ -87,7 +87,7 @@ func TestCreateMultipleObject(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdCreate(buf) + cmd := NewCmdCreate(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.json") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json") cmd.Run(cmd, []string{}) @@ -121,7 +121,7 @@ func TestCreateDirectory(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdCreate(buf) + cmd := NewCmdCreate(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook") cmd.Run(cmd, []string{}) diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index 0c3fcbb4c7e..f660521909b 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -55,7 +55,7 @@ $ kubectl delete pod 1234-56-7890-234234-456456 $ kubectl delete pods --all` ) -func (f *Factory) NewCmdDelete(out io.Writer) *cobra.Command { +func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command { var filenames util.StringList cmd := &cobra.Command{ Use: "delete ([-f FILENAME] | (RESOURCE [(ID | -l label | --all)]", @@ -73,7 +73,7 @@ func (f *Factory) NewCmdDelete(out io.Writer) *cobra.Command { return cmd } -func RunDelete(f *Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error { +func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error { cmdNamespace, err := f.DefaultNamespace() if err != nil { return err diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index 48ca007831c..44a6b45aa0d 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -47,7 +47,7 @@ func TestDeleteObject(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdDelete(buf) + cmd := NewCmdDelete(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.json") cmd.Run(cmd, []string{}) @@ -75,7 +75,7 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdDelete(buf) + cmd := NewCmdDelete(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.json") cmd.Run(cmd, []string{}) @@ -106,7 +106,7 @@ func TestDeleteMultipleObject(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdDelete(buf) + cmd := NewCmdDelete(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.json") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json") cmd.Run(cmd, []string{}) @@ -138,7 +138,7 @@ func TestDeleteMultipleObjectIgnoreMissing(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdDelete(buf) + cmd := NewCmdDelete(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.json") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json") cmd.Run(cmd, []string{}) @@ -170,7 +170,7 @@ func TestDeleteDirectory(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdDelete(buf) + cmd := NewCmdDelete(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook") cmd.Run(cmd, []string{}) @@ -211,7 +211,7 @@ func TestDeleteMultipleSelector(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdDelete(buf) + cmd := NewCmdDelete(f, buf) cmd.Flags().Set("selector", "a=b") cmd.Run(cmd, []string{"pods,services"}) diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index 86abd6a0403..fbecc20fa8c 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -20,11 +20,11 @@ import ( "fmt" "io" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/spf13/cobra" ) -func (f *Factory) NewCmdDescribe(out io.Writer) *cobra.Command { +func NewCmdDescribe(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "describe RESOURCE ID", Short: "Show details of a specific resource", @@ -34,13 +34,13 @@ This command joins many API calls together to form a detailed description of a given resource.`, Run: func(cmd *cobra.Command, args []string) { err := RunDescribe(f, out, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } return cmd } -func RunDescribe(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error { +func RunDescribe(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { cmdNamespace, err := f.DefaultNamespace() if err != nil { return err @@ -48,7 +48,7 @@ func RunDescribe(f *Factory, out io.Writer, cmd *cobra.Command, args []string) e mapper, _ := f.Object() // TODO: use resource.Builder instead - mapping, namespace, name, err := util.ResourceFromArgs(cmd, args, mapper, cmdNamespace) + mapping, namespace, name, err := cmdutil.ResourceFromArgs(cmd, args, mapper, cmdNamespace) if err != nil { return err } diff --git a/pkg/kubectl/cmd/describe_test.go b/pkg/kubectl/cmd/describe_test.go index f736073f764..992f384445c 100644 --- a/pkg/kubectl/cmd/describe_test.go +++ b/pkg/kubectl/cmd/describe_test.go @@ -37,7 +37,7 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { tf.Namespace = "non-default" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdDescribe(buf) + cmd := NewCmdDescribe(f, buf) cmd.Run(cmd, []string{"type", "foo"}) if d.Name != "foo" || d.Namespace != "non-default" { diff --git a/pkg/kubectl/cmd/exec.go b/pkg/kubectl/cmd/exec.go index 229bb01c656..397565e0569 100644 --- a/pkg/kubectl/cmd/exec.go +++ b/pkg/kubectl/cmd/exec.go @@ -24,7 +24,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/remotecommand" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/docker/docker/pkg/term" "github.com/golang/glog" "github.com/spf13/cobra" @@ -38,7 +38,7 @@ $ kubectl exec -p 123456-7890 -c ruby-container date $ kubectl exec -p 123456-7890 -c ruby-container -i -t -- bash -il` ) -func (f *Factory) NewCmdExec(cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { +func NewCmdExec(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "exec -p POD -c CONTAINER -- COMMAND [args...]", Short: "Execute a command in a container.", @@ -46,7 +46,7 @@ func (f *Factory) NewCmdExec(cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.C Example: exec_example, Run: func(cmd *cobra.Command, args []string) { err := RunExec(f, cmdIn, cmdOut, cmdErr, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } cmd.Flags().StringP("pod", "p", "", "Pod name") @@ -57,14 +57,14 @@ func (f *Factory) NewCmdExec(cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.C return cmd } -func RunExec(f *Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobra.Command, args []string) error { - podName := util.GetFlagString(cmd, "pod") +func RunExec(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobra.Command, args []string) error { + podName := cmdutil.GetFlagString(cmd, "pod") if len(podName) == 0 { - return util.UsageError(cmd, "POD is required for exec") + return cmdutil.UsageError(cmd, "POD is required for exec") } if len(args) < 1 { - return util.UsageError(cmd, "COMMAND is required for exec") + return cmdutil.UsageError(cmd, "COMMAND is required for exec") } namespace, err := f.DefaultNamespace() @@ -86,14 +86,14 @@ func RunExec(f *Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobra.C glog.Fatalf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase) } - containerName := util.GetFlagString(cmd, "container") + containerName := cmdutil.GetFlagString(cmd, "container") if len(containerName) == 0 { containerName = pod.Spec.Containers[0].Name } var stdin io.Reader - tty := util.GetFlagBool(cmd, "tty") - if util.GetFlagBool(cmd, "stdin") { + tty := cmdutil.GetFlagBool(cmd, "tty") + if cmdutil.GetFlagBool(cmd, "stdin") { stdin = cmdIn if tty { if file, ok := cmdIn.(*os.File); ok { diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index d3077e5a853..47df1df3f19 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -22,7 +22,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/spf13/cobra" ) @@ -42,7 +42,7 @@ $ kubectl expose service nginx --port=443 --target-port=8443 --service-name=ngin $ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream` ) -func (f *Factory) NewCmdExposeService(out io.Writer) *cobra.Command { +func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "expose RESOURCE NAME --port=port [--protocol=TCP|UDP] [--target-port=number-or-name] [--service-name=name] [--public-ip=ip] [--create-external-load-balancer=bool]", Short: "Take a replicated application and expose it as Kubernetes Service", @@ -50,10 +50,10 @@ func (f *Factory) NewCmdExposeService(out io.Writer) *cobra.Command { Example: expose_example, Run: func(cmd *cobra.Command, args []string) { err := RunExpose(f, out, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } - util.AddPrinterFlags(cmd) + cmdutil.AddPrinterFlags(cmd) cmd.Flags().String("generator", "service/v1", "The name of the API generator to use. Default is 'service/v1'.") cmd.Flags().String("protocol", "TCP", "The network protocol for the service to be created. Default is 'tcp'.") cmd.Flags().Int("port", -1, "The port that the service should serve on. Required.") @@ -69,13 +69,13 @@ func (f *Factory) NewCmdExposeService(out io.Writer) *cobra.Command { return cmd } -func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error { +func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { var name, resource string switch l := len(args); { case l == 2: resource, name = args[0], args[1] default: - return util.UsageError(cmd, "the type and name of a resource to expose are required arguments") + return cmdutil.UsageError(cmd, "the type and name of a resource to expose are required arguments") } namespace, err := f.DefaultNamespace() @@ -87,20 +87,20 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err return err } - generatorName := util.GetFlagString(cmd, "generator") + generatorName := cmdutil.GetFlagString(cmd, "generator") generator, found := kubectl.Generators[generatorName] if !found { - return util.UsageError(cmd, fmt.Sprintf("generator %q not found.", generator)) + return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generator)) } names := generator.ParamNames() params := kubectl.MakeParams(cmd, names) - if len(util.GetFlagString(cmd, "service-name")) == 0 { + if len(cmdutil.GetFlagString(cmd, "service-name")) == 0 { params["name"] = name } else { - params["name"] = util.GetFlagString(cmd, "service-name") + params["name"] = cmdutil.GetFlagString(cmd, "service-name") } - if s, found := params["selector"]; !found || len(s) == 0 || util.GetFlagInt(cmd, "port") < 1 { + if s, found := params["selector"]; !found || len(s) == 0 || cmdutil.GetFlagInt(cmd, "port") < 1 { mapper, _ := f.Object() v, k, err := mapper.VersionAndKindForResource(resource) if err != nil { @@ -117,21 +117,21 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err } params["selector"] = s } - if util.GetFlagInt(cmd, "port") < 0 { + if cmdutil.GetFlagInt(cmd, "port") < 0 { ports, err := f.PortsForResource(mapping, namespace, name) if err != nil { return err } if len(ports) == 0 { - return util.UsageError(cmd, "couldn't find a suitable port via --port flag or introspection") + return cmdutil.UsageError(cmd, "couldn't find a suitable port via --port flag or introspection") } if len(ports) > 1 { - return util.UsageError(cmd, "more than one port to choose from, please explicitly specify a port using the --port flag.") + return cmdutil.UsageError(cmd, "more than one port to choose from, please explicitly specify a port using the --port flag.") } params["port"] = ports[0] } } - if util.GetFlagBool(cmd, "create-external-load-balancer") { + if cmdutil.GetFlagBool(cmd, "create-external-load-balancer") { params["create-external-load-balancer"] = "true" } @@ -145,16 +145,16 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err return err } - inline := util.GetFlagString(cmd, "overrides") + inline := cmdutil.GetFlagString(cmd, "overrides") if len(inline) > 0 { - service, err = util.Merge(service, inline, "Service") + service, err = cmdutil.Merge(service, inline, "Service") if err != nil { return err } } // TODO: extract this flag to a central location, when such a location exists. - if !util.GetFlagBool(cmd, "dry-run") { + if !cmdutil.GetFlagBool(cmd, "dry-run") { service, err = client.Services(namespace).Create(service.(*api.Service)) if err != nil { return err diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 8f091434e3e..7cf2e749b0b 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -23,7 +23,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" @@ -59,7 +59,7 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7` // NewCmdGet creates a command object for the generic "get" action, which // retrieves one or more resources from a server. -func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command { +func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", Short: "Display one or many resources", @@ -67,10 +67,10 @@ func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command { Example: get_example, Run: func(cmd *cobra.Command, args []string) { err := RunGet(f, out, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } - util.AddPrinterFlags(cmd) + cmdutil.AddPrinterFlags(cmd) cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on") cmd.Flags().BoolP("watch", "w", false, "After listing/getting the requested object, watch for changes.") cmd.Flags().Bool("watch-only", false, "Watch for changes to the requested object(s), without listing/getting first.") @@ -79,8 +79,8 @@ func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command { // RunGet implements the generic Get command // TODO: convert all direct flag accessors to a struct and pass that instead of cmd -func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error { - selector := util.GetFlagString(cmd, "selector") +func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { + selector := cmdutil.GetFlagString(cmd, "selector") mapper, typer := f.Object() cmdNamespace, err := f.DefaultNamespace() @@ -89,7 +89,7 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error } // handle watch separately since we cannot watch multiple resource types - isWatch, isWatchOnly := util.GetFlagBool(cmd, "watch"), util.GetFlagBool(cmd, "watch-only") + isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only") if isWatch || isWatchOnly { r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). NamespaceParam(cmdNamespace).DefaultNamespace(). @@ -146,7 +146,7 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error ResourceTypeOrNameArgs(true, args...). ContinueOnError(). Latest() - printer, generic, err := util.PrinterForCommand(cmd) + printer, generic, err := cmdutil.PrinterForCommand(cmd) if err != nil { return err } @@ -159,7 +159,7 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error defaultVersion := clientConfig.Version // the outermost object will be converted to the output-version - version := util.OutputVersion(cmd, defaultVersion) + version := cmdutil.OutputVersion(cmd, defaultVersion) r := b.Flatten().Do() obj, err := r.Object() diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index 4c53c57d3ed..a432872bd8c 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -99,7 +99,7 @@ func TestGetUnknownSchemaObject(t *testing.T) { tf.ClientConfig = &client.Config{Version: latest.Version} buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Run(cmd, []string{"type", "foo"}) @@ -128,7 +128,7 @@ func TestGetSchemaObject(t *testing.T) { tf.ClientConfig = &client.Config{Version: "v1beta3"} buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.Run(cmd, []string{"replicationcontrollers", "foo"}) if !strings.Contains(buf.String(), "\"foo\"") { @@ -148,7 +148,7 @@ func TestGetObjects(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Run(cmd, []string{"pods", "foo"}) @@ -174,7 +174,7 @@ func TestGetListObjects(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Run(cmd, []string{"pods"}) @@ -210,7 +210,7 @@ func TestGetMultipleTypeObjects(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Run(cmd, []string{"pods,services"}) @@ -247,7 +247,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { tf.ClientConfig = &client.Config{Version: "v1beta1"} buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Flags().Set("output", "json") @@ -298,7 +298,7 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Flags().Set("selector", "a=b") @@ -344,7 +344,7 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Run(cmd, []string{"services/bar", "node/foo"}) @@ -430,7 +430,7 @@ func TestWatchSelector(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Flags().Set("watch", "true") @@ -469,7 +469,7 @@ func TestWatchResource(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Flags().Set("watch", "true") @@ -507,7 +507,7 @@ func TestWatchOnlyResource(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdGet(buf) + cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) cmd.Flags().Set("watch-only", "true") diff --git a/pkg/kubectl/cmd/label.go b/pkg/kubectl/cmd/label.go index 55b7e90953c..733a75ff3b4 100644 --- a/pkg/kubectl/cmd/label.go +++ b/pkg/kubectl/cmd/label.go @@ -22,7 +22,7 @@ import ( "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/spf13/cobra" @@ -50,7 +50,7 @@ $ kubectl label pods foo status=unhealthy --resource-version=1 $ kubectl label pods foo bar-` ) -func (f *Factory) NewCmdLabel(out io.Writer) *cobra.Command { +func NewCmdLabel(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "label [--overwrite] RESOURCE NAME KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]", Short: "Update the labels on a resource", @@ -58,10 +58,10 @@ func (f *Factory) NewCmdLabel(out io.Writer) *cobra.Command { Example: label_example, Run: func(cmd *cobra.Command, args []string) { err := RunLabel(f, out, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } - util.AddPrinterFlags(cmd) + cmdutil.AddPrinterFlags(cmd) cmd.Flags().Bool("overwrite", false, "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels.") cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on") cmd.Flags().Bool("all", false, "select all resources in the namespace of the specified resource types") @@ -149,7 +149,7 @@ func labelFunc(obj runtime.Object, overwrite bool, resourceVersion string, label return obj, nil } -func RunLabel(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error { +func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { resources, labelArgs := []string{}, []string{} first := true for _, s := range args { @@ -163,20 +163,20 @@ func RunLabel(f *Factory, out io.Writer, cmd *cobra.Command, args []string) erro case first && !isLabel: resources = append(resources, s) case !first && !isLabel: - return util.UsageError(cmd, "all resources must be specified before label changes: %s", s) + return cmdutil.UsageError(cmd, "all resources must be specified before label changes: %s", s) } } if len(resources) < 1 { - return util.UsageError(cmd, "one or more resources must be specified as or /") + return cmdutil.UsageError(cmd, "one or more resources must be specified as or /") } if len(labelArgs) < 1 { - return util.UsageError(cmd, "at least one label update is required") + return cmdutil.UsageError(cmd, "at least one label update is required") } - selector := util.GetFlagString(cmd, "selector") - all := util.GetFlagBool(cmd, "all") - overwrite := util.GetFlagBool(cmd, "overwrite") - resourceVersion := util.GetFlagString(cmd, "resource-version") + selector := cmdutil.GetFlagString(cmd, "selector") + all := cmdutil.GetFlagBool(cmd, "all") + overwrite := cmdutil.GetFlagBool(cmd, "overwrite") + resourceVersion := cmdutil.GetFlagString(cmd, "resource-version") cmdNamespace, err := f.DefaultNamespace() if err != nil { @@ -204,7 +204,7 @@ func RunLabel(f *Factory, out io.Writer, cmd *cobra.Command, args []string) erro } // only apply resource version locking on a single resource if !one && len(resourceVersion) > 0 { - return util.UsageError(cmd, "--resource-version may only be used with a single resource") + return cmdutil.UsageError(cmd, "--resource-version may only be used with a single resource") } // TODO: support bulk generic output a la Get diff --git a/pkg/kubectl/cmd/label_test.go b/pkg/kubectl/cmd/label_test.go index 1933ba17280..53f435efcd9 100644 --- a/pkg/kubectl/cmd/label_test.go +++ b/pkg/kubectl/cmd/label_test.go @@ -294,7 +294,7 @@ func TestLabelErrors(t *testing.T) { tf.ClientConfig = &client.Config{Version: "v1beta1"} buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdLabel(buf) + cmd := NewCmdLabel(f, buf) cmd.SetOutput(buf) for k, v := range testCase.flags { @@ -351,7 +351,7 @@ func TestLabelMultipleObjects(t *testing.T) { tf.ClientConfig = &client.Config{Version: "v1beta1"} buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdLabel(buf) + cmd := NewCmdLabel(f, buf) cmd.Flags().Set("all", "true") if err := RunLabel(f, buf, cmd, []string{"pods", "a=b"}); err != nil { diff --git a/pkg/kubectl/cmd/log.go b/pkg/kubectl/cmd/log.go index 3324784c793..6808bc0d4bb 100644 --- a/pkg/kubectl/cmd/log.go +++ b/pkg/kubectl/cmd/log.go @@ -22,7 +22,7 @@ import ( "strconv" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" libutil "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/spf13/cobra" ) @@ -57,7 +57,7 @@ func selectContainer(pod *api.Pod, in io.Reader, out io.Writer) string { } } -func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command { +func NewCmdLog(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "log [-f] POD [CONTAINER]", Short: "Print the logs for a container in a pod.", @@ -65,7 +65,7 @@ func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command { Example: log_example, Run: func(cmd *cobra.Command, args []string) { err := RunLog(f, out, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } cmd.Flags().BoolP("follow", "f", false, "Specify if the logs should be streamed.") @@ -73,13 +73,13 @@ func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command { return cmd } -func RunLog(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error { +func RunLog(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { if len(args) == 0 { - return util.UsageError(cmd, "POD is required for log") + return cmdutil.UsageError(cmd, "POD is required for log") } if len(args) > 2 { - return util.UsageError(cmd, "log POD [CONTAINER]") + return cmdutil.UsageError(cmd, "log POD [CONTAINER]") } namespace, err := f.DefaultNamespace() @@ -109,7 +109,7 @@ func RunLog(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error } follow := false - if util.GetFlagBool(cmd, "follow") { + if cmdutil.GetFlagBool(cmd, "follow") { follow = true } diff --git a/pkg/kubectl/cmd/portforward.go b/pkg/kubectl/cmd/portforward.go index 51d7cc3925c..1e4ebf1019a 100644 --- a/pkg/kubectl/cmd/portforward.go +++ b/pkg/kubectl/cmd/portforward.go @@ -22,7 +22,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/portforward" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/golang/glog" "github.com/spf13/cobra" ) @@ -42,7 +42,7 @@ $ kubectl port-forward -p mypod :5000 $ kubectl port-forward -p mypod 0:5000` ) -func (f *Factory) NewCmdPortForward() *cobra.Command { +func NewCmdPortForward(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ Use: "port-forward -p POD [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N]", Short: "Forward one or more local ports to a pod.", @@ -50,7 +50,7 @@ func (f *Factory) NewCmdPortForward() *cobra.Command { Example: portforward_example, Run: func(cmd *cobra.Command, args []string) { err := RunPortForward(f, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } cmd.Flags().StringP("pod", "p", "", "Pod name") @@ -58,14 +58,14 @@ func (f *Factory) NewCmdPortForward() *cobra.Command { return cmd } -func RunPortForward(f *Factory, cmd *cobra.Command, args []string) error { - podName := util.GetFlagString(cmd, "pod") +func RunPortForward(f *cmdutil.Factory, cmd *cobra.Command, args []string) error { + podName := cmdutil.GetFlagString(cmd, "pod") if len(podName) == 0 { - return util.UsageError(cmd, "POD is required for exec") + return cmdutil.UsageError(cmd, "POD is required for exec") } if len(args) < 1 { - return util.UsageError(cmd, "at least 1 PORT is required for port-forward") + return cmdutil.UsageError(cmd, "at least 1 PORT is required for port-forward") } namespace, err := f.DefaultNamespace() diff --git a/pkg/kubectl/cmd/proxy.go b/pkg/kubectl/cmd/proxy.go index 8ab50abf4cc..9c067c9f80e 100644 --- a/pkg/kubectl/cmd/proxy.go +++ b/pkg/kubectl/cmd/proxy.go @@ -22,7 +22,7 @@ import ( "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/golang/glog" "github.com/spf13/cobra" ) @@ -36,7 +36,7 @@ $ kubectl proxy --port=8011 --www=./local/www/ $ kubectl proxy --api-prefix=k8s-api` ) -func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command { +func NewCmdProxy(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix]", Short: "Run a proxy to the Kubernetes API server", @@ -44,7 +44,7 @@ func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command { Example: proxy_example, Run: func(cmd *cobra.Command, args []string) { err := RunProxy(f, out, cmd) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } cmd.Flags().StringP("www", "w", "", "Also serve static files from the given directory under the specified prefix.") @@ -54,8 +54,8 @@ func (f *Factory) NewCmdProxy(out io.Writer) *cobra.Command { return cmd } -func RunProxy(f *Factory, out io.Writer, cmd *cobra.Command) error { - port := util.GetFlagInt(cmd, "port") +func RunProxy(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { + port := cmdutil.GetFlagInt(cmd, "port") fmt.Fprintf(out, "Starting to serve on localhost:%d", port) clientConfig, err := f.ClientConfig() @@ -63,16 +63,16 @@ func RunProxy(f *Factory, out io.Writer, cmd *cobra.Command) error { return err } - staticPrefix := util.GetFlagString(cmd, "www-prefix") + staticPrefix := cmdutil.GetFlagString(cmd, "www-prefix") if !strings.HasSuffix(staticPrefix, "/") { staticPrefix += "/" } - apiProxyPrefix := util.GetFlagString(cmd, "api-prefix") + apiProxyPrefix := cmdutil.GetFlagString(cmd, "api-prefix") if !strings.HasSuffix(apiProxyPrefix, "/") { apiProxyPrefix += "/" } - server, err := kubectl.NewProxyServer(util.GetFlagString(cmd, "www"), apiProxyPrefix, staticPrefix, clientConfig) + server, err := kubectl.NewProxyServer(cmdutil.GetFlagString(cmd, "www"), apiProxyPrefix, staticPrefix, clientConfig) if err != nil { return err } diff --git a/pkg/kubectl/cmd/resize.go b/pkg/kubectl/cmd/resize.go index 227e1b3ca04..b68fb5fd7d7 100644 --- a/pkg/kubectl/cmd/resize.go +++ b/pkg/kubectl/cmd/resize.go @@ -23,7 +23,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait" "github.com/spf13/cobra" ) @@ -45,7 +45,7 @@ $ kubectl resize --current-replicas=2 --replicas=3 replicationcontrollers foo` retryTimeout = 10 * time.Second ) -func (f *Factory) NewCmdResize(out io.Writer) *cobra.Command { +func NewCmdResize(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "resize [--resource-version=version] [--current-replicas=count] --replicas=COUNT RESOURCE ID", Short: "Set a new size for a Replication Controller.", @@ -53,7 +53,7 @@ func (f *Factory) NewCmdResize(out io.Writer) *cobra.Command { Example: resize_example, Run: func(cmd *cobra.Command, args []string) { err := RunResize(f, out, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } cmd.Flags().String("resource-version", "", "Precondition for resource version. Requires that the current resource version match this value in order to resize.") @@ -62,10 +62,10 @@ func (f *Factory) NewCmdResize(out io.Writer) *cobra.Command { return cmd } -func RunResize(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error { - count := util.GetFlagInt(cmd, "replicas") +func RunResize(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { + count := cmdutil.GetFlagInt(cmd, "replicas") if len(args) != 2 || count < 0 { - return util.UsageError(cmd, "--replicas=COUNT RESOURCE ID") + return cmdutil.UsageError(cmd, "--replicas=COUNT RESOURCE ID") } cmdNamespace, err := f.DefaultNamespace() @@ -75,7 +75,7 @@ func RunResize(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err mapper, _ := f.Object() // TODO: use resource.Builder instead - mapping, namespace, name, err := util.ResourceFromArgs(cmd, args, mapper, cmdNamespace) + mapping, namespace, name, err := cmdutil.ResourceFromArgs(cmd, args, mapper, cmdNamespace) if err != nil { return err } @@ -85,8 +85,8 @@ func RunResize(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err return err } - resourceVersion := util.GetFlagString(cmd, "resource-version") - currentSize := util.GetFlagInt(cmd, "current-replicas") + resourceVersion := cmdutil.GetFlagString(cmd, "resource-version") + currentSize := cmdutil.GetFlagInt(cmd, "current-replicas") precondition := &kubectl.ResizePrecondition{currentSize, resourceVersion} cond := kubectl.ResizeCondition(resizer, precondition, namespace, name, uint(count)) diff --git a/pkg/kubectl/cmd/rollingupdate.go b/pkg/kubectl/cmd/rollingupdate.go index d1fa4480b94..7b81a729585 100644 --- a/pkg/kubectl/cmd/rollingupdate.go +++ b/pkg/kubectl/cmd/rollingupdate.go @@ -23,7 +23,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/spf13/cobra" ) @@ -44,7 +44,7 @@ $ kubectl rolling-update frontend-v1 -f frontend-v2.json $ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -` ) -func (f *Factory) NewCmdRollingUpdate(out io.Writer) *cobra.Command { +func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "rolling-update OLD_CONTROLLER_NAME -f NEW_CONTROLLER_SPEC", // rollingupdate is deprecated. @@ -54,7 +54,7 @@ func (f *Factory) NewCmdRollingUpdate(out io.Writer) *cobra.Command { Example: rollingUpdate_example, Run: func(cmd *cobra.Command, args []string) { err := RunRollingUpdate(f, out, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } cmd.Flags().String("update-period", updatePeriod, `Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`) @@ -64,20 +64,20 @@ func (f *Factory) NewCmdRollingUpdate(out io.Writer) *cobra.Command { return cmd } -func RunRollingUpdate(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error { +func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { if os.Args[1] == "rollingupdate" { printDeprecationWarning("rolling-update", "rollingupdate") } - filename := util.GetFlagString(cmd, "filename") + filename := cmdutil.GetFlagString(cmd, "filename") if len(filename) == 0 { - return util.UsageError(cmd, "Must specify filename for new controller") + return cmdutil.UsageError(cmd, "Must specify filename for new controller") } - period := util.GetFlagDuration(cmd, "update-period") - interval := util.GetFlagDuration(cmd, "poll-interval") - timeout := util.GetFlagDuration(cmd, "timeout") + period := cmdutil.GetFlagDuration(cmd, "update-period") + interval := cmdutil.GetFlagDuration(cmd, "poll-interval") + timeout := cmdutil.GetFlagDuration(cmd, "timeout") if len(args) != 1 { - return util.UsageError(cmd, "Must specify the controller to update") + return cmdutil.UsageError(cmd, "Must specify the controller to update") } oldName := args[0] @@ -98,11 +98,11 @@ func RunRollingUpdate(f *Factory, out io.Writer, cmd *cobra.Command, args []stri } newRc, ok := obj.(*api.ReplicationController) if !ok { - return util.UsageError(cmd, "%s does not specify a valid ReplicationController", filename) + return cmdutil.UsageError(cmd, "%s does not specify a valid ReplicationController", filename) } newName := newRc.Name if oldName == newName { - return util.UsageError(cmd, "%s cannot have the same name as the existing ReplicationController %s", + return cmdutil.UsageError(cmd, "%s cannot have the same name as the existing ReplicationController %s", filename, oldName) } @@ -127,7 +127,7 @@ func RunRollingUpdate(f *Factory, out io.Writer, cmd *cobra.Command, args []stri } } if !hasLabel { - return util.UsageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s", + return cmdutil.UsageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s", filename, oldName) } // TODO: handle resizes during rolling update diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index 567b481f720..9ca8ff75925 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -22,7 +22,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/spf13/cobra" ) @@ -42,7 +42,7 @@ $ kubectl run-container nginx --image=dockerfile/nginx --dry-run $ kubectl run-container nginx --image=dockerfile/nginx --overrides='{ "apiVersion": "v1beta1", "desiredState": { ... } }'` ) -func (f *Factory) NewCmdRunContainer(out io.Writer) *cobra.Command { +func NewCmdRunContainer(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "run-container NAME --image=image [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json]", Short: "Run a particular image on the cluster.", @@ -50,10 +50,10 @@ func (f *Factory) NewCmdRunContainer(out io.Writer) *cobra.Command { Example: run_example, Run: func(cmd *cobra.Command, args []string) { err := RunRunContainer(f, out, cmd, args) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } - util.AddPrinterFlags(cmd) + cmdutil.AddPrinterFlags(cmd) cmd.Flags().String("generator", "run-container/v1", "The name of the API generator to use. Default is 'run-container-controller/v1'.") cmd.Flags().String("image", "", "The image for the container to run.") cmd.Flags().IntP("replicas", "r", 1, "Number of replicas to create for this container. Default is 1.") @@ -64,9 +64,9 @@ func (f *Factory) NewCmdRunContainer(out io.Writer) *cobra.Command { return cmd } -func RunRunContainer(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error { +func RunRunContainer(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { if len(args) != 1 { - return util.UsageError(cmd, "NAME is required for run-container") + return cmdutil.UsageError(cmd, "NAME is required for run-container") } namespace, err := f.DefaultNamespace() @@ -79,10 +79,10 @@ func RunRunContainer(f *Factory, out io.Writer, cmd *cobra.Command, args []strin return err } - generatorName := util.GetFlagString(cmd, "generator") + generatorName := cmdutil.GetFlagString(cmd, "generator") generator, found := kubectl.Generators[generatorName] if !found { - return util.UsageError(cmd, fmt.Sprintf("Generator: %s not found.", generator)) + return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not found.", generator)) } names := generator.ParamNames() params := kubectl.MakeParams(cmd, names) @@ -98,16 +98,16 @@ func RunRunContainer(f *Factory, out io.Writer, cmd *cobra.Command, args []strin return err } - inline := util.GetFlagString(cmd, "overrides") + inline := cmdutil.GetFlagString(cmd, "overrides") if len(inline) > 0 { - controller, err = util.Merge(controller, inline, "ReplicationController") + controller, err = cmdutil.Merge(controller, inline, "ReplicationController") if err != nil { return err } } // TODO: extract this flag to a central location, when such a location exists. - if !util.GetFlagBool(cmd, "dry-run") { + if !cmdutil.GetFlagBool(cmd, "dry-run") { controller, err = client.ReplicationControllers(namespace).Create(controller.(*api.ReplicationController)) if err != nil { return err diff --git a/pkg/kubectl/cmd/stop.go b/pkg/kubectl/cmd/stop.go index fa7c6fc00ed..40dcc460e28 100644 --- a/pkg/kubectl/cmd/stop.go +++ b/pkg/kubectl/cmd/stop.go @@ -44,7 +44,7 @@ $ kubectl stop -f service.json $ kubectl stop -f path/to/resources` ) -func (f *Factory) NewCmdStop(out io.Writer) *cobra.Command { +func NewCmdStop(f *cmdutil.Factory, out io.Writer) *cobra.Command { flags := &struct { Filenames util.StringList }{} diff --git a/pkg/kubectl/cmd/update.go b/pkg/kubectl/cmd/update.go index 8a676512528..cbca1e6f3c0 100644 --- a/pkg/kubectl/cmd/update.go +++ b/pkg/kubectl/cmd/update.go @@ -40,7 +40,7 @@ $ cat pod.json | kubectl update -f - $ kubectl update pods my-pod --patch='{ "apiVersion": "v1beta1", "desiredState": { "manifest": [{ "cpu": 100 }]}}'` ) -func (f *Factory) NewCmdUpdate(out io.Writer) *cobra.Command { +func NewCmdUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command { var filenames util.StringList cmd := &cobra.Command{ Use: "update -f FILENAME", @@ -57,7 +57,7 @@ func (f *Factory) NewCmdUpdate(out io.Writer) *cobra.Command { return cmd } -func RunUpdate(f *Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error { +func RunUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error { schema, err := f.Validator() if err != nil { return err @@ -117,7 +117,7 @@ func RunUpdate(f *Factory, out io.Writer, cmd *cobra.Command, args []string, fil } -func updateWithPatch(cmd *cobra.Command, args []string, f *Factory, patch string) (string, error) { +func updateWithPatch(cmd *cobra.Command, args []string, f *cmdutil.Factory, patch string) (string, error) { cmdNamespace, err := f.DefaultNamespace() if err != nil { return "", err diff --git a/pkg/kubectl/cmd/update_test.go b/pkg/kubectl/cmd/update_test.go index 5f3a4b059ac..000db5d505a 100644 --- a/pkg/kubectl/cmd/update_test.go +++ b/pkg/kubectl/cmd/update_test.go @@ -47,7 +47,7 @@ func TestUpdateObject(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdUpdate(buf) + cmd := NewCmdUpdate(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.json") cmd.Run(cmd, []string{}) @@ -83,7 +83,7 @@ func TestUpdateMultipleObject(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdUpdate(buf) + cmd := NewCmdUpdate(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.json") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json") cmd.Run(cmd, []string{}) @@ -115,7 +115,7 @@ func TestUpdateDirectory(t *testing.T) { tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := f.NewCmdUpdate(buf) + cmd := NewCmdUpdate(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook") cmd.Flags().Set("namespace", "test") cmd.Run(cmd, []string{}) diff --git a/pkg/kubectl/cmd/util/clientcache.go b/pkg/kubectl/cmd/util/clientcache.go new file mode 100644 index 00000000000..56a3f785174 --- /dev/null +++ b/pkg/kubectl/cmd/util/clientcache.go @@ -0,0 +1,76 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" +) + +// clientCache caches previously loaded clients for reuse, and ensures MatchServerVersion +// is invoked only once +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: have a better config copy method + config := *c.defaultConfig + if len(version) != 0 { + config.Version = version + } + client.SetKubernetesDefaults(&config) + + 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 { + return nil, err + } + + if client, ok := c.clients[config.Version]; ok { + return client, nil + } + + client, err := client.New(config) + if err != nil { + return nil, err + } + + c.clients[config.Version] = client + return client, nil +} diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go new file mode 100644 index 00000000000..2b5784397e1 --- /dev/null +++ b/pkg/kubectl/cmd/util/factory.go @@ -0,0 +1,391 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "io" + "os" + "strconv" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +const ( + FlagMatchBinaryVersion = "match-server-version" +) + +// Factory provides abstractions that allow the Kubectl command to be extended across multiple types +// of resources and different API sets. +// TODO: make the functions interfaces +// TODO: pass the various interfaces on the factory directly into the command constructors (so the +// commands are decoupled from the factory). +type Factory struct { + clients *clientCache + flags *pflag.FlagSet + + // Returns interfaces for dealing with arbitrary runtime.Objects. + Object func() (meta.RESTMapper, runtime.ObjectTyper) + // Returns a client for accessing Kubernetes resources or an error. + Client func() (*client.Client, 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 + // for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer. + RESTClient func(mapping *meta.RESTMapping) (resource.RESTClient, error) + // Returns a Describer for displaying the specified RESTMapping type or an error. + Describer func(mapping *meta.RESTMapping) (kubectl.Describer, error) + // Returns a Printer for formatting objects of the given type or an error. + Printer func(mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) + // Returns a Resizer for changing the size of the specified RESTMapping type or an error + Resizer func(mapping *meta.RESTMapping) (kubectl.Resizer, error) + // Returns a Reaper for gracefully shutting down resources. + Reaper func(mapping *meta.RESTMapping) (kubectl.Reaper, error) + // PodSelectorForResource returns the pod selector associated with the provided resource name + // or an error. + PodSelectorForResource func(mapping *meta.RESTMapping, namespace, name string) (string, error) + // PortForResource returns the ports associated with the provided resource name or an error + PortsForResource func(mapping *meta.RESTMapping, namespace, name string) ([]string, error) + // Returns a schema that can validate objects stored on disk. + Validator func() (validation.Schema, error) + // Returns the default namespace to use in cases where no other namespace is specified + DefaultNamespace func() (string, error) +} + +// 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. +func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { + mapper := kubectl.ShortcutExpander{latest.RESTMapper} + + flags := pflag.NewFlagSet("", pflag.ContinueOnError) + + clientConfig := optionalClientConfig + if optionalClientConfig == nil { + clientConfig = DefaultClientConfig(flags) + } + + clients := &clientCache{ + clients: make(map[string]*client.Client), + loader: clientConfig, + } + + return &Factory{ + clients: clients, + flags: flags, + + Object: func() (meta.RESTMapper, runtime.ObjectTyper) { + cfg, err := clientConfig.ClientConfig() + CheckErr(err) + cmdApiVersion := cfg.Version + + return kubectl.OutputVersionMapper{mapper, cmdApiVersion}, api.Scheme + }, + Client: func() (*client.Client, error) { + return clients.ClientForVersion("") + }, + ClientConfig: func() (*client.Config, error) { + return clients.ClientConfigForVersion("") + }, + RESTClient: func(mapping *meta.RESTMapping) (resource.RESTClient, error) { + client, err := clients.ClientForVersion(mapping.APIVersion) + if err != nil { + return nil, err + } + return client.RESTClient, nil + }, + Describer: func(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(mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) { + return kubectl.NewHumanReadablePrinter(noHeaders), nil + }, + PodSelectorForResource: func(mapping *meta.RESTMapping, namespace, name string) (string, error) { + // TODO: replace with a swagger schema based approach (identify pod selector via schema introspection) + client, err := clients.ClientForVersion("") + if err != nil { + return "", err + } + switch mapping.Kind { + case "ReplicationController": + rc, err := client.ReplicationControllers(namespace).Get(name) + if err != nil { + return "", err + } + return kubectl.MakeLabels(rc.Spec.Selector), nil + case "Pod": + rc, err := client.Pods(namespace).Get(name) + if err != nil { + return "", err + } + if len(rc.Labels) == 0 { + return "", fmt.Errorf("the pod has no labels and cannot be exposed") + } + return kubectl.MakeLabels(rc.Labels), nil + case "Service": + rc, err := client.ReplicationControllers(namespace).Get(name) + if err != nil { + return "", err + } + if rc.Spec.Selector == nil { + return "", fmt.Errorf("the service has no pod selector set") + } + return kubectl.MakeLabels(rc.Spec.Selector), nil + default: + return "", fmt.Errorf("it is not possible to get a pod selector from %s", mapping.Kind) + } + }, + PortsForResource: func(mapping *meta.RESTMapping, namespace, name string) ([]string, error) { + // TODO: replace with a swagger schema based approach (identify pod selector via schema introspection) + client, err := clients.ClientForVersion("") + if err != nil { + return nil, err + } + switch mapping.Kind { + case "ReplicationController": + rc, err := client.ReplicationControllers(namespace).Get(name) + if err != nil { + return nil, err + } + return getPorts(rc.Spec.Template.Spec), nil + case "Pod": + pod, err := client.Pods(namespace).Get(name) + if err != nil { + return nil, err + } + return getPorts(pod.Spec), nil + default: + return nil, fmt.Errorf("it is not possible to get ports from %s", mapping.Kind) + } + }, + Resizer: func(mapping *meta.RESTMapping) (kubectl.Resizer, error) { + client, err := clients.ClientForVersion(mapping.APIVersion) + if err != nil { + return nil, err + } + return kubectl.ResizerFor(mapping.Kind, client) + }, + Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { + client, err := clients.ClientForVersion(mapping.APIVersion) + if err != nil { + return nil, err + } + return kubectl.ReaperFor(mapping.Kind, client) + }, + Validator: func() (validation.Schema, error) { + if flags.Lookup("validate").Value.String() == "true" { + client, err := clients.ClientForVersion("") + if err != nil { + return nil, err + } + return &clientSwaggerSchema{client, api.Scheme}, nil + } + return validation.NullSchema{}, nil + }, + DefaultNamespace: func() (string, error) { + return clientConfig.Namespace() + }, + } +} + +// 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) + + // This is necessary as github.com/spf13/cobra doesn't support "global" + // pflags currently. See https://github.com/spf13/cobra/issues/44. + util.AddPFlagSetToPFlagSet(pflag.CommandLine, flags) + + // Hack for global access to validation flag. + // TODO: Refactor out after configuration flag overhaul. + if f.flags.Lookup("validate") == nil { + f.flags.Bool("validate", false, "If true, use a schema to validate the input before sending it") + } + + 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") +} + +func getPorts(spec api.PodSpec) []string { + result := []string{} + for _, container := range spec.Containers { + for _, port := range container.Ports { + result = append(result, strconv.Itoa(port.ContainerPort)) + } + } + return result +} + +type clientSwaggerSchema struct { + c *client.Client + t runtime.ObjectTyper +} + +func (c *clientSwaggerSchema) ValidateBytes(data []byte) error { + version, _, err := c.t.DataVersionAndKind(data) + if err != nil { + return err + } + schemaData, err := c.c.RESTClient.Get(). + AbsPath("/swaggerapi/api", version). + Do(). + Raw() + if err != nil { + return err + } + schema, err := validation.NewSwaggerSchemaFromBytes(schemaData) + if err != nil { + return err + } + return schema.ValidateBytes(data) +} + +// 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 and merge rules: +// 1. CommandLineLocation - this parsed from the command line, so it must be late bound +// 2. EnvVarLocation +// 3. CurrentDirectoryLocation +// 4. HomeDirectoryLocation +// Empty filenames are ignored. Files with non-deserializable content produced errors. +// The first file to set a particular value or map key wins and the value or map key is never changed. +// This means that the first file to set CurrentContext will have its context preserved. It also means +// that if two files specify a "red-user", only values from the first file's red-user are used. Even +// non-conflicting entries from the second file's "red-user" are discarded. +// 2. Determine the context to use based on the first hit in this chain +// 1. command line argument - again, parsed from the command line, so it must be late bound +// 2. CurrentContext from the merged kubeconfig file +// 3. Empty is allowed at this stage +// 3. Determine the cluster info and auth info to use. At this point, we may or may not have a context. They +// are built based on the first hit in this chain. (run it twice, once for auth, once for cluster) +// 1. command line argument +// 2. If context is present, then use the context value +// 3. Empty is allowed +// 4. Determine the actual cluster info to use. At this point, we may or may not have a cluster info. Build +// each piece of the cluster info based on the chain: +// 1. command line argument +// 2. If cluster info is present and a value for the attribute is present, use it. +// 3. If you don't have a server location, bail. +// 5. Auth info is build using the same rules as cluster info, EXCEPT that you can only have one authentication +// technique per auth info. The following conditions result in an error: +// 1. If there are two conflicting techniques specified from the command line, 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. +// 2. Use default values and potentially prompt for auth information +func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + flags.StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") + + overrides := &clientcmd.ConfigOverrides{} + flagNames := clientcmd.RecommendedConfigOverrideFlags("") + // short flagnames are disabled by default. These are here for compatibility with existing scripts + flagNames.AuthOverrideFlags.AuthPathShort = "a" + flagNames.ClusterOverrideFlags.APIServerShort = "s" + + clientcmd.BindOverrideFlags(overrides, flags, flagNames) + clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) + + return 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() + _, kind, err := api.Scheme.ObjectVersionAndKind(obj) + if err != nil { + return err + } + + mapping, err := mapper.RESTMapping(kind) + if err != nil { + return err + } + + printer, err := f.PrinterForMapping(cmd, mapping) + if err != nil { + return err + } + return printer.PrintObj(obj, out) +} + +// PrinterForMapping returns a printer suitable for displaying the provided resource type. +// Requires that printer flags have been added to cmd (see AddPrinterFlags). +func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.ResourcePrinter, error) { + printer, ok, err := PrinterForCommand(cmd) + if err != nil { + return nil, err + } + if ok { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + defaultVersion := clientConfig.Version + + version := OutputVersion(cmd, defaultVersion) + if len(version) == 0 { + version = mapping.APIVersion + } + if len(version) == 0 { + return nil, fmt.Errorf("you must specify an output-version when using this output format") + } + printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, version) + } else { + printer, err = f.Printer(mapping, GetFlagBool(cmd, "no-headers")) + if err != nil { + return nil, err + } + } + return printer, nil +} + +// ClientMapperForCommand returns a ClientMapper for the factory. +func (f *Factory) ClientMapperForCommand() resource.ClientMapper { + return resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) { + return f.RESTClient(mapping) + }) +} diff --git a/pkg/kubectl/cmd/factory_test.go b/pkg/kubectl/cmd/util/factory_test.go similarity index 98% rename from pkg/kubectl/cmd/factory_test.go rename to pkg/kubectl/cmd/util/factory_test.go index d3a9efc4176..fe620609b65 100644 --- a/pkg/kubectl/cmd/factory_test.go +++ b/pkg/kubectl/cmd/util/factory_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cmd +package util import ( "testing" diff --git a/pkg/kubectl/cmd/version.go b/pkg/kubectl/cmd/version.go index de890f3e77d..9fc5ece5a5c 100644 --- a/pkg/kubectl/cmd/version.go +++ b/pkg/kubectl/cmd/version.go @@ -22,24 +22,24 @@ import ( "github.com/spf13/cobra" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" - "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" + cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" ) -func (f *Factory) NewCmdVersion(out io.Writer) *cobra.Command { +func NewCmdVersion(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Print the client and server version information.", Run: func(cmd *cobra.Command, args []string) { err := RunVersion(f, out, cmd) - util.CheckErr(err) + cmdutil.CheckErr(err) }, } cmd.Flags().BoolP("client", "c", false, "Client version only (no server required).") return cmd } -func RunVersion(f *Factory, out io.Writer, cmd *cobra.Command) error { - if util.GetFlagBool(cmd, "client") { +func RunVersion(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { + if cmdutil.GetFlagBool(cmd, "client") { kubectl.GetClientVersion(out) return nil }