command Factory should provide Printers

The factory knows all possible types, and should abstract the process of
creating all printers. A future refactor can further reduce the
dependencies between printer code and internal types.
This commit is contained in:
Clayton Coleman 2017-02-19 17:39:43 -05:00
parent 90fdd067e9
commit 19ae89dcd8
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
7 changed files with 114 additions and 75 deletions

View File

@ -41,6 +41,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers"
)
type InternalType struct {
@ -214,13 +215,15 @@ type TestFactory struct {
Typer runtime.ObjectTyper
Client kubectl.RESTClient
UnstructuredClient kubectl.RESTClient
Describer kubectl.Describer
Printer kubectl.ResourcePrinter
Describer printers.Describer
Printer printers.ResourcePrinter
CommandPrinter printers.ResourcePrinter
Validator validation.Schema
Namespace string
ClientConfig *restclient.Config
Err error
Command string
GenericPrinter bool
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
@ -331,11 +334,15 @@ func (f *FakeFactory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (r
return f.tf.UnstructuredClient, f.tf.Err
}
func (f *FakeFactory) Describer(*meta.RESTMapping) (kubectl.Describer, error) {
func (f *FakeFactory) Describer(*meta.RESTMapping) (printers.Describer, error) {
return f.tf.Describer, f.tf.Err
}
func (f *FakeFactory) Printer(mapping *meta.RESTMapping, options kubectl.PrintOptions) (kubectl.ResourcePrinter, error) {
func (f *FakeFactory) PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error) {
return f.tf.CommandPrinter, f.tf.GenericPrinter, f.tf.Err
}
func (f *FakeFactory) Printer(mapping *meta.RESTMapping, options printers.PrintOptions) (printers.ResourcePrinter, error) {
return f.tf.Printer, f.tf.Err
}
@ -451,7 +458,7 @@ func (f *FakeFactory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, ob
return nil
}
func (f *FakeFactory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (kubectl.ResourcePrinter, error) {
func (f *FakeFactory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) {
return f.tf.Printer, f.tf.Err
}
@ -587,11 +594,15 @@ func (f *fakeAPIFactory) UnstructuredClientForMapping(m *meta.RESTMapping) (reso
return f.tf.UnstructuredClient, f.tf.Err
}
func (f *fakeAPIFactory) Describer(*meta.RESTMapping) (kubectl.Describer, error) {
func (f *fakeAPIFactory) PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error) {
return f.tf.CommandPrinter, f.tf.GenericPrinter, f.tf.Err
}
func (f *fakeAPIFactory) Describer(*meta.RESTMapping) (printers.Describer, error) {
return f.tf.Describer, f.tf.Err
}
func (f *fakeAPIFactory) Printer(mapping *meta.RESTMapping, options kubectl.PrintOptions) (kubectl.ResourcePrinter, error) {
func (f *fakeAPIFactory) Printer(mapping *meta.RESTMapping, options printers.PrintOptions) (printers.ResourcePrinter, error) {
return f.tf.Printer, f.tf.Err
}
@ -664,7 +675,7 @@ func (f *fakeAPIFactory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper,
return printer.PrintObj(obj, out)
}
func (f *fakeAPIFactory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (kubectl.ResourcePrinter, error) {
func (f *fakeAPIFactory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) {
return f.tf.Printer, f.tf.Err
}

View File

@ -51,6 +51,7 @@ import (
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers"
)
const (
@ -136,7 +137,8 @@ type ClientAccessFactory interface {
// BindExternalFlags adds any flags defined by external projects (not part of pflags)
BindExternalFlags(flags *pflag.FlagSet)
DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *kubectl.PrintOptions
// TODO: Break the dependency on cmd here.
DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *printers.PrintOptions
// DefaultResourceFilterFunc returns a collection of FilterFuncs suitable for filtering specific resource types.
DefaultResourceFilterFunc() kubectl.Filters
@ -144,7 +146,7 @@ type ClientAccessFactory interface {
SuggestedPodTemplateResources() []schema.GroupResource
// Returns a Printer for formatting objects of the given type or an error.
Printer(mapping *meta.RESTMapping, options kubectl.PrintOptions) (kubectl.ResourcePrinter, error)
Printer(mapping *meta.RESTMapping, options printers.PrintOptions) (printers.ResourcePrinter, error)
// Pauser marks the object in the info as paused. Currently supported only for Deployments.
// Returns the patched object in bytes and any error that occured during the encoding or
// in case the object is already paused.
@ -193,7 +195,7 @@ type ObjectMappingFactory interface {
// Returns a RESTClient for working with Unstructured objects.
UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error)
// Returns a Describer for displaying the specified RESTMapping type or an error.
Describer(mapping *meta.RESTMapping) (kubectl.Describer, error)
Describer(mapping *meta.RESTMapping) (printers.Describer, error)
// LogsForObject returns a request for the logs associated with the provided object
LogsForObject(object, options runtime.Object) (*restclient.Request, error)
@ -211,10 +213,6 @@ type ObjectMappingFactory interface {
// AttachablePodForObject returns the pod to which to attach given an object.
AttachablePodForObject(object runtime.Object) (*api.Pod, error)
// PrinterForMapping returns a printer suitable for displaying the provided resource type.
// Requires that printer flags have been added to cmd (see AddPrinterFlags).
PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (kubectl.ResourcePrinter, error)
// Returns a schema that can validate objects stored on disk.
Validator(validate bool, cacheDir string) (validation.Schema, error)
// SwaggerSchema returns the schema declaration for the provided group version kind.
@ -224,6 +222,14 @@ type ObjectMappingFactory interface {
// BuilderFactory holds the second level of factory methods. These functions depend upon ObjectMappingFactory and ClientAccessFactory methods.
// Generally they depend upon client mapper functions
type BuilderFactory interface {
// PrinterForCommand returns the default printer for the command. It requires that certain options
// are declared on the command (see AddPrinterFlags). Returns a printer, true if the printer is
// generic (is not internal), or an error if a printer could not be found.
// TODO: Break the dependency on cmd here.
PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error)
// PrinterForMapping returns a printer suitable for displaying the provided resource type.
// Requires that printer flags have been added to cmd (see AddPrinterFlags).
PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error)
// PrintObject prints an api object given command line flags to modify the output format
PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error
// One stop shopping for a Builder

View File

@ -19,13 +19,16 @@ limitations under the License.
package util
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers"
)
type ring2Factory struct {
@ -42,6 +45,54 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac
return f
}
func (f *ring2Factory) PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error) {
mapper, typer := f.objectMappingFactory.Object()
// TODO: used by the custom column implementation and the name implementation, break this dependency
decoders := []runtime.Decoder{f.clientAccessFactory.Decoder(true), unstructured.UnstructuredJSONScheme}
return PrinterForCommand(cmd, mapper, typer, decoders)
}
func (f *ring2Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) {
printer, generic, err := f.PrinterForCommand(cmd)
if err != nil {
return nil, err
}
// Make sure we output versioned data for generic printers
if generic {
if mapping == nil {
return nil, fmt.Errorf("no serialization format found")
}
version := mapping.GroupVersionKind.GroupVersion()
if version.Empty() {
return nil, fmt.Errorf("no serialization format found")
}
printer = printers.NewVersionedPrinter(printer, mapping.ObjectConvertor, version, mapping.GroupVersionKind.GroupVersion())
} else {
// Some callers do not have "label-columns" so we can't use the GetFlagStringSlice() helper
columnLabel, err := cmd.Flags().GetStringSlice("label-columns")
if err != nil {
columnLabel = []string{}
}
printer, err = f.clientAccessFactory.Printer(mapping, printers.PrintOptions{
NoHeaders: GetFlagBool(cmd, "no-headers"),
WithNamespace: withNamespace,
Wide: GetWideFlag(cmd),
ShowAll: GetFlagBool(cmd, "show-all"),
ShowLabels: GetFlagBool(cmd, "show-labels"),
AbsoluteTimestamps: isWatch(cmd),
ColumnLabels: columnLabel,
})
if err != nil {
return nil, err
}
printer = maybeWrapSortingPrinter(cmd, printer)
}
return printer, nil
}
func (f *ring2Factory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error {
// try to get a typed object
_, typer := f.objectMappingFactory.Object()
@ -66,7 +117,7 @@ func (f *ring2Factory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, o
return err
}
printer, err := f.objectMappingFactory.PrinterForMapping(cmd, mapping, false)
printer, err := f.PrinterForMapping(cmd, mapping, false)
if err != nil {
return err
}

View File

@ -49,6 +49,8 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
)
type ring0Factory struct {
@ -360,12 +362,12 @@ func (f *ring0Factory) BindExternalFlags(flags *pflag.FlagSet) {
flags.AddGoFlagSet(flag.CommandLine)
}
func (f *ring0Factory) DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *kubectl.PrintOptions {
func (f *ring0Factory) DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *printers.PrintOptions {
columnLabel, err := cmd.Flags().GetStringSlice("label-columns")
if err != nil {
columnLabel = []string{}
}
opts := &kubectl.PrintOptions{
opts := &printers.PrintOptions{
NoHeaders: GetFlagBool(cmd, "no-headers"),
WithNamespace: withNamespace,
Wide: GetWideFlag(cmd),
@ -392,8 +394,10 @@ func (f *ring0Factory) SuggestedPodTemplateResources() []schema.GroupResource {
}
}
func (f *ring0Factory) Printer(mapping *meta.RESTMapping, options kubectl.PrintOptions) (kubectl.ResourcePrinter, error) {
return kubectl.NewHumanReadablePrinter(options), nil
func (f *ring0Factory) Printer(mapping *meta.RESTMapping, options printers.PrintOptions) (printers.ResourcePrinter, error) {
p := printers.NewHumanReadablePrinter(options)
printersinternal.AddHandlers(p)
return p, nil
}
func (f *ring0Factory) Pauser(info *resource.Info) ([]byte, error) {

View File

@ -27,7 +27,6 @@ import (
"time"
"github.com/emicklei/go-restful/swagger"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -48,6 +47,8 @@ import (
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
)
type ring1Factory struct {
@ -58,10 +59,12 @@ func NewObjectMappingFactory(clientAccessFactory ClientAccessFactory) ObjectMapp
f := &ring1Factory{
clientAccessFactory: clientAccessFactory,
}
return f
}
// TODO: This method should return an error now that it can fail. Alternatively, it needs to
// return lazy implementations of mapper and typer that don't hit the wire until they are
// invoked.
func (f *ring1Factory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
mapper := api.Registry.RESTMapper()
discoveryClient, err := f.clientAccessFactory.DiscoveryClient()
@ -143,7 +146,7 @@ func (f *ring1Factory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (
return restclient.RESTClientFor(cfg)
}
func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (kubectl.Describer, error) {
func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (printers.Describer, error) {
mappingVersion := mapping.GroupVersionKind.GroupVersion()
if mapping.GroupVersionKind.Group == federation.GroupName {
fedClientSet, err := f.clientAccessFactory.FederationClientSetForVersion(&mappingVersion)
@ -151,7 +154,7 @@ func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (kubectl.Describer,
return nil, err
}
if mapping.GroupVersionKind.Kind == "Cluster" {
return &kubectl.ClusterDescriber{Interface: fedClientSet}, nil
return &printersinternal.ClusterDescriber{Interface: fedClientSet}, nil
}
}
@ -166,7 +169,7 @@ func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (kubectl.Describer,
}
// try to get a describer
if describer, ok := kubectl.DescriberFor(mapping.GroupVersionKind.GroupKind(), clientset); ok {
if describer, ok := printersinternal.DescriberFor(mapping.GroupVersionKind.GroupKind(), clientset); ok {
return describer, nil
}
// if this is a kind we don't have a describer for yet, go generic if possible
@ -178,7 +181,7 @@ func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (kubectl.Describer,
}
// helper function to make a generic describer, or return an error
func genericDescriber(clientAccessFactory ClientAccessFactory, mapping *meta.RESTMapping) (kubectl.Describer, error) {
func genericDescriber(clientAccessFactory ClientAccessFactory, mapping *meta.RESTMapping) (printers.Describer, error) {
clientConfig, err := clientAccessFactory.ClientConfig()
if err != nil {
return nil, err
@ -202,7 +205,7 @@ func genericDescriber(clientAccessFactory ClientAccessFactory, mapping *meta.RES
}
eventsClient := clientSet.Core()
return kubectl.GenericDescriberFor(mapping, dynamicClient, eventsClient), nil
return printersinternal.GenericDescriberFor(mapping, dynamicClient, eventsClient), nil
}
func (f *ring1Factory) LogsForObject(object, options runtime.Object) (*restclient.Request, error) {
@ -361,48 +364,6 @@ func (f *ring1Factory) AttachablePodForObject(object runtime.Object) (*api.Pod,
return pod, err
}
func (f *ring1Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (kubectl.ResourcePrinter, error) {
printer, generic, err := PrinterForCommand(cmd)
if err != nil {
return nil, err
}
// Make sure we output versioned data for generic printers
if generic {
if mapping == nil {
return nil, fmt.Errorf("no serialization format found")
}
version := mapping.GroupVersionKind.GroupVersion()
if version.Empty() {
return nil, fmt.Errorf("no serialization format found")
}
printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, version, mapping.GroupVersionKind.GroupVersion())
} else {
// Some callers do not have "label-columns" so we can't use the GetFlagStringSlice() helper
columnLabel, err := cmd.Flags().GetStringSlice("label-columns")
if err != nil {
columnLabel = []string{}
}
printer, err = f.clientAccessFactory.Printer(mapping, kubectl.PrintOptions{
NoHeaders: GetFlagBool(cmd, "no-headers"),
WithNamespace: withNamespace,
Wide: GetWideFlag(cmd),
ShowAll: GetFlagBool(cmd, "show-all"),
ShowLabels: GetFlagBool(cmd, "show-labels"),
AbsoluteTimestamps: isWatch(cmd),
ColumnLabels: columnLabel,
})
if err != nil {
return nil, err
}
printer = maybeWrapSortingPrinter(cmd, printer)
}
return printer, nil
}
func (f *ring1Factory) Validator(validate bool, cacheDir string) (validation.Schema, error) {
if validate {
discovery, err := f.clientAccessFactory.DiscoveryClient()

View File

@ -47,6 +47,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers"
utilexec "k8s.io/kubernetes/pkg/util/exec"
)
@ -672,7 +673,7 @@ func MustPrintWithKinds(objs []runtime.Object, infos []*resource.Info, sorter *k
// FilterResourceList receives a list of runtime objects.
// If any objects are filtered, that number is returned along with a modified list.
func FilterResourceList(obj runtime.Object, filterFuncs kubectl.Filters, filterOpts *kubectl.PrintOptions) (int, []runtime.Object, error) {
func FilterResourceList(obj runtime.Object, filterFuncs kubectl.Filters, filterOpts *printers.PrintOptions) (int, []runtime.Object, error) {
items, err := meta.ExtractList(obj)
if err != nil {
return 0, []runtime.Object{obj}, utilerrors.NewAggregate([]error{err})
@ -697,7 +698,7 @@ func FilterResourceList(obj runtime.Object, filterFuncs kubectl.Filters, filterO
return filterCount, list, nil
}
func PrintFilterCount(hiddenObjNum int, resource string, options *kubectl.PrintOptions) {
func PrintFilterCount(hiddenObjNum int, resource string, options *printers.PrintOptions) {
if !options.NoHeaders && !options.ShowAll && hiddenObjNum > 0 {
glog.V(2).Infof(" info: %d completed object(s) was(were) not shown in %s list. Pass --show-all to see all objects.\n\n", hiddenObjNum, resource)
}

View File

@ -22,8 +22,10 @@ import (
"strings"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers"
"github.com/spf13/cobra"
)
@ -105,7 +107,7 @@ func ValidateOutputArgs(cmd *cobra.Command) error {
// PrinterForCommand returns the default printer for this command.
// Requires that printer flags have been added to cmd (see AddPrinterFlags).
func PrinterForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error) {
func PrinterForCommand(cmd *cobra.Command, mapper meta.RESTMapper, typer runtime.ObjectTyper, decoders []runtime.Decoder) (printers.ResourcePrinter, bool, error) {
outputFormat := GetFlagString(cmd, "output")
// templates are logically optional for specifying a format.
@ -131,7 +133,10 @@ func PrinterForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error
if cmd.Flags().Lookup("allow-missing-template-keys") != nil {
allowMissingTemplateKeys = GetFlagBool(cmd, "allow-missing-template-keys")
}
printer, generic, err := kubectl.GetPrinter(outputFormat, templateFile, GetFlagBool(cmd, "no-headers"), allowMissingTemplateKeys)
printer, generic, err := printers.GetStandardPrinter(
outputFormat, templateFile, GetFlagBool(cmd, "no-headers"), allowMissingTemplateKeys,
mapper, typer, decoders,
)
if err != nil {
return nil, generic, err
}
@ -144,7 +149,7 @@ func PrinterForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error
// object passed is non-generic, it attempts to print the object using a HumanReadablePrinter.
// Requires that printer flags have been added to cmd (see AddPrinterFlags).
func PrintResourceInfoForCommand(cmd *cobra.Command, info *resource.Info, f Factory, out io.Writer) error {
printer, generic, err := PrinterForCommand(cmd)
printer, generic, err := f.PrinterForCommand(cmd)
if err != nil {
return err
}
@ -157,7 +162,7 @@ func PrintResourceInfoForCommand(cmd *cobra.Command, info *resource.Info, f Fact
return printer.PrintObj(info.Object, out)
}
func maybeWrapSortingPrinter(cmd *cobra.Command, printer kubectl.ResourcePrinter) kubectl.ResourcePrinter {
func maybeWrapSortingPrinter(cmd *cobra.Command, printer printers.ResourcePrinter) printers.ResourcePrinter {
sorting, err := cmd.Flags().GetString("sort-by")
if err != nil {
// error can happen on missing flag or bad flag type. In either case, this command didn't intent to sort