Refactor Get and Describe to allow extension of types

Get should use ResourceMapper, allow Printer to be abstracted,
and extract Describe as *Describer types.
This commit is contained in:
Clayton Coleman
2014-10-27 15:56:34 -04:00
parent 39882a3555
commit 09cfa364c5
18 changed files with 753 additions and 339 deletions

View File

@@ -35,13 +35,38 @@ import (
"github.com/spf13/cobra"
)
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types
// of resources and different API sets.
type Factory struct {
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
Client func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error)
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
Client func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error)
Describer func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error)
Printer func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error)
}
func RunKubectl(out io.Writer) {
// NewFactory creates a factory with the default Kubernetes resources defined
func NewFactory() *Factory {
return &Factory{
Mapper: latest.RESTMapper,
Typer: api.Scheme,
Client: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) {
return getKubeClient(cmd), nil
},
Describer: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Describer, error) {
describer, ok := kubectl.DescriberFor(mapping.Kind, getKubeClient(cmd))
if !ok {
return nil, fmt.Errorf("No description has been implemented for %q", mapping.Kind)
}
return describer, nil
},
Printer: func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) {
return kubectl.NewHumanReadablePrinter(noHeaders), nil
},
}
}
func (f *Factory) Run(out io.Writer) {
// Parent command to which all subcommands are added.
cmds := &cobra.Command{
Use: "kubectl",
@@ -52,15 +77,6 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
Run: runHelp,
}
factory := &Factory{
Mapper: latest.NewDefaultRESTMapper(),
Typer: api.Scheme,
Client: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error) {
// Will handle all resources defined by the command
return getKubeClient(cmd), nil
},
}
// 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
@@ -78,12 +94,12 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
cmds.AddCommand(NewCmdVersion(out))
cmds.AddCommand(NewCmdProxy(out))
cmds.AddCommand(NewCmdGet(out))
cmds.AddCommand(NewCmdDescribe(out))
cmds.AddCommand(factory.NewCmdCreate(out))
cmds.AddCommand(factory.NewCmdUpdate(out))
cmds.AddCommand(factory.NewCmdDelete(out))
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(NewCmdNamespace(out))
cmds.AddCommand(NewCmdLog(out))

View File

@@ -47,7 +47,7 @@ Examples:
client, err := f.Client(cmd, mapping)
checkErr(err)
err = kubectl.NewRESTModifier(client, mapping).Create(namespace, data)
err = kubectl.NewRESTHelper(client, mapping).Create(namespace, data)
checkErr(err)
fmt.Fprintf(out, "%s\n", name)
},

View File

@@ -54,7 +54,7 @@ Examples:
client, err := f.Client(cmd, mapping)
checkErr(err)
err = kubectl.NewRESTModifier(client, mapping).Delete(namespace, name)
err = kubectl.NewRESTHelper(client, mapping).Delete(namespace, name)
checkErr(err)
fmt.Fprintf(out, "%s\n", name)
},

View File

@@ -17,13 +17,13 @@ limitations under the License.
package cmd
import (
"fmt"
"io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/spf13/cobra"
)
func NewCmdDescribe(out io.Writer) *cobra.Command {
func (f *Factory) NewCmdDescribe(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "describe <resource> <id>",
Short: "Show details of a specific resource",
@@ -32,13 +32,14 @@ func NewCmdDescribe(out io.Writer) *cobra.Command {
This command joins many API calls together to form a detailed description of a
given resource.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 2 {
usageError(cmd, "Need to supply a resource and an ID")
}
resource := args[0]
id := args[1]
err := kubectl.Describe(out, getKubeClient(cmd), resource, id)
mapping, namespace, name := ResourceFromArgs(cmd, args, f.Mapper)
describer, err := f.Describer(cmd, mapping)
checkErr(err)
s, err := describer.Describe(namespace, name)
checkErr(err)
fmt.Fprintf(out, "%s\n", s)
},
}
return cmd

View File

@@ -20,12 +20,13 @@ import (
"io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/spf13/cobra"
)
func NewCmdGet(out io.Writer) *cobra.Command {
func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "get [(-o|--output=)table|json|yaml|template] [-t <file>|--template=<file>] <resource> [<id>]",
Use: "get [(-o|--output=)console|json|yaml|...] <resource> [<id>]",
Short: "Display one or many resources",
Long: `Display one or many resources.
@@ -44,20 +45,24 @@ Examples:
$ kubectl get -f json pod 1234-56-7890-234234-456456
<list single pod in json output format>`,
Run: func(cmd *cobra.Command, args []string) {
var resource, id string
if len(args) == 0 {
usageError(cmd, "Need to supply a resource.")
}
if len(args) >= 1 {
resource = args[0]
}
if len(args) >= 2 {
id = args[1]
}
mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, f.Mapper)
selector := getFlagString(cmd, "selector")
labels, err := labels.ParseSelector(selector)
checkErr(err)
client, err := f.Client(cmd, mapping)
checkErr(err)
obj, err := kubectl.NewRESTHelper(client, mapping).Get(namespace, name, labels)
checkErr(err)
outputFormat := getFlagString(cmd, "output")
templateFile := getFlagString(cmd, "template")
selector := getFlagString(cmd, "selector")
err := kubectl.Get(out, getKubeClient(cmd).RESTClient, getKubeNamespace(cmd), resource, id, selector, outputFormat, getFlagBool(cmd, "no-headers"), templateFile)
defaultPrinter, err := f.Printer(cmd, mapping, getFlagBool(cmd, "no-headers"))
checkErr(err)
err = kubectl.Print(out, obj, outputFormat, templateFile, defaultPrinter)
checkErr(err)
},
}

View File

@@ -21,7 +21,6 @@ import (
"github.com/spf13/cobra"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
@@ -37,7 +36,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
if len(args) == 2 {
resource := args[0]
namespace = api.NamespaceDefault
namespace = getKubeNamespace(cmd)
name = args[1]
if len(name) == 0 || len(resource) == 0 {
usageError(cmd, "Must specify filename or command line params")
@@ -63,6 +62,62 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
return
}
// ResourceFromArgs expects two arguments with a given type, and extracts the fields necessary
// to uniquely locate a resource. Displays a usageError if that contract is not satisfied, or
// a generic error if any other problems occur.
func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string) {
if len(args) != 2 {
usageError(cmd, "Must provide resource and name command line params")
}
resource := args[0]
namespace = getKubeNamespace(cmd)
name = args[1]
if len(name) == 0 || len(resource) == 0 {
usageError(cmd, "Must provide resource and name command line params")
}
version, kind, err := mapper.VersionAndKindForResource(resource)
checkErr(err)
mapping, err = mapper.RESTMapping(version, kind)
checkErr(err)
return
}
// ResourceFromArgs expects two arguments with a given type, and extracts the fields necessary
// to uniquely locate a resource. Displays a usageError if that contract is not satisfied, or
// a generic error if any other problems occur.
func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string) {
if len(args) == 0 || len(args) > 2 {
usageError(cmd, "Must provide resource or a resource and name as command line params")
}
resource := args[0]
if len(resource) == 0 {
usageError(cmd, "Must provide resource or a resource and name as command line params")
}
namespace = getKubeNamespace(cmd)
if len(args) == 2 {
name = args[1]
if len(name) == 0 {
usageError(cmd, "Must provide resource or a resource and name as command line params")
}
}
version, kind, err := mapper.VersionAndKindForResource(resource)
checkErr(err)
mapping, err = mapper.RESTMapping(version, kind)
checkErr(err)
return
}
// ResourceFromFile retrieves the name and namespace from a valid file. If the file does not
// resolve to a known type an error is returned. The returned mapping can be used to determine
// the correct REST endpoint to modify this resource with.
func ResourceFromFile(filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string, data []byte) {
configData, err := readConfigData(filename)
checkErr(err)

View File

@@ -47,7 +47,7 @@ Examples:
client, err := f.Client(cmd, mapping)
checkErr(err)
err = kubectl.NewRESTModifier(client, mapping).Update(namespace, name, true, data)
err = kubectl.NewRESTHelper(client, mapping).Update(namespace, name, true, data)
checkErr(err)
fmt.Fprintf(out, "%s\n", name)
},