mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 23:15:14 +00:00
Make describe able to be invoked on local resources
Support a more flexible access mechanism for getting a describer given only an object.
This commit is contained in:
parent
879bc3a677
commit
85d96f9e7a
@ -19,6 +19,7 @@ package kubectl
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@ -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) {
|
||||
@ -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
|
||||
@ -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")
|
||||
@ -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")
|
||||
@ -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)
|
||||
@ -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 {
|
||||
@ -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)
|
||||
@ -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))
|
||||
@ -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 {
|
||||
@ -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 {
|
||||
@ -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.
|
||||
@ -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 {
|
||||
@ -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 {
|
||||
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
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||
package kubectl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -104,3 +106,83 @@ func TestPodDescribeResultsSorted(t *testing.T) {
|
||||
}
|
||||
VerifyDatesInOrder(out, "\n" /* rowDelimiter */, "\t" /* columnDelimiter */, t)
|
||||
}
|
||||
|
||||
func TestDescribers(t *testing.T) {
|
||||
first := &api.Event{}
|
||||
second := &api.Pod{}
|
||||
var third *api.Pod
|
||||
testErr := fmt.Errorf("test")
|
||||
d := Describers{}
|
||||
d.Add(
|
||||
func(e *api.Event, p *api.Pod) (string, error) {
|
||||
if e != first {
|
||||
t.Errorf("first argument not equal: %#v", e)
|
||||
}
|
||||
if p != second {
|
||||
t.Errorf("second argument not equal: %#v", p)
|
||||
}
|
||||
return "test", testErr
|
||||
},
|
||||
)
|
||||
if out, err := d.DescribeObject(first, second); out != "test" || err != testErr {
|
||||
t.Errorf("unexpected result: %s %v", out, err)
|
||||
}
|
||||
|
||||
if out, err := d.DescribeObject(first, second, third); out != "" || err == nil {
|
||||
t.Errorf("unexpected result: %s %v", out, err)
|
||||
} else {
|
||||
if noDescriber, ok := err.(ErrNoDescriber); ok {
|
||||
if !reflect.DeepEqual(noDescriber.Types, []string{"*api.Event", "*api.Pod", "*api.Pod"}) {
|
||||
t.Errorf("unexpected describer: %v", err)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("unexpected error type: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
d.Add(
|
||||
func(e *api.Event) (string, error) {
|
||||
if e != first {
|
||||
t.Errorf("first argument not equal: %#v", e)
|
||||
}
|
||||
return "simpler", testErr
|
||||
},
|
||||
)
|
||||
if out, err := d.DescribeObject(first); out != "simpler" || err != testErr {
|
||||
t.Errorf("unexpected result: %s %v", out, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultDescribers(t *testing.T) {
|
||||
out, err := DefaultObjectDescriber.DescribeObject(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "foo") {
|
||||
t.Errorf("unexpected output: %s", out)
|
||||
}
|
||||
|
||||
out, err = DefaultObjectDescriber.DescribeObject(&api.Service{ObjectMeta: api.ObjectMeta{Name: "foo"}})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "foo") {
|
||||
t.Errorf("unexpected output: %s", out)
|
||||
}
|
||||
|
||||
out, err = DefaultObjectDescriber.DescribeObject(&api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "foo") {
|
||||
t.Errorf("unexpected output: %s", out)
|
||||
}
|
||||
|
||||
out, err = DefaultObjectDescriber.DescribeObject(&api.Node{ObjectMeta: api.ObjectMeta{Name: "foo"}})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "foo") {
|
||||
t.Errorf("unexpected output: %s", out)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user