Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Describe functions available to local objects #4880

Merged
merged 1 commit into from
Mar 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
241 changes: 221 additions & 20 deletions pkg/kubectl/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package kubectl
import (
"fmt"
"io"
"reflect"
"sort"
"strings"
"time"
Expand All @@ -30,11 +31,32 @@ import (
)

// Describer generates output for the named resource or an error
// if the output could not be generated.
// if the output could not be generated. Implementors typically
// abstract the retrieval of the named object from a remote server.
type Describer interface {
Describe(namespace, name string) (output string, err error)
}

// ObjectDescriber is an interface for displaying arbitrary objects with extra
// information. Use when an object is in hand (on disk, or already retrieved).
// Implementors may ignore the additional information passed on extra, or use it
// by default. ObjectDescribers may return ErrNoDescriber if no suitable describer
// is found.
type ObjectDescriber interface {
DescribeObject(object interface{}, extra ...interface{}) (output string, err error)
}

// ErrNoDescriber is a structured error indicating the provided object or objects
// cannot be described.
type ErrNoDescriber struct {
Types []string
}

// Error implements the error interface.
func (e ErrNoDescriber) Error() string {
return fmt.Sprintf("no describer has been defined for %v", e.Types)
}

// Describer returns the default describe functions for each of the standard
// Kubernetes types.
func DescriberFor(kind string, c *client.Client) (Describer, bool) {
Expand All @@ -55,6 +77,25 @@ func DescriberFor(kind string, c *client.Client) (Describer, bool) {
return nil, false
}

// DefaultObjectDescriber can describe the default Kubernetes objects.
var DefaultObjectDescriber ObjectDescriber

func init() {
d := &Describers{}
err := d.Add(
describeLimitRange,
describeQuota,
describePod,
describeService,
describeReplicationController,
describeNode,
)
if err != nil {
glog.Fatalf("Cannot register describers: %v", err)
}
DefaultObjectDescriber = d
}

// LimitRangeDescriber generates information about a limit range
type LimitRangeDescriber struct {
client.Interface
Expand All @@ -67,7 +108,10 @@ func (d *LimitRangeDescriber) Describe(namespace, name string) (string, error) {
if err != nil {
return "", err
}
return describeLimitRange(limitRange)
}

func describeLimitRange(limitRange *api.LimitRange) (string, error) {
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "Name:\t%s\n", limitRange.Name)
fmt.Fprintf(out, "Type\tResource\tMin\tMax\n")
Expand Down Expand Up @@ -121,6 +165,10 @@ func (d *ResourceQuotaDescriber) Describe(namespace, name string) (string, error
return "", err
}

return describeQuota(resourceQuota)
}

func describeQuota(resourceQuota *api.ResourceQuota) (string, error) {
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "Name:\t%s\n", resourceQuota.Name)
fmt.Fprintf(out, "Resource\tUsed\tHard\n")
Expand Down Expand Up @@ -172,12 +220,6 @@ func (d *PodDescriber) Describe(namespace, name string) (string, error) {
return "", err
}

// TODO: remove me when pods are converted
spec := &api.PodSpec{}
if err := api.Scheme.Convert(&pod.Spec, spec); err != nil {
glog.Errorf("Unable to convert pod manifest: %v", err)
}

var events *api.EventList
if ref, err := api.GetReference(pod); err != nil {
glog.Errorf("Unable to construct reference to '%#v': %v", pod, err)
Expand All @@ -186,13 +228,22 @@ func (d *PodDescriber) Describe(namespace, name string) (string, error) {
events, _ = d.Events(namespace).Search(ref)
}

rcs, err := getReplicationControllersForLabels(rc, labels.Set(pod.Labels))
if err != nil {
return "", err
}

return describePod(pod, rcs, events)
}

func describePod(pod *api.Pod, rcs []api.ReplicationController, events *api.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "Name:\t%s\n", pod.Name)
fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(spec))
fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&pod.Spec))
fmt.Fprintf(out, "Host:\t%s\n", pod.Status.Host+"/"+pod.Status.HostIP)
fmt.Fprintf(out, "Labels:\t%s\n", formatLabels(pod.Labels))
fmt.Fprintf(out, "Status:\t%s\n", string(pod.Status.Phase))
fmt.Fprintf(out, "Replication Controllers:\t%s\n", getReplicationControllersForLabels(rc, labels.Set(pod.Labels)))
fmt.Fprintf(out, "Replication Controllers:\t%s\n", printReplicationControllersByLabels(rcs))
if len(pod.Status.Conditions) > 0 {
fmt.Fprint(out, "Conditions:\n Type\tStatus\n")
for _, c := range pod.Status.Conditions {
Expand Down Expand Up @@ -230,9 +281,17 @@ func (d *ReplicationControllerDescriber) Describe(namespace, name string) (strin

events, _ := d.Events(namespace).Search(controller)

return describeReplicationController(controller, events, running, waiting, succeeded, failed)
}

func describeReplicationController(controller *api.ReplicationController, events *api.EventList, running, waiting, succeeded, failed int) (string, error) {
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "Name:\t%s\n", controller.Name)
fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&controller.Spec.Template.Spec))
if controller.Spec.Template != nil {
fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&controller.Spec.Template.Spec))
} else {
fmt.Fprintf(out, "Image(s):\t%s\n", "<no template>")
}
fmt.Fprintf(out, "Selector:\t%s\n", formatLabels(controller.Spec.Selector))
fmt.Fprintf(out, "Labels:\t%s\n", formatLabels(controller.Labels))
fmt.Fprintf(out, "Replicas:\t%d current / %d desired\n", controller.Status.Replicas, controller.Spec.Replicas)
Expand All @@ -257,13 +316,16 @@ func (d *ServiceDescriber) Describe(namespace, name string) (string, error) {
return "", err
}

endpoints, err := d.Endpoints(namespace).Get(name)
if err != nil {
endpoints = &api.Endpoints{}
}

endpoints, _ := d.Endpoints(namespace).Get(name)
events, _ := d.Events(namespace).Search(service)

return describeService(service, endpoints, events)
}

func describeService(service *api.Service, endpoints *api.Endpoints, events *api.EventList) (string, error) {
if endpoints == nil {
endpoints = &api.Endpoints{}
}
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "Name:\t%s\n", service.Name)
fmt.Fprintf(out, "Labels:\t%s\n", formatLabels(service.Labels))
Expand Down Expand Up @@ -309,6 +371,10 @@ func (d *NodeDescriber) Describe(namespace, name string) (string, error) {

events, _ := d.Events(namespace).Search(node)

return describeNode(node, pods, events)
}

func describeNode(node *api.Node, pods []api.Pod, events *api.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "Name:\t%s\n", node.Name)
if len(node.Status.Conditions) > 0 {
Expand Down Expand Up @@ -342,9 +408,6 @@ func (d *NodeDescriber) Describe(namespace, name string) (string, error) {
}
fmt.Fprintf(out, "Pods:\t(%d in total)\n", len(pods))
for _, pod := range pods {
if pod.Status.Host != name {
continue
}
fmt.Fprintf(out, " %s\n", pod.Name)
}
if events != nil {
Expand Down Expand Up @@ -377,12 +440,12 @@ func describeEvents(el *api.EventList, w io.Writer) {
// labels.
// TODO Move this to pkg/client and ideally implement it server-side (instead
// of getting all RC's and searching through them manually).
func getReplicationControllersForLabels(c client.ReplicationControllerInterface, labelsToMatch labels.Labels) string {
func getReplicationControllersForLabels(c client.ReplicationControllerInterface, labelsToMatch labels.Labels) ([]api.ReplicationController, error) {
// Get all replication controllers.
// TODO this needs a namespace scope as argument
rcs, err := c.List(labels.Everything())
if err != nil {
glog.Fatalf("Error getting replication controllers: %v\n", err)
return nil, fmt.Errorf("error getting replication controllers: %v", err)
}

// Find the ones that match labelsToMatch.
Expand All @@ -393,7 +456,10 @@ func getReplicationControllersForLabels(c client.ReplicationControllerInterface,
matchingRCs = append(matchingRCs, controller)
}
}
return matchingRCs, nil
}

func printReplicationControllersByLabels(matchingRCs []api.ReplicationController) string {
// Format the matching RC's into strings.
var rcStrings []string
for _, controller := range matchingRCs {
Expand Down Expand Up @@ -426,3 +492,138 @@ func getPodStatusForReplicationController(c client.PodInterface, controller *api
}
return
}

// newErrNoDescriber creates a new ErrNoDescriber with the names of the provided types.
func newErrNoDescriber(types ...reflect.Type) error {
names := []string{}
for _, t := range types {
names = append(names, t.String())
}
return ErrNoDescriber{Types: names}
}

// Describers implements ObjectDescriber against functions registered via Add. Those functions can
// be strongly typed. Types are exactly matched (no conversion or assignable checks).
type Describers struct {
searchFns map[reflect.Type][]typeFunc
}

// DescribeObject implements ObjectDescriber and will attempt to print the provided object to a string,
// if at least one describer function has been registered with the exact types passed, or if any
// describer can print the exact object in its first argument (the remainder will be provided empty
// values). If no function registered with Add can satisfy the passed objects, an ErrNoDescriber will
// be returned
// TODO: reorder and partial match extra.
func (d *Describers) DescribeObject(exact interface{}, extra ...interface{}) (string, error) {
exactType := reflect.TypeOf(exact)
fns, ok := d.searchFns[exactType]
if !ok {
return "", newErrNoDescriber(exactType)
}
if len(extra) == 0 {
for _, typeFn := range fns {
if len(typeFn.Extra) == 0 {
return typeFn.Describe(exact, extra...)
}
}
typeFn := fns[0]
for _, t := range typeFn.Extra {
v := reflect.New(t).Elem()
extra = append(extra, v.Interface())
}
return fns[0].Describe(exact, extra...)
}

types := []reflect.Type{}
for _, obj := range extra {
types = append(types, reflect.TypeOf(obj))
}
for _, typeFn := range fns {
if typeFn.Matches(types) {
return typeFn.Describe(exact, extra...)
}
}
return "", newErrNoDescriber(append([]reflect.Type{exactType}, types...)...)
}

// Add adds one or more describer functions to the Describer. The passed function must
// match the signature:
//
// func(...) (string, error)
//
// Any number of arguments may be provided.
func (d *Describers) Add(fns ...interface{}) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it simplify anything to just make the argument a map of kind to describer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wouldn't be able to pass extra fields (in the future) but that's not a short term concern. I can simplify it for now - my primary goal was to be able to describe a kind from a file.

On Mar 3, 2015, at 3:36 PM, Brian Grant notifications@github.com wrote:

In pkg/kubectl/describe.go:

  • }
  • for _, typeFn := range fns {
  •   if typeFn.Matches(types) {
    
  •       return typeFn.Describe(exact, extra...)
    
  •   }
    
  • }
  • return "", newErrNoDescriber(append([]reflect.Type{exactType}, types...)...)
    +}

+// Add adds one or more describer functions to the Describer. The passed function must
+// match the signature:
+//
+// func(...) (string, error)
+//
+// Any number of arguments may be provided.
+func (d *Describers) Add(fns ...interface{}) error {
Would it simplify anything to just make the argument a map of kind to describer?


Reply to this email directly or view it on GitHub.

for _, fn := range fns {
fv := reflect.ValueOf(fn)
ft := fv.Type()
if ft.Kind() != reflect.Func {
return fmt.Errorf("expected func, got: %v", ft)
}
if ft.NumIn() == 0 {
return fmt.Errorf("expected at least one 'in' params, got: %v", ft)
}
if ft.NumOut() != 2 {
return fmt.Errorf("expected two 'out' params - (string, error), got: %v", ft)
}
types := []reflect.Type{}
for i := 0; i < ft.NumIn(); i++ {
types = append(types, ft.In(i))
}
if ft.Out(0) != reflect.TypeOf(string("")) {
return fmt.Errorf("expected string return, got: %v", ft)
}
var forErrorType error
// This convolution is necessary, otherwise TypeOf picks up on the fact
// that forErrorType is nil.
errorType := reflect.TypeOf(&forErrorType).Elem()
if ft.Out(1) != errorType {
return fmt.Errorf("expected error return, got: %v", ft)
}

exact := types[0]
extra := types[1:]
if d.searchFns == nil {
d.searchFns = make(map[reflect.Type][]typeFunc)
}
fns := d.searchFns[exact]
fn := typeFunc{Extra: extra, Fn: fv}
fns = append(fns, fn)
d.searchFns[exact] = fns
}
return nil
}

// typeFunc holds information about a describer function and the types it accepts
type typeFunc struct {
Extra []reflect.Type
Fn reflect.Value
}

// Matches returns true when the passed types exactly match the Extra list.
// TODO: allow unordered types to be matched and reorderd.
func (fn typeFunc) Matches(types []reflect.Type) bool {
if len(fn.Extra) != len(types) {
return false
}
for i := range types {
if fn.Extra[i] != types[i] {
return false
}
}
return true
}

// Describe invokes the nested function with the exact number of arguments.
func (fn typeFunc) Describe(exact interface{}, extra ...interface{}) (string, error) {
values := []reflect.Value{reflect.ValueOf(exact)}
for _, obj := range extra {
values = append(values, reflect.ValueOf(obj))
}
out := fn.Fn.Call(values)
s := out[0].Interface().(string)
var err error
if !out[1].IsNil() {
err = out[1].Interface().(error)
}
return s, err
}