implement rbac policy checks + fix #457

mine
derailed 2020-01-01 14:29:49 -07:00
parent 1c1cd76a9c
commit b950546b88
80 changed files with 453 additions and 450 deletions

View File

@ -48,7 +48,6 @@ type Connection interface {
ValidNamespaces() ([]v1.Namespace, error)
SupportsRes(grp string, versions []string) (string, bool, error)
ServerVersion() (*version.Info, error)
FetchNodes() (*v1.NodeList, error)
CurrentNamespaceName() (string, error)
}
@ -74,13 +73,18 @@ func InitConnectionOrDie(config *Config) *APIClient {
}
func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview {
res := GVR(gvr).AsGVR()
if ns == "-" {
ns = ""
}
spec := NewGVR(gvr)
res := spec.AsGVR()
return &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: ns,
Group: res.Group,
Resource: res.Resource,
Namespace: ns,
Group: res.Group,
Resource: res.Resource,
Subresource: spec.SubResource(),
},
},
}
@ -88,19 +92,22 @@ func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview {
// CanI checks if user has access to a certain resource.
func (a *APIClient) CanI(ns, gvr string, verbs []string) (bool, error) {
log.Debug().Msgf("AUTH %q:%q -- %v", ns, gvr, verbs)
sar := makeSAR(ns, gvr)
dial := a.DialOrDie().AuthorizationV1().SelfSubjectAccessReviews()
for _, v := range verbs {
sar.Spec.ResourceAttributes.Verb = v
resp, err := dial.Create(sar)
if err != nil {
log.Error().Err(err).Msgf("CanI")
log.Warn().Err(err).Msgf(" Dial Failed!")
return false, err
}
if !resp.Status.Allowed {
return false, fmt.Errorf("%s access denied for current user on %q:%s", v, ns, gvr)
log.Debug().Msgf(" NO %q ;(", v)
return false, fmt.Errorf("`%s access denied for user on %q:%s", v, ns, gvr)
}
}
log.Debug().Msgf(" YES!")
return true, nil
}
@ -119,11 +126,6 @@ func (a *APIClient) ServerVersion() (*version.Info, error) {
return discovery.ServerVersion()
}
// FetchNodes returns all available nodes.
func (a *APIClient) FetchNodes() (*v1.NodeList, error) {
return a.DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{})
}
// ValidNamespaces returns all available namespaces.
func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
nn, err := a.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{})

View File

@ -6,39 +6,75 @@ import (
"strings"
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"vbom.ml/util/sortorder"
)
// GVR represents a kubernetes resource schema as a string.
// Format is group/version/resources
type GVR string
type GVR struct {
raw, g, v, r, sr string
}
// NewGVR builds a new gvr from a group, version, resource.
func NewGVR(g, v, r string) GVR {
return GVR(path.Join(g, v, r))
func NewGVR(gvr string) GVR {
var g, v, r, sr string
tokens := strings.Split(gvr, ":")
raw := gvr
if len(tokens) == 2 {
raw, sr = tokens[0], tokens[1]
}
tokens = strings.Split(raw, "/")
switch len(tokens) {
case 3:
g, v, r = tokens[0], tokens[1], tokens[2]
case 2:
v, r = tokens[0], tokens[1]
case 1:
r = tokens[0]
default:
panic(fmt.Sprintf("can't parse GVR %q", gvr))
}
return GVR{raw: gvr, g: g, v: v, r: r, sr: sr}
}
func NewGVRFromMeta(a metav1.APIResource) GVR {
return GVR{
raw: path.Join(a.Group, a.Version, a.Name),
g: a.Group,
v: a.Version,
r: a.Name,
}
}
// FromGVAndR builds a gvr from a group/version and resource.
func FromGVAndR(gv, r string) GVR {
return GVR(path.Join(gv, r))
return NewGVR(path.Join(gv, r))
}
// ResName returns a resource . separated descriptor in the shape of kind.version.group.
func (g GVR) ResName() string {
return g.ToR() + "." + g.ToV() + "." + g.ToG()
// AsResourceName returns a resource . separated descriptor in the shape of kind.version.group.
func (g GVR) AsResourceName() string {
return g.r + "." + g.v + "." + g.g
}
// SubResource returns a sub resource if available.
func (g GVR) SubResource() string {
return g.sr
}
// String returns gvr as string.
func (g GVR) String() string {
return string(g)
return g.raw
}
// AsGV returns the group version scheme representation.
func (g GVR) AsGV() schema.GroupVersion {
return schema.GroupVersion{
Group: g.ToG(),
Version: g.ToV(),
Group: g.g,
Version: g.v,
}
}
@ -53,41 +89,22 @@ func (g GVR) AsGVR() schema.GroupVersionResource {
// ToV returns the resource version.
func (g GVR) ToV() string {
tokens := strings.Split(string(g), "/")
if len(tokens) < 2 {
return ""
}
return tokens[len(tokens)-2]
return g.v
}
// ToRAndG returns the resource and group.
func (g GVR) ToRAndG() (string, string) {
tokens := strings.Split(string(g), "/")
switch len(tokens) {
case 3:
return tokens[2], tokens[0]
case 2:
return tokens[1], "core"
default:
return tokens[0], "core"
}
return g.r, g.g
}
// ToR returns the resource name.
func (g GVR) ToR() string {
tokens := strings.Split(string(g), "/")
return tokens[len(tokens)-1]
return g.r
}
// ToG returns the resource group name.
func (g GVR) ToG() string {
tokens := strings.Split(string(g), "/")
switch len(tokens) {
case 3:
return tokens[0]
default:
return ""
}
return g.g
}
// GVRs represents a collection of gvr.

View File

@ -1,6 +1,7 @@
package client_test
import (
"path"
"sort"
"testing"
@ -10,9 +11,17 @@ import (
)
func TestGVRSort(t *testing.T) {
gg := client.GVRs{"v1/pods", "v1/services", "apps/v1/deployments"}
gg := client.GVRs{
client.NewGVR("v1/pods"),
client.NewGVR("v1/services"),
client.NewGVR("apps/v1/deployments"),
}
sort.Sort(gg)
assert.Equal(t, client.GVRs{"v1/pods", "v1/services", "apps/v1/deployments"}, gg)
assert.Equal(t, client.GVRs{
client.NewGVR("v1/pods"),
client.NewGVR("v1/services"),
client.NewGVR("apps/v1/deployments"),
}, gg)
}
func TestGVRCan(t *testing.T) {
@ -50,7 +59,7 @@ func TestAsGVR(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).AsGVR())
assert.Equal(t, u.e, client.NewGVR(u.gvr).AsGVR())
})
}
}
@ -68,7 +77,7 @@ func TestAsGV(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).AsGV())
assert.Equal(t, u.e, client.NewGVR(u.gvr).AsGV())
})
}
}
@ -85,12 +94,12 @@ func TestNewGVR(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.NewGVR(u.g, u.v, u.r).String())
assert.Equal(t, u.e, client.NewGVR(path.Join(u.g, u.v, u.r)).String())
})
}
}
func TestResName(t *testing.T) {
func TestGVRAsResourceName(t *testing.T) {
uu := map[string]struct {
gvr string
e string
@ -104,7 +113,7 @@ func TestResName(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).ResName())
assert.Equal(t, u.e, client.NewGVR(u.gvr).AsResourceName())
})
}
}
@ -123,7 +132,7 @@ func TestToR(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).ToR())
assert.Equal(t, u.e, client.NewGVR(u.gvr).ToR())
})
}
}
@ -142,7 +151,7 @@ func TestToG(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).ToG())
assert.Equal(t, u.e, client.NewGVR(u.gvr).ToG())
})
}
}
@ -161,7 +170,7 @@ func TestToV(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, client.GVR(u.gvr).ToV())
assert.Equal(t, u.e, client.NewGVR(u.gvr).ToV())
})
}
}
@ -179,7 +188,7 @@ func TestToString(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.gvr, client.GVR(u.gvr).String())
assert.Equal(t, u.gvr, client.NewGVR(u.gvr).String())
})
}
}

View File

@ -101,6 +101,11 @@ func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsL
// FetchNodesMetrics return all metrics for pods in a given namespace.
func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
auth, err := m.CanI("", "metrics.k8s.io/v1beta1/nodes", []string{"list"})
if !auth || err != nil {
return nil, err
}
client, err := m.MXDial()
if err != nil {
return nil, err
@ -111,6 +116,11 @@ func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
// FetchPodsMetrics return all metrics for pods in a given namespace.
func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, error) {
auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", []string{"list"})
if !auth || err != nil {
return &mv1beta1.PodMetricsList{}, err
}
client, err := m.MXDial()
if err != nil {
return nil, err
@ -121,6 +131,11 @@ func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, e
// FetchPodMetrics return all metrics for pods in a given namespace.
func (m *MetricsServer) FetchPodMetrics(ns, sel string) (*mv1beta1.PodMetrics, error) {
auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", []string{"get"})
if !auth || err != nil {
return nil, err
}
client, err := m.MXDial()
if err != nil {
return nil, err

View File

@ -135,25 +135,6 @@ func (mock *MockConnection) DynDialOrDie() dynamic.Interface {
return ret0
}
func (mock *MockConnection) FetchNodes() (*v1.NodeList, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("FetchNodes", params, []reflect.Type{reflect.TypeOf((**v1.NodeList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 *v1.NodeList
var ret1 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(*v1.NodeList)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0, ret1
}
func (mock *MockConnection) HasMetrics() bool {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
@ -478,23 +459,6 @@ func (c *MockConnection_DynDialOrDie_OngoingVerification) GetCapturedArguments()
func (c *MockConnection_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierMockConnection) FetchNodes() *MockConnection_FetchNodes_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchNodes", params, verifier.timeout)
return &MockConnection_FetchNodes_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type MockConnection_FetchNodes_OngoingVerification struct {
mock *MockConnection
methodInvocations []pegomock.MethodInvocation
}
func (c *MockConnection_FetchNodes_OngoingVerification) GetCapturedArguments() {
}
func (c *MockConnection_FetchNodes_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierMockConnection) HasMetrics() *MockConnection_HasMetrics_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout)

View File

@ -29,9 +29,6 @@ func (a *Alias) Clear() {
// Ensure makes sure alias are loaded.
func (a *Alias) Ensure() (config.Alias, error) {
// if len(a.Alias) > 0 {
// return a.Alias, nil
// }
if err := LoadResources(a.factory); err != nil {
return config.Alias{}, err
}
@ -51,12 +48,12 @@ func (a *Alias) load() error {
if _, ok := a.Alias[meta.Kind]; ok {
continue
}
a.Define(string(gvr), strings.ToLower(meta.Kind), meta.Name)
a.Define(gvr.String(), strings.ToLower(meta.Kind), meta.Name)
if meta.SingularName != "" {
a.Define(string(gvr), meta.SingularName)
a.Define(gvr.String(), meta.SingularName)
}
if meta.ShortNames != nil {
a.Define(string(gvr), meta.ShortNames...)
a.Define(gvr.String(), meta.ShortNames...)
}
}

View File

@ -6,7 +6,6 @@ import (
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/watch"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
@ -24,11 +23,11 @@ var _ Loggable = &Container{}
// TailLogs tails a given container logs
func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error {
fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
fac, ok := ctx.Value(internal.KeyFactory).(Factory)
if !ok {
return errors.New("Expecting an informer")
}
o, err := fac.Get("v1/pods", opts.Path, labels.Everything())
o, err := fac.Get("v1/pods", opts.Path, true, labels.Everything())
if err != nil {
return err
}
@ -42,7 +41,13 @@ func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts Lo
}
// Logs fetch container logs for a given pod and container.
func (c *Container) Logs(path string, opts *v1.PodLogOptions) *restclient.Request {
func (c *Container) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) {
ns, _ := client.Namespaced(path)
auth, err := c.Client().CanI(ns, "v1/pods:log", []string{"get"})
if !auth || err != nil {
return nil, err
}
ns, n := client.Namespaced(path)
return c.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts)
return c.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts), nil
}

View File

@ -20,6 +20,11 @@ var _ Runnable = &CronJob{}
// Run a CronJob.
func (c *CronJob) Run(path string) error {
ns, n := client.Namespaced(path)
auth, err := c.Client().CanI(ns, "batch/v1beta1/cronjobs", []string{"get", "create"})
if !auth || err != nil {
return err
}
cj, err := c.Client().DialOrDie().BatchV1beta1().CronJobs(ns).Get(n, metav1.GetOptions{})
if err != nil {
return err

View File

@ -16,14 +16,13 @@ func Describe(c client.Connection, gvr client.GVR, ns, n string) (string, error)
return "", err
}
GVR := client.GVR(gvr)
gvk, err := m.KindFor(GVR.AsGVR())
gvk, err := m.KindFor(gvr.AsGVR())
if err != nil {
log.Error().Err(err).Msgf("No GVK for resource %s", gvr)
return "", err
}
mapping, err := mapper.ResourceFor(GVR.ResName(), gvk.Kind)
mapping, err := mapper.ResourceFor(gvr.AsResourceName(), gvk.Kind)
if err != nil {
log.Error().Err(err).Msgf("Unable to find mapper for %s %s", gvr, n)
return "", err
@ -34,6 +33,5 @@ func Describe(c client.Connection, gvr client.GVR, ns, n string) (string, error)
return "", err
}
log.Debug().Msgf("DESCRIBE FOR %q -- %q:%q", gvr, ns, n)
return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true})
}

View File

@ -6,7 +6,6 @@ import (
"fmt"
"github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -28,8 +27,12 @@ var _ Scalable = &Deployment{}
// Scale a Deployment.
func (d *Deployment) Scale(path string, replicas int32) error {
log.Debug().Msgf("SCALING DEPLOYMENT!! %q:%d", path, replicas)
ns, n := client.Namespaced(path)
auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", []string{"get", "update"})
if !auth || err != nil {
return err
}
scale, err := d.Client().DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{})
if err != nil {
return err
@ -42,29 +45,33 @@ func (d *Deployment) Scale(path string, replicas int32) error {
// Restart a Deployment rollout.
func (d *Deployment) Restart(path string) error {
o, err := d.Get(string(d.gvr), path, labels.Everything())
o, err := d.Get(d.gvr.String(), path, true, labels.Everything())
if err != nil {
return err
}
var ds appsv1.Deployment
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
return err
}
ns, _ := client.Namespaced(path)
auth, err := d.Client().CanI(ns, "apps/v1/deployments", []string{"patch"})
if !auth || err != nil {
return err
}
update, err := polymorphichelpers.ObjectRestarterFn(&ds)
if err != nil {
return err
}
_, err = d.Client().DialOrDie().AppsV1().Deployments(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
return err
}
// TailLogs tail logs for all pods represented by this Deployment.
func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
o, err := d.Get(string(d.gvr), opts.Path, labels.Everything())
o, err := d.Get(d.gvr.String(), opts.Path, true, labels.Everything())
if err != nil {
return err
}
@ -74,7 +81,6 @@ func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOpti
if err != nil {
return errors.New("expecting Deployment resource")
}
if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
return fmt.Errorf("No valid selector found on Deployment %s", opts.Path)
}

View File

@ -31,33 +31,35 @@ var _ Restartable = &DaemonSet{}
// Restart a DaemonSet rollout.
func (d *DaemonSet) Restart(path string) error {
o, err := d.Get(string(d.gvr), path, labels.Everything())
o, err := d.Get(d.gvr.String(), path, true, labels.Everything())
if err != nil {
return err
}
var ds appsv1.DaemonSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
return err
}
auth, err := d.Client().CanI(ds.Namespace, "apps/v1/daemonsets", []string{"patch"})
if !auth || err != nil {
return err
}
update, err := polymorphichelpers.ObjectRestarterFn(&ds)
if err != nil {
return err
}
_, err = d.Client().DialOrDie().AppsV1().DaemonSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
return err
}
// TailLogs tail logs for all pods represented by this DaemonSet.
func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
o, err := d.Get("apps/v1/daemonsets", opts.Path, labels.Everything())
o, err := d.Get(d.gvr.String(), opts.Path, true, labels.Everything())
if err != nil {
return err
}
var ds appsv1.DaemonSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
@ -86,7 +88,7 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L
}
ns, _ := client.Namespaced(opts.Path)
oo, err := f.List("v1/pods", ns, lsel)
oo, err := f.List("v1/pods", ns, true, lsel)
if err != nil {
return err
}
@ -96,7 +98,7 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L
}
po := Pod{}
po.Init(f, "v1/pods")
po.Init(f, client.NewGVR("v1/pods"))
for _, o := range oo {
var pod v1.Pod
err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)

View File

@ -20,12 +20,16 @@ func (g *Generic) Init(f Factory, gvr client.GVR) {
// Delete a Generic.
func (g *Generic) Delete(path string, cascade, force bool) error {
ns, n := client.Namespaced(path)
auth, err := g.Client().CanI(ns, g.gvr.String(), []string{"delete"})
if !auth || err != nil {
return err
}
p := metav1.DeletePropagationOrphan
if cascade {
p = metav1.DeletePropagationBackground
}
ns, n := client.Namespaced(path)
opts := metav1.DeleteOptions{PropagationPolicy: &p}
if ns != "-" {
return g.dynClient().Namespace(ns).Delete(n, &opts)

View File

@ -5,6 +5,8 @@ import (
"github.com/derailed/tview"
runewidth "github.com/mattn/go-runewidth"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func toPerc(v1, v2 float64) float64 {
@ -18,3 +20,13 @@ func toPerc(v1, v2 float64) float64 {
func Truncate(str string, width int) string {
return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis))
}
// FetchNodes returns a collection of nodes.
func FetchNodes(f Factory) (*v1.NodeList, error) {
auth, err := f.Client().CanI("", "v1/nodes", []string{"list"})
if !auth || err != nil {
return nil, err
}
return f.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{})
}

View File

@ -21,7 +21,7 @@ var _ Loggable = &Job{}
// TailLogs tail logs for all pods represented by this Job.
func (j *Job) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
o, err := j.Get(string(j.gvr), opts.Path, labels.Everything())
o, err := j.Get(j.gvr.String(), opts.Path, true, labels.Everything())
if err != nil {
return err
}

View File

@ -32,14 +32,20 @@ var _ Accessor = &Pod{}
var _ Loggable = &Pod{}
// Logs fetch container logs for a given pod and container.
func (p *Pod) Logs(path string, opts *v1.PodLogOptions) *restclient.Request {
func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) {
ns, _ := client.Namespaced(path)
auth, err := p.Client().CanI(ns, "v1/pods:log", []string{"get"})
if !auth || err != nil {
return nil, err
}
ns, n := client.Namespaced(path)
return p.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts)
return p.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts), nil
}
// Containers returns all container names on pod
func (p *Pod) Containers(path string, includeInit bool) ([]string, error) {
o, err := p.Get("v1/pod", path, labels.Everything())
o, err := p.Get(p.gvr.String(), path, true, labels.Everything())
if err != nil {
return nil, err
}
@ -77,7 +83,7 @@ func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error
if !ok {
return errors.New("Expecting an informer")
}
o, err := fac.Get("v1/pods", opts.Path, labels.Everything())
o, err := fac.Get(p.gvr.String(), opts.Path, true, labels.Everything())
if err != nil {
return err
}
@ -119,7 +125,10 @@ func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptio
TailLines: &opts.Lines,
Previous: opts.Previous,
}
req := logger.Logs(opts.Path, &o)
req, err := logger.Logs(opts.Path, &o)
if err != nil {
return err
}
ctxt, cancelFunc := context.WithCancel(ctx)
req.Context(ctxt)

View File

@ -92,6 +92,10 @@ func (p *PortForwarder) Start(path, co, address string, ports []string) (*portfo
p.path, p.container, p.ports, p.age = path, co, ports, time.Now()
ns, n := client.Namespaced(path)
auth, err := p.CanI(ns, "v1/pods", []string{"get"})
if !auth || err != nil {
return nil, err
}
pod, err := p.DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{})
if err != nil {
return nil, err
@ -100,6 +104,11 @@ func (p *PortForwarder) Start(path, co, address string, ports []string) (*portfo
return nil, fmt.Errorf("unable to forward port because pod is not running. Current status=%v", pod.Status.Phase)
}
auth, err = p.CanI(ns, "v1/pods:portforward", []string{"update"})
if !auth || err != nil {
return nil, err
}
rcfg := p.RestConfigOrDie()
rcfg.GroupVersion = &schema.GroupVersion{Group: "", Version: "v1"}
rcfg.APIPath = "/api"

View File

@ -1,5 +1,9 @@
package dao
import (
"github.com/derailed/k9s/internal/client"
)
// PortForward represents a port forward dao.
type PortForward struct {
Generic
@ -10,6 +14,12 @@ var _ Nuker = &PortForward{}
// Delete a portforward.
func (p *PortForward) Delete(path string, cascade, force bool) error {
ns, _ := client.Namespaced(path)
auth, err := p.Client().CanI(ns, "v1/pods:portforward", []string{"delete"})
if !auth || err != nil {
return err
}
p.Factory.DeleteForwarder(path)
return nil
}

View File

@ -25,19 +25,19 @@ var resMetas = ResourceMetas{}
// Customize here for non resource types or types with metrics or logs.
func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
m := Accessors{
"contexts": &Context{},
"containers": &Container{},
"screendumps": &ScreenDump{},
"benchmarks": &Benchmark{},
"portforwards": &PortForward{},
"v1/services": &Service{},
"v1/pods": &Pod{},
"apps/v1/deployments": &Deployment{},
"apps/v1/daemonsets": &DaemonSet{},
"extensions/v1beta1/daemonsets": &DaemonSet{},
"apps/v1/statefulsets": &StatefulSet{},
"batch/v1beta1/cronjobs": &CronJob{},
"batch/v1/jobs": &Job{},
client.NewGVR("contexts"): &Context{},
client.NewGVR("containers"): &Container{},
client.NewGVR("screendumps"): &ScreenDump{},
client.NewGVR("benchmarks"): &Benchmark{},
client.NewGVR("portforwards"): &PortForward{},
client.NewGVR("v1/services"): &Service{},
client.NewGVR("v1/pods"): &Pod{},
client.NewGVR("apps/v1/deployments"): &Deployment{},
client.NewGVR("apps/v1/daemonsets"): &DaemonSet{},
client.NewGVR("extensions/v1beta1/daemonsets"): &DaemonSet{},
client.NewGVR("apps/v1/statefulsets"): &StatefulSet{},
client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{},
client.NewGVR("batch/v1/jobs"): &Job{},
}
r, ok := m[gvr]
@ -52,7 +52,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
// RegisterMeta registers a new resource meta object.
func RegisterMeta(gvr string, res metav1.APIResource) {
resMetas[client.GVR(gvr)] = res
resMetas[client.NewGVR(gvr)] = res
}
// AllGVRs returns all cluster resources.
@ -93,41 +93,39 @@ func LoadResources(f Factory) error {
return err
}
loadNonResource(resMetas)
loadCRDs(f, resMetas)
if err := loadCRDs(f, resMetas); err != nil {
log.Warn().Err(err).Msgf("CRDs load failed!")
}
return nil
}
// BOZO!! Need contermeasure for direct commands!
func loadNonResource(m ResourceMetas) {
m["aliases"] = metav1.APIResource{
m[client.NewGVR("aliases")] = metav1.APIResource{
Name: "aliases",
Kind: "Aliases",
Categories: []string{"k9s"},
}
m["contexts"] = metav1.APIResource{
m[client.NewGVR("contexts")] = metav1.APIResource{
Name: "contexts",
Kind: "Contexts",
ShortNames: []string{"ctx"},
Categories: []string{"k9s"},
}
m["screendumps"] = metav1.APIResource{
m[client.NewGVR("screendumps")] = metav1.APIResource{
Name: "screendumps",
Kind: "ScreenDumps",
ShortNames: []string{"sd"},
Verbs: []string{"delete"},
Categories: []string{"k9s"},
}
m["benchmarks"] = metav1.APIResource{
m[client.NewGVR("benchmarks")] = metav1.APIResource{
Name: "benchmarks",
Kind: "Benchmarks",
ShortNames: []string{"be"},
Verbs: []string{"delete"},
Categories: []string{"k9s"},
}
m["portforwards"] = metav1.APIResource{
m[client.NewGVR("portforwards")] = metav1.APIResource{
Name: "portforwards",
Namespaced: true,
Kind: "PortForwards",
@ -135,7 +133,7 @@ func loadNonResource(m ResourceMetas) {
Verbs: []string{"delete"},
Categories: []string{"k9s"},
}
m["containers"] = metav1.APIResource{
m[client.NewGVR("containers")] = metav1.APIResource{
Name: "containers",
Kind: "Containers",
Categories: []string{"k9s"},
@ -145,23 +143,23 @@ func loadNonResource(m ResourceMetas) {
}
func loadRBAC(m ResourceMetas) {
m["rbac"] = metav1.APIResource{
m[client.NewGVR("rbac")] = metav1.APIResource{
Name: "rbacs",
Kind: "Rules",
Categories: []string{"k9s"},
}
m["policy"] = metav1.APIResource{
m[client.NewGVR("policy")] = metav1.APIResource{
Name: "policies",
Kind: "Rules",
Namespaced: true,
Categories: []string{"k9s"},
}
m["users"] = metav1.APIResource{
m[client.NewGVR("users")] = metav1.APIResource{
Name: "users",
Kind: "User",
Categories: []string{"k9s"},
}
m["groups"] = metav1.APIResource{
m[client.NewGVR("groups")] = metav1.APIResource{
Name: "groups",
Kind: "Group",
Categories: []string{"k9s"},
@ -188,17 +186,13 @@ func loadPreferred(f Factory, m ResourceMetas) error {
return nil
}
func loadCRDs(f Factory, m ResourceMetas) error {
func loadCRDs(f Factory, m ResourceMetas) {
log.Debug().Msgf("Loading CRDs...")
const crdGVR = "apiextensions.k8s.io/v1beta1/customresourcedefinitions"
_, err := f.CanForResource("", crdGVR, "list")
if err != nil {
return err
}
oo, err := f.List(crdGVR, "", labels.Everything())
oo, err := f.List(crdGVR, "", true, labels.Everything())
if err != nil {
log.Warn().Err(err).Msgf("Fail CRDs load")
return nil
return
}
log.Debug().Msgf(">>> CRDS count %d", len(oo))
@ -208,11 +202,9 @@ func loadCRDs(f Factory, m ResourceMetas) error {
log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs))
continue
}
gvr := client.NewGVR(meta.Group, meta.Version, meta.Name)
gvr := client.NewGVRFromMeta(meta)
m[gvr] = meta
}
return nil
}
func extractMeta(o runtime.Object) (metav1.APIResource, []error) {

View File

@ -28,6 +28,10 @@ var _ Scalable = &StatefulSet{}
// Scale a StatefulSet.
func (s *StatefulSet) Scale(path string, replicas int32) error {
ns, n := client.Namespaced(path)
auth, err := s.Client().CanI(ns, "apps/v1/statefulsets:scale", []string{"get", "update"})
if !auth || err != nil {
return err
}
scale, err := s.Client().DialOrDie().AppsV1().StatefulSets(ns).GetScale(n, metav1.GetOptions{})
if err != nil {
return err
@ -40,29 +44,33 @@ func (s *StatefulSet) Scale(path string, replicas int32) error {
// Restart a StatefulSet rollout.
func (s *StatefulSet) Restart(path string) error {
o, err := s.Get(string(s.gvr), path, labels.Everything())
o, err := s.Get(s.gvr.String(), path, true, labels.Everything())
if err != nil {
return err
}
var ds appsv1.StatefulSet
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
if err != nil {
return err
}
ns, _ := client.Namespaced(path)
auth, err := s.Client().CanI(ns, "apps/v1/statefulsets", []string{"patch"})
if !auth || err != nil {
return err
}
update, err := polymorphichelpers.ObjectRestarterFn(&ds)
if err != nil {
return err
}
_, err = s.Client().DialOrDie().AppsV1().StatefulSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
return err
}
// TailLogs tail logs for all pods represented by this StatefulSet.
func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
o, err := s.Get(string(s.gvr), opts.Path, labels.Everything())
o, err := s.Get(s.gvr.String(), opts.Path, true, labels.Everything())
if err != nil {
return err
}
@ -72,7 +80,6 @@ func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOpt
if err != nil {
return errors.New("expecting StatefulSet resource")
}
if sts.Spec.Selector == nil || len(sts.Spec.Selector.MatchLabels) == 0 {
return fmt.Errorf("No valid selector found on StatefulSet %s", opts.Path)
}

View File

@ -21,7 +21,7 @@ var _ Loggable = &Service{}
// TailLogs tail logs for all pods represented by this Service.
func (s *Service) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
o, err := s.Get(string(s.gvr), opts.Path, labels.Everything())
o, err := s.Get(s.gvr.String(), opts.Path, true, labels.Everything())
if err != nil {
return err
}

View File

@ -18,16 +18,16 @@ type Factory interface {
Client() client.Connection
// Get fetch a given resource.
Get(gvr, path string, sel labels.Selector) (runtime.Object, error)
Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error)
// List fetch a collection of resources.
List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error)
List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error)
// ForResource fetch an informer for a given resource.
ForResource(ns, gvr string) informers.GenericInformer
// CanForResource fetch an informer for a given resource if authorized
CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error)
CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error)
// WaitForCacheSync synchronize the cache.
WaitForCacheSync()
@ -86,5 +86,5 @@ type Runnable interface {
// Logger represents a resource that exposes logs.
type Logger interface {
// Logs tails a resource logs.
Logs(path string, opts *v1.PodLogOptions) *restclient.Request
Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error)
}

View File

@ -60,28 +60,29 @@ func makeAliases() *dao.Alias {
type testFactory struct{}
var _ model.Factory = testFactory{}
var _ dao.Factory = testFactory{}
func (f testFactory) Client() client.Connection {
return nil
}
func (f testFactory) Get(gvr, path string, sel labels.Selector) (runtime.Object, error) {
func (f testFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) {
return nil, nil
}
func (f testFactory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) {
func (f testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) {
return nil, nil
}
func (f testFactory) ForResource(ns, gvr string) informers.GenericInformer {
return nil
}
func (f testFactory) CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error) {
func (f testFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
return nil, nil
}
func (f testFactory) WaitForCacheSync() {}
func (f testFactory) Forwarders() watch.Forwarders {
return nil
}
func (f testFactory) DeleteForwarder(string) {}
func makeFactory() model.Factory {
func makeFactory() dao.Factory {
return testFactory{}
}

View File

@ -31,7 +31,7 @@ func (c *Container) List(ctx context.Context) ([]runtime.Object, error) {
}
ns, _ := render.Namespaced(path)
c.namespace = ns
o, err := c.factory.Get("v1/pods", path, labels.Everything())
o, err := c.factory.Get("v1/pods", path, true, labels.Everything())
if err != nil {
return nil, err
}

View File

@ -6,6 +6,7 @@ import (
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/watch"
@ -47,29 +48,30 @@ func TestContainerHydrate(t *testing.T) {
type podFactory struct{}
var _ model.Factory = testFactory{}
var _ dao.Factory = testFactory{}
func (f podFactory) Client() client.Connection {
return nil
}
func (f podFactory) Get(gvr, path string, sel labels.Selector) (runtime.Object, error) {
func (f podFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) {
var m map[string]interface{}
if err := yaml.Unmarshal([]byte(poYaml()), &m); err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: m}, nil
}
func (f podFactory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) {
func (f podFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) {
return nil, nil
}
func (f podFactory) ForResource(ns, gvr string) informers.GenericInformer { return nil }
func (f podFactory) CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error) {
func (f podFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
return nil, nil
}
func (f podFactory) WaitForCacheSync() {}
func (f podFactory) Forwarders() watch.Forwarders { return nil }
func (f podFactory) DeleteForwarder(string) {}
func makePodFactory() model.Factory {
func makePodFactory() dao.Factory {
return podFactory{}
}

View File

@ -21,8 +21,8 @@ func (c *CustomResourceDefinition) List(ctx context.Context) ([]runtime.Object,
lsel = sel.AsSelector()
}
gvr := "apiextensions.k8s.io/v1beta1/customresourcedefinitions"
oo, err := c.factory.List(gvr, "-", lsel)
const gvr = "apiextensions.k8s.io/v1beta1/customresourcedefinitions"
oo, err := c.factory.List(gvr, "-", true, lsel)
if err != nil {
return nil, err
}

View File

@ -26,12 +26,12 @@ type Generic struct {
// List returns a collection of node resources.
func (g *Generic) List(ctx context.Context) ([]runtime.Object, error) {
// Ensures the factory is tracking this resource
_, err := g.factory.CanForResource(g.namespace, g.gvr)
_, err := g.factory.CanForResource(g.namespace, g.gvr, []string{"list"})
if err != nil {
return nil, err
}
gvr := client.GVR(g.gvr)
gvr := client.NewGVR(g.gvr)
fcodec, codec := g.codec(gvr.AsGV())
c, err := g.client(fcodec, gvr)

View File

@ -41,7 +41,7 @@ func (h *HorizontalPodAutoscaler) List(ctx context.Context) ([]runtime.Object, e
}
func (h *HorizontalPodAutoscaler) list(gvr string, sel labels.Selector) ([]runtime.Object, error) {
oo, err := h.factory.List(gvr, h.namespace, sel)
oo, err := h.factory.List(gvr, h.namespace, true, sel)
if err != nil {
return nil, err
}

View File

@ -173,25 +173,6 @@ func (mock *MockClusterMeta) DynDialOrDie() dynamic.Interface {
return ret0
}
func (mock *MockClusterMeta) FetchNodes() (*v1.NodeList, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("FetchNodes", params, []reflect.Type{reflect.TypeOf((**v1.NodeList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 *v1.NodeList
var ret1 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(*v1.NodeList)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0, ret1
}
func (mock *MockClusterMeta) GetNodes() (*v1.NodeList, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
@ -607,23 +588,6 @@ func (c *MockClusterMeta_DynDialOrDie_OngoingVerification) GetCapturedArguments(
func (c *MockClusterMeta_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierMockClusterMeta) FetchNodes() *MockClusterMeta_FetchNodes_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchNodes", params, verifier.timeout)
return &MockClusterMeta_FetchNodes_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type MockClusterMeta_FetchNodes_OngoingVerification struct {
mock *MockClusterMeta
methodInvocations []pegomock.MethodInvocation
}
func (c *MockClusterMeta_FetchNodes_OngoingVerification) GetCapturedArguments() {
}
func (c *MockClusterMeta_FetchNodes_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierMockClusterMeta) GetNodes() *MockClusterMeta_GetNodes_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetNodes", params, verifier.timeout)

View File

@ -135,25 +135,6 @@ func (mock *MockConnection) DynDialOrDie() dynamic.Interface {
return ret0
}
func (mock *MockConnection) FetchNodes() (*v1.NodeList, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("FetchNodes", params, []reflect.Type{reflect.TypeOf((**v1.NodeList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 *v1.NodeList
var ret1 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(*v1.NodeList)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0, ret1
}
func (mock *MockConnection) HasMetrics() bool {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
@ -478,23 +459,6 @@ func (c *MockConnection_DynDialOrDie_OngoingVerification) GetCapturedArguments()
func (c *MockConnection_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierMockConnection) FetchNodes() *MockConnection_FetchNodes_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchNodes", params, verifier.timeout)
return &MockConnection_FetchNodes_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type MockConnection_FetchNodes_OngoingVerification struct {
mock *MockConnection
methodInvocations []pegomock.MethodInvocation
}
func (c *MockConnection_FetchNodes_OngoingVerification) GetCapturedArguments() {
}
func (c *MockConnection_FetchNodes_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierMockConnection) HasMetrics() *MockConnection_HasMetrics_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout)

View File

@ -5,10 +5,10 @@ import (
"fmt"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@ -24,7 +24,7 @@ type Node struct {
// List returns a collection of node resources.
func (n *Node) List(_ context.Context) ([]runtime.Object, error) {
nn, err := n.factory.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{})
nn, err := dao.FetchNodes(n.factory)
if err != nil {
return nil, err
}
@ -37,6 +37,7 @@ func (n *Node) List(_ context.Context) ([]runtime.Object, error) {
}
oo[i] = &unstructured.Unstructured{Object: o}
}
return oo, nil
}
@ -99,8 +100,8 @@ func nodeMetricsFor(o runtime.Object, mmx *mv1beta1.NodeMetricsList) *mv1beta1.N
return nil
}
func (n *Node) nodePods(f Factory, node string) ([]*v1.Pod, error) {
pp, err := f.List("v1/pods", render.AllNamespaces, labels.Everything())
func (n *Node) nodePods(f dao.Factory, node string) ([]*v1.Pod, error) {
pp, err := f.List("v1/pods", render.AllNamespaces, true, labels.Everything())
if err != nil {
return nil, err
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log"
rbacv1 "k8s.io/api/rbac/v1"
@ -117,8 +118,8 @@ func (p *Policy) loadRoleBinding(kind, name string) (render.Policies, error) {
return rows, nil
}
func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) {
oo, err := f.List("rbac.authorization.k8s.io/v1/clusterrolebindings", render.ClusterScope, labels.Everything())
func fetchClusterRoleBindings(f dao.Factory) ([]rbacv1.ClusterRoleBinding, error) {
oo, err := f.List(crbGVR, render.ClusterScope, true, labels.Everything())
if err != nil {
return nil, err
}
@ -135,8 +136,8 @@ func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) {
return crbs, nil
}
func fetchRoleBindings(f Factory) ([]rbacv1.RoleBinding, error) {
oo, err := f.List("rbac.authorization.k8s.io/v1/rolebindings", render.ClusterScope, labels.Everything())
func fetchRoleBindings(f dao.Factory) ([]rbacv1.RoleBinding, error) {
oo, err := f.List(rbGVR, render.ClusterScope, true, labels.Everything())
if err != nil {
return nil, err
}
@ -173,7 +174,7 @@ func (p *Policy) fetchRoleBindingSubjects(kind, name string) ([]string, error) {
func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) {
const gvr = "rbac.authorization.k8s.io/v1/clusterroles"
oo, err := p.factory.List(gvr, render.ClusterScope, labels.Everything())
oo, err := p.factory.List(gvr, render.ClusterScope, true, labels.Everything())
if err != nil {
return nil, err
}
@ -193,7 +194,7 @@ func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) {
func (p *Policy) fetchRoles() ([]rbacv1.Role, error) {
const gvr = "rbac.authorization.k8s.io/v1/roles"
oo, err := p.factory.List(gvr, render.AllNamespaces, labels.Everything())
oo, err := p.factory.List(gvr, render.AllNamespaces, true, labels.Everything())
if err != nil {
return nil, err
}

View File

@ -37,7 +37,8 @@ func (r *Rbac) List(ctx context.Context) ([]runtime.Object, error) {
return r.Resource.List(ctx)
}
switch client.GVR(r.gvr).ToR() {
res := client.NewGVR(r.gvr)
switch res.ToR() {
case "clusterrolebindings":
return r.loadClusterRoleBinding(path)
case "rolebindings":
@ -47,12 +48,12 @@ func (r *Rbac) List(ctx context.Context) ([]runtime.Object, error) {
case "roles":
return r.loadRole(path)
default:
return nil, fmt.Errorf("expecting clusterrole/role but found %s", client.GVR(r.gvr).ToR())
return nil, fmt.Errorf("expecting clusterrole/role but found %s", res.ToR())
}
}
func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) {
o, err := r.factory.Get(crbGVR, path, labels.Everything())
o, err := r.factory.Get(crbGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
@ -63,7 +64,7 @@ func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) {
return nil, err
}
crbo, err := r.factory.Get(crGVR, client.FQN("-", crb.RoleRef.Name), labels.Everything())
crbo, err := r.factory.Get(crGVR, client.FQN("-", crb.RoleRef.Name), true, labels.Everything())
if err != nil {
return nil, err
}
@ -77,7 +78,7 @@ func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) {
}
func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) {
o, err := r.factory.Get(rbGVR, path, labels.Everything())
o, err := r.factory.Get(rbGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
@ -88,7 +89,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) {
}
if rb.RoleRef.Kind == "ClusterRole" {
o, e := r.factory.Get(crGVR, client.FQN("-", rb.RoleRef.Name), labels.Everything())
o, e := r.factory.Get(crGVR, client.FQN("-", rb.RoleRef.Name), true, labels.Everything())
if e != nil {
return nil, e
}
@ -100,7 +101,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) {
return asRuntimeObjects(parseRules(render.ClusterScope, "-", cr.Rules)), nil
}
ro, err := r.factory.Get(rGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), labels.Everything())
ro, err := r.factory.Get(rGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), true, labels.Everything())
if err != nil {
return nil, err
}
@ -114,7 +115,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) {
}
func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) {
o, err := r.factory.Get(crGVR, path, labels.Everything())
o, err := r.factory.Get(crGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}
@ -129,7 +130,7 @@ func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) {
}
func (r *Rbac) loadRole(path string) ([]runtime.Object, error) {
o, err := r.factory.Get(rGVR, path, labels.Everything())
o, err := r.factory.Get(rGVR, path, true, labels.Everything())
if err != nil {
return nil, err
}

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@ -12,11 +13,11 @@ import (
// Resource represents a generic resource model.
type Resource struct {
namespace, gvr string
factory Factory
factory dao.Factory
}
// Init initializes the model.
func (r *Resource) Init(ns, gvr string, f Factory) {
func (r *Resource) Init(ns, gvr string, f dao.Factory) {
r.namespace, r.gvr, r.factory = ns, gvr, f
}
@ -28,7 +29,7 @@ func (r *Resource) List(ctx context.Context) ([]runtime.Object, error) {
lsel = sel.AsSelector()
}
return r.factory.List(r.gvr, r.namespace, lsel)
return r.factory.List(r.gvr, r.namespace, true, lsel)
}
// Hydrate renders all rows.

View File

@ -7,13 +7,14 @@ import (
"time"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/runtime"
)
const (
refreshRate = 1 * time.Second
refreshRate = 2 * time.Second
noDataCount = 2
)
@ -159,17 +160,17 @@ func (t *Table) fireTableLoadFailed(err error) {
}
func (t *Table) list(ctx context.Context, l Lister) ([]runtime.Object, error) {
factory, ok := ctx.Value(internal.KeyFactory).(Factory)
factory, ok := ctx.Value(internal.KeyFactory).(dao.Factory)
if !ok {
return nil, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory))
}
l.Init(t.namespace, string(t.gvr), factory)
l.Init(t.namespace, t.gvr, factory)
return l.List(ctx)
}
func (t *Table) reconcile(ctx context.Context) error {
meta, ok := Registry[string(t.gvr)]
meta, ok := Registry[t.gvr]
if !ok {
log.Debug().Msgf("Resource %s not found in registry. Going generic!", t.gvr)
meta = ResourceMeta{

View File

@ -3,13 +3,10 @@ package model
import (
"context"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/watch"
"github.com/derailed/tview"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
)
// Igniter represents a runnable view.
@ -60,7 +57,7 @@ type Renderer interface {
// Lister represents a resource lister.
type Lister interface {
// Init initializes a resource.
Init(ns, gvr string, f Factory)
Init(ns, gvr string, f dao.Factory)
// List returns a collection of resources.
List(context.Context) ([]runtime.Object, error)
@ -69,30 +66,6 @@ type Lister interface {
Hydrate(oo []runtime.Object, rr render.Rows, r Renderer) error
}
// Factory represents a K8s resource factory.
type Factory interface {
// Client retrieves an api client.
Client() client.Connection
// Get fetch a given resource.
Get(gvr, path string, sel labels.Selector) (runtime.Object, error)
// List fetch a collection of resources.
List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error)
// ForResource fetch an informer for a given resource.
ForResource(ns, gvr string) informers.GenericInformer
// CanForResource fetch an informer for a given resource.
CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error)
// WaitForCacheSync synchronize the cache.
WaitForCacheSync()
// Forwards returns all portforwards.
Forwarders() watch.Forwarders
}
// ResourceMeta represents model info about a resource.
type ResourceMeta struct {
Model Lister

View File

@ -38,7 +38,7 @@ func (Alias) Render(o interface{}, ns string, r *Row) error {
}
r.ID = a.GVR
gvr := client.GVR(a.GVR)
gvr := client.NewGVR(a.GVR)
res, grp := gvr.ToRAndG()
r.Fields = append(r.Fields,
res,

View File

@ -283,6 +283,7 @@ func (t *Table) ClearMarks() {
// Refresh update the table data.
func (t *Table) Refresh() {
// BOZO!! Really want to tell model reload now. Refactor!
t.Update(t.model.Peek())
}

View File

@ -18,7 +18,7 @@ import (
)
func TestAliasNew(t *testing.T) {
v := view.NewAlias(client.GVR("aliases"))
v := view.NewAlias(client.NewGVR("aliases"))
assert.Nil(t, v.Init(makeContext()))
assert.Equal(t, "Aliases", v.Name())
@ -26,7 +26,7 @@ func TestAliasNew(t *testing.T) {
}
func TestAliasSearch(t *testing.T) {
v := view.NewAlias(client.GVR("aliases"))
v := view.NewAlias(client.NewGVR("aliases"))
assert.Nil(t, v.Init(makeContext()))
v.GetTable().SetModel(&testModel{})
v.GetTable().SearchBuff().SetActive(true)
@ -39,7 +39,7 @@ func TestAliasSearch(t *testing.T) {
}
func TestAliasGoto(t *testing.T) {
v := view.NewAlias(client.GVR("aliases"))
v := view.NewAlias(client.NewGVR("aliases"))
assert.Nil(t, v.Init(makeContext()))
v.GetTable().Select(0, 0)

View File

@ -404,7 +404,7 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
if err := a.inject(NewAlias("aliases")); err != nil {
if err := a.inject(NewAlias(client.NewGVR("aliases"))); err != nil {
a.Flash().Err(err)
}

View File

@ -50,7 +50,8 @@ func (b *Browser) Init(ctx context.Context) error {
return err
}
if !dao.IsK9sMeta(b.meta) {
if _, e := b.app.factory.CanForResource(b.app.Config.ActiveNamespace(), b.GVR()); e != nil {
log.Debug().Msgf("BROWSER ACTIVE_NS %q", b.app.Config.ActiveNamespace())
if _, e := b.app.factory.CanForResource(b.app.Config.ActiveNamespace(), b.GVR(), []string{"list", "watch"}); e != nil {
return e
}
}
@ -196,7 +197,7 @@ func (b *Browser) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
if b.enterFn != nil {
f = b.enterFn
}
f(b.app, b.GetModel().GetNamespace(), string(b.gvr), path)
f(b.app, b.GetModel().GetNamespace(), b.gvr.String(), path)
return nil
}
@ -236,7 +237,7 @@ func (b *Browser) describeCmd(evt *tcell.EventKey) *tcell.EventKey {
if path == "" {
return evt
}
describeResource(b.app, b.GetModel().GetNamespace(), string(b.gvr), path)
describeResource(b.app, b.GetModel().GetNamespace(), b.gvr.String(), path)
return nil
}
@ -319,7 +320,7 @@ func (b *Browser) defaultContext() context.Context {
ctx := context.Background()
ctx = context.WithValue(ctx, internal.KeyFactory, b.app.factory)
ctx = context.WithValue(ctx, internal.KeyGVR, string(b.gvr))
ctx = context.WithValue(ctx, internal.KeyGVR, b.gvr.String())
ctx = context.WithValue(ctx, internal.KeyPath, b.Path)
ctx = context.WithValue(ctx, internal.KeyLabels, "")

View File

@ -5,6 +5,7 @@ import (
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/ui"
@ -12,7 +13,6 @@ import (
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
@ -142,7 +142,7 @@ func (c *ClusterInfo) updateStyle() {
}
func fetchResources(app *App) (*v1.NodeList, *mv1beta1.NodeMetricsList, error) {
nos, err := app.factory.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{})
nn, err := dao.FetchNodes(app.factory)
if err != nil {
return nil, nil, err
}
@ -153,7 +153,7 @@ func fetchResources(app *App) (*v1.NodeList, *mv1beta1.NodeMetricsList, error) {
return nil, nil, err
}
return nos, nmx, nil
return nn, nmx, nil
}
func (c *ClusterInfo) refreshMetrics(cluster *model.Cluster, row int) {

View File

@ -121,7 +121,7 @@ func (c *Command) viewMetaFor(cmd string) (string, *MetaViewer, error) {
return "", nil, fmt.Errorf("Huh? `%s` Command not found", cmd)
}
v, ok := customViewers[client.GVR(gvr)]
v, ok := customViewers[client.NewGVR(gvr)]
if !ok {
return gvr, &MetaViewer{viewerFn: NewBrowser}, nil
}
@ -133,10 +133,10 @@ func (c *Command) componentFor(gvr string, v *MetaViewer) ResourceViewer {
var view ResourceViewer
if v.viewerFn != nil {
log.Debug().Msgf("Custom viewer for %s", gvr)
view = v.viewerFn(client.GVR(gvr))
view = v.viewerFn(client.NewGVR(gvr))
} else {
log.Debug().Msgf("Generic viewer for %s", gvr)
view = NewBrowser(client.GVR(gvr))
view = NewBrowser(client.NewGVR(gvr))
}
if v.enterFn != nil {
@ -152,7 +152,7 @@ func (c *Command) exec(gvr string, comp model.Component, clearStack bool) error
return fmt.Errorf("No component given for %s", gvr)
}
g := client.GVR(gvr)
g := client.NewGVR(gvr)
c.app.Flash().Infof("Viewing %s resource...", g.ToR())
log.Debug().Msgf("Running Command %s", gvr)
c.app.Config.SetActiveView(g.ToR())

View File

@ -9,7 +9,7 @@ import (
)
func TestContainerNew(t *testing.T) {
c := view.NewContainer(client.GVR("containers"))
c := view.NewContainer(client.NewGVR("containers"))
assert.Nil(t, c.Init(makeCtx()))
assert.Equal(t, "Containers", c.Name())

View File

@ -44,7 +44,7 @@ func (c *Context) useCtx(app *App, _, res, path string) {
}
func (c *Context) useContext(name string) error {
res, err := dao.AccessorFor(c.App().factory, client.GVR(c.GVR()))
res, err := dao.AccessorFor(c.App().factory, client.NewGVR(c.GVR()))
if err != nil {
return nil
}

View File

@ -9,7 +9,7 @@ import (
)
func TestContext(t *testing.T) {
ctx := view.NewContext(client.GVR("contexts"))
ctx := view.NewContext(client.NewGVR("contexts"))
assert.Nil(t, ctx.Init(makeCtx()))
assert.Equal(t, "Contexts", ctx.Name())

View File

@ -34,7 +34,7 @@ func NewCronJob(gvr client.GVR) ResourceViewer {
func (c *CronJob) showJobs(app *App, ns, gvr, path string) {
log.Debug().Msgf("Showing Jobs %q:%q -- %q", ns, gvr, path)
o, err := app.factory.Get(gvr, path, labels.Everything())
o, err := app.factory.Get(gvr, path, true, labels.Everything())
if err != nil {
app.Flash().Err(err)
return
@ -47,7 +47,7 @@ func (c *CronJob) showJobs(app *App, ns, gvr, path string) {
return
}
v := NewJob(client.GVR("batch/v1/jobs"))
v := NewJob(client.NewGVR("batch/v1/jobs"))
v.SetContextFn(jobCtx(path, string(cj.UID)))
if err := app.inject(v); err != nil {
app.Flash().Err(err)
@ -73,7 +73,7 @@ func (c *CronJob) trigger(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
res, err := dao.AccessorFor(c.App().factory, client.GVR(c.GVR()))
res, err := dao.AccessorFor(c.App().factory, client.NewGVR(c.GVR()))
if err != nil {
return nil
}

View File

@ -43,7 +43,7 @@ func (d *Deploy) bindKeys(aa ui.KeyActions) {
}
func (d *Deploy) showPods(app *App, _, _, path string) {
o, err := app.factory.Get(d.GVR(), path, labels.Everything())
o, err := app.factory.Get(d.GVR(), path, true, labels.Everything())
if err != nil {
app.Flash().Err(err)
return

View File

@ -9,7 +9,7 @@ import (
)
func TestDeploy(t *testing.T) {
v := view.NewDeploy(client.GVR("apps/v1/deployments"))
v := view.NewDeploy(client.NewGVR("apps/v1/deployments"))
assert.Nil(t, v.Init(makeCtx()))
assert.Equal(t, "Deployments", v.Name())

View File

@ -42,7 +42,7 @@ func (d *DaemonSet) bindKeys(aa ui.KeyActions) {
}
func (d *DaemonSet) showPods(app *App, _, _, path string) {
o, err := app.factory.Get(d.GVR(), path, labels.Everything())
o, err := app.factory.Get(d.GVR(), path, true, labels.Everything())
if err != nil {
d.App().Flash().Err(err)
return

View File

@ -9,7 +9,7 @@ import (
)
func TestDaemonSet(t *testing.T) {
v := view.NewDaemonSet(client.GVR("apps/v1/daemonsets"))
v := view.NewDaemonSet(client.NewGVR("apps/v1/daemonsets"))
assert.Nil(t, v.Init(makeCtx()))
assert.Equal(t, "DaemonSets", v.Name())

View File

@ -8,6 +8,7 @@ import (
"strconv"
"strings"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render"
@ -34,7 +35,7 @@ type Help struct {
// NewHelp returns a new help viewer.
func NewHelp() *Help {
return &Help{
Table: NewTable(helpTitle),
Table: NewTable(client.NewGVR("help")),
}
}

View File

@ -14,7 +14,7 @@ func TestHelp(t *testing.T) {
ctx := makeCtx()
app := ctx.Value(internal.KeyApp).(*view.App)
po := view.NewPod(client.GVR("v1/pods"))
po := view.NewPod(client.NewGVR("v1/pods"))
po.Init(ctx)
app.Content.Push(po)

View File

@ -62,7 +62,7 @@ func defaultK9sEnv(app *App, sel string, row render.Row) K9sEnv {
func describeResource(app *App, _, gvr, path string) {
ns, n := client.Namespaced(path)
yaml, err := dao.Describe(app.Conn(), client.GVR(gvr), ns, n)
yaml, err := dao.Describe(app.Conn(), client.NewGVR(gvr), ns, n)
if err != nil {
app.Flash().Errf("Describe command failed: %s", err)
return
@ -100,7 +100,7 @@ func showPods(app *App, path, labelSel, fieldSel string) {
log.Debug().Msgf("SHOW PODS %q -- %q -- %q", path, labelSel, fieldSel)
app.switchNS("")
v := NewPod(client.GVR("v1/pods"))
v := NewPod(client.NewGVR("v1/pods"))
v.SetContextFn(podCtx(path, labelSel, fieldSel))
v.GetTable().SetColorerFn(render.Pod{}.ColorerFunc())

View File

@ -24,7 +24,7 @@ func NewJob(gvr client.GVR) ResourceViewer {
}
func (*Job) showPods(app *App, _, gvr, path string) {
o, err := app.factory.Get(gvr, path, labels.Everything())
o, err := app.factory.Get(gvr, path, true, labels.Everything())
if err != nil {
app.Flash().Err(err)
return

View File

@ -28,7 +28,7 @@ func TestLogAnsi(t *testing.T) {
}
func TestLogFlush(t *testing.T) {
v := NewLog(client.GVR("v1/pods"), "fred/p1", "blee", false)
v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false)
v.Init(makeContext())
v.Flush(2, []string{"blee", "bozo"})
@ -41,7 +41,7 @@ func TestLogFlush(t *testing.T) {
}
func TestLogViewSave(t *testing.T) {
v := NewLog(client.GVR("v1/pods"), "fred/p1", "blee", false)
v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false)
v.Init(makeContext())
app := makeApp()
@ -55,7 +55,7 @@ func TestLogViewSave(t *testing.T) {
}
func TestLogViewNav(t *testing.T) {
v := NewLog(client.GVR("v1/pods"), "fred/p1", "blee", false)
v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false)
v.Init(makeContext())
var buff []string
@ -70,7 +70,7 @@ func TestLogViewNav(t *testing.T) {
}
func TestLogViewClear(t *testing.T) {
v := NewLog(client.GVR("v1/pods"), "fred/p1", "blee", false)
v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false)
v.Init(makeContext())
v.Flush(2, []string{"blee", "bozo"})

View File

@ -59,7 +59,7 @@ func (l *LogsExtender) showLogs(path string, prev bool) {
log.Debug().Msgf("SHOWING LOGS path %q", path)
// Need to load and wait for pods
ns, _ := render.Namespaced(path)
_, err := l.App().factory.CanForResource(ns, "v1/pods", watch.ReadVerbs...)
_, err := l.App().factory.CanForResource(ns, "v1/pods", watch.ReadVerbs)
if err != nil {
l.App().Flash().Err(err)
return
@ -70,7 +70,7 @@ func (l *LogsExtender) showLogs(path string, prev bool) {
if l.containerFn != nil {
co = l.containerFn()
}
if err := l.App().inject(NewLog(client.GVR(l.GVR()), path, co, prev)); err != nil {
if err := l.App().inject(NewLog(client.NewGVR(l.GVR()), path, co, prev)); err != nil {
l.App().Flash().Err(err)
}
}

View File

@ -45,7 +45,7 @@ func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
}
sel := n.GetTable().GetSelectedItem()
gvr := client.GVR(n.GVR()).AsGVR()
gvr := client.NewGVR(n.GVR()).AsGVR()
o, err := n.App().factory.Client().DynDialOrDie().Resource(gvr).Get(sel, metav1.GetOptions{})
if err != nil {
n.App().Flash().Errf("Unable to get resource %q -- %s", n.GVR(), err)

View File

@ -9,7 +9,7 @@ import (
)
func TestNSCleanser(t *testing.T) {
ns := view.NewNamespace(client.GVR("v1/namespaces"))
ns := view.NewNamespace(client.NewGVR("v1/namespaces"))
assert.Nil(t, ns.Init(makeCtx()))
assert.Equal(t, "Namespaces", ns.Name())

View File

@ -54,7 +54,7 @@ func (p *Pod) bindKeys(aa ui.KeyActions) {
func (p *Pod) showContainers(app *App, ns, gvr, path string) {
log.Debug().Msgf("SHOW CONTAINERS %q -- %q -- %q", gvr, ns, path)
co := NewContainer(client.GVR("containers"))
co := NewContainer(client.NewGVR("containers"))
co.SetContextFn(p.podContext)
if err := app.inject(co); err != nil {
app.Flash().Err(err)
@ -73,7 +73,7 @@ func (p *Pod) killCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
res, err := dao.AccessorFor(p.App().factory, client.GVR(p.GVR()))
res, err := dao.AccessorFor(p.App().factory, client.NewGVR(p.GVR()))
if err != nil {
p.App().Flash().Err(err)
return nil
@ -140,7 +140,7 @@ func (p *Pod) shellIn(path, co string) {
// Helpers...
func fetchContainers(f *watch.Factory, path string, includeInit bool) ([]string, error) {
o, err := f.Get("v1/pods", path, labels.Everything())
o, err := f.Get("v1/pods", path, true, labels.Everything())
if err != nil {
return nil, err
}

View File

@ -12,7 +12,7 @@ import (
)
func TestPodNew(t *testing.T) {
po := view.NewPod(client.GVR("v1/pods"))
po := view.NewPod(client.NewGVR("v1/pods"))
assert.Nil(t, po.Init(makeCtx()))
assert.Equal(t, "Pods", po.Name())

View File

@ -26,7 +26,7 @@ type Policy struct {
// NewPolicy returns a new viewer.
func NewPolicy(app *App, subject, name string) *Policy {
p := Policy{
ResourceViewer: NewBrowser(client.GVR("policy")),
ResourceViewer: NewBrowser(client.NewGVR("policy")),
subjectKind: subject,
subjectName: name,
}

View File

@ -9,6 +9,7 @@ import (
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/perf"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/ui"
@ -57,7 +58,7 @@ func (p *PortForward) bindKeys(aa ui.KeyActions) {
}
func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
if err := p.App().inject(NewBenchmark("benchmarks")); err != nil {
if err := p.App().inject(NewBenchmark(client.NewGVR("benchmarks"))); err != nil {
p.App().Flash().Err(err)
}
@ -133,15 +134,20 @@ func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
sel := p.GetTable().GetSelectedItem()
if sel == "" {
path := p.GetTable().GetSelectedItem()
if path == "" {
return nil
}
log.Debug().Msgf("PF DELETE %q", sel)
log.Debug().Msgf("PF DELETE %q", path)
showModal(p.App().Content.Pages, fmt.Sprintf("Delete PortForward `%s?", sel), func() {
p.App().factory.DeleteForwarder(sel)
p.App().Flash().Infof("PortForward %s deleted!", sel)
showModal(p.App().Content.Pages, fmt.Sprintf("Delete PortForward `%s?", path), func() {
var pf dao.PortForward
pf.Init(p.App().factory, client.NewGVR("portforwards"))
if err := pf.Delete(path, true, true); err != nil {
p.App().Flash().Err(err)
return
}
p.App().Flash().Infof("PortForward %s deleted!", path)
p.GetTable().Refresh()
})

View File

@ -9,7 +9,7 @@ import (
)
func TestPortForwardNew(t *testing.T) {
pf := view.NewPortForward(client.GVR("portforwards"))
pf := view.NewPortForward(client.NewGVR("portforwards"))
assert.Nil(t, pf.Init(makeCtx()))
assert.Equal(t, "PortForwards", pf.Name())

View File

@ -36,7 +36,7 @@ func (r *Rbac) bindKeys(aa ui.KeyActions) {
}
func showRules(app *App, _, gvr, path string) {
v := NewRbac(client.GVR("rbac"))
v := NewRbac(client.NewGVR("rbac"))
v.SetContextFn(rbacCtxt(gvr, path))
if err := app.inject(v); err != nil {

View File

@ -9,7 +9,7 @@ import (
)
func TestRbacNew(t *testing.T) {
v := view.NewRbac(client.GVR("rbac"))
v := view.NewRbac(client.NewGVR("rbac"))
assert.Nil(t, v.Init(makeCtx()))
assert.Equal(t, "Rbac", v.Name())

View File

@ -19,103 +19,103 @@ func loadCustomViewers() MetaViewers {
}
func coreRes(vv MetaViewers) {
vv["v1/namespaces"] = MetaViewer{
vv[client.NewGVR("v1/namespaces")] = MetaViewer{
viewerFn: NewNamespace,
}
vv["v1/events"] = MetaViewer{
vv[client.NewGVR("v1/events")] = MetaViewer{
viewerFn: NewEvent,
}
vv["v1/pods"] = MetaViewer{
vv[client.NewGVR("v1/pods")] = MetaViewer{
viewerFn: NewPod,
}
vv["v1/services"] = MetaViewer{
vv[client.NewGVR("v1/services")] = MetaViewer{
viewerFn: NewService,
}
vv["v1/nodes"] = MetaViewer{
vv[client.NewGVR("v1/nodes")] = MetaViewer{
viewerFn: NewNode,
}
vv["v1/secrets"] = MetaViewer{
vv[client.NewGVR("v1/secrets")] = MetaViewer{
viewerFn: NewSecret,
}
}
func miscRes(vv MetaViewers) {
vv["contexts"] = MetaViewer{
vv[client.NewGVR("contexts")] = MetaViewer{
viewerFn: NewContext,
}
vv["containers"] = MetaViewer{
vv[client.NewGVR("containers")] = MetaViewer{
viewerFn: NewContainer,
}
vv["portforwards"] = MetaViewer{
vv[client.NewGVR("portforwards")] = MetaViewer{
viewerFn: NewPortForward,
}
vv["screendumps"] = MetaViewer{
vv[client.NewGVR("screendumps")] = MetaViewer{
viewerFn: NewScreenDump,
}
vv["benchmarks"] = MetaViewer{
vv[client.NewGVR("benchmarks")] = MetaViewer{
viewerFn: NewBenchmark,
}
vv["aliases"] = MetaViewer{
vv[client.NewGVR("aliases")] = MetaViewer{
viewerFn: NewAlias,
}
}
func appsRes(vv MetaViewers) {
vv["apps/v1/deployments"] = MetaViewer{
vv[client.NewGVR("apps/v1/deployments")] = MetaViewer{
viewerFn: NewDeploy,
}
vv["apps/v1/replicasets"] = MetaViewer{
vv[client.NewGVR("apps/v1/replicasets")] = MetaViewer{
viewerFn: NewReplicaSet,
}
vv["apps/v1/statefulsets"] = MetaViewer{
vv[client.NewGVR("apps/v1/statefulsets")] = MetaViewer{
viewerFn: NewStatefulSet,
}
vv["apps/v1/daemonsets"] = MetaViewer{
vv[client.NewGVR("apps/v1/daemonsets")] = MetaViewer{
viewerFn: NewDaemonSet,
}
vv["extensions/v1beta1/daemonsets"] = MetaViewer{
vv[client.NewGVR("extensions/v1beta1/daemonsets")] = MetaViewer{
viewerFn: NewDaemonSet,
}
}
func rbacRes(vv MetaViewers) {
vv["rbac"] = MetaViewer{
vv[client.NewGVR("rbac")] = MetaViewer{
enterFn: showRules,
}
vv["users"] = MetaViewer{
vv[client.NewGVR("users")] = MetaViewer{
viewerFn: NewUser,
}
vv["groups"] = MetaViewer{
vv[client.NewGVR("groups")] = MetaViewer{
viewerFn: NewGroup,
}
vv["rbac.authorization.k8s.io/v1/clusterroles"] = MetaViewer{
vv[client.NewGVR("rbac.authorization.k8s.io/v1/clusterroles")] = MetaViewer{
enterFn: showRules,
}
vv["rbac.authorization.k8s.io/v1/roles"] = MetaViewer{
vv[client.NewGVR("rbac.authorization.k8s.io/v1/roles")] = MetaViewer{
enterFn: showRules,
}
vv["rbac.authorization.k8s.io/v1/clusterrolebindings"] = MetaViewer{
vv[client.NewGVR("rbac.authorization.k8s.io/v1/clusterrolebindings")] = MetaViewer{
enterFn: showRules,
}
vv["rbac.authorization.k8s.io/v1/rolebindings"] = MetaViewer{
vv[client.NewGVR("rbac.authorization.k8s.io/v1/rolebindings")] = MetaViewer{
enterFn: showRules,
}
}
func batchRes(vv MetaViewers) {
vv["batch/v1beta1/cronjobs"] = MetaViewer{
vv[client.NewGVR("batch/v1beta1/cronjobs")] = MetaViewer{
viewerFn: NewCronJob,
}
vv["batch/v1/jobs"] = MetaViewer{
vv[client.NewGVR("batch/v1/jobs")] = MetaViewer{
viewerFn: NewJob,
}
}
func extRes(vv MetaViewers) {
vv["apiextensions.k8s.io/v1/customresourcedefinitions"] = MetaViewer{
vv[client.NewGVR("apiextensions.k8s.io/v1/customresourcedefinitions")] = MetaViewer{
enterFn: showCRD,
}
vv["apiextensions.k8s.io/v1beta1/customresourcedefinitions"] = MetaViewer{
vv[client.NewGVR("apiextensions.k8s.io/v1beta1/customresourcedefinitions")] = MetaViewer{
enterFn: showCRD,
}
}

View File

@ -51,7 +51,7 @@ func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (r *RestartExtender) restartRollout(path string) error {
res, err := dao.AccessorFor(r.App().factory, client.GVR(r.GVR()))
res, err := dao.AccessorFor(r.App().factory, client.NewGVR(r.GVR()))
if err != nil {
return nil
}

View File

@ -49,7 +49,7 @@ func (r *ReplicaSet) bindKeys(aa ui.KeyActions) {
}
func (r *ReplicaSet) showPods(app *App, _, gvr, path string) {
o, err := app.factory.Get(r.GVR(), path, labels.Everything())
o, err := app.factory.Get(r.GVR(), path, true, labels.Everything())
if err != nil {
app.Flash().Err(err)
return
@ -104,7 +104,7 @@ func (r *ReplicaSet) showModal(msg string, done func(int, string)) {
// Helpers...
func findRS(f *watch.Factory, path string) (*v1.ReplicaSet, error) {
o, err := f.Get("apps/v1/replicasets", path, labels.Everything())
o, err := f.Get("apps/v1/replicasets", path, true, labels.Everything())
if err != nil {
return nil, err
}
@ -119,7 +119,7 @@ func findRS(f *watch.Factory, path string) (*v1.ReplicaSet, error) {
}
func findDP(f *watch.Factory, path string) (*appsv1.Deployment, error) {
o, err := f.Get("apps/v1/deployments", path, labels.Everything())
o, err := f.Get("apps/v1/deployments", path, true, labels.Everything())
if err != nil {
return nil, err
}

View File

@ -106,7 +106,7 @@ func (s *ScaleExtender) makeStyledForm() *tview.Form {
}
func (s *ScaleExtender) scale(path string, replicas int) error {
res, err := dao.AccessorFor(s.App().factory, client.GVR(s.GVR()))
res, err := dao.AccessorFor(s.App().factory, client.NewGVR(s.GVR()))
if err != nil {
return nil
}

View File

@ -9,7 +9,7 @@ import (
)
func TestScreenDumpNew(t *testing.T) {
po := view.NewScreenDump(client.GVR("screendumps"))
po := view.NewScreenDump(client.NewGVR("screendumps"))
assert.Nil(t, po.Init(makeCtx()))
assert.Equal(t, "ScreenDumps", po.Name())

View File

@ -39,7 +39,7 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
o, err := s.App().factory.Get("v1/secrets", path, labels.Everything())
o, err := s.App().factory.Get(s.GVR(), path, true, labels.Everything())
if err != nil {
s.App().Flash().Err(err)
return nil

View File

@ -9,7 +9,7 @@ import (
)
func TestSecretNew(t *testing.T) {
s := view.NewSecret(client.GVR("v1/secrets"))
s := view.NewSecret(client.NewGVR("v1/secrets"))
assert.Nil(t, s.Init(makeCtx()))
assert.Equal(t, "Secrets", s.Name())

View File

@ -50,7 +50,7 @@ func (s *StatefulSet) showPods(app *App, _, gvr, path string) {
}
func (s *StatefulSet) sts(path string) (*appsv1.StatefulSet, error) {
o, err := s.App().factory.Get(s.GVR(), path, labels.Everything())
o, err := s.App().factory.Get(s.GVR(), path, true, labels.Everything())
if err != nil {
return nil, err
}

View File

@ -9,7 +9,7 @@ import (
)
func TestStatefulSetNew(t *testing.T) {
s := view.NewStatefulSet(client.GVR("apps/v1/statefulsets"))
s := view.NewStatefulSet(client.NewGVR("apps/v1/statefulsets"))
assert.Nil(t, s.Init(makeCtx()))
assert.Equal(t, "StatefulSets", s.Name())

View File

@ -47,7 +47,7 @@ func (s *Service) bindKeys(aa ui.KeyActions) {
}
func (s *Service) showPods(app *App, ns, gvr, path string) {
o, err := app.factory.Get(gvr, path, labels.Everything())
o, err := app.factory.Get(gvr, path, true, labels.Everything())
if err != nil {
app.Flash().Err(err)
return

View File

@ -128,7 +128,7 @@ func init() {
}
func TestServiceNew(t *testing.T) {
s := view.NewService(client.GVR("v1/services"))
s := view.NewService(client.NewGVR("v1/services"))
assert.Nil(t, s.Init(makeCtx()))
assert.Equal(t, "Services", s.Name())

View File

@ -27,7 +27,7 @@ type Table struct {
// NewTable returns a new viewer.
func NewTable(gvr client.GVR) *Table {
return &Table{
Table: ui.NewTable(string(gvr)),
Table: ui.NewTable(gvr.String()),
gvr: gvr,
}
}
@ -50,7 +50,7 @@ func (t *Table) Init(ctx context.Context) (err error) {
func (t *Table) Name() string { return t.BaseTitle }
// GVR returns a resource descriptor.
func (t *Table) GVR() string { return string(t.gvr) }
func (t *Table) GVR() string { return t.gvr.String() }
// SetBindKeysFn adds additional key bindings.
func (t *Table) SetBindKeysFn(f BindKeysFunc) { t.bindKeysFn = f }
@ -134,7 +134,7 @@ func (t *Table) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
o, err := t.app.factory.Get(string(t.gvr), path, labels.Everything())
o, err := t.app.factory.Get(t.GVR(), path, true, labels.Everything())
if err != nil {
t.app.Flash().Errf("Unable to get resource %q -- %s", t.gvr, err)
return nil

View File

@ -8,6 +8,7 @@ import (
"time"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render"
@ -18,7 +19,7 @@ import (
)
func TestTableSave(t *testing.T) {
v := NewTable("test")
v := NewTable(client.NewGVR("test"))
v.Init(makeContext())
v.SetTitle("k9s-test")
@ -31,7 +32,7 @@ func TestTableSave(t *testing.T) {
}
func TestTableNew(t *testing.T) {
v := NewTable("test")
v := NewTable(client.NewGVR("test"))
v.Init(makeContext())
data := render.NewTableData()
@ -60,7 +61,7 @@ func TestTableNew(t *testing.T) {
}
func TestTableViewFilter(t *testing.T) {
v := NewTable("test")
v := NewTable(client.NewGVR("test"))
v.Init(makeContext())
v.SetModel(&testTableModel{})
v.SearchBuff().SetActive(true)
@ -70,7 +71,7 @@ func TestTableViewFilter(t *testing.T) {
}
func TestTableViewSort(t *testing.T) {
v := NewTable("test")
v := NewTable(client.NewGVR("test"))
v.Init(makeContext())
v.SetModel(&testTableModel{})
v.SortColCmd(1, true)(nil)

View File

@ -40,7 +40,7 @@ func NewFactory(client client.Connection) *Factory {
// Start initializes the informers until caller cancels the context.
func (f *Factory) Start(ns string) {
log.Debug().Msgf("Starting factory in ns `%q", ns)
log.Debug().Msgf("Factory START with ns `%q", ns)
f.stopChan = make(chan struct{})
for ns, fac := range f.factories {
log.Debug().Msgf("Starting factory in ns %q", ns)
@ -61,38 +61,60 @@ func (f *Factory) Terminate() {
}
// List returns a resource collection.
func (f *Factory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) {
inf, err := f.CanForResource(ns, gvr, "list")
func (f *Factory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) {
defer func(t time.Time) {
log.Debug().Msgf("LIST time %v", time.Since(t))
}(time.Now())
Dump(f)
log.Debug().Msgf("List %q:%q", ns, gvr)
inf, err := f.CanForResource(ns, gvr, []string{"list", "watch"})
if err != nil {
return nil, err
}
if ns == clusterScope {
return inf.Lister().List(sel)
ns = allNamespaces
}
if wait {
f.waitForCacheSync(ns)
}
return inf.Lister().ByNamespace(ns).List(sel)
}
// Get retrieves a given resource.
func (f *Factory) Get(gvr, path string, sel labels.Selector) (runtime.Object, error) {
func (f *Factory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) {
defer func(t time.Time) {
log.Debug().Msgf("GET time %v", time.Since(t))
}(time.Now())
ns, n := namespaced(path)
inf, err := f.CanForResource(ns, gvr, "get")
inf, err := f.CanForResource(ns, gvr, []string{"get"})
if err != nil {
return nil, err
}
if ns == clusterScope {
return inf.Lister().Get(n)
ns = allNamespaces
}
if wait {
f.waitForCacheSync(ns)
}
return inf.Lister().ByNamespace(ns).Get(n)
}
func (f *Factory) waitForCacheSync(ns string) {
if fac, ok := f.factories[ns]; ok {
fac.WaitForCacheSync(f.stopChan)
}
}
// WaitForCacheSync waits for all factories to update their cache.
func (f *Factory) WaitForCacheSync() {
for _, fac := range f.factories {
for ns, fac := range f.factories {
m := fac.WaitForCacheSync(f.stopChan)
for k, v := range m {
log.Debug().Msgf("CACHE -- Loaded %q:%v", k, v)
log.Debug().Msgf("CACHE `%q Loaded %t:%s", ns, v, k)
}
}
}
@ -120,16 +142,15 @@ func (f *Factory) isClusterWide() bool {
return ok
}
func (f *Factory) preload(_ string) {
_, _ = f.CanForResource("", "apiextensions.k8s.io/v1beta1/customresourcedefinitions", ReadVerbs...)
// BOZO!!
// _, _ = f.CanForResource(ns, "v1/pods", verbs...)
// _, _ = f.CanForResource(clusterScope, "rbac.authorization.k8s.io/v1/clusterroles", verbs...)
// _, _ = f.CanForResource(allNamespaces, "rbac.authorization.k8s.io/v1/roles", verbs...)
}
// CanForResource return an informer is user has access.
func (f *Factory) CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error) {
func (f *Factory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) {
// If user can access resource cluster wide, prefer cluster wide factory.
if ns != allNamespaces {
auth, err := f.Client().CanI(allNamespaces, gvr, verbs)
if auth && err == nil {
return f.ForResource(allNamespaces, gvr), nil
}
}
auth, err := f.Client().CanI(ns, gvr, verbs)
if err != nil {
return nil, err
@ -145,23 +166,31 @@ func (f *Factory) CanForResource(ns, gvr string, verbs ...string) (informers.Gen
func (f *Factory) ForResource(ns, gvr string) informers.GenericInformer {
fact := f.ensureFactory(ns)
inf := fact.ForResource(toGVR(gvr))
if inf == nil {
log.Error().Err(fmt.Errorf("MEOW! No informer for %q:%q", ns, gvr))
return inf
}
log.Debug().Msgf("FOR_RESOURCE %q:%q", ns, gvr)
fact.Start(f.stopChan)
return inf
}
func (f *Factory) ensureFactory(ns string) di.DynamicSharedInformerFactory {
if ns == clusterScope {
ns = allNamespaces
}
if fac, ok := f.factories[ns]; ok {
return fac
}
log.Debug().Msgf("FACTORY_NEW for ns %q", ns)
f.factories[ns] = di.NewFilteredDynamicSharedInformerFactory(
f.client.DynDialOrDie(),
defaultResync,
ns,
nil,
)
f.preload(ns)
return f.factories[ns]
}
@ -173,6 +202,7 @@ func (f *Factory) AddForwarder(pf Forwarder) {
// DeleteForwarder deletes portforward for a given container.
func (f *Factory) DeleteForwarder(path string) {
f.forwarders.Dump()
count := f.forwarders.Kill(path)
log.Warn().Msgf("Deleted (%d) portforward for %q", count, path)

View File

@ -37,19 +37,10 @@ func Dump(f *Factory) {
}
// Debug for debug.
func Debug(f *Factory, gvr string) {
func Debug(f *Factory, ns string, gvr string) {
log.Debug().Msgf("----------- DEBUG FACTORY (%s) -------------", gvr)
inf := f.factories[allNamespaces].ForResource(toGVR(gvr))
inf := f.factories[ns].ForResource(toGVR(gvr))
for i, k := range inf.Informer().GetStore().ListKeys() {
log.Debug().Msgf("%d -- %s", i, k)
}
}
// Show for debug.
func Show(f *Factory, ns, gvr string) {
log.Debug().Msgf("----------- SHOW FACTORIES %q -------------", ns)
inf := f.ForResource(ns, gvr)
for _, k := range inf.Informer().GetStore().ListKeys() {
log.Debug().Msgf(" Key: %s", k)
}
}