207 lines
4.9 KiB
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
|
|
}
|