k9s/internal/resource/base.go

207 lines
4.9 KiB
Go

package resource
import (
"bytes"
"context"
"fmt"
"path"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/watch"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/kubernetes/pkg/kubectl/describe"
versioned "k8s.io/kubernetes/pkg/kubectl/describe/versioned"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
type (
// Cruder represent a crudable Kubernetes resource.
Cruder interface {
Get(ns string, name string) (interface{}, error)
List(ns string) (k8s.Collection, error)
Delete(ns string, name string, cascade, force bool) error
SetLabelSelector(string)
SetFieldSelector(string)
GetLabelSelector() string
GetFieldSelector() string
HasSelectors() bool
}
// Connection represents a Kubenetes apiserver connection.
Connection k8s.Connection
// Factory creates new tabular resources.
Factory interface {
New(interface{}) Columnar
}
// Base resource.
Base struct {
Factory
Connection Connection
path string
Resource Cruder
}
)
// NewBase returns a new base
func NewBase(c Connection, r Cruder) *Base {
return &Base{Connection: c, Resource: r}
}
// HasSelectors returns true if field or label selectors are set.
func (b *Base) HasSelectors() bool {
return b.Resource.HasSelectors()
}
// SetPodMetrics attach pod metrics to resource.
func (b *Base) SetPodMetrics(*mv1beta1.PodMetrics) {}
// SetNodeMetrics attach node metrics to resource.
func (b *Base) SetNodeMetrics(*mv1beta1.NodeMetrics) {}
// SetFieldSelector refines query results via selector.
func (b *Base) SetFieldSelector(s string) {
b.Resource.SetFieldSelector(s)
}
// SetLabelSelector refines query results via labels.
func (b *Base) SetLabelSelector(s string) {
b.Resource.SetLabelSelector(s)
}
// GetFieldSelector returns field selector.
func (b *Base) GetFieldSelector() string {
return b.Resource.GetFieldSelector()
}
// GetLabelSelector returns label selector.
func (b *Base) GetLabelSelector() string {
return b.Resource.GetLabelSelector()
}
// Name returns the resource namespaced name.
func (b *Base) Name() string {
return b.path
}
// NumCols designates if column is numerical.
func (*Base) NumCols(n string) map[string]bool {
return map[string]bool{}
}
// ExtFields returns extended fields in relation to headers.
func (*Base) ExtFields() Properties {
return Properties{}
}
// Get a resource by name
func (b *Base) Get(path string) (Columnar, error) {
ns, n := namespaced(path)
i, err := b.Resource.Get(ns, n)
if err != nil {
return nil, err
}
return b.New(i), nil
}
// List all resources
func (b *Base) List(ns string) (Columnars, error) {
ii, err := b.Resource.List(ns)
if err != nil {
return nil, err
}
cc := make(Columnars, 0, len(ii))
for i := 0; i < len(ii); i++ {
cc = append(cc, b.New(ii[i]))
}
return cc, nil
}
// Describe a given resource.
func (b *Base) Describe(kind, pa string) (string, error) {
mapping, err := k8s.RestMapping.Find(kind)
if err != nil {
g, v, n := b.Resource.(*k8s.Resource).GetInfo()
mapper := k8s.RestMapper{b.Connection}
var e error
mapping, e = mapper.ResourceFor(fmt.Sprintf("%s.%s.%s", n, v, g))
if e != nil {
log.Debug().Err(err).Msgf("Unable to find mapper for %s %s", kind, pa)
return "", err
}
}
return b.doDescribe(pa, mapping)
}
func (b *Base) doDescribe(pa string, mapping *meta.RESTMapping) (string, error) {
ns, n := namespaced(pa)
d, err := versioned.Describer(b.Connection.Config().Flags(), mapping)
if err != nil {
log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping)
return "", err
}
return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true})
}
// Delete a resource by name.
func (b *Base) Delete(path string, cascade, force bool) error {
ns, n := namespaced(path)
return b.Resource.Delete(ns, n, cascade, force)
}
func (*Base) namespacedName(m metav1.ObjectMeta) string {
return path.Join(m.Namespace, m.Name)
}
func (*Base) marshalObject(o runtime.Object) (string, error) {
var (
buff bytes.Buffer
p printers.YAMLPrinter
)
err := p.PrintObj(o, &buff)
if err != nil {
log.Error().Msgf("Marshal Error %v", err)
return "", err
}
return buff.String(), nil
}
func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error {
i := ctx.Value(IKey("informer")).(*watch.Informer)
pods, err := i.List(watch.PodIndex, opts.Namespace, metav1.ListOptions{
LabelSelector: toSelector(sel),
})
if err != nil {
return err
}
if len(pods) > 1 {
opts.MultiPods = true
}
pr := NewPod(b.Connection)
for _, p := range pods {
po := p.(*v1.Pod)
if po.Status.Phase == v1.PodRunning {
opts.Namespace, opts.Name = po.Namespace, po.Name
if err := pr.PodLogs(ctx, c, opts); err != nil {
return err
}
}
}
return nil
}