diff --git a/.golangci.yml b/.golangci.yml index e5a47e1c..bf0d6d22 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -100,7 +100,7 @@ linters-settings: - atomicalign enable-all: false disable: - - shadow + # - shadow disable-all: false golint: # minimal confidence for issues, default is 0.8 diff --git a/internal/config/alias.go b/internal/config/alias.go index ba071a0f..34e53655 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -45,7 +45,7 @@ func (a Aliases) loadDefaults() { a.Alias["cr"] = "rbac.authorization.k8s.io/v1/clusterroles" a.Alias["crb"] = "rbac.authorization.k8s.io/v1/clusterrolebindings" a.Alias["ro"] = "rbac.authorization.k8s.io/v1/roles" - a.Alias["rob"] = "rbac.authorization.k8s.io/v1/rolebindings" + a.Alias["rb"] = "rbac.authorization.k8s.io/v1/rolebindings" a.Alias["np"] = "networking.k8s.io/v1/networkpolicies" { a.Alias["ctx"] = contexts diff --git a/internal/config/ns.go b/internal/config/ns.go index 2415b1b3..dc66d0ef 100644 --- a/internal/config/ns.go +++ b/internal/config/ns.go @@ -6,7 +6,7 @@ import ( const ( // MaxFavoritesNS number # favorite namespaces to keep in the configuration. - MaxFavoritesNS = 10 + MaxFavoritesNS = 9 defaultNS = "default" allNS = "all" ) diff --git a/internal/config/style.go b/internal/config/style.go index 1edf125c..14289964 100644 --- a/internal/config/style.go +++ b/internal/config/style.go @@ -132,7 +132,7 @@ func newStyle() Style { Body: newBody(), Frame: newFrame(), Info: newInfo(), - Table: newTable(), + Table: newGetTable(), Views: newViews(), } } @@ -211,7 +211,7 @@ func newInfo() Info { } // NewTable returns a new table style. -func newTable() Table { +func newGetTable() Table { return Table{ FgColor: "aqua", BgColor: "black", @@ -293,7 +293,7 @@ func (s *Styles) Title() Title { } // Table returns table styles. -func (s *Styles) Table() Table { +func (s *Styles) GetTable() Table { return s.K9s.Table } diff --git a/internal/config/style_test.go b/internal/config/style_test.go index 45fb2a56..fe1d9d95 100644 --- a/internal/config/style_test.go +++ b/internal/config/style_test.go @@ -16,7 +16,7 @@ func TestSkinNone(t *testing.T) { assert.Equal(t, "cadetblue", s.Body().FgColor) assert.Equal(t, "black", s.Body().BgColor) - assert.Equal(t, "black", s.Table().BgColor) + assert.Equal(t, "black", s.GetTable().BgColor) assert.Equal(t, tcell.ColorCadetBlue, s.FgColor()) assert.Equal(t, tcell.ColorBlack, s.BgColor()) assert.Equal(t, tcell.ColorBlack, tview.Styles.PrimitiveBackgroundColor) @@ -32,7 +32,7 @@ func TestSkin(t *testing.T) { assert.Equal(t, "white", s.Body().FgColor) assert.Equal(t, "black", s.Body().BgColor) - assert.Equal(t, "black", s.Table().BgColor) + assert.Equal(t, "black", s.GetTable().BgColor) assert.Equal(t, tcell.ColorWhite, s.FgColor()) assert.Equal(t, tcell.ColorBlack, s.BgColor()) assert.Equal(t, tcell.ColorBlack, tview.Styles.PrimitiveBackgroundColor) diff --git a/internal/dao/alias.go b/internal/dao/alias.go new file mode 100644 index 00000000..596b96f2 --- /dev/null +++ b/internal/dao/alias.go @@ -0,0 +1,8 @@ +package dao + +// Alias represents an alias resource. +type Alias struct { + Generic +} + +var _ Accessor = &Alias{} diff --git a/internal/dao/benchmark.go b/internal/dao/benchmark.go new file mode 100644 index 00000000..d459c378 --- /dev/null +++ b/internal/dao/benchmark.go @@ -0,0 +1,21 @@ +package dao + +import ( + "os" + + "github.com/rs/zerolog/log" +) + +// Benchmark represents a benchmark resource. +type Benchmark struct { + Generic +} + +var _ Accessor = &Benchmark{} +var _ Nuker = &Benchmark{} + +// Delete a Benchmark. +func (d *Benchmark) Delete(path string, cascade, force bool) error { + log.Debug().Msgf("Benchmark DELETE %q", path) + return os.Remove(path) +} diff --git a/internal/dao/container.go b/internal/dao/container.go new file mode 100644 index 00000000..d21d0b94 --- /dev/null +++ b/internal/dao/container.go @@ -0,0 +1,51 @@ +package dao + +import ( + "context" + "errors" + + "github.com/derailed/k9s/internal" + "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/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + restclient "k8s.io/client-go/rest" +) + +type Container struct { + Generic +} + +var _ Accessor = &Container{} +var _ Loggable = &Container{} + +// Logs tails a given container logs +func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error { + log.Debug().Msgf("CO TAILLOGS %#v", ctx) + log.Debug().Msgf("CO TAILLOGS %#v", opts) + + fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) + if !ok { + return errors.New("Expecting an informer") + } + o, err := fac.Get("v1/pods", opts.Path, labels.Everything()) + if err != nil { + return err + } + + var po v1.Pod + if runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil { + return err + } + + return tailLogs(ctx, c, logChan, opts) +} + +// Logs fetch container logs for a given pod and container. +func (c *Container) Logs(path string, opts *v1.PodLogOptions) *restclient.Request { + ns, n := k8s.Namespaced(path) + return c.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts) +} diff --git a/internal/dao/context.go b/internal/dao/context.go index 547b9f32..6aac2863 100644 --- a/internal/dao/context.go +++ b/internal/dao/context.go @@ -13,7 +13,7 @@ import ( ) type Context struct { - Resource + Generic } var _ Accessor = &Context{} @@ -47,16 +47,16 @@ func (c *Context) List(string, metav1.ListOptions) ([]runtime.Object, error) { } // Delete a Context. -func (c *Context) Delete(ns, n string, cascade, force bool) error { +func (c *Context) Delete(path string, cascade, force bool) error { ctx, err := c.config().CurrentContextName() if err != nil { return err } - if ctx == n { - return fmt.Errorf("trying to delete your current context %s", n) + if ctx == path { + return fmt.Errorf("trying to delete your current context %s", path) } - return c.config().DelContext(n) + return c.config().DelContext(path) } // MustCurrentContextName return the active context name. diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go new file mode 100644 index 00000000..9b2464de --- /dev/null +++ b/internal/dao/cronjob.go @@ -0,0 +1,43 @@ +package dao + +import ( + "github.com/derailed/k9s/internal/k8s" + batchv1 "k8s.io/api/batch/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" +) + +const maxJobNameSize = 42 + +type CronJob struct { + Generic +} + +var _ Accessor = &CronJob{} +var _ Runnable = &CronJob{} + +// Run a CronJob. +func (c *CronJob) Run(path string) error { + ns, n := k8s.Namespaced(path) + cj, err := c.Client().DialOrDie().BatchV1beta1().CronJobs(ns).Get(n, metav1.GetOptions{}) + if err != nil { + return err + } + + var jobName = cj.Name + if len(cj.Name) >= maxJobNameSize { + jobName = cj.Name[0:maxJobNameSize] + } + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: jobName + "-manual-" + rand.String(3), + Namespace: ns, + Labels: cj.Spec.JobTemplate.Labels, + }, + Spec: cj.Spec.JobTemplate.Spec, + } + _, err = c.Client().DialOrDie().BatchV1().Jobs(ns).Create(job) + + return err +} diff --git a/internal/dao/describe.go b/internal/dao/describe.go index ccba22b9..4091f2f9 100644 --- a/internal/dao/describe.go +++ b/internal/dao/describe.go @@ -33,5 +33,6 @@ func Describe(c k8s.Connection, gvr 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}) } diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 3f30b866..e975c142 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -16,7 +17,7 @@ import ( ) type Deployment struct { - Resource + Generic } var _ Accessor = &Deployment{} @@ -25,7 +26,8 @@ var _ Restartable = &Deployment{} var _ Scalable = &Deployment{} // Scale a Deployment. -func (d *Deployment) Scale(ns, n string, replicas int32) error { +func (d *Deployment) Scale(path string, replicas int32) error { + ns, n := k8s.Namespaced(path) scale, err := d.Client().DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{}) if err != nil { return err @@ -37,8 +39,8 @@ func (d *Deployment) Scale(ns, n string, replicas int32) error { } // Restart a Deployment rollout. -func (d *Deployment) Restart(ns, n string) error { - o, err := d.Get(ns, string(d.gvr), n, labels.Everything()) +func (d *Deployment) Restart(path string) error { + o, err := d.Get(string(d.gvr), path, labels.Everything()) if err != nil { return err } @@ -54,14 +56,14 @@ func (d *Deployment) Restart(ns, n string) error { return err } - _, err = d.Client().DialOrDie().AppsV1().Deployments(ns).Patch(ds.Name, types.StrategicMergePatchType, update) + _, err = d.Client().DialOrDie().AppsV1().Deployments(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update) return err } // Logs tail logs for all pods represented by this Deployment. func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - log.Debug().Msgf("Tailing Deployment %q -- %q", opts.Namespace, opts.Name) - o, err := d.Get(opts.Namespace, string(d.gvr), opts.Name, labels.Everything()) + log.Debug().Msgf("Tailing Deployment %q -- %q", opts.Path) + o, err := d.Get(string(d.gvr), opts.Path, labels.Everything()) if err != nil { return err } @@ -73,7 +75,7 @@ func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOpti } if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 { - return fmt.Errorf("No valid selector found on Deployment %s", opts.FQN()) + return fmt.Errorf("No valid selector found on Deployment %s", opts.Path) } return podLogs(ctx, c, dp.Spec.Selector.MatchLabels, opts) diff --git a/internal/dao/ds.go b/internal/dao/ds.go index d031f1da..0538348b 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/watch" "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" @@ -20,7 +21,7 @@ import ( ) type DaemonSet struct { - Resource + Generic } var _ Accessor = &DaemonSet{} @@ -28,8 +29,8 @@ var _ Loggable = &DaemonSet{} var _ Restartable = &DaemonSet{} // Restart a DaemonSet rollout. -func (d *DaemonSet) Restart(ns, n string) error { - o, err := d.Get(ns, string(d.gvr), n, labels.Everything()) +func (d *DaemonSet) Restart(path string) error { + o, err := d.Get(string(d.gvr), path, labels.Everything()) if err != nil { return err } @@ -45,14 +46,14 @@ func (d *DaemonSet) Restart(ns, n string) error { return err } - _, err = d.Client().DialOrDie().AppsV1().DaemonSets(ns).Patch(ds.Name, types.StrategicMergePatchType, update) + _, err = d.Client().DialOrDie().AppsV1().DaemonSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update) return err } // Logs tail logs for all pods represented by this DaemonSet. func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - log.Debug().Msgf("Tailing DaemonSet %q -- %q", opts.Namespace, opts.Name) - o, err := d.Get(opts.Namespace, "apps/v1/daemonsets", opts.Name, labels.Everything()) + log.Debug().Msgf("Tailing DaemonSet %q", opts.Path) + o, err := d.Get("apps/v1/daemonsets", opts.Path, labels.Everything()) if err != nil { return err } @@ -64,7 +65,7 @@ func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptio } if ds.Spec.Selector == nil || len(ds.Spec.Selector.MatchLabels) == 0 { - return fmt.Errorf("No valid selector found on daemonset %s", opts.FQN()) + return fmt.Errorf("no valid selector found on daemonset %q", opts.Path) } return podLogs(ctx, c, ds.Spec.Selector.MatchLabels, opts) @@ -84,7 +85,8 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L return err } - oo, err := f.List(opts.Namespace, "v1/pods", lsel) + ns, _ := k8s.Namespaced(opts.Path) + oo, err := f.List("v1/pods", ns, lsel) if err != nil { return err } @@ -94,17 +96,17 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L } po := Pod{} + po.Init(f, "v1/pods") for _, o := range oo { var pod v1.Pod err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod) if err != nil { return err } - if pod.Status.Phase == v1.PodRunning { - opts.Namespace, opts.Name = pod.Namespace, pod.Name - if err := po.TailLogs(ctx, c, opts); err != nil { - return err - } + log.Debug().Msgf("TAILING logs on pod %q", pod.Name) + opts.Path = k8s.FQN(pod.Namespace, pod.Name) + if err := po.TailLogs(ctx, c, opts); err != nil { + return err } } return nil diff --git a/internal/dao/generic.go b/internal/dao/generic.go new file mode 100644 index 00000000..a05e8c61 --- /dev/null +++ b/internal/dao/generic.go @@ -0,0 +1,34 @@ +package dao + +import ( + "github.com/derailed/k9s/internal/k8s" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/dynamic" +) + +type Generic struct { + Factory + + gvr GVR +} + +func (r *Generic) Init(f Factory, gvr GVR) { + r.Factory, r.gvr = f, gvr +} + +// Delete a Generic. +func (g *Generic) Delete(path string, cascade, force bool) error { + p := metav1.DeletePropagationOrphan + if cascade { + p = metav1.DeletePropagationBackground + } + + ns, n := k8s.Namespaced(path) + return g.dynClient().Namespace(ns).Delete(n, &metav1.DeleteOptions{ + PropagationPolicy: &p, + }) +} + +func (g *Generic) dynClient() dynamic.NamespaceableResourceInterface { + return g.Client().DynDialOrDie().Resource(g.gvr.AsGVR()) +} diff --git a/internal/dao/job.go b/internal/dao/job.go new file mode 100644 index 00000000..b6703cfb --- /dev/null +++ b/internal/dao/job.go @@ -0,0 +1,41 @@ +package dao + +import ( + "context" + "errors" + "fmt" + + "github.com/rs/zerolog/log" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +type Job struct { + Generic +} + +var _ Accessor = &Job{} +var _ Loggable = &Job{} + +// Logs tail logs for all pods represented by this Job. +func (j *Job) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { + log.Debug().Msgf("Tailing Job %#v", opts) + o, err := j.Get(string(j.gvr), opts.Path, labels.Everything()) + if err != nil { + return err + } + + var job batchv1.Job + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job) + if err != nil { + return errors.New("expecting a job resource") + } + + if job.Spec.Selector == nil || len(job.Spec.Selector.MatchLabels) == 0 { + return fmt.Errorf("No valid selector found on Job %s", opts.Path) + } + + return podLogs(ctx, c, job.Spec.Selector.MatchLabels, opts) +} diff --git a/internal/dao/log_options.go b/internal/dao/log_options.go index 0eb5fd71..90d65d7f 100644 --- a/internal/dao/log_options.go +++ b/internal/dao/log_options.go @@ -1,57 +1,42 @@ package dao import ( - "path" "strings" "github.com/derailed/k9s/internal/color" + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/tview" runewidth "github.com/mattn/go-runewidth" ) -type ( - // Fqn uniquely describes a container - Fqn struct { - Namespace, Name, Container string - } - - // LogOptions represent logger options. - LogOptions struct { - Fqn - - Lines int64 - Color color.Paint - Previous bool - SingleContainer bool - MultiPods bool - } -) +// LogOptions represent logger options. +type LogOptions struct { + Path string + Container string + Lines int64 + Color color.Paint + Previous bool + SingleContainer bool + MultiPods bool +} // HasContainer checks if a container is present. func (o LogOptions) HasContainer() bool { return o.Container != "" } -// FQN returns resource fully qualified name. -func (o LogOptions) FQN() string { - return FQN(o.Namespace, o.Name) -} - -// Path returns resource descriptor path. -func (o LogOptions) Path() string { - return o.FQN() + ":" + o.Container -} - // FixedSizeName returns a normalize fixed size pod name if possible. func (o LogOptions) FixedSizeName() string { - tokens := strings.Split(o.Name, "-") + _, n := k8s.Namespaced(o.Path) + tokens := strings.Split(n, "-") if len(tokens) < 3 { - return o.Name + return n } var s []string for i := 0; i < len(tokens)-1; i++ { s = append(s, tokens[i]) } + return Truncate(strings.Join(s, "-"), 15) + "-" + tokens[len(tokens)-1] } @@ -65,12 +50,13 @@ func colorize(c color.Paint, txt string) string { // DecorateLog add a log header to display po/co information along with the log message. func (o LogOptions) DecorateLog(msg string) string { + _, n := k8s.Namespaced(o.Path) if msg == "" { return msg } if o.MultiPods { - return colorize(o.Color, o.Name+":"+o.Container+" ") + msg + return colorize(o.Color, n+":"+o.Container+" ") + msg } if !o.SingleContainer { @@ -88,17 +74,18 @@ func Truncate(str string, width int) string { return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis)) } -// Namespaced return a namesapace and a name. -func Namespaced(n string) (string, string) { - ns, po := path.Split(n) +// BOZO!! +// // Namespaced return a namesapace and a name. +// func Namespaced(n string) (string, string) { +// ns, po := path.Split(n) - return strings.Trim(ns, "/"), po -} +// return strings.Trim(ns, "/"), po +// } -// FQN returns a fully qualified resource name. -func FQN(ns, n string) string { - if ns == "" { - return n - } - return ns + "/" + n -} +// // FQN returns a fully qualified resource name. +// func FQN(ns, n string) string { +// if ns == "" { +// return n +// } +// return ns + "/" + n +// } diff --git a/internal/dao/logger.go b/internal/dao/logger.go deleted file mode 100644 index 07a0cc0f..00000000 --- a/internal/dao/logger.go +++ /dev/null @@ -1 +0,0 @@ -package dao diff --git a/internal/dao/pod.go b/internal/dao/pod.go index d4548e9b..5b320036 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -11,6 +11,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/color" + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/watch" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" @@ -22,24 +23,23 @@ import ( const defaultTimeout = 1 * time.Second -type Logger interface { - Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request -} - +// Pod represents a pod resource. type Pod struct { - Resource + Generic } var _ Accessor = &Pod{} +var _Loggable = &Pod{} // Logs fetch container logs for a given pod and container. -func (p *Pod) Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request { +func (p *Pod) Logs(path string, opts *v1.PodLogOptions) *restclient.Request { + ns, n := k8s.Namespaced(path) return p.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts) } // Containers returns all container names on pod -func (p *Pod) Containers(ns, n string, includeInit bool) ([]string, error) { - o, err := p.Get(ns, "v1/pod", n, labels.Everything()) +func (p *Pod) Containers(path string, includeInit bool) ([]string, error) { + o, err := p.Get("v1/pod", path, labels.Everything()) if err != nil { return nil, err } @@ -78,8 +78,7 @@ func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error if !ok { return errors.New("Expecting an informer") } - ns, n := Namespaced(opts.FQN()) - o, err := fac.Get(ns, "v1/pods", n, labels.Everything()) + o, err := fac.Get("v1/pods", opts.Path, labels.Everything()) if err != nil { return err } @@ -114,14 +113,14 @@ func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error } func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptions) error { - log.Debug().Msgf("Tailing logs for %q -- %q -- %q", opts.Namespace, opts.Name, opts.Container) + log.Debug().Msgf("Tailing logs for %q -- %q", opts.Path, opts.Container) o := v1.PodLogOptions{ Container: opts.Container, Follow: true, TailLines: &opts.Lines, Previous: opts.Previous, } - req := logger.Logs(opts.Namespace, opts.Name, &o) + req := logger.Logs(opts.Path, &o) ctxt, cancelFunc := context.WithCancel(ctx) req.Context(ctxt) @@ -132,8 +131,8 @@ func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptio stream, err := req.Stream() atomic.StoreInt32(&blocked, 0) if err != nil { - log.Error().Err(err).Msgf("Log stream failed for `%s", opts.Path()) - return fmt.Errorf("Unable to obtain log stream for %s", opts.Path()) + log.Error().Err(err).Msgf("Log stream failed for `%s", opts.Path) + return fmt.Errorf("Unable to obtain log stream for %s", opts.Path) } go readLogs(ctx, stream, c, opts) @@ -150,7 +149,7 @@ func logsTimeout(cancel context.CancelFunc, blocked *int32) { func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) { defer func() { - log.Debug().Msgf(">>> Closing stream `%s", opts.Path()) + log.Debug().Msgf(">>> Closing stream `%s", opts.Path) if err := stream.Close(); err != nil { log.Error().Err(err).Msg("Cloing stream") } diff --git a/internal/dao/portforward.go b/internal/dao/portforward.go new file mode 100644 index 00000000..9120711e --- /dev/null +++ b/internal/dao/portforward.go @@ -0,0 +1,19 @@ +package dao + +import ( + "github.com/rs/zerolog/log" +) + +type PortForward struct { + Generic +} + +var _ Accessor = &PortForward{} +var _ Nuker = &PortForward{} + +// Delete a portforward. +func (p *PortForward) Delete(path string, cascade, force bool) error { + log.Debug().Msgf("PortForward DELETE %q", path) + p.Factory.DeleteForwarder(path) + return nil +} diff --git a/internal/dao/reconcile.go b/internal/dao/reconcile.go index 52d5c1ae..f51e6357 100644 --- a/internal/dao/reconcile.go +++ b/internal/dao/reconcile.go @@ -3,6 +3,7 @@ package dao import ( "context" "fmt" + "time" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/model" @@ -12,14 +13,14 @@ import ( // Reconcile previous vs current state and emits delta events. func Reconcile(ctx context.Context, table render.TableData, gvr GVR) (render.TableData, error) { - path, ok := ctx.Value(internal.KeySelection).(string) + defer func(t time.Time) { + log.Debug().Msgf("Reconcile elapsed: %v", time.Since(t)) + }(time.Now()) + + path, ok := ctx.Value(internal.KeyPath).(string) if !ok { return table, fmt.Errorf("no path specified for %s", gvr) } - if path != "" { - log.Debug().Msgf("########## OVERRIDING NS %q", path) - table.Namespace = path - } log.Debug().Msgf(" Reconcile %q in ns %q with path %q", gvr, table.Namespace, path) factory, ok := ctx.Value(internal.KeyFactory).(Factory) if !ok { @@ -41,10 +42,11 @@ func Reconcile(ctx context.Context, table render.TableData, gvr GVR) (render.Tab table.Header = m.Renderer.Header(table.Namespace) oo, err := m.Model.List(ctx) if err != nil { - panic(err) + return table, err } log.Debug().Msgf("Model returned [%d] items", len(oo)) rows := make(render.Rows, len(oo)) + // BOZO!! Pass in header len to avoid recomputing the header. if err := m.Model.Hydrate(oo, rows, m.Renderer); err != nil { return table, err } @@ -65,7 +67,7 @@ func update(table *render.TableData, rows render.Rows) { continue } if index, ok := table.RowEvents.FindIndex(row.ID); ok { - delta := render.NewDeltaRow(table.RowEvents[index].Row, row) + delta := render.NewDeltaRow(table.RowEvents[index].Row, row, table.Header.HasAge()) if delta.IsBlank() { table.RowEvents[index].Kind, table.RowEvents[index].Deltas = render.EventUnchanged, blankDelta } else { diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 04f46e3a..d76f4f6a 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -15,21 +15,35 @@ import ( // MetaViewers represents a collection of meta viewers. type ResourceMetas map[GVR]metav1.APIResource +// Accessors represents a collection of dao accessors. +type Accessors map[GVR]Accessor + var resMetas ResourceMetas +// AccessorFor returns a client accessor for a resource if registered. +// Otherwise it returns a generic accessor. +// Customize here for non resource types or types with metrics or logs. func AccessorFor(f Factory, gvr GVR) (Accessor, error) { - m := map[GVR]Accessor{ + m := Accessors{ + "alias": &Alias{}, "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{}, } r, ok := m[gvr] if !ok { - r = &Resource{} + r = &Generic{} log.Warn().Msgf("No DAO registry entry for %q. Going generic!", gvr) } r.Init(f, gvr) @@ -56,6 +70,17 @@ func MetaFor(gvr GVR) (metav1.APIResource, error) { return m, nil } +// IsK9sMeta checks for non resource meta. +func IsK9sMeta(m metav1.APIResource) bool { + for _, c := range m.Categories { + if c == "k9s" { + return true + } + } + + return false +} + // Load hydrates server preferred+CRDs resource metadata. func Load(f *watch.Factory) error { resMetas = make(ResourceMetas, 100) @@ -70,23 +95,82 @@ func Load(f *watch.Factory) error { } func loadNonResource(m ResourceMetas) error { + m["aliases"] = metav1.APIResource{ + Name: "aliases", + SingularName: "alias", + Namespaced: false, + Kind: "Aliases", + Verbs: []string{}, + Categories: []string{"k9s"}, + } m["contexts"] = metav1.APIResource{ Name: "contexts", SingularName: "context", Namespaced: false, - Kind: "Context", + Kind: "Contexts", ShortNames: []string{"ctx"}, Verbs: []string{}, - Categories: []string{"K9s"}, + Categories: []string{"k9s"}, } m["screendumps"] = metav1.APIResource{ Name: "screendumps", SingularName: "screendump", Namespaced: false, - Kind: "ScreenDump", + Kind: "ScreenDumps", ShortNames: []string{"sd"}, Verbs: []string{"delete"}, - Categories: []string{"K9s"}, + Categories: []string{"k9s"}, + } + m["benchmarks"] = metav1.APIResource{ + Name: "benchmarks", + SingularName: "benchmark", + Namespaced: false, + Kind: "Benchmarks", + ShortNames: []string{"be"}, + Verbs: []string{"delete"}, + Categories: []string{"k9s"}, + } + m["portforwards"] = metav1.APIResource{ + Name: "portforwards", + SingularName: "portforward", + Namespaced: true, + Kind: "PortForwards", + ShortNames: []string{"pf"}, + Verbs: []string{"delete"}, + Categories: []string{"k9s"}, + } + // BOZO!! policies can't be launch on command + m["rbac"] = metav1.APIResource{ + Name: "Rbac", + SingularName: "Rbac", + Namespaced: false, + Kind: "RBAC", + Categories: []string{"k9s"}, + } + // BOZO!! Containers can't be launch on command + m["containers"] = metav1.APIResource{ + Name: "containers", + SingularName: "container", + Namespaced: false, + Kind: "Containers", + Verbs: []string{}, + Categories: []string{"k9s"}, + } + m["users"] = metav1.APIResource{ + Name: "users", + SingularName: "user", + Namespaced: false, + Kind: "User", + Verbs: []string{}, + Categories: []string{"k9s"}, + } + m["groups"] = metav1.APIResource{ + Name: "groups", + SingularName: "group", + Namespaced: false, + Kind: "group", + Verbs: []string{}, + Categories: []string{"k9s"}, } return nil @@ -113,7 +197,7 @@ func loadPreferred(f *watch.Factory, m ResourceMetas) error { } func loadCRDs(f *watch.Factory, m ResourceMetas) error { - oo, err := f.List("", "apiextensions.k8s.io/v1beta1/customresourcedefinitions", labels.Everything()) + oo, err := f.List("apiextensions.k8s.io/v1beta1/customresourcedefinitions", "", labels.Everything()) if err != nil { return err } diff --git a/internal/dao/resource.go b/internal/dao/resource.go deleted file mode 100644 index 1441e4f1..00000000 --- a/internal/dao/resource.go +++ /dev/null @@ -1,32 +0,0 @@ -package dao - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/dynamic" -) - -type Resource struct { - Factory - - gvr GVR -} - -func (r *Resource) Init(f Factory, gvr GVR) { - r.Factory, r.gvr = f, gvr -} - -// Delete a Generic. -func (r *Resource) Delete(ns, n string, cascade, force bool) error { - p := metav1.DeletePropagationOrphan - if cascade { - p = metav1.DeletePropagationBackground - } - - return r.dynClient().Namespace(ns).Delete(n, &metav1.DeleteOptions{ - PropagationPolicy: &p, - }) -} - -func (r *Resource) dynClient() dynamic.NamespaceableResourceInterface { - return r.Client().DynDialOrDie().Resource(r.gvr.AsGVR()) -} diff --git a/internal/dao/screen_dump.go b/internal/dao/screen_dump.go index bfd4ee06..b13a116c 100644 --- a/internal/dao/screen_dump.go +++ b/internal/dao/screen_dump.go @@ -2,20 +2,19 @@ package dao import ( "os" - "path/filepath" "github.com/rs/zerolog/log" ) type ScreenDump struct { - Resource + Generic } var _ Accessor = &ScreenDump{} var _ Nuker = &ScreenDump{} // Delete a ScreenDump. -func (d *ScreenDump) Delete(dir, sel string, cascade, force bool) error { - log.Debug().Msgf("ScreenDump DELETE %q:%q", dir, sel) - return os.Remove(filepath.Join("/"+dir, sel)) +func (d *ScreenDump) Delete(path string, cascade, force bool) error { + log.Debug().Msgf("ScreenDump DELETE %q", path) + return os.Remove(path) } diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 1ff40a9e..9c0a8705 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -16,7 +17,7 @@ import ( ) type StatefulSet struct { - Resource + Generic } var _ Accessor = &StatefulSet{} @@ -25,7 +26,8 @@ var _ Restartable = &StatefulSet{} var _ Scalable = &StatefulSet{} // Scale a StatefulSet. -func (s *StatefulSet) Scale(ns, n string, replicas int32) error { +func (s *StatefulSet) Scale(path string, replicas int32) error { + ns, n := k8s.Namespaced(path) scale, err := s.Client().DialOrDie().AppsV1().StatefulSets(ns).GetScale(n, metav1.GetOptions{}) if err != nil { return err @@ -37,8 +39,8 @@ func (s *StatefulSet) Scale(ns, n string, replicas int32) error { } // Restart a StatefulSet rollout. -func (s *StatefulSet) Restart(ns, n string) error { - o, err := s.Get(ns, string(s.gvr), n, labels.Everything()) +func (s *StatefulSet) Restart(path string) error { + o, err := s.Get(string(s.gvr), path, labels.Everything()) if err != nil { return err } @@ -54,14 +56,14 @@ func (s *StatefulSet) Restart(ns, n string) error { return err } - _, err = s.Client().DialOrDie().AppsV1().StatefulSets(ns).Patch(ds.Name, types.StrategicMergePatchType, update) + _, err = s.Client().DialOrDie().AppsV1().StatefulSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update) return err } // Logs tail logs for all pods represented by this StatefulSet. func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - log.Debug().Msgf("Tailing StatefulSet %q -- %q", opts.Namespace, opts.Name) - o, err := s.Get(opts.Namespace, string(s.gvr), opts.Name, labels.Everything()) + log.Debug().Msgf("Tailing StatefulSet %q", opts.Path) + o, err := s.Get(string(s.gvr), opts.Path, labels.Everything()) if err != nil { return err } @@ -73,7 +75,7 @@ func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOpt } if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 { - return fmt.Errorf("No valid selector found on StatefulSet %s", opts.FQN()) + return fmt.Errorf("No valid selector found on StatefulSet %s", opts.Path) } return podLogs(ctx, c, dp.Spec.Selector.MatchLabels, opts) diff --git a/internal/dao/svc.go b/internal/dao/svc.go new file mode 100644 index 00000000..348815fd --- /dev/null +++ b/internal/dao/svc.go @@ -0,0 +1,40 @@ +package dao + +import ( + "context" + "errors" + "fmt" + + "github.com/rs/zerolog/log" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +type Service struct { + Generic +} + +var _ Accessor = &Service{} +var _ Loggable = &Service{} + +// Logs tail logs for all pods represented by this Service. +func (s *Service) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { + log.Debug().Msgf("Tailing Service %q", opts.Path) + o, err := s.Get(string(s.gvr), opts.Path, labels.Everything()) + if err != nil { + return err + } + var svc v1.Service + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &svc) + if err != nil { + return errors.New("expecting Service resource") + } + + if svc.Spec.Selector == nil || len(svc.Spec.Selector) == 0 { + return fmt.Errorf("no valid selector found on Service %s", opts.Path) + } + + return podLogs(ctx, c, svc.Spec.Selector, opts) +} diff --git a/internal/dao/types.go b/internal/dao/types.go index 4ba488ca..65f7dff7 100644 --- a/internal/dao/types.go +++ b/internal/dao/types.go @@ -4,10 +4,13 @@ import ( "context" "github.com/derailed/k9s/internal/k8s" + "github.com/derailed/k9s/internal/watch" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/informers" + restclient "k8s.io/client-go/rest" ) type Factory interface { @@ -15,7 +18,7 @@ type Factory interface { Client() k8s.Connection // Get fetch a given resource. - Get(ns, gvr, n string, sel labels.Selector) (runtime.Object, error) + Get(gvr, path string, sel labels.Selector) (runtime.Object, error) // List fetch a collection of resources. List(ns, gvr string, sel labels.Selector) ([]runtime.Object, error) @@ -25,6 +28,12 @@ type Factory interface { // WaitForCacheSync synchronize the cache. WaitForCacheSync() map[schema.GroupVersionResource]bool + + // DeleteForwarder deletes a pod forwarder. + DeleteForwarder(path string) + + // Forwards returns all portforwards. + Forwarders() watch.Forwarders } // Accessor represents an accessible k8s resource. @@ -42,13 +51,13 @@ type Loggable interface { } type Scalable interface { - Scale(ns, n string, replicas int32) error + Scale(path string, replicas int32) error } // Nuker represents a resource deleter. type Nuker interface { // Delete removes a resource from the api server. - Delete(ns, n string, cascade, force bool) error + Delete(path string, cascade, force bool) error } // Switchable represents a switchable resource. @@ -60,5 +69,16 @@ type Switchable interface { // Restartable represents a restartable resource. type Restartable interface { // Restart performs a rollout restart. - Restart(ns, n string) error + Restart(path string) error +} + +// Runnable represents a runnable resource. +type Runnable interface { + // Run triggers a run. + Run(path string) error +} + +// Loggers represents a resource that exposes logs. +type Logger interface { + Logs(path string, opts *v1.PodLogOptions) *restclient.Request } diff --git a/internal/k8s/api.go b/internal/k8s/api.go index e19003de..c2e365fe 100644 --- a/internal/k8s/api.go +++ b/internal/k8s/api.go @@ -58,8 +58,6 @@ type ( ServerVersion() (*version.Info, error) FetchNodes() (*v1.NodeList, error) CurrentNamespaceName() (string, error) - CheckNSAccess(ns string) error - CheckListNSAccess() error CanI(ns, gvr string, verbs []string) (bool, error) } @@ -85,25 +83,6 @@ func InitConnectionOrDie(config *Config) *APIClient { return &conn } -// CheckListNSAccess check if current user can list namespaces. -func (a *APIClient) CheckListNSAccess() error { - ns := NewNamespace(a) - _, err := ns.List("", metav1.ListOptions{}) - return err -} - -// CheckNSAccess asserts if user can access a namespace. -func (a *APIClient) CheckNSAccess(n string) error { - ns := NewNamespace(a) - if n == "" { - _, err := ns.List(n, metav1.ListOptions{}) - return err - } - - _, err := ns.Get("", n) - return err -} - func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview { res := GVR(gvr).AsGVR() return &authorizationv1.SelfSubjectAccessReview{ diff --git a/internal/k8s/cluster_role.go b/internal/k8s/cluster_role.go deleted file mode 100644 index 97819983..00000000 --- a/internal/k8s/cluster_role.go +++ /dev/null @@ -1,42 +0,0 @@ -package k8s - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// ClusterRole represents a Kubernetes ClusterRole -type ClusterRole struct { - *base - Connection -} - -// NewClusterRole returns a new ClusterRole. -func NewClusterRole(c Connection) *ClusterRole { - return &ClusterRole{&base{}, c} -} - -// Get a cluster role. -func (c *ClusterRole) Get(_, n string) (interface{}, error) { - panic("NYI") - return c.DialOrDie().RbacV1().ClusterRoles().Get(n, metav1.GetOptions{}) -} - -// List all ClusterRoles on a cluster. -func (c *ClusterRole) List(ns string, opts metav1.ListOptions) (Collection, error) { - panic("NYI") - rr, err := c.DialOrDie().RbacV1().ClusterRoles().List(opts) - if err != nil { - return nil, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } - - return cc, nil -} - -// Delete a ClusterRole. -func (c *ClusterRole) Delete(_, n string, cascade, force bool) error { - return c.DialOrDie().RbacV1().ClusterRoles().Delete(n, nil) -} diff --git a/internal/k8s/cluster_roleb.go b/internal/k8s/cluster_roleb.go deleted file mode 100644 index 07ba7a74..00000000 --- a/internal/k8s/cluster_roleb.go +++ /dev/null @@ -1,42 +0,0 @@ -package k8s - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// ClusterRoleBinding represents a Kubernetes ClusterRoleBinding -type ClusterRoleBinding struct { - *base - Connection -} - -// NewClusterRoleBinding returns a new ClusterRoleBinding. -func NewClusterRoleBinding(c Connection) *ClusterRoleBinding { - return &ClusterRoleBinding{&base{}, c} -} - -// Get a service. -func (c *ClusterRoleBinding) Get(_, n string) (interface{}, error) { - panic("NYI") - return c.DialOrDie().RbacV1().ClusterRoleBindings().Get(n, metav1.GetOptions{}) -} - -// List all ClusterRoleBindings on a cluster. -func (c *ClusterRoleBinding) List(ns string, opts metav1.ListOptions) (Collection, error) { - panic("NYI") - rr, err := c.DialOrDie().RbacV1().ClusterRoleBindings().List(opts) - if err != nil { - return Collection{}, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } - - return cc, nil -} - -// Delete a ClusterRoleBinding. -func (c *ClusterRoleBinding) Delete(_, n string, cascade, force bool) error { - return c.DialOrDie().RbacV1().ClusterRoleBindings().Delete(n, nil) -} diff --git a/internal/k8s/context.go b/internal/k8s/context.go index b6f334f8..4ffee002 100644 --- a/internal/k8s/context.go +++ b/internal/k8s/context.go @@ -1,108 +1,109 @@ package k8s -import ( - "fmt" +// BOZO!! +// import ( +// "fmt" - "github.com/rs/zerolog/log" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/tools/clientcmd/api" -) +// "github.com/rs/zerolog/log" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// "k8s.io/client-go/tools/clientcmd" +// "k8s.io/client-go/tools/clientcmd/api" +// ) -// NamedContext represents a named cluster context. -type NamedContext struct { - Name string - Context *api.Context - config *Config -} +// // NamedContext represents a named cluster context. +// type NamedContext struct { +// Name string +// Context *api.Context +// config *Config +// } -// NewNamedContext returns a new named context. -func NewNamedContext(c *Config, n string, ctx *api.Context) *NamedContext { - return &NamedContext{Name: n, Context: ctx, config: c} -} +// // NewNamedContext returns a new named context. +// func NewNamedContext(c *Config, n string, ctx *api.Context) *NamedContext { +// return &NamedContext{Name: n, Context: ctx, config: c} +// } -// MustCurrentContextName return the active context name. -func (c *NamedContext) MustCurrentContextName() string { - cl, err := c.config.CurrentContextName() - if err != nil { - log.Fatal().Err(err).Msg("Fetching current context") - } - return cl -} +// // MustCurrentContextName return the active context name. +// func (c *NamedContext) MustCurrentContextName() string { +// cl, err := c.config.CurrentContextName() +// if err != nil { +// log.Fatal().Err(err).Msg("Fetching current context") +// } +// return cl +// } -// ---------------------------------------------------------------------------- +// // ---------------------------------------------------------------------------- -// Context represents a Kubernetes Context. -type Context struct { - *base - Connection -} +// // Context represents a Kubernetes Context. +// type Context struct { +// *base +// Connection +// } -// NewContext returns a new Context. -func NewContext(c Connection) *Context { - return &Context{&base{}, c} -} +// // NewContext returns a new Context. +// func NewContext(c Connection) *Context { +// return &Context{&base{}, c} +// } -// Get a Context. -func (c *Context) Get(_, n string) (interface{}, error) { - ctx, err := c.Config().GetContext(n) - if err != nil { - return nil, err - } - return &NamedContext{Name: n, Context: ctx}, nil -} +// // Get a Context. +// func (c *Context) Get(_, n string) (interface{}, error) { +// ctx, err := c.Config().GetContext(n) +// if err != nil { +// return nil, err +// } +// return &NamedContext{Name: n, Context: ctx}, nil +// } -// List all Contexts on the current cluster. -func (c *Context) List(string, metav1.ListOptions) (Collection, error) { - ctxs, err := c.Config().Contexts() - if err != nil { - return nil, err - } - cc := make([]interface{}, 0, len(ctxs)) - for k, v := range ctxs { - cc = append(cc, NewNamedContext(c.Config(), k, v)) - } +// // List all Contexts on the current cluster. +// func (c *Context) List(string, metav1.ListOptions) (Collection, error) { +// ctxs, err := c.Config().Contexts() +// if err != nil { +// return nil, err +// } +// cc := make([]interface{}, 0, len(ctxs)) +// for k, v := range ctxs { +// cc = append(cc, NewNamedContext(c.Config(), k, v)) +// } - return cc, nil -} +// return cc, nil +// } -// Delete a Context. -func (c *Context) Delete(_, n string, cascade, force bool) error { - ctx, err := c.Config().CurrentContextName() - if err != nil { - return err - } - if ctx == n { - return fmt.Errorf("trying to delete your current context %s", n) - } - return c.Config().DelContext(n) -} +// // Delete a Context. +// func (c *Context) Delete(_, n string, cascade, force bool) error { +// ctx, err := c.Config().CurrentContextName() +// if err != nil { +// return err +// } +// if ctx == n { +// return fmt.Errorf("trying to delete your current context %s", n) +// } +// return c.Config().DelContext(n) +// } -// MustCurrentContextName return the active context name. -func (c *Context) MustCurrentContextName() string { - cl, err := c.Config().CurrentContextName() - if err != nil { - log.Fatal().Err(err).Msg("Fetching current context") - } - return cl -} +// // MustCurrentContextName return the active context name. +// func (c *Context) MustCurrentContextName() string { +// cl, err := c.Config().CurrentContextName() +// if err != nil { +// log.Fatal().Err(err).Msg("Fetching current context") +// } +// return cl +// } -// Switch to another context. -func (c *Context) Switch(ctx string) error { - c.SwitchContextOrDie(ctx) - return nil -} +// // Switch to another context. +// func (c *Context) Switch(ctx string) error { +// c.SwitchContextOrDie(ctx) +// return nil +// } -// KubeUpdate modifies kubeconfig default context. -func (c *Context) KubeUpdate(n string) error { - config, err := c.Config().RawConfig() - if err != nil { - return err - } - if err := c.Switch(n); err != nil { - return err - } - return clientcmd.ModifyConfig( - clientcmd.NewDefaultPathOptions(), config, true, - ) -} +// // KubeUpdate modifies kubeconfig default context. +// func (c *Context) KubeUpdate(n string) error { +// config, err := c.Config().RawConfig() +// if err != nil { +// return err +// } +// if err := c.Switch(n); err != nil { +// return err +// } +// return clientcmd.ModifyConfig( +// clientcmd.NewDefaultPathOptions(), config, true, +// ) +// } diff --git a/internal/k8s/cronjob.go b/internal/k8s/cronjob.go deleted file mode 100644 index 3bc778c1..00000000 --- a/internal/k8s/cronjob.go +++ /dev/null @@ -1,76 +0,0 @@ -package k8s - -import ( - "errors" - - batchv1 "k8s.io/api/batch/v1" - batchv1beta1 "k8s.io/api/batch/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/rand" -) - -const maxJobNameSize = 42 - -// CronJob represents a Kubernetes CronJob. -type CronJob struct { - *base - Connection -} - -// NewCronJob returns a new CronJob. -func NewCronJob(c Connection) *CronJob { - return &CronJob{&base{}, c} -} - -// Get a CronJob. -func (c *CronJob) Get(ns, n string) (interface{}, error) { - return c.DialOrDie().BatchV1beta1().CronJobs(ns).Get(n, metav1.GetOptions{}) -} - -// List all CronJobs in a given namespace. -func (c *CronJob) List(ns string, opts metav1.ListOptions) (Collection, error) { - rr, err := c.DialOrDie().BatchV1beta1().CronJobs(ns).List(opts) - if err != nil { - return nil, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } - - return cc, nil -} - -// Delete a CronJob. -func (c *CronJob) Delete(ns, n string, cascade, force bool) error { - return c.DialOrDie().BatchV1beta1().CronJobs(ns).Delete(n, nil) -} - -// Run the job associated with this cronjob. -func (c *CronJob) Run(ns, n string) error { - cj, err := c.Get(ns, n) - if err != nil { - return err - } - - cronJob, ok := cj.(*batchv1beta1.CronJob) - if !ok { - return errors.New("Expecting valid cronjob") - } - var jobName = cronJob.Name - if len(cronJob.Name) >= maxJobNameSize { - jobName = cronJob.Name[0:maxJobNameSize] - } - - job := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: jobName + "-manual-" + rand.String(3), - Namespace: ns, - Labels: cronJob.Spec.JobTemplate.Labels, - }, - Spec: cronJob.Spec.JobTemplate.Spec, - } - - _, err = c.DialOrDie().BatchV1().Jobs(ns).Create(job) - return err -} diff --git a/internal/k8s/dp.go b/internal/k8s/dp.go deleted file mode 100644 index 20990a7a..00000000 --- a/internal/k8s/dp.go +++ /dev/null @@ -1,72 +0,0 @@ -package k8s - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/kubectl/pkg/polymorphichelpers" -) - -// Deployment represents a Kubernetes Deployment. -type Deployment struct { - *base - Connection -} - -// NewDeployment returns a new Deployment. -func NewDeployment(c Connection) *Deployment { - return &Deployment{&base{}, c} -} - -// Get a deployment. -func (d *Deployment) Get(ns, n string) (interface{}, error) { - panic("NYI") - return d.DialOrDie().AppsV1().Deployments(ns).Get(n, metav1.GetOptions{}) -} - -// List all Deployments in a given namespace. -func (d *Deployment) List(ns string, opts metav1.ListOptions) (Collection, error) { - panic("NYI") - rr, err := d.DialOrDie().AppsV1().Deployments(ns).List(opts) - if err != nil { - return nil, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } - - return cc, nil -} - -// Delete a Deployment. -func (d *Deployment) Delete(ns, n string, cascade, force bool) error { - return d.DialOrDie().AppsV1().Deployments(ns).Delete(n, nil) -} - -// Scale a Deployment. -func (d *Deployment) Scale(ns, n string, replicas int32) error { - scale, err := d.DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{}) - if err != nil { - return err - } - - scale.Spec.Replicas = replicas - _, err = d.DialOrDie().AppsV1().Deployments(ns).UpdateScale(n, scale) - return err -} - -// Restart a Deployment rollout. -func (d *Deployment) Restart(ns, n string) error { - - dp, err := d.DialOrDie().AppsV1().Deployments(ns).Get(n, metav1.GetOptions{}) - if err != nil { - return err - } - update, err := polymorphichelpers.ObjectRestarterFn(dp) - if err != nil { - return err - } - - _, err = d.DialOrDie().AppsV1().Deployments(ns).Patch(dp.Name, types.StrategicMergePatchType, update) - return err -} diff --git a/internal/k8s/ds.go b/internal/k8s/ds.go deleted file mode 100644 index c21da128..00000000 --- a/internal/k8s/ds.go +++ /dev/null @@ -1,73 +0,0 @@ -package k8s - -// BOZO!! -// import ( -// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -// "k8s.io/apimachinery/pkg/types" -// "k8s.io/kubectl/pkg/polymorphichelpers" -// ) - -// // DaemonSet represents a Kubernetes DaemonSet -// type DaemonSet struct { -// *base -// Connection -// } - -// // NewDaemonSet returns a new DaemonSet. -// func NewDaemonSet(c Connection) *DaemonSet { -// return &DaemonSet{&base{}, c} -// } - -// // Get a DaemonSet. -// func (d *DaemonSet) Get(ns, n string) (interface{}, error) { -// panic("NYI") -// return d.DialOrDie().AppsV1().DaemonSets(ns).Get(n, metav1.GetOptions{}) -// } - -// // List all DaemonSets in a given namespace. -// func (d *DaemonSet) List(ns string, opts metav1.ListOptions) (Collection, error) { -// panic("NYI") -// rr, err := d.DialOrDie().AppsV1().DaemonSets(ns).List(opts) -// if err != nil { -// return nil, err -// } -// cc := make(Collection, len(rr.Items)) -// for i, r := range rr.Items { -// cc[i] = r -// } - -// return cc, nil -// } - -// // Delete a DaemonSet. -// func (d *DaemonSet) Delete(ns, n string, cascade, force bool) error { -// p := metav1.DeletePropagationOrphan -// if cascade { -// p = metav1.DeletePropagationBackground -// } -// return d.DialOrDie().AppsV1().DaemonSets(ns).Delete(n, &metav1.DeleteOptions{ -// PropagationPolicy: &p, -// }) -// } - -// // Restart a DaemonSet rollout. -// func (d *DaemonSet) Restart(f *watch.Factory, ns, n string) error { -// o, err := f.Get(ns, "apps/v1/deamonsets", n, 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 -// } - -// update, err := polymorphichelpers.ObjectRestarterFn(ds) -// if err != nil { -// return err -// } - -// _, err = f.Client().DialOrDie().AppsV1().DaemonSets(ns).Patch(ds.Name, types.StrategicMergePatchType, update) -// return err -// } diff --git a/internal/k8s/helpers.go b/internal/k8s/helpers.go index ca946311..a5b7d4a8 100644 --- a/internal/k8s/helpers.go +++ b/internal/k8s/helpers.go @@ -20,8 +20,17 @@ func toPerc(v1, v2 float64) float64 { return math.Round((v1 / v2) * 100) } -func namespaced(n string) (string, string) { +// Namespaced converts a resource path to namespace and resource name. +func Namespaced(n string) (string, string) { ns, po := path.Split(n) return strings.Trim(ns, "/"), po } + +// FQN returns a fully qualified resource name. +func FQN(ns, n string) string { + if ns == "" { + return n + } + return ns + "/" + n +} diff --git a/internal/k8s/job.go b/internal/k8s/job.go deleted file mode 100644 index bace25a0..00000000 --- a/internal/k8s/job.go +++ /dev/null @@ -1,94 +0,0 @@ -package k8s - -import ( - "fmt" - "strings" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - restclient "k8s.io/client-go/rest" -) - -type ( - // Job represents a Kubernetes Job. - Job struct { - *base - Connection - } - - // Loggable represents a K8s resource that has containers and can be logged. - Loggable interface { - Containers(ns, n string, includeInit bool) ([]string, error) - Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request - } -) - -// NewJob returns a new Job. -func NewJob(c Connection) *Job { - return &Job{&base{}, c} -} - -// Get a Job. -func (j *Job) Get(ns, n string) (interface{}, error) { - return j.DialOrDie().BatchV1().Jobs(ns).Get(n, metav1.GetOptions{}) -} - -// List all Jobs in a given namespace. -func (j *Job) List(ns string, opts metav1.ListOptions) (Collection, error) { - rr, err := j.DialOrDie().BatchV1().Jobs(ns).List(opts) - if err != nil { - return nil, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } - - return cc, nil -} - -// Delete a Job. -func (j *Job) Delete(ns, n string, cascade, force bool) error { - return j.DialOrDie().BatchV1().Jobs(ns).Delete(n, nil) -} - -// Containers returns all container names on job. -func (j *Job) Containers(ns, n string, includeInit bool) ([]string, error) { - pod, err := j.assocPod(ns, n) - if err != nil { - return nil, err - } - return NewPod(j).Containers(ns, pod, includeInit) -} - -// Logs fetch container logs for a given job and container. -func (j *Job) Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request { - pod, err := j.assocPod(ns, n) - if err != nil { - return nil - } - - return NewPod(j).Logs(ns, pod, opts) -} - -// Events retrieved jobs events. -func (j *Job) Events(ns, n string) (*v1.EventList, error) { - e := j.DialOrDie().CoreV1().Events(ns) - return e.List(metav1.ListOptions{ - FieldSelector: e.GetFieldSelector(&n, &ns, nil, nil).String(), - }) -} - -func (j *Job) assocPod(ns, n string) (string, error) { - ee, err := j.Events(ns, n) - if err != nil { - return "", err - } - - for _, e := range ee.Items { - if strings.Contains(e.Message, "Created pod: ") { - return strings.TrimSpace(strings.Replace(e.Message, "Created pod: ", "", 1)), nil - } - } - return "", fmt.Errorf("unable to find associated pod name for job: %s/%s", ns, n) -} diff --git a/internal/k8s/node.go b/internal/k8s/node.go deleted file mode 100644 index f75793f8..00000000 --- a/internal/k8s/node.go +++ /dev/null @@ -1,40 +0,0 @@ -package k8s - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Node represents a Kubernetes node. -type Node struct { - *base - Connection -} - -// NewNode returns a new Node. -func NewNode(c Connection) *Node { - return &Node{&base{}, c} -} - -// Get a node. -func (n *Node) Get(_, name string) (interface{}, error) { - return n.DialOrDie().CoreV1().Nodes().Get(name, metav1.GetOptions{}) -} - -// List all nodes on the cluster. -func (n *Node) List(ns string, opts metav1.ListOptions) (Collection, error) { - rr, err := n.DialOrDie().CoreV1().Nodes().List(opts) - if err != nil { - return nil, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } - - return cc, nil -} - -// Delete a node. -func (n *Node) Delete(_, name string, cascade, force bool) error { - return n.DialOrDie().CoreV1().Nodes().Delete(name, nil) -} diff --git a/internal/k8s/ns.go b/internal/k8s/ns.go deleted file mode 100644 index 505cbfae..00000000 --- a/internal/k8s/ns.go +++ /dev/null @@ -1,41 +0,0 @@ -package k8s - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Namespace represents a Kubernetes namespace. -type Namespace struct { - *base - Connection -} - -// NewNamespace returns a new Namespace. -func NewNamespace(c Connection) *Namespace { - return &Namespace{&base{}, c} -} - -// Get a active namespace. -func (n *Namespace) Get(_, name string) (interface{}, error) { - panic("NYI") - return n.DialOrDie().CoreV1().Namespaces().Get(name, metav1.GetOptions{}) -} - -// List all active namespaces on the cluster. -func (n *Namespace) List(ns string, opts metav1.ListOptions) (Collection, error) { - panic("NYI") - rr, err := n.DialOrDie().CoreV1().Namespaces().List(opts) - if err != nil { - return nil, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } - return cc, nil -} - -// Delete a namespace. -func (n *Namespace) Delete(_, name string, cascade, force bool) error { - return n.DialOrDie().CoreV1().Namespaces().Delete(name, nil) -} diff --git a/internal/k8s/port_forward.go b/internal/k8s/port_forward.go index 5c2c846a..2e19d301 100644 --- a/internal/k8s/port_forward.go +++ b/internal/k8s/port_forward.go @@ -91,7 +91,7 @@ func (p *PortForward) FQN() string { func (p *PortForward) Start(path, co string, ports []string) (*portforward.PortForwarder, error) { p.path, p.container, p.ports, p.age = path, co, ports, time.Now() - ns, n := namespaced(path) + ns, n := Namespaced(path) pod, err := p.DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{}) if err != nil { return nil, err diff --git a/internal/k8s/pv.go b/internal/k8s/pv.go index ff2d628a..07e2a650 100644 --- a/internal/k8s/pv.go +++ b/internal/k8s/pv.go @@ -1,41 +1,42 @@ package k8s -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) +// BOZO!! +// import ( +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -// PersistentVolume represents a Kubernetes PersistentVolume. -type PersistentVolume struct { - *base - Connection -} +// // PersistentVolume represents a Kubernetes PersistentVolume. +// type PersistentVolume struct { +// *base +// Connection +// } -// NewPersistentVolume returns a new PersistentVolume. -func NewPersistentVolume(c Connection) *PersistentVolume { - return &PersistentVolume{&base{}, c} -} +// // NewPersistentVolume returns a new PersistentVolume. +// func NewPersistentVolume(c Connection) *PersistentVolume { +// return &PersistentVolume{&base{}, c} +// } -// Get a PersistentVolume. -func (p *PersistentVolume) Get(_, n string) (interface{}, error) { - return p.DialOrDie().CoreV1().PersistentVolumes().Get(n, metav1.GetOptions{}) -} +// // Get a PersistentVolume. +// func (p *PersistentVolume) Get(_, n string) (interface{}, error) { +// return p.DialOrDie().CoreV1().PersistentVolumes().Get(n, metav1.GetOptions{}) +// } -// List all PersistentVolumes in a given namespace. -func (p *PersistentVolume) List(ns string, opts metav1.ListOptions) (Collection, error) { - rr, err := p.DialOrDie().CoreV1().PersistentVolumes().List(opts) - if err != nil { - return nil, err - } +// // List all PersistentVolumes in a given namespace. +// func (p *PersistentVolume) List(ns string, opts metav1.ListOptions) (Collection, error) { +// rr, err := p.DialOrDie().CoreV1().PersistentVolumes().List(opts) +// if err != nil { +// return nil, err +// } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } +// cc := make(Collection, len(rr.Items)) +// for i, r := range rr.Items { +// cc[i] = r +// } - return cc, nil -} +// return cc, nil +// } -// Delete a PersistentVolume. -func (p *PersistentVolume) Delete(_, n string, cascade, force bool) error { - return p.DialOrDie().CoreV1().PersistentVolumes().Delete(n, nil) -} +// // Delete a PersistentVolume. +// func (p *PersistentVolume) Delete(_, n string, cascade, force bool) error { +// return p.DialOrDie().CoreV1().PersistentVolumes().Delete(n, nil) +// } diff --git a/internal/k8s/pvc.go b/internal/k8s/pvc.go index 90e447ea..0f8e9cc6 100644 --- a/internal/k8s/pvc.go +++ b/internal/k8s/pvc.go @@ -1,40 +1,41 @@ package k8s -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) +// BOZO!! +// import ( +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -// PersistentVolumeClaim represents a Kubernetes PersistentVolumeClaim. -type PersistentVolumeClaim struct { - *base - Connection -} +// // PersistentVolumeClaim represents a Kubernetes PersistentVolumeClaim. +// type PersistentVolumeClaim struct { +// *base +// Connection +// } -// NewPersistentVolumeClaim returns a new PersistentVolumeClaim. -func NewPersistentVolumeClaim(c Connection) *PersistentVolumeClaim { - return &PersistentVolumeClaim{&base{}, c} -} +// // NewPersistentVolumeClaim returns a new PersistentVolumeClaim. +// func NewPersistentVolumeClaim(c Connection) *PersistentVolumeClaim { +// return &PersistentVolumeClaim{&base{}, c} +// } -// Get a PersistentVolumeClaim. -func (p *PersistentVolumeClaim) Get(ns, n string) (interface{}, error) { - return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Get(n, metav1.GetOptions{}) -} +// // Get a PersistentVolumeClaim. +// func (p *PersistentVolumeClaim) Get(ns, n string) (interface{}, error) { +// return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Get(n, metav1.GetOptions{}) +// } -// List all PersistentVolumeClaims in a given namespace. -func (p *PersistentVolumeClaim) List(ns string, opts metav1.ListOptions) (Collection, error) { - rr, err := p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).List(opts) - if err != nil { - return nil, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } +// // List all PersistentVolumeClaims in a given namespace. +// func (p *PersistentVolumeClaim) List(ns string, opts metav1.ListOptions) (Collection, error) { +// rr, err := p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).List(opts) +// if err != nil { +// return nil, err +// } +// cc := make(Collection, len(rr.Items)) +// for i, r := range rr.Items { +// cc[i] = r +// } - return cc, nil -} +// return cc, nil +// } -// Delete a PersistentVolumeClaim. -func (p *PersistentVolumeClaim) Delete(ns, n string, cascade, force bool) error { - return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Delete(n, nil) -} +// // Delete a PersistentVolumeClaim. +// func (p *PersistentVolumeClaim) Delete(ns, n string, cascade, force bool) error { +// return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Delete(n, nil) +// } diff --git a/internal/k8s/resource.go b/internal/k8s/resource.go index 7a1b9991..de15b7b1 100644 --- a/internal/k8s/resource.go +++ b/internal/k8s/resource.go @@ -1,103 +1,105 @@ package k8s -import ( - "fmt" +// BOZO!! +// import ( +// "fmt" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" -) +// "github.com/derailed/k9s/internal/dao" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" +// "k8s.io/apimachinery/pkg/runtime" +// "k8s.io/apimachinery/pkg/runtime/serializer" +// "k8s.io/client-go/dynamic" +// "k8s.io/client-go/rest" +// ) -// Resource represents a Kubernetes Resource -type Resource struct { - *base - Connection +// // Resource represents a Kubernetes Resource +// type Resource struct { +// *base +// Connection - gvr GVR -} +// gvr dao.GVR +// } -// NewResource returns a new Resource. -func NewResource(c Connection, gvr GVR) *Resource { - return &Resource{base: &base{}, Connection: c, gvr: gvr} -} +// // NewResource returns a new Resource. +// func NewResource(c Connection, gvr GVR) *Resource { +// return &Resource{base: &base{}, Connection: c, gvr: gvr} +// } -// GetInfo returns info about apigroup. -func (r *Resource) GetInfo() GVR { - return r.gvr -} +// // GetInfo returns info about apigroup. +// func (r *Resource) GetInfo() GVR { +// return r.gvr +// } -func (r *Resource) nsRes() dynamic.NamespaceableResourceInterface { - return r.DynDialOrDie().Resource(r.gvr.AsGVR()) -} +// func (r *Resource) nsRes() dynamic.NamespaceableResourceInterface { +// return r.DynDialOrDie().Resource(r.gvr.AsGVR()) +// } -// Get a Resource. -func (r *Resource) Get(ns, n string) (interface{}, error) { - return r.nsRes().Namespace(ns).Get(n, metav1.GetOptions{}) -} +// // Get a Resource. +// func (r *Resource) Get(ns, n string) (interface{}, error) { +// return r.nsRes().Namespace(ns).Get(n, metav1.GetOptions{}) +// } -// List all Resources in a given namespace. -func (r *Resource) List(ns string, opts metav1.ListOptions) (Collection, error) { - obj, err := r.listAll(ns, r.gvr.ToR()) - if err != nil { - return nil, err - } - return Collection{obj.(*metav1beta1.Table)}, nil -} +// // List all Resources in a given namespace. +// func (r *Resource) List(ns string, opts metav1.ListOptions) (Collection, error) { +// obj, err := r.listAll(ns, r.gvr.ToR()) +// if err != nil { +// return nil, err +// } +// return Collection{obj.(*metav1beta1.Table)}, nil +// } -// Delete a Resource. -func (r *Resource) Delete(ns, n string, cascade, force bool) error { - return r.nsRes().Namespace(ns).Delete(n, nil) -} +// // Delete a Resource. +// func (r *Resource) Delete(ns, n string, cascade, force bool) error { +// return r.nsRes().Namespace(ns).Delete(n, nil) +// } -// ---------------------------------------------------------------------------- -// Helpers... +// // ---------------------------------------------------------------------------- +// // Helpers... -const gvFmt = "application/json;as=Table;v=%s;g=%s, application/json" +// const gvFmt = "application/json;as=Table;v=%s;g=%s, application/json" -func (r *Resource) listAll(ns, n string) (runtime.Object, error) { - a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName) - _, codec := r.codec() +// func (r *Resource) listAll(ns, n string) (runtime.Object, error) { +// a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName) +// _, codec := r.codec() - c, err := r.getClient() - if err != nil { - return nil, err - } +// c, err := r.getClient() +// if err != nil { +// return nil, err +// } - return c.Get(). - SetHeader("Accept", a). - Namespace(ns). - Resource(n). - VersionedParams(&metav1beta1.TableOptions{}, codec). - Do().Get() -} +// return c.Get(). +// SetHeader("Accept", a). +// Namespace(ns). +// Resource(n). +// VersionedParams(&metav1beta1.TableOptions{}, codec). +// Do().Get() +// } -func (r *Resource) getClient() (*rest.RESTClient, error) { - crConfig := r.RestConfigOrDie() - gv := r.gvr.AsGV() - crConfig.GroupVersion = &gv - crConfig.APIPath = "/apis" - if len(r.gvr.ToG()) == 0 { - crConfig.APIPath = "/api" - } - codec, _ := r.codec() - crConfig.NegotiatedSerializer = codec.WithoutConversion() +// func (r *Resource) getClient() (*rest.RESTClient, error) { +// crConfig := r.RestConfigOrDie() +// gv := r.gvr.AsGV() +// crConfig.GroupVersion = &gv +// crConfig.APIPath = "/apis" +// if len(r.gvr.ToG()) == 0 { +// crConfig.APIPath = "/api" +// } +// codec, _ := r.codec() +// crConfig.NegotiatedSerializer = codec.WithoutConversion() - crRestClient, err := rest.RESTClientFor(crConfig) - if err != nil { - return nil, err - } - return crRestClient, nil -} +// crRestClient, err := rest.RESTClientFor(crConfig) +// if err != nil { +// return nil, err +// } +// return crRestClient, nil +// } -func (r *Resource) codec() (serializer.CodecFactory, runtime.ParameterCodec) { - scheme := runtime.NewScheme() - gv := r.gvr.AsGV() - metav1.AddToGroupVersion(scheme, gv) - scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) - scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) +// func (r *Resource) codec() (serializer.CodecFactory, runtime.ParameterCodec) { +// scheme := runtime.NewScheme() +// gv := r.gvr.AsGV() +// metav1.AddToGroupVersion(scheme, gv) +// scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) +// scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) - return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme) -} +// return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme) +// } diff --git a/internal/k8s/sts.go b/internal/k8s/sts.go deleted file mode 100644 index f032d109..00000000 --- a/internal/k8s/sts.go +++ /dev/null @@ -1,76 +0,0 @@ -package k8s - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/kubectl/pkg/polymorphichelpers" -) - -// StatefulSet manages a Kubernetes StatefulSet. -type StatefulSet struct { - *base - Connection -} - -// NewStatefulSet instantiates a new StatefulSet. -func NewStatefulSet(c Connection) *StatefulSet { - return &StatefulSet{&base{}, c} -} - -// Get a StatefulSet. -func (s *StatefulSet) Get(ns, n string) (interface{}, error) { - return s.DialOrDie().AppsV1().StatefulSets(ns).Get(n, metav1.GetOptions{}) -} - -// List all StatefulSets in a given namespace. -func (s *StatefulSet) List(ns string, opts metav1.ListOptions) (Collection, error) { - rr, err := s.DialOrDie().AppsV1().StatefulSets(ns).List(opts) - if err != nil { - return nil, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } - - return cc, nil -} - -// Delete a StatefulSet. -func (s *StatefulSet) Delete(ns, n string, cascade, force bool) error { - p := metav1.DeletePropagationOrphan - if cascade { - p = metav1.DeletePropagationBackground - } - return s.DialOrDie().AppsV1().StatefulSets(ns).Delete(n, &metav1.DeleteOptions{ - PropagationPolicy: &p, - }) -} - -// Scale a StatefulSet. -func (s *StatefulSet) Scale(ns, n string, replicas int32) error { - scale, err := s.DialOrDie().AppsV1().StatefulSets(ns).GetScale(n, metav1.GetOptions{}) - if err != nil { - return err - } - - scale.Spec.Replicas = replicas - _, err = s.DialOrDie().AppsV1().StatefulSets(ns).UpdateScale(n, scale) - return err -} - -// Restart a StatefulSet rollout. -func (s *StatefulSet) Restart(ns, n string) error { - - sts, err := s.DialOrDie().AppsV1().StatefulSets(ns).Get(n, metav1.GetOptions{}) - if err != nil { - return err - } - update, err := polymorphichelpers.ObjectRestarterFn(sts) - if err != nil { - return err - } - - _, err = s.DialOrDie().AppsV1().StatefulSets(ns).Patch(sts.Name, types.StrategicMergePatchType, update) - return err -} diff --git a/internal/k8s/svc.go b/internal/k8s/svc.go deleted file mode 100644 index 7165a170..00000000 --- a/internal/k8s/svc.go +++ /dev/null @@ -1,42 +0,0 @@ -package k8s - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Service represents a Kubernetes Service. -type Service struct { - *base - Connection -} - -// NewService returns a new Service. -func NewService(c Connection) *Service { - return &Service{&base{}, c} -} - -// Get a service. -func (s *Service) Get(ns, n string) (interface{}, error) { - panic("NYI") - return s.DialOrDie().CoreV1().Services(ns).Get(n, metav1.GetOptions{}) -} - -// List all Services in a given namespace. -func (s *Service) List(ns string, opts metav1.ListOptions) (Collection, error) { - panic("NYI") - rr, err := s.DialOrDie().CoreV1().Services(ns).List(opts) - if err != nil { - return nil, err - } - cc := make(Collection, len(rr.Items)) - for i, r := range rr.Items { - cc[i] = r - } - - return cc, nil -} - -// Delete a Service. -func (s *Service) Delete(ns, n string, cascade, force bool) error { - return s.DialOrDie().CoreV1().Services(ns).Delete(n, nil) -} diff --git a/internal/keys.go b/internal/keys.go index a583fc28..597db607 100644 --- a/internal/keys.go +++ b/internal/keys.go @@ -5,10 +5,17 @@ type ContextKey string const ( // Factory represents a factory context key. - KeyFactory ContextKey = "factory" - KeySelection = "selection" - KeyLabels = "labels" - KeyFields = "fields" - KeyTable = "table" - KeyDir = "dir" + KeyFactory ContextKey = "factory" + KeyLabels = "labels" + KeyFields = "fields" + KeyTable = "table" + KeyDir = "dir" + KeyPath = "path" + KeySubject = "subject" + KeyGVR = "gvr" + KeyForwards = "forwards" + KeyContainers = "containers" + KeyBenchCfg = "benchcfg" + KeyAliases = "aliases" + KeyUID = "uid" ) diff --git a/internal/model/alias.go b/internal/model/alias.go new file mode 100644 index 00000000..5f5def5e --- /dev/null +++ b/internal/model/alias.go @@ -0,0 +1,52 @@ +package model + +import ( + "context" + "errors" + "sort" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/render" + "k8s.io/apimachinery/pkg/runtime" +) + +// Alias represents a collection of aliases. +type Alias struct { + Resource +} + +// List returns a collection of screen dumps. +func (b *Alias) List(ctx context.Context) ([]runtime.Object, error) { + aa, ok := ctx.Value(internal.KeyAliases).(config.Alias) + if !ok { + return nil, errors.New("no aliases found in context") + } + + m := make(config.ShortNames, len(aa)) + for alias, gvr := range aa { + if _, ok := m[gvr]; ok { + m[gvr] = append(m[gvr], alias) + } else { + m[gvr] = []string{alias} + } + } + + oo := make([]runtime.Object, 0, len(m)) + for gvr, aliases := range m { + sort.StringSlice(aliases).Sort() + oo = append(oo, render.AliasRes{GVR: gvr, Aliases: aliases}) + } + + return oo, nil +} + +// Hydrate returns a pod as container rows. +func (b *Alias) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { + for i, o := range oo { + if err := re.Render(o, render.NonResource, &rr[i]); err != nil { + return err + } + } + return nil +} diff --git a/internal/model/benchmark.go b/internal/model/benchmark.go new file mode 100644 index 00000000..2826a0ab --- /dev/null +++ b/internal/model/benchmark.go @@ -0,0 +1,47 @@ +package model + +import ( + "context" + "errors" + "io/ioutil" + "path/filepath" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/render" + "k8s.io/apimachinery/pkg/runtime" +) + +// Benchmark represents a collection of benchmarks. +type Benchmark struct { + Resource +} + +// List returns a collection of screen dumps. +func (b *Benchmark) List(ctx context.Context) ([]runtime.Object, error) { + dir, ok := ctx.Value(internal.KeyDir).(string) + if !ok { + return nil, errors.New("no benchmark dir found in context") + } + + ff, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + oo := make([]runtime.Object, len(ff)) + for i, f := range ff { + oo[i] = render.BenchInfo{File: f, Path: filepath.Join(dir, f.Name())} + } + + return oo, nil +} + +// Hydrate returns a pod as container rows. +func (b *Benchmark) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { + for i, o := range oo { + if err := re.Render(o, render.NonResource, &rr[i]); err != nil { + return err + } + } + return nil +} diff --git a/internal/model/container.go b/internal/model/container.go index f866a816..be0cd101 100644 --- a/internal/model/container.go +++ b/internal/model/container.go @@ -2,6 +2,7 @@ package model import ( "context" + "fmt" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/k8s" @@ -28,10 +29,13 @@ type Container struct { // List returns a collection of containers func (c *Container) List(ctx context.Context) ([]runtime.Object, error) { c.pod = nil - sel := ctx.Value(internal.KeySelection).(string) - ns, n := render.Namespaced(sel) + path, ok := ctx.Value(internal.KeyPath).(string) + if !ok { + return nil, fmt.Errorf("no context path for %q", c.gvr) + } + ns, _ := render.Namespaced(path) c.namespace = ns - o, err := c.factory.Get(ns, "v1/pods", n, labels.Everything()) + o, err := c.factory.Get("v1/pods", path, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/model/generic.go b/internal/model/generic.go index 0f199162..03d8ce7b 100644 --- a/internal/model/generic.go +++ b/internal/model/generic.go @@ -40,7 +40,7 @@ func (g *Generic) List(ctx context.Context) ([]runtime.Object, error) { // BOZO!! Need to know if gvr is namespaced or not o, err := c.Get(). SetHeader("Accept", fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)). - // Namespace(g.namespace). + Namespace(g.namespace). Resource(gvr.ToR()). VersionedParams(&metav1beta1.TableOptions{}, codec). Do().Get() diff --git a/internal/model/job.go b/internal/model/job.go new file mode 100644 index 00000000..00c8c21d --- /dev/null +++ b/internal/model/job.go @@ -0,0 +1,87 @@ +package model + +import ( + "context" + "errors" + "strings" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/k8s" + "github.com/derailed/k9s/internal/render" + "github.com/rs/zerolog/log" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +// Job represents a collections of jobs. +type Job struct { + Resource +} + +// List returns a collection of screen dumps. +func (c *Job) List(ctx context.Context) ([]runtime.Object, error) { + uid, ok := ctx.Value(internal.KeyUID).(string) + if !ok { + log.Debug().Msgf("NO UID in context") + } + path, ok := ctx.Value(internal.KeyPath).(string) + if !ok { + return nil, errors.New("no cronjob path found in context") + } + + oo, err := c.Resource.List(ctx) + if err != nil { + return nil, err + } + if uid == "" { + return oo, nil + } + + _, cronName := k8s.Namespaced(path) + jj := make([]runtime.Object, 0, len(oo)) + for _, j := range oo { + var job batchv1.Job + err = runtime.DefaultUnstructuredConverter.FromUnstructured(j.(*unstructured.Unstructured).Object, &job) + if err != nil { + return nil, err + } + if !isNamedAfter(cronName, job.Name) { + continue + } + id, ok := job.Spec.Selector.MatchLabels["controller-uid"] + if !ok { + continue + } + if isControlledBy(uid, id) { + log.Debug().Msgf("Job %s -- %#v -- %q -- %q", job.Name, job.Labels, uid, path) + jj = append(jj, j) + } + } + + return jj, nil +} + +// Hydrate returns a pod as container rows. +func (c *Job) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { + for i, o := range oo { + if err := re.Render(o, c.namespace, &rr[i]); err != nil { + return err + } + } + return nil +} + +func isControlledBy(cuid, id string) bool { + tokens := strings.Split(cuid, "-") + root := strings.Join(tokens[2:], "-") + return strings.Contains(id, root) +} + +func isNamedAfter(p, n string) bool { + tokens := strings.Split(n, "-") + if len(tokens) == 0 || tokens[0] != p { + return false + } + return true +} diff --git a/internal/model/node.go b/internal/model/node.go index d1ce2c84..803f1eb5 100644 --- a/internal/model/node.go +++ b/internal/model/node.go @@ -44,7 +44,7 @@ func (n *Node) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { mx := k8s.NewMetricsServer(n.factory.Client().(k8s.Connection)) mmx, err := mx.FetchNodesMetrics() if err != nil { - return err + log.Warn().Err(err).Msg("No node metrics") } var index int @@ -80,7 +80,7 @@ func nodeMetricsFor(o runtime.Object, mmx *mv1beta1.NodeMetricsList) *mv1beta1.N } func (n *Node) nodePods(f Factory, node string) ([]*v1.Pod, error) { - pp, err := f.List("", "v1/pods", labels.Everything()) + pp, err := f.List("v1/pods", render.AllNamespaces, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/model/pod.go b/internal/model/pod.go index d5e40c2c..93a346a4 100644 --- a/internal/model/pod.go +++ b/internal/model/pod.go @@ -2,7 +2,6 @@ package model import ( "context" - "fmt" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/k8s" @@ -26,27 +25,21 @@ func (p *Pod) List(ctx context.Context) ([]runtime.Object, error) { return oo, err } - fieldSel, ok := ctx.Value(internal.KeyFields).(string) + sel, ok := ctx.Value(internal.KeyFields).(string) if !ok { return oo, nil } - - sel, err := labels.ConvertSelectorToLabelsMap(fieldSel) + fsel, err := labels.ConvertSelectorToLabelsMap(sel) if err != nil { return nil, err } - - nodeName, ok := sel["spec.nodeName"] - if !ok { - return nil, fmt.Errorf("NYI field selector %q", nodeName) - } + nodeName := fsel["spec.nodeName"] var res []runtime.Object for _, o := range oo { u := o.(*unstructured.Unstructured) spec := u.Object["spec"].(map[string]interface{}) - log.Debug().Msgf("Spec node %q -- %q", nodeName, spec["nodeName"]) - if spec["nodeName"] == nodeName { + if nodeName == "" || spec["nodeName"] == nodeName { res = append(res, o) } } diff --git a/internal/model/portforward.go b/internal/model/portforward.go new file mode 100644 index 00000000..8b9c4f73 --- /dev/null +++ b/internal/model/portforward.go @@ -0,0 +1,77 @@ +package model + +import ( + "context" + "fmt" + "strings" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/k8s" + "github.com/derailed/k9s/internal/render" + "github.com/rs/zerolog/log" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// PortForward represents a portforward model. +type PortForward struct { + Resource + + pod *v1.Pod +} + +// List returns a collection of screen dumps. +func (c *PortForward) List(ctx context.Context) ([]runtime.Object, error) { + config, ok := ctx.Value(internal.KeyBenchCfg).(*config.Bench) + if !ok { + return nil, fmt.Errorf("no benchconfig found in context") + } + + cc := config.Benchmarks.Containers + oo := make([]runtime.Object, 0, len(c.factory.Forwarders())) + for _, f := range c.factory.Forwarders() { + cfg := render.BenchCfg{ + C: config.Benchmarks.Defaults.C, + N: config.Benchmarks.Defaults.N, + } + if config, ok := cc[containerID(f.Path(), f.Container())]; ok { + cfg.C, cfg.N = config.C, config.N + cfg.Host, cfg.Path = config.HTTP.Host, config.HTTP.Path + } + oo = append(oo, render.ForwardRes{ + Forwarder: f, + Config: cfg, + }) + } + + return oo, nil +} + +// Hydrate returns a pod as container rows. +func (c *PortForward) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { + for i, o := range oo { + log.Debug().Msgf("PortFWD GOT %#v", o) + res, ok := o.(render.ForwardRes) + if !ok { + return fmt.Errorf("expecting a forwardres but got %T", o) + } + + if err := re.Render(res, render.NonResource, &rr[i]); err != nil { + return err + } + } + + return nil +} + +// ---------------------------------------------------------------------------- +// Helpers... + +// ContainerID computes container ID based on ns/po/co. +func containerID(path, co string) string { + ns, n := k8s.Namespaced(path) + po := strings.Split(n, "-")[0] + + return ns + "/" + po + ":" + co +} diff --git a/internal/model/rbac.go b/internal/model/rbac.go new file mode 100644 index 00000000..d611e3cb --- /dev/null +++ b/internal/model/rbac.go @@ -0,0 +1,196 @@ +package model + +import ( + "context" + "fmt" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/k8s" + "github.com/derailed/k9s/internal/render" + "github.com/rs/zerolog/log" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +type Rbac struct { + Resource +} + +func (r *Rbac) List(ctx context.Context) ([]runtime.Object, error) { + gvr, ok := ctx.Value(internal.KeyGVR).(string) + if !ok { + return nil, fmt.Errorf("expecting a context gvr") + } + r.gvr = gvr + path, ok := ctx.Value(internal.KeyPath).(string) + log.Debug().Msgf("LISTING RBACK %q--%q", r.gvr, path) + if !ok || path == "" { + return r.Resource.List(ctx) + } + + switch k8s.GVR(r.gvr).ToR() { + case "clusterrolebindings": + return r.loadClusterRoleBinding(path) + case "rolebindings": + return r.loadRoleBinding(path) + case "clusterroles": + return r.loadClusterRole(path) + case "roles": + return r.loadRole(path) + default: + return nil, fmt.Errorf("expecting clusterrole/role but found %s", k8s.GVR(r.gvr).ToR()) + } +} + +func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { + o, err := r.factory.Get("rbac.authorization.k8s.io/v1/clusterrolebindings", path, labels.Everything()) + if err != nil { + return nil, err + } + + var crb rbacv1.ClusterRoleBinding + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) + if err != nil { + return nil, err + } + + kind := "rbac.authorization.k8s.io/v1/clusterroles" + crbo, err := r.factory.Get(kind, k8s.FQN("-", crb.RoleRef.Name), labels.Everything()) + if err != nil { + return nil, err + } + var cr rbacv1.ClusterRole + err = runtime.DefaultUnstructuredConverter.FromUnstructured(crbo.(*unstructured.Unstructured).Object, &cr) + if err != nil { + return nil, err + } + return r.parseRules(cr.Rules), nil +} + +func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { + o, err := r.factory.Get("rbac.authorization.k8s.io/v1/rolebindings", path, labels.Everything()) + if err != nil { + return nil, err + } + + var rb rbacv1.RoleBinding + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) + if err != nil { + return nil, err + } + + if rb.RoleRef.Kind == "ClusterRole" { + kind := "rbac.authorization.k8s.io/v1/clusterroles" + o, err := r.factory.Get(kind, k8s.FQN("-", rb.RoleRef.Name), labels.Everything()) + if err != nil { + return nil, err + } + var cr rbacv1.ClusterRole + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) + if err != nil { + return nil, err + } + return r.parseRules(cr.Rules), nil + } + + kind := "rbac.authorization.k8s.io/v1/roles" + ro, err := r.factory.Get(kind, k8s.FQN(rb.Namespace, rb.RoleRef.Name), labels.Everything()) + if err != nil { + return nil, err + } + var role rbacv1.Role + err = runtime.DefaultUnstructuredConverter.FromUnstructured(ro.(*unstructured.Unstructured).Object, &role) + if err != nil { + return nil, err + } + + return r.parseRules(role.Rules), nil +} + +func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) { + o, err := r.factory.Get("rbac.authorization.k8s.io/v1/clusterroles", path, labels.Everything()) + if err != nil { + return nil, err + } + + var cr rbacv1.ClusterRole + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) + if err != nil { + return nil, err + } + + return r.parseRules(cr.Rules), nil +} + +func (r *Rbac) loadRole(path string) ([]runtime.Object, error) { + o, err := r.factory.Get("rbac.authorization.k8s.io/v1/roles", path, labels.Everything()) + if err != nil { + return nil, err + } + + var ro rbacv1.Role + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro) + if err != nil { + return nil, err + } + + return r.parseRules(ro.Rules), nil +} + +func makeRes(res, grp string, vv []string) *render.PolicyRes { + return &render.PolicyRes{ + Resource: res, + Group: grp, + Verbs: vv, + } +} + +func (r *Rbac) parseRules(rules []rbacv1.PolicyRule) []runtime.Object { + m := make([]runtime.Object, 0, len(rules)) + for _, rule := range rules { + for _, grp := range rule.APIGroups { + for _, res := range rule.Resources { + k := res + if grp != "" { + k = res + "." + grp + } + for _, na := range rule.ResourceNames { + m = upsert(m, makeRes(FQN(k, na), grp, rule.Verbs)) + } + m = upsert(m, makeRes(k, grp, rule.Verbs)) + } + } + for _, nres := range rule.NonResourceURLs { + if nres[0] != '/' { + nres = "/" + nres + } + m = upsert(m, makeRes(nres, "", rule.Verbs)) + } + } + + return m +} + +func upsert(rr []runtime.Object, p *render.PolicyRes) []runtime.Object { + idx, ok := find(rr, p.Resource) + if !ok { + return append(rr, p) + } + rr[idx] = p + + return rr +} + +// Find locates a row by id. Retturns false is not found. +func find(rr []runtime.Object, res string) (int, bool) { + for i, r := range rr { + p := r.(*render.PolicyRes) + if p.Resource == res { + return i, true + } + } + + return 0, false +} diff --git a/internal/model/registry.go b/internal/model/registry.go index d102a9bb..59c83a90 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -6,6 +6,7 @@ import ( // BOZO!! Break up deps and merge into single registrar var Registry = map[string]ResourceMeta{ + // Custom... "containers": ResourceMeta{ Model: &Container{}, Renderer: &render.Container{}, @@ -18,7 +19,24 @@ var Registry = map[string]ResourceMeta{ Model: &ScreenDump{}, Renderer: &render.ScreenDump{}, }, + "rbac": ResourceMeta{ + Model: &Rbac{}, + Renderer: &render.Rbac{}, + }, + "portforwards": ResourceMeta{ + Model: &PortForward{}, + Renderer: &render.PortForward{}, + }, + "benchmarks": ResourceMeta{ + Model: &Benchmark{}, + Renderer: &render.Benchmark{}, + }, + "aliases": ResourceMeta{ + Model: &Alias{}, + Renderer: &render.Alias{}, + }, + // Core... "v1/pods": ResourceMeta{ Model: &Pod{}, Renderer: &render.Pod{}, @@ -30,7 +48,20 @@ var Registry = map[string]ResourceMeta{ "v1/namespaces": ResourceMeta{ Renderer: &render.Namespace{}, }, + "v1/endpoints": ResourceMeta{ + Renderer: &render.Endpoints{}, + }, + "v1/services": ResourceMeta{ + Renderer: &render.Service{}, + }, + "v1/configmaps": ResourceMeta{ + Renderer: &render.ConfigMap{}, + }, + "v1/secrets": ResourceMeta{ + Renderer: &render.Secret{}, + }, + // Apps... "apps/v1/deployments": ResourceMeta{ Renderer: &render.Deployment{}, }, @@ -43,31 +74,32 @@ var Registry = map[string]ResourceMeta{ "apps/v1/daemonsets": ResourceMeta{ Renderer: &render.DaemonSet{}, }, + + // Extensions... "extensions/v1beta1/daemonsets": ResourceMeta{ Renderer: &render.DaemonSet{}, }, + "extensions/v1beta1/ingresses": ResourceMeta{ + Renderer: &render.Ingress{}, + }, - // "v1/services": ResourceMeta{ - // Renderer: &render.Service{}, - // }, - // "v1/configmaps": ResourceMeta{ - // Renderer: &render.ConfigMap{}, - // }, - // "v1/secrets": ResourceMeta{ - // Renderer: &render.ConfigMap{}, - // }, - // "batch/v1beta1/cronjobs": ResourceMeta{ - // Renderer: &render.CronJob{}, - // }, - // "batch/v1/jobs": ResourceMeta{ - // Renderer: &render.Job{}, - // }, + // Batch... + "batch/v1beta1/cronjobs": ResourceMeta{ + Renderer: &render.CronJob{}, + }, + "batch/v1/jobs": ResourceMeta{ + Model: &Job{}, + Renderer: &render.Job{}, + }, + // CRDs... "apiextensions.k8s.io/v1beta1/customresourcedefinitions": ResourceMeta{ Renderer: &render.CustomResourceDefinition{}, }, + // RBAC... "rbac.authorization.k8s.io/v1/clusterroles": ResourceMeta{ + Model: &Rbac{}, Renderer: &render.ClusterRole{}, }, "rbac.authorization.k8s.io/v1/clusterrolebindings": ResourceMeta{ diff --git a/internal/model/resource.go b/internal/model/resource.go index 0c093855..26aa056e 100644 --- a/internal/model/resource.go +++ b/internal/model/resource.go @@ -6,7 +6,6 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" ) @@ -28,8 +27,8 @@ func (r *Resource) List(ctx context.Context) ([]runtime.Object, error) { if sel, err := labels.ConvertSelectorToLabelsMap(strLabel); ok && err == nil { lsel = sel.AsSelector() } - - oo, err := r.factory.List(r.namespace, r.gvr, lsel) + log.Debug().Msgf("^^^^^Listing with selector %q:%q--%#v", r.namespace, r.gvr, lsel) + oo, err := r.factory.List(r.gvr, r.namespace, lsel) r.factory.WaitForCacheSync() return oo, err @@ -41,9 +40,8 @@ func (r *Resource) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) err var index int for _, o := range oo { - res := o.(*unstructured.Unstructured) var row render.Row - if err := re.Render(res, r.namespace, &row); err != nil { + if err := re.Render(o, r.namespace, &row); err != nil { return err } rr[index] = row diff --git a/internal/model/screen_dump.go b/internal/model/screen_dump.go index 9ec31c2e..a61b3b0c 100644 --- a/internal/model/screen_dump.go +++ b/internal/model/screen_dump.go @@ -3,25 +3,22 @@ package model import ( "context" "errors" - "fmt" "io/ioutil" - "os" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/render" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" ) -// ScreenDump represents a container model. +// ScreenDump represents a collections of screendumps. type ScreenDump struct { Resource pod *v1.Pod } -// List returns a collection of containers +// List returns a collection of screen dumps. func (c *ScreenDump) List(ctx context.Context) ([]runtime.Object, error) { dir, ok := ctx.Value(internal.KeyDir).(string) if !ok { @@ -35,7 +32,7 @@ func (c *ScreenDump) List(ctx context.Context) ([]runtime.Object, error) { oo := make([]runtime.Object, len(ff)) for i, f := range ff { - oo[i] = FileRes{file: f, dir: dir} + oo[i] = render.FileRes{File: f, Dir: dir} } return oo, nil @@ -44,38 +41,9 @@ func (c *ScreenDump) List(ctx context.Context) ([]runtime.Object, error) { // Hydrate returns a pod as container rows. func (c *ScreenDump) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { for i, o := range oo { - res, ok := o.(FileRes) - if !ok { - return fmt.Errorf("expecting a file resource but got %T", o) - } - - if err := re.Render(res, render.NonResource, &rr[i]); err != nil { + if err := re.Render(o, render.NonResource, &rr[i]); err != nil { return err } } - return nil } - -// ---------------------------------------------------------------------------- - -// FileRes represents a file resource. -type FileRes struct { - file os.FileInfo - dir string -} - -func (c FileRes) GetFile() os.FileInfo { return c.file } -func (c FileRes) GetDir() string { return c.dir } - -// GetObjectKind returns a schema object. -func (c FileRes) GetObjectKind() schema.ObjectKind { - - return nil -} - -// DeepCopyObject returns a container copy. -func (c FileRes) DeepCopyObject() runtime.Object { - - return c -} diff --git a/internal/model/stack.go b/internal/model/stack.go index 6c95615a..0d91e137 100644 --- a/internal/model/stack.go +++ b/internal/model/stack.go @@ -106,12 +106,6 @@ func (s *Stack) Pop() (Component, bool) { c := s.components[s.size()] s.components = s.components[:s.size()] s.notify(StackPop, c) - c.Stop() - - if top := s.Top(); top != nil { - log.Debug().Msgf("Calling Start on %s", top.Name()) - top.Start() - } return c, true } diff --git a/internal/model/subject.go b/internal/model/subject.go new file mode 100644 index 00000000..7b0bb5eb --- /dev/null +++ b/internal/model/subject.go @@ -0,0 +1,133 @@ +package model + +import ( + "context" + "errors" + "fmt" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/render" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Subject represents a subject model. +type Subject struct { + Resource + + subjectKind string +} + +// List returns a collection of subjects. +func (s *Subject) List(ctx context.Context) ([]runtime.Object, error) { + var ok bool + s.subjectKind, ok = ctx.Value(internal.KeySubject).(string) + if !ok { + return nil, errors.New("expecting a subject") + } + + crbs, err := s.factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) + if err != nil { + return nil, err + } + + rbs, err := s.factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) + if err != nil { + return nil, err + } + + return append(crbs, rbs...), nil +} + +// Hydrate returns a pod as container rows. +func (s *Subject) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { + for i, o := range oo { + res, ok := o.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("expecting unstructured but got %T", o) + } + + if err := re.Render(res, render.AllNamespaces, &rr[i]); err != nil { + return err + } + } + + return nil +} + +func (s *Subject) fetchClusterRoleBindings() ([]runtime.Object, error) { + oo, err := s.factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) + if err != nil { + return nil, err + } + + rows := make([]runtime.Object, 0, len(oo)) + for _, o := range oo { + var crb rbacv1.ClusterRoleBinding + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) + if err != nil { + return nil, err + } + for _, subject := range crb.Subjects { + if subject.Kind != s.subjectKind { + continue + } + rows = append(rows, SubjectRes{ + id: subject.Name, + fields: render.Fields{subject.Name, "ClusterRoleBinding", crb.Name}, + }) + } + } + + return rows, nil +} + +func (s *Subject) fetchRoleBindings() ([]runtime.Object, error) { + oo, err := s.factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) + if err != nil { + return nil, err + } + + rows := make([]runtime.Object, 0, len(oo)) + for _, o := range oo { + var rb rbacv1.RoleBinding + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) + if err != nil { + return nil, err + } + for _, subject := range rb.Subjects { + if subject.Kind == s.subjectKind { + rows = append(rows, SubjectRes{ + id: subject.Name, + fields: render.Fields{subject.Name, "RoleBinding", rb.Name}, + }) + } + } + } + + return rows, nil +} + +// ---------------------------------------------------------------------------- + +// SubjectRes represents a subject resource. +type SubjectRes struct { + id string + fields render.Fields +} + +func (s SubjectRes) GetID() string { return s.id } +func (s SubjectRes) GetFields() render.Fields { return s.fields } + +// GetObjectKind returns a schema object. +func (s SubjectRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (s SubjectRes) DeepCopyObject() runtime.Object { + return s +} diff --git a/internal/model/types.go b/internal/model/types.go index a082b8ef..d6ea76d1 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -5,6 +5,7 @@ import ( "github.com/derailed/k9s/internal/k8s" "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" @@ -69,45 +70,24 @@ type Lister interface { Hydrate([]runtime.Object, render.Rows, Renderer) error } -// BOZO!! -// type Connection interface { -// // DialOrDie dials client api. -// DialOrDie() kubernetes.Interface - -// // MXDial dials metrics api. -// MXDial() (*versioned.Clientset, error) - -// // DynDialOrDie dials dynamic client api. -// DynDialOrDie() dynamic.Interface - -// // RestConfigOrDie return a client configuration. -// RestConfigOrDie() *restclient.Config - -// // Config returns the current kubeconfig. -// Config() *k8s.Config - -// // CachedDiscovery returns a cached client. -// CachedDiscovery() (*disk.CachedDiscoveryClient, error) - -// // SwithContextOrDie switch to a new kube context. -// SwitchContextOrDie(ctx string) -// } - type Factory interface { // Client retrieves an api client. Client() k8s.Connection // Get fetch a given resource. - Get(ns, gvr, n string, sel labels.Selector) (runtime.Object, error) + Get(gvr, path string, sel labels.Selector) (runtime.Object, error) // List fetch a collection of resources. - List(ns, gvr string, sel labels.Selector) ([]runtime.Object, error) + List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) // ForResource fetch an informer for a given resource. ForResource(ns, gvr string) informers.GenericInformer // WaitForCacheSync synchronize the cache. WaitForCacheSync() map[schema.GroupVersionResource]bool + + // Forwards returns all portforwards. + Forwarders() watch.Forwarders } // ResourceMeta represents model info about a resource. diff --git a/internal/render/alias.go b/internal/render/alias.go index 23a0155d..974e1881 100644 --- a/internal/render/alias.go +++ b/internal/render/alias.go @@ -6,6 +6,8 @@ import ( "github.com/derailed/k9s/internal/k8s" "github.com/gdamore/tcell" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) // Alias renders a aliases to screen. @@ -24,25 +26,24 @@ func (Alias) Header(ns string) HeaderRow { Header{Name: "RESOURCE"}, Header{Name: "COMMAND"}, Header{Name: "APIGROUP"}, + // Header{Name: "AGE", Decorator: ageDecorator}, } } // Render renders a K8s resource to screen. func (Alias) Render(o interface{}, gvr string, r *Row) error { - aliases, ok := o.([]string) + a, ok := o.(AliasRes) if !ok { - return fmt.Errorf("Expected Alias, but got %T", o) + return fmt.Errorf("expected aliasres, but got %T", o) } - g := k8s.GVR(gvr) - r.ID = string(gvr) + g := k8s.GVR(a.GVR) + r.ID = string(g) r.Fields = Fields{ g.ToR(), - strings.Join(aliases, ","), + strings.Join(a.Aliases, ","), g.ToG(), - // Pad(g.ToR(), 30), - // Pad(strings.Join(aliases, ","), 70), - // Pad(g.ToG(), 30), + // time.Now().String(), } return nil @@ -50,15 +51,18 @@ func (Alias) Render(o interface{}, gvr string, r *Row) error { // Helpers... -// Pad a string up to the given length or truncates if greater than length. -func Pad(s string, width int) string { - if len(s) == width { - return s - } - - if len(s) > width { - return Truncate(s, width) - } - - return s + strings.Repeat(" ", width-len(s)) +// AliasRes represents an alias resource. +type AliasRes struct { + GVR string + Aliases []string +} + +// GetObjectKind returns a schema object. +func (AliasRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (a AliasRes) DeepCopyObject() runtime.Object { + return a } diff --git a/internal/render/bench.go b/internal/render/benchmark.go similarity index 70% rename from internal/render/bench.go rename to internal/render/benchmark.go index 8b157129..af62bf08 100644 --- a/internal/render/bench.go +++ b/internal/render/benchmark.go @@ -7,12 +7,13 @@ import ( "regexp" "strconv" "strings" - "time" "github.com/derailed/tview" "github.com/gdamore/tcell" "golang.org/x/text/language" "golang.org/x/text/message" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) var ( @@ -23,17 +24,11 @@ var ( toastRx = regexp.MustCompile(`Error distribution`) ) -// BenchInfo represents benchmark run info. -type BenchInfo struct { - File os.FileInfo - Path string -} - -// Bench renders a benchmarks to screen. -type Bench struct{} +// Benchmark renders a benchmarks to screen. +type Benchmark struct{} // ColorerFunc colors a resource row. -func (Bench) ColorerFunc() ColorerFunc { +func (Benchmark) ColorerFunc() ColorerFunc { return func(ns string, re RowEvent) tcell.Color { c := tcell.ColorPaleGreen statusCol := 2 @@ -45,25 +40,25 @@ func (Bench) ColorerFunc() ColorerFunc { } // Header returns a header row. -func (Bench) Header(ns string) HeaderRow { +func (Benchmark) Header(ns string) HeaderRow { return HeaderRow{ - Header{Name: "NAMESPACE", Align: tview.AlignLeft}, - Header{Name: "NAME", Align: tview.AlignLeft}, - Header{Name: "STATUS", Align: tview.AlignLeft}, - Header{Name: "TIME", Align: tview.AlignLeft}, + Header{Name: "NAMESPACE"}, + Header{Name: "NAME"}, + Header{Name: "STATUS"}, + Header{Name: "TIME"}, Header{Name: "REQ/S", Align: tview.AlignRight}, Header{Name: "2XX", Align: tview.AlignRight}, Header{Name: "4XX/5XX", Align: tview.AlignRight}, - Header{Name: "REPORT", Align: tview.AlignLeft}, - Header{Name: "AGE", Align: tview.AlignLeft}, + Header{Name: "REPORT"}, + Header{Name: "AGE", Decorator: ageDecorator}, } } // Render renders a K8s resource to screen. -func (b Bench) Render(o interface{}, ns string, r *Row) error { +func (b Benchmark) Render(o interface{}, ns string, r *Row) error { bench, ok := o.(BenchInfo) if !ok { - return fmt.Errorf("Expected string, but got %T", o) + return fmt.Errorf("expecting benchinfo but got `%T", o) } data, err := b.readFile(bench.Path) @@ -71,19 +66,20 @@ func (b Bench) Render(o interface{}, ns string, r *Row) error { return fmt.Errorf("Unable to load bench file %s", bench.Path) } + r.ID = bench.Path r.Fields = make(Fields, len(b.Header(ns))) if err := b.initRow(r.Fields, bench.File); err != nil { return err } b.augmentRow(r.Fields, data) - r.ID = bench.Path return nil } +// ---------------------------------------------------------------------------- // Helpers... -func (Bench) readFile(file string) (string, error) { +func (Benchmark) readFile(file string) (string, error) { data, err := ioutil.ReadFile(file) if err != nil { return "", err @@ -91,7 +87,7 @@ func (Bench) readFile(file string) (string, error) { return string(data), nil } -func (Bench) initRow(row Fields, f os.FileInfo) error { +func (Benchmark) initRow(row Fields, f os.FileInfo) error { tokens := strings.Split(f.Name(), "_") if len(tokens) < 2 { return fmt.Errorf("Invalid file name %s", f.Name()) @@ -99,12 +95,12 @@ func (Bench) initRow(row Fields, f os.FileInfo) error { row[0] = tokens[0] row[1] = tokens[1] row[7] = f.Name() - row[8] = time.Since(f.ModTime()).String() + row[8] = timeToAge(f.ModTime()) return nil } -func (b Bench) augmentRow(fields Fields, data string) { +func (b Benchmark) augmentRow(fields Fields, data string) { if len(data) == 0 { return } @@ -137,7 +133,7 @@ func (b Bench) augmentRow(fields Fields, data string) { fields[col] = b.countReq(me) } -func (Bench) countReq(rr [][]string) string { +func (Benchmark) countReq(rr [][]string) string { if len(rr) == 0 { return "0" } @@ -156,3 +152,19 @@ func asNum(n int) string { p := message.NewPrinter(language.English) return p.Sprintf("%d", n) } + +// BenchInfo represents benchmark run info. +type BenchInfo struct { + File os.FileInfo + Path string +} + +// GetObjectKind returns a schema object. +func (BenchInfo) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (b BenchInfo) DeepCopyObject() runtime.Object { + return b +} diff --git a/internal/render/cr.go b/internal/render/cr.go index 8fed9612..b8bac0d0 100644 --- a/internal/render/cr.go +++ b/internal/render/cr.go @@ -3,6 +3,7 @@ package render import ( "fmt" + "github.com/derailed/k9s/internal/k8s" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -28,7 +29,7 @@ func (ClusterRole) Header(string) HeaderRow { func (ClusterRole) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected ClusterRole, but got %T", o) + return fmt.Errorf("expecting clusterrole, but got %T", o) } var cr rbacv1.ClusterRole err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &cr) @@ -36,12 +37,11 @@ func (ClusterRole) Render(o interface{}, ns string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) - fields = append(fields, + r.ID = k8s.FQN("-", cr.ObjectMeta.Name) + r.Fields = Fields{ cr.Name, toAge(cr.ObjectMeta.CreationTimestamp), - ) - r.ID, r.Fields = MetaFQN(cr.ObjectMeta), fields + } return nil } diff --git a/internal/render/crb.go b/internal/render/crb.go index 87480b93..866cece4 100644 --- a/internal/render/crb.go +++ b/internal/render/crb.go @@ -3,6 +3,7 @@ package render import ( "fmt" + "github.com/derailed/k9s/internal/k8s" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -20,7 +21,7 @@ func (ClusterRoleBinding) ColorerFunc() ColorerFunc { func (ClusterRoleBinding) Header(string) HeaderRow { return HeaderRow{ Header{Name: "NAME"}, - Header{Name: "ROLE"}, + Header{Name: "CLUSTERROLE"}, Header{Name: "KIND"}, Header{Name: "SUBJECTS"}, Header{Name: "AGE", Decorator: ageDecorator}, @@ -41,15 +42,14 @@ func (ClusterRoleBinding) Render(o interface{}, ns string, r *Row) error { kind, ss := renderSubjects(crb.Subjects) - fields := make(Fields, 0, len(r.Fields)) - fields = append(fields, + r.ID = k8s.FQN("-", crb.ObjectMeta.Name) + r.Fields = Fields{ crb.Name, crb.RoleRef.Name, kind, ss, toAge(crb.ObjectMeta.CreationTimestamp), - ) - r.ID, r.Fields = MetaFQN(crb.ObjectMeta), fields + } return nil } diff --git a/internal/render/crd.go b/internal/render/crd.go index 7f5d4b9a..cd3fd8d4 100644 --- a/internal/render/crd.go +++ b/internal/render/crd.go @@ -38,13 +38,11 @@ func (CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error { log.Error().Err(err).Msgf("Fields timestamp %v", err) } - fields := make(Fields, 0, len(r.Fields)) - fields = append(fields, + r.ID = FQN(ClusterWide, meta["name"].(string)) + r.Fields = Fields{ meta["name"].(string), toAge(metav1.Time{t}), - ) - - r.ID, r.Fields = FQN("", meta["name"].(string)), fields + } return nil } diff --git a/internal/render/cj.go b/internal/render/cronjob.go similarity index 86% rename from internal/render/cj.go rename to internal/render/cronjob.go index 7c192cf6..69a1cf90 100644 --- a/internal/render/cj.go +++ b/internal/render/cronjob.go @@ -35,7 +35,7 @@ func (CronJob) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (CronJob) Render(o interface{}, ns string, r *Row) error { +func (c CronJob) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected CronJob, but got %T", o) @@ -51,11 +51,12 @@ func (CronJob) Render(o interface{}, ns string, r *Row) error { lastScheduled = toAgeHuman(toAge(*cj.Status.LastScheduleTime)) } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(cj.ObjectMeta) + r.Fields = make(Fields, 0, len(c.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, cj.Namespace) + r.Fields = append(r.Fields, cj.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, cj.Name, cj.Spec.Schedule, boolPtrToStr(cj.Spec.Suspend), @@ -64,7 +65,5 @@ func (CronJob) Render(o interface{}, ns string, r *Row) error { toAge(cj.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(cj.ObjectMeta), fields - return nil } diff --git a/internal/render/cj_test.go b/internal/render/cronjob_test.go similarity index 100% rename from internal/render/cj_test.go rename to internal/render/cronjob_test.go diff --git a/internal/render/delta.go b/internal/render/delta.go index d93bec52..229a6093 100644 --- a/internal/render/delta.go +++ b/internal/render/delta.go @@ -1,18 +1,18 @@ package render -import "github.com/rs/zerolog/log" - // DeltaRow represents a collection of row detlas between old and new row. type DeltaRow []string // NewDeltaRow computes the delta between 2 rows. -func NewDeltaRow(o, n Row) DeltaRow { +func NewDeltaRow(o, n Row, excludeLast bool) DeltaRow { deltas := make(DeltaRow, len(o.Fields)) // Exclude age col oldFields := o.Fields[:len(o.Fields)-1] + if !excludeLast { + oldFields = o.Fields[:len(o.Fields)] + } for i, old := range oldFields { if old != "" && old != n.Fields[i] { - log.Debug().Msgf("OLD VS NEW %q:%q", old, n.Fields[i]) deltas[i] = old } } diff --git a/internal/render/dp.go b/internal/render/dp.go index 814e6c85..7b61960d 100644 --- a/internal/render/dp.go +++ b/internal/render/dp.go @@ -7,9 +7,7 @@ import ( "github.com/derailed/tview" "github.com/gdamore/tcell" - "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" "k8s.io/apimachinery/pkg/runtime" ) @@ -17,10 +15,6 @@ import ( // Deployment renders a K8s Deployment to screen. type Deployment struct{} -func isAllNamespace(ns string) bool { - return ns == "" -} - // ColorerFunc colors a resource row. func (Deployment) ColorerFunc() ColorerFunc { return func(ns string, r RowEvent) tcell.Color { @@ -54,7 +48,6 @@ func (Deployment) Header(ns string) HeaderRow { Header{Name: "READY"}, Header{Name: "UP-TO-DATE", Align: tview.AlignRight}, Header{Name: "AVAILABLE", Align: tview.AlignRight}, - Header{Name: "SELECTOR"}, Header{Name: "AGE", Decorator: ageDecorator}, ) } @@ -81,21 +74,8 @@ func (d Deployment) Render(o interface{}, ns string, r *Row) error { strconv.Itoa(int(dp.Status.AvailableReplicas))+"/"+strconv.Itoa(int(*dp.Spec.Replicas)), strconv.Itoa(int(dp.Status.UpdatedReplicas)), strconv.Itoa(int(dp.Status.AvailableReplicas)), - asSelector(dp.Spec.Selector), toAge(dp.ObjectMeta.CreationTimestamp), ) return nil } - -//Helpers... - -func asSelector(s *metav1.LabelSelector) string { - sel, err := metav1.LabelSelectorAsSelector(s) - if err != nil { - log.Error().Err(err).Msg("Selector conversion failed") - return NAValue - } - - return sel.String() -} diff --git a/internal/render/ds.go b/internal/render/ds.go index b8584b1c..2d91f09c 100644 --- a/internal/render/ds.go +++ b/internal/render/ds.go @@ -49,7 +49,6 @@ func (DaemonSet) Header(ns string) HeaderRow { Header{Name: "READY", Align: tview.AlignRight}, Header{Name: "UP-TO-DATE", Align: tview.AlignRight}, Header{Name: "AVAILABLE", Align: tview.AlignRight}, - Header{Name: "NODE_SELECTOR"}, Header{Name: "AGE", Decorator: ageDecorator}, ) } @@ -78,7 +77,6 @@ func (d DaemonSet) Render(o interface{}, ns string, r *Row) error { strconv.Itoa(int(ds.Status.NumberReady)), strconv.Itoa(int(ds.Status.UpdatedNumberScheduled)), strconv.Itoa(int(ds.Status.NumberAvailable)), - mapToStr(ds.Spec.Template.Spec.NodeSelector), toAge(ds.ObjectMeta.CreationTimestamp), ) diff --git a/internal/render/event.go b/internal/render/event.go index 50b9c69d..f751bb54 100644 --- a/internal/render/event.go +++ b/internal/render/event.go @@ -224,16 +224,17 @@ type ColorerFunc func(ns string, evt RowEvent) tcell.Color // DefaultColorer set the default table row colors. func DefaultColorer(ns string, evt RowEvent) tcell.Color { + var col = StdColor switch evt.Kind { case EventAdd: - return AddColor + col = AddColor case EventUpdate: - return ModColor + col = ModColor case EventDelete: - return KillColor - default: - return StdColor + col = KillColor } + + return col } type StringSet []string diff --git a/internal/render/generic.go b/internal/render/generic.go index 45c67928..cd9e1742 100644 --- a/internal/render/generic.go +++ b/internal/render/generic.go @@ -26,8 +26,11 @@ func (Generic) ColorerFunc() ColorerFunc { // Header returns a header row. func (g *Generic) Header(ns string) HeaderRow { - h := make(HeaderRow, 0, len(g.table.ColumnDefinitions)) + if g.table == nil { + return HeaderRow{} + } + h := make(HeaderRow, 0, len(g.table.ColumnDefinitions)) if ns == "" { h = append(h, Header{Name: "NAMESPACE"}) } diff --git a/internal/render/helpers.go b/internal/render/helpers.go index 074cc900..32634137 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -9,6 +9,7 @@ import ( "github.com/derailed/tview" runewidth "github.com/mattn/go-runewidth" + "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/duration" "k8s.io/apimachinery/pkg/watch" @@ -26,6 +27,20 @@ const ( NAValue = "n/a" ) +func asSelector(s *metav1.LabelSelector) string { + sel, err := metav1.LabelSelectorAsSelector(s) + if err != nil { + log.Error().Err(err).Msg("Selector conversion failed") + return NAValue + } + + return sel.String() +} + +func isAllNamespace(ns string) bool { + return ns == AllNamespaces +} + type metric struct { cpu, mem string } @@ -217,3 +232,16 @@ func in(ll []string, s string) bool { } return false } + +// Pad a string up to the given length or truncates if greater than length. +func Pad(s string, width int) string { + if len(s) == width { + return s + } + + if len(s) > width { + return Truncate(s, width) + } + + return s + strings.Repeat(" ", width-len(s)) +} diff --git a/internal/render/ing.go b/internal/render/ing.go index fae2f5ef..5c124ec6 100644 --- a/internal/render/ing.go +++ b/internal/render/ing.go @@ -35,7 +35,7 @@ func (Ingress) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (Ingress) Render(o interface{}, ns string, r *Row) error { +func (i Ingress) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected Ingress, but got %T", o) @@ -46,11 +46,12 @@ func (Ingress) Render(o interface{}, ns string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(ing.ObjectMeta) + r.Fields = make(Fields, 0, len(i.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, ing.Namespace) + r.Fields = append(r.Fields, ing.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, ing.Name, toHosts(ing.Spec.Rules), toAddress(ing.Status.LoadBalancer), @@ -58,8 +59,6 @@ func (Ingress) Render(o interface{}, ns string, r *Row) error { toAge(ing.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(ing.ObjectMeta), fields - return nil } @@ -89,15 +88,13 @@ func toTLSPorts(tls []v1beta1.IngressTLS) string { } func toHosts(rr []v1beta1.IngressRule) string { - var s string - var i int + hh := make([]string, 0, len(rr)) for _, r := range rr { - s += r.Host - if i < len(rr)-1 { - s += "," + if r.Host == "" { + r.Host = "*" } - i++ + hh = append(hh, r.Host) } - return s + return strings.Join(hh, ",") } diff --git a/internal/render/job.go b/internal/render/job.go index 6fe545c0..63fa81b8 100644 --- a/internal/render/job.go +++ b/internal/render/job.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/rs/zerolog/log" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -39,37 +40,37 @@ func (Job) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (Job) Render(o interface{}, ns string, r *Row) error { +func (j Job) Render(o interface{}, ns string, r *Row) error { + log.Debug().Msgf("JOB RENDER %q", ns) raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected Job, but got %T", o) } - var j batchv1.Job - err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &j) + var job batchv1.Job + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &job) if err != nil { return err } - cc, ii := toContainers(j.Spec.Template.Spec) - - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(job.ObjectMeta) + r.Fields = make(Fields, 0, len(j.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, j.Namespace) + r.Fields = append(r.Fields, job.Namespace) } - fields = append(fields, - j.Name, - toCompletion(j.Spec, j.Status), - toDuration(j.Status), + cc, ii := toContainers(job.Spec.Template.Spec) + r.Fields = append(r.Fields, + job.Name, + toCompletion(job.Spec, job.Status), + toDuration(job.Status), cc, ii, - toAge(j.ObjectMeta.CreationTimestamp), + toAge(job.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(j.ObjectMeta), fields - return nil } +// ---------------------------------------------------------------------------- // Helpers... const maxShow = 2 diff --git a/internal/render/ns.go b/internal/render/ns.go index 2c5caa26..b42477d0 100644 --- a/internal/render/ns.go +++ b/internal/render/ns.go @@ -17,10 +17,13 @@ type Namespace struct{} func (Namespace) ColorerFunc() ColorerFunc { return func(ns string, r RowEvent) tcell.Color { c := DefaultColorer(ns, r) - - if r.Kind == EventAdd || r.Kind == EventUpdate { + if r.Kind == EventAdd { return c } + + if r.Kind == EventUpdate { + c = StdColor + } switch strings.TrimSpace(r.Row.Fields[1]) { case "Inactive", Terminating: c = ErrColor @@ -54,13 +57,12 @@ func (Namespace) Render(o interface{}, _ string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) - fields = append(fields, + r.ID = MetaFQN(ns.ObjectMeta) + r.Fields = Fields{ ns.Name, string(ns.Status.Phase), toAge(ns.ObjectMeta.CreationTimestamp), - ) - r.ID, r.Fields = MetaFQN(ns.ObjectMeta), fields + } return nil } diff --git a/internal/render/forward.go b/internal/render/portforward.go similarity index 50% rename from internal/render/forward.go rename to internal/render/portforward.go index 8222e387..e8ec7ab8 100644 --- a/internal/render/forward.go +++ b/internal/render/portforward.go @@ -5,6 +5,8 @@ import ( "strings" "github.com/gdamore/tcell" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) // Forwarder represents a port forwarder. @@ -25,18 +27,18 @@ type Forwarder interface { Age() string } -// Forward renders a portforwards to screen. -type Forward struct{} +// PortForward renders a portforwards to screen. +type PortForward struct{} // ColorerFunc colors a resource row. -func (Forward) ColorerFunc() ColorerFunc { +func (PortForward) ColorerFunc() ColorerFunc { return func(ns string, re RowEvent) tcell.Color { return tcell.ColorSkyblue } } // Header returns a header row. -func (Forward) Header(ns string) HeaderRow { +func (PortForward) Header(ns string) HeaderRow { return HeaderRow{ Header{Name: "NAMESPACE"}, Header{Name: "NAME"}, @@ -50,10 +52,10 @@ func (Forward) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (f Forward) Render(o interface{}, gvr string, r *Row) error { - pf, ok := o.(PortForwarder) +func (f PortForward) Render(o interface{}, gvr string, r *Row) error { + pf, ok := o.(ForwardRes) if !ok { - return fmt.Errorf("expecting a portforward but got %T", o) + return fmt.Errorf("expecting a ForwardRes but got %T", o) } ports := strings.Split(pf.Ports()[0], ":") @@ -65,9 +67,9 @@ func (f Forward) Render(o interface{}, gvr string, r *Row) error { na, pf.Container(), strings.Join(pf.Ports(), ","), - UrlFor(pf.Host(), pf.HttpPath(), ports[0]), - asNum(pf.C()), - asNum(pf.N()), + UrlFor(pf.Config.Host, pf.Config.Path, ports[0]), + asNum(pf.Config.C), + asNum(pf.Config.N), pf.Age(), } @@ -76,26 +78,27 @@ func (f Forward) Render(o interface{}, gvr string, r *Row) error { // Helpers... -type PortForwarder interface { - Forwarder - BenchConfigurator -} +// type PortForwarder interface { +// Forwarder +// BenchConfigurator +// } -type BenchConfigurators map[string]BenchConfigurator +// type BenchConfigurators map[string]BenchConfigurator -type BenchConfigurator interface { - // C returns the number of concurent connections. - C() int +// BOZO!! +// type BenchConfigurator interface { +// // C returns the number of concurent connections. +// C() int - // N returns the number of requests. - N() int +// // N returns the number of requests. +// N() int - // Host returns the forward host address. - Host() string +// // Host returns the forward host address. +// Host() string - // Path returns the http path. - HttpPath() string -} +// // Path returns the http path. +// HttpPath() string +// } // UrlFor computes fq url for a given benchmark configuration. func UrlFor(host, path, port string) string { @@ -108,3 +111,25 @@ func UrlFor(host, path, port string) string { return "http://" + host + ":" + port + path } + +// BenchCfg represents a benchmark configuration. +type BenchCfg struct { + C, N int + Host, Path string +} + +// ForwardRes represents a benchmark resource. +type ForwardRes struct { + Forwarder + Config BenchCfg +} + +// GetObjectKind returns a schema object. +func (f ForwardRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (f ForwardRes) DeepCopyObject() runtime.Object { + return f +} diff --git a/internal/render/rbac.go b/internal/render/rbac.go index a24351d4..38f8464e 100644 --- a/internal/render/rbac.go +++ b/internal/render/rbac.go @@ -1,7 +1,32 @@ package render import ( + "fmt" + "strings" + "github.com/gdamore/tcell" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const allVerbs = "*" + +var ( + k8sVerbs = []string{ + "get", + "list", + "watch", + "create", + "patch", + "update", + "delete", + "deletecollection", + } + + httpTok8sVerbs = map[string]string{ + "post": "create", + "put": "update", + } ) // Rbac renders a rbac to screen. @@ -26,8 +51,95 @@ func (Rbac) Header(ns string) HeaderRow { // Render renders a K8s resource to screen. func (Rbac) Render(o interface{}, gvr string, r *Row) error { - panic("NYI") + p, ok := o.(*PolicyRes) + if !ok { + return fmt.Errorf("expecting policyres in renderer for %q", gvr) + } + + if p.Group != "" { + p.Group = toGroup(p.Group) + } else { + p.Group = "core" + } + r.Fields = append(r.Fields, p.Resource, p.Group) + r.Fields = append(r.Fields, asVerbs(p.Verbs)...) + r.ID = p.Resource + return nil } // Helpers... + +func asVerbs(verbs []string) []string { + const ( + verbLen = 4 + unknownLen = 30 + ) + + r := make([]string, 0, len(k8sVerbs)+1) + for _, v := range k8sVerbs { + r = append(r, toVerbIcon(hasVerb(verbs, v))) + } + + var unknowns []string + for _, v := range verbs { + if hv, ok := httpTok8sVerbs[v]; ok { + v = hv + } + if !hasVerb(k8sVerbs, v) && v != allVerbs { + unknowns = append(unknowns, v) + } + } + + return append(r, Truncate(strings.Join(unknowns, ","), unknownLen)) +} + +func toVerbIcon(ok bool) string { + if ok { + return "[green::b] ✓ [::]" + } + return "[orangered::b] 𐄂 [::]" +} + +func hasVerb(verbs []string, verb string) bool { + if len(verbs) == 1 && verbs[0] == allVerbs { + return true + } + + for _, v := range verbs { + if hv, ok := httpTok8sVerbs[v]; ok { + if hv == verb { + return true + } + } + if v == verb { + return true + } + } + + return false +} + +func toGroup(g string) string { + if g == "" { + return "v1" + } + return g +} + +type PolicyRes struct { + Resource, Group string + ResourceName string + NonResourceURL string + Verbs []string +} + +// GetObjectKind returns a schema object. +func (p PolicyRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (p PolicyRes) DeepCopyObject() runtime.Object { + return p +} diff --git a/internal/render/ro.go b/internal/render/ro.go index 7292f3a4..cff62be2 100644 --- a/internal/render/ro.go +++ b/internal/render/ro.go @@ -30,7 +30,7 @@ func (Role) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (Role) Render(o interface{}, ns string, r *Row) error { +func (r Role) Render(o interface{}, ns string, row *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected Role, but got %T", o) @@ -41,15 +41,15 @@ func (Role) Render(o interface{}, ns string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) + row.ID = MetaFQN(ro.ObjectMeta) + row.Fields = make(Fields, 0, len(r.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, ro.Namespace) + row.Fields = append(row.Fields, ro.Namespace) } - fields = append(fields, + row.Fields = append(row.Fields, ro.Name, toAge(ro.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(ro.ObjectMeta), fields return nil } diff --git a/internal/render/rb.go b/internal/render/rob.go similarity index 88% rename from internal/render/rb.go rename to internal/render/rob.go index 53a80db2..f8ff0472 100644 --- a/internal/render/rb.go +++ b/internal/render/rob.go @@ -34,7 +34,7 @@ func (RoleBinding) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (RoleBinding) Render(o interface{}, ns string, r *Row) error { +func (r RoleBinding) Render(o interface{}, ns string, row *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected RoleBinding, but got %T", o) @@ -47,18 +47,18 @@ func (RoleBinding) Render(o interface{}, ns string, r *Row) error { kind, ss := renderSubjects(rb.Subjects) - fields := make(Fields, 0, len(r.Fields)) + row.ID = MetaFQN(rb.ObjectMeta) + row.Fields = make(Fields, 0, len(r.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, rb.Namespace) + row.Fields = append(row.Fields, rb.Namespace) } - fields = append(fields, + row.Fields = append(row.Fields, rb.Name, rb.RoleRef.Name, kind, ss, toAge(rb.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(rb.ObjectMeta), fields return nil } diff --git a/internal/render/rb_test.go b/internal/render/rob_test.go similarity index 100% rename from internal/render/rb_test.go rename to internal/render/rob_test.go diff --git a/internal/render/row.go b/internal/render/row.go index 742e401a..b6146c7a 100644 --- a/internal/render/row.go +++ b/internal/render/row.go @@ -7,6 +7,8 @@ import ( "vbom.ml/util/sortorder" ) +const ageCol = "AGE" + // Fields represents a collection of row fields. type Fields []string @@ -29,7 +31,21 @@ type Header struct { // HeaderRow represents a table header. type HeaderRow []Header +// HasAge returns true if table has an age column. +func (h HeaderRow) HasAge() bool { + for _, r := range h { + if r.Name == ageCol { + return true + } + } + + return false +} + func (h HeaderRow) AgeCol(col int) bool { + if !h.HasAge() { + return false + } return col == len(h)-1 } diff --git a/internal/render/screen_dump.go b/internal/render/screen_dump.go index 89d053ab..13149934 100644 --- a/internal/render/screen_dump.go +++ b/internal/render/screen_dump.go @@ -7,6 +7,8 @@ import ( "time" "github.com/gdamore/tcell" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) // ScreenDump renders a screendumps to screen. @@ -35,27 +37,39 @@ func (ScreenDump) Header(ns string) HeaderRow { // Render renders a K8s resource to screen. func (b ScreenDump) Render(o interface{}, ns string, r *Row) error { - f, ok := o.(ScreenDumper) + f, ok := o.(FileRes) if !ok { - return fmt.Errorf("Expected string, but got %T", o) + return fmt.Errorf("expecting screendumper, but got %T", o) } - r.ID = filepath.Join(f.GetDir(), f.GetFile().Name()) + r.ID = filepath.Join(f.Dir, f.File.Name()) r.Fields = Fields{ - f.GetFile().Name(), - timeToAge(f.GetFile().ModTime()), + f.File.Name(), + timeToAge(f.File.ModTime()), } return nil } +// ---------------------------------------------------------------------------- // Helpers... func timeToAge(timestamp time.Time) string { return time.Since(timestamp).String() } -type ScreenDumper interface { - GetFile() os.FileInfo - GetDir() string +// FileRes represents a file resource. +type FileRes struct { + File os.FileInfo + Dir string +} + +// GetObjectKind returns a schema object. +func (c FileRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (c FileRes) DeepCopyObject() runtime.Object { + return c } diff --git a/internal/resource/base.go b/internal/resource/base.go index be25df2b..0af435a1 100644 --- a/internal/resource/base.go +++ b/internal/resource/base.go @@ -2,18 +2,12 @@ package resource import ( "bytes" - "context" "errors" - "fmt" "path" - "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/watch" "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/runtime" genericprinters "k8s.io/cli-runtime/pkg/printers" "k8s.io/kubectl/pkg/describe" @@ -163,43 +157,44 @@ func (*Base) marshalObject(o runtime.Object) (string, error) { return buff.String(), nil } -func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error { - f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) - if !ok { - return fmt.Errorf("no factory in context for pod logs") - } +// BOZO!! +// func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error { +// f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) +// if !ok { +// return fmt.Errorf("no factory in context for pod logs") +// } - ls, err := metav1.ParseToLabelSelector(toSelector(sel)) - if err != nil { - return err - } - lsel, err := metav1.LabelSelectorAsSelector(ls) - if err != nil { - return err - } - inf := f.ForResource(opts.Namespace, "v1/pods") - pods, err := inf.Lister().List(lsel) - if err != nil { - return err - } +// ls, err := metav1.ParseToLabelSelector(toSelector(sel)) +// if err != nil { +// return err +// } +// lsel, err := metav1.LabelSelectorAsSelector(ls) +// if err != nil { +// return err +// } +// inf := f.ForResource(opts.Namespace, "v1/pods") +// pods, err := inf.Lister().List(lsel) +// if err != nil { +// return err +// } - if len(pods) > 1 { - opts.MultiPods = true - } - pr := NewPod(b.Connection) - for _, p := range pods { - var po v1.Pod - err := runtime.DefaultUnstructuredConverter.FromUnstructured(p.(*unstructured.Unstructured).Object, &po) - if err != nil { - // BOZO!! - panic(err) - } - 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 -} +// if len(pods) > 1 { +// opts.MultiPods = true +// } +// pr := NewPod(b.Connection) +// for _, p := range pods { +// var po v1.Pod +// err := runtime.DefaultUnstructuredConverter.FromUnstructured(p.(*unstructured.Unstructured).Object, &po) +// if err != nil { +// // BOZO!! +// panic(err) +// } +// 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 +// } diff --git a/internal/resource/cm.go b/internal/resource/cm.go index 08317942..9c227127 100644 --- a/internal/resource/cm.go +++ b/internal/resource/cm.go @@ -1,6 +1,7 @@ package resource -// NewConfigMapList returns a new resource list. -func NewConfigMapList(c Connection, ns string) List { - return NewCustomList(c, true, "", "v1/configmaps") -} +// BOZO!! +// // NewConfigMapList returns a new resource list. +// func NewConfigMapList(c Connection, ns string) List { +// return NewCustomList(c, true, "", "v1/configmaps") +// } diff --git a/internal/resource/container.go b/internal/resource/container.go index 59161a46..b52d3ede 100644 --- a/internal/resource/container.go +++ b/internal/resource/container.go @@ -1,264 +1,265 @@ package resource -import ( - "context" - "errors" - "fmt" - "strconv" - "strings" +// BOZO!! +// import ( +// "context" +// "errors" +// "fmt" +// "strconv" +// "strings" - "github.com/derailed/k9s/internal/k8s" - v1 "k8s.io/api/core/v1" - mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" -) +// "github.com/derailed/k9s/internal/k8s" +// v1 "k8s.io/api/core/v1" +// mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" +// ) -type ( - // Container represents a container on a pod. - Container struct { - *Base +// type ( +// // Container represents a container on a pod. +// Container struct { +// *Base - pod *v1.Pod - instance v1.Container - metrics *mv1beta1.PodMetrics - } -) +// pod *v1.Pod +// instance v1.Container +// metrics *mv1beta1.PodMetrics +// } +// ) -// NewContainerList returns a collection of container. -func NewContainerList(c Connection, pod *v1.Pod) List { - return NewList( - NotNamespaced, - "containers", - NewContainer(c, pod), - 0, - ) -} +// // NewContainerList returns a collection of container. +// func NewContainerList(c Connection, pod *v1.Pod) List { +// return NewList( +// NotNamespaced, +// "containers", +// NewContainer(c, pod), +// 0, +// ) +// } -// NewContainer returns a new set of containers. -func NewContainer(c Connection, pod *v1.Pod) *Container { - co := Container{ - Base: &Base{Connection: c, Resource: k8s.NewPod(c)}, - pod: pod, - } - co.Factory = &co +// // NewContainer returns a new set of containers. +// func NewContainer(c Connection, pod *v1.Pod) *Container { +// co := Container{ +// Base: &Base{Connection: c, Resource: k8s.NewPod(c)}, +// pod: pod, +// } +// co.Factory = &co - return &co -} +// return &co +// } -// New builds a new Container instance from a k8s resource. -func (r *Container) New(i interface{}) (Columnar, error) { - co := NewContainer(r.Connection, r.pod) - coi, ok := i.(v1.Container) - if !ok { - return nil, errors.New("Expecting a container resource") - } - co.instance = coi - co.path = r.namespacedName(r.pod.ObjectMeta) + ":" + co.instance.Name +// // New builds a new Container instance from a k8s resource. +// func (r *Container) New(i interface{}) (Columnar, error) { +// co := NewContainer(r.Connection, r.pod) +// coi, ok := i.(v1.Container) +// if !ok { +// return nil, errors.New("Expecting a container resource") +// } +// co.instance = coi +// co.path = r.namespacedName(r.pod.ObjectMeta) + ":" + co.instance.Name - return co, nil -} +// return co, nil +// } -// SetPodMetrics set the current k8s resource metrics on associated pod. -func (r *Container) SetPodMetrics(m *mv1beta1.PodMetrics) { - r.metrics = m -} +// // SetPodMetrics set the current k8s resource metrics on associated pod. +// func (r *Container) SetPodMetrics(m *mv1beta1.PodMetrics) { +// r.metrics = m +// } -// Marshal resource to yaml. -func (r *Container) Marshal(path string) (string, error) { - return "", nil -} +// // Marshal resource to yaml. +// func (r *Container) Marshal(path string) (string, error) { +// return "", nil +// } -// Logs tails a given container logs -func (r *Container) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { - res, ok := r.Resource.(k8s.Loggable) - if !ok { - return fmt.Errorf("Resource %T is not Loggable", r.Resource) - } +// // Logs tails a given container logs +// func (r *Container) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { +// res, ok := r.Resource.(k8s.Loggable) +// if !ok { +// return fmt.Errorf("Resource %T is not Loggable", r.Resource) +// } - return tailLogs(ctx, res, c, opts) -} +// return tailLogs(ctx, res, c, opts) +// } -// List resources for a given namespace. -func (r *Container) List(ctx context.Context, ns string) (Columnars, error) { - icos := r.pod.Spec.InitContainers - cos := r.pod.Spec.Containers +// // List resources for a given namespace. +// func (r *Container) List(ctx context.Context, ns string) (Columnars, error) { +// icos := r.pod.Spec.InitContainers +// cos := r.pod.Spec.Containers - cc := make(Columnars, 0, len(icos)+len(cos)) - for _, co := range icos { - res, err := r.New(co) - if err != nil { - return nil, err - } - cc = append(cc, res) - } - for _, co := range cos { - res, err := r.New(co) - if err != nil { - return nil, err - } - cc = append(cc, res) - } +// cc := make(Columnars, 0, len(icos)+len(cos)) +// for _, co := range icos { +// res, err := r.New(co) +// if err != nil { +// return nil, err +// } +// cc = append(cc, res) +// } +// for _, co := range cos { +// res, err := r.New(co) +// if err != nil { +// return nil, err +// } +// cc = append(cc, res) +// } - return cc, nil -} +// return cc, nil +// } -// Header return resource header. -func (*Container) Header(ns string) Row { - return append(Row{}, - "NAME", - "IMAGE", - "READY", - "STATE", - "RS", - "PROBES(L:R)", - "CPU", - "MEM", - "%CPU", - "%MEM", - "PORTS", - "AGE", - ) -} +// // Header return resource header. +// func (*Container) Header(ns string) Row { +// return append(Row{}, +// "NAME", +// "IMAGE", +// "READY", +// "STATE", +// "RS", +// "PROBES(L:R)", +// "CPU", +// "MEM", +// "%CPU", +// "%MEM", +// "PORTS", +// "AGE", +// ) +// } -// NumCols designates if column is numerical. -func (*Container) NumCols(n string) map[string]bool { - return map[string]bool{ - "CPU": true, - "MEM": true, - "%CPU": true, - "%MEM": true, - "RS": true, - } -} +// // NumCols designates if column is numerical. +// func (*Container) NumCols(n string) map[string]bool { +// return map[string]bool{ +// "CPU": true, +// "MEM": true, +// "%CPU": true, +// "%MEM": true, +// "RS": true, +// } +// } -// Fields retrieves displayable fields. -func (r *Container) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - i := r.instance +// // Fields retrieves displayable fields. +// func (r *Container) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) +// i := r.instance - c, p := gatherMetrics(i, r.metrics) +// c, p := gatherMetrics(i, r.metrics) - ready, state, restarts := "false", MissingValue, "0" - cs := getContainerStatus(i.Name, r.pod.Status) - if cs != nil { - ready, state, restarts = boolToStr(cs.Ready), toState(cs.State), strconv.Itoa(int(cs.RestartCount)) - } +// ready, state, restarts := "false", MissingValue, "0" +// cs := getContainerStatus(i.Name, r.pod.Status) +// if cs != nil { +// ready, state, restarts = boolToStr(cs.Ready), toState(cs.State), strconv.Itoa(int(cs.RestartCount)) +// } - return append(ff, - i.Name, - i.Image, - ready, - state, - restarts, - probe(i.LivenessProbe)+":"+probe(i.ReadinessProbe), - c.cpu, - c.mem, - p.cpu, - p.mem, - toStrPorts(i.Ports), - toAge(r.pod.CreationTimestamp), - ) -} +// return append(ff, +// i.Name, +// i.Image, +// ready, +// state, +// restarts, +// probe(i.LivenessProbe)+":"+probe(i.ReadinessProbe), +// c.cpu, +// c.mem, +// p.cpu, +// p.mem, +// toStrPorts(i.Ports), +// toAge(r.pod.CreationTimestamp), +// ) +// } -// ---------------------------------------------------------------------------- -// Helpers... +// // ---------------------------------------------------------------------------- +// // Helpers... -func gatherMetrics(co v1.Container, mx *mv1beta1.PodMetrics) (c, p metric) { - c, p = noMetric(), noMetric() - if mx == nil { - return - } +// func gatherMetrics(co v1.Container, mx *mv1beta1.PodMetrics) (c, p metric) { +// c, p = noMetric(), noMetric() +// if mx == nil { +// return +// } - var ( - cpu int64 - mem float64 - ) - for _, c := range mx.Containers { - if c.Name == co.Name { - cpu = c.Usage.Cpu().MilliValue() - mem = k8s.ToMB(c.Usage.Memory().Value()) - break - } - } - c = metric{ - cpu: ToMillicore(cpu), - mem: ToMi(mem), - } +// var ( +// cpu int64 +// mem float64 +// ) +// for _, c := range mx.Containers { +// if c.Name == co.Name { +// cpu = c.Usage.Cpu().MilliValue() +// mem = k8s.ToMB(c.Usage.Memory().Value()) +// break +// } +// } +// c = metric{ +// cpu: ToMillicore(cpu), +// mem: ToMi(mem), +// } - rcpu, rmem := containerResources(co) - if rcpu != nil { - p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue()))) - } - if rmem != nil { - p.mem = AsPerc(toPerc(mem, k8s.ToMB(rmem.Value()))) - } +// rcpu, rmem := containerResources(co) +// if rcpu != nil { +// p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue()))) +// } +// if rmem != nil { +// p.mem = AsPerc(toPerc(mem, k8s.ToMB(rmem.Value()))) +// } - return -} +// return +// } -func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { - for _, c := range status.ContainerStatuses { - if c.Name == co { - return &c - } - } +// func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { +// for _, c := range status.ContainerStatuses { +// if c.Name == co { +// return &c +// } +// } - for _, c := range status.InitContainerStatuses { - if c.Name == co { - return &c - } - } +// for _, c := range status.InitContainerStatuses { +// if c.Name == co { +// return &c +// } +// } - return nil -} +// return nil +// } -func toStrPorts(pp []v1.ContainerPort) string { - ports := make([]string, len(pp)) - for i, p := range pp { - if len(p.Name) > 0 { - ports[i] = p.Name + ":" - } - ports[i] += strconv.Itoa(int(p.ContainerPort)) - if p.Protocol != "TCP" { - ports[i] += "╱" + string(p.Protocol) - } - } +// func toStrPorts(pp []v1.ContainerPort) string { +// ports := make([]string, len(pp)) +// for i, p := range pp { +// if len(p.Name) > 0 { +// ports[i] = p.Name + ":" +// } +// ports[i] += strconv.Itoa(int(p.ContainerPort)) +// if p.Protocol != "TCP" { +// ports[i] += "╱" + string(p.Protocol) +// } +// } - return strings.Join(ports, ",") -} +// return strings.Join(ports, ",") +// } -func toState(s v1.ContainerState) string { - switch { - case s.Waiting != nil: - if s.Waiting.Reason != "" { - return s.Waiting.Reason - } - return "Waiting" +// func toState(s v1.ContainerState) string { +// switch { +// case s.Waiting != nil: +// if s.Waiting.Reason != "" { +// return s.Waiting.Reason +// } +// return "Waiting" - case s.Terminated != nil: - if s.Terminated.Reason != "" { - return s.Terminated.Reason - } - return Terminating - case s.Running != nil: - return Running - default: - return MissingValue - } -} +// case s.Terminated != nil: +// if s.Terminated.Reason != "" { +// return s.Terminated.Reason +// } +// return Terminating +// case s.Running != nil: +// return Running +// default: +// return MissingValue +// } +// } -func toRes(r v1.ResourceList) (string, string) { - cpu, mem := r[v1.ResourceCPU], r[v1.ResourceMemory] +// func toRes(r v1.ResourceList) (string, string) { +// cpu, mem := r[v1.ResourceCPU], r[v1.ResourceMemory] - return ToMillicore(cpu.MilliValue()), ToMi(k8s.ToMB(mem.Value())) -} +// return ToMillicore(cpu.MilliValue()), ToMi(k8s.ToMB(mem.Value())) +// } -func probe(p *v1.Probe) string { - if p == nil { - return "off" - } - return "on" -} +// func probe(p *v1.Probe) string { +// if p == nil { +// return "off" +// } +// return "on" +// } -func asMi(v int64) float64 { - return float64(v) / 1024 * 1024 -} +// func asMi(v int64) float64 { +// return float64(v) / 1024 * 1024 +// } diff --git a/internal/resource/container_test.go b/internal/resource/container_test.go index 2fb499b5..d98082fb 100644 --- a/internal/resource/container_test.go +++ b/internal/resource/container_test.go @@ -1,114 +1,115 @@ package resource -import ( - "testing" +// BOZO!! +// import ( +// "testing" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" -) +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/core/v1" +// "k8s.io/apimachinery/pkg/api/resource" +// ) -func TestProbe(t *testing.T) { - uu := map[string]struct { - probe *v1.Probe - e string - }{ - "defined": {&v1.Probe{}, "on"}, - "undefined": {nil, "off"}, - } +// func TestProbe(t *testing.T) { +// uu := map[string]struct { +// probe *v1.Probe +// e string +// }{ +// "defined": {&v1.Probe{}, "on"}, +// "undefined": {nil, "off"}, +// } - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, probe(u.probe)) - }) - } -} +// for k := range uu { +// u := uu[k] +// t.Run(k, func(t *testing.T) { +// assert.Equal(t, u.e, probe(u.probe)) +// }) +// } +// } -func TestAsMi(t *testing.T) { - uu := map[string]struct { - mem int64 - e float64 - }{ - "zero": {0, 0}, - "1Mb": {1024 * 1024, 1.048576e+06}, - "10Mb": {10 * 1024 * 1024, 1.048576e+07}, - } +// func TestAsMi(t *testing.T) { +// uu := map[string]struct { +// mem int64 +// e float64 +// }{ +// "zero": {0, 0}, +// "1Mb": {1024 * 1024, 1.048576e+06}, +// "10Mb": {10 * 1024 * 1024, 1.048576e+07}, +// } - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, asMi(u.mem)) - }) - } -} +// for k := range uu { +// u := uu[k] +// t.Run(k, func(t *testing.T) { +// assert.Equal(t, u.e, asMi(u.mem)) +// }) +// } +// } -func TestToRes(t *testing.T) { - uu := map[string]struct { - res v1.ResourceList - ecpu, emem string - }{ - "cool": {v1.ResourceList{ - v1.ResourceCPU: toQty("10m"), - v1.ResourceMemory: toQty("20Mi"), - }, - "10", "20"}, - "noRes": {v1.ResourceList{}, - "0", "0"}, - } +// func TestToRes(t *testing.T) { +// uu := map[string]struct { +// res v1.ResourceList +// ecpu, emem string +// }{ +// "cool": {v1.ResourceList{ +// v1.ResourceCPU: toQty("10m"), +// v1.ResourceMemory: toQty("20Mi"), +// }, +// "10", "20"}, +// "noRes": {v1.ResourceList{}, +// "0", "0"}, +// } - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - cpu, mem := toRes(u.res) - assert.Equal(t, u.ecpu, cpu) - assert.Equal(t, u.emem, mem) - }) - } -} +// for k := range uu { +// u := uu[k] +// t.Run(k, func(t *testing.T) { +// cpu, mem := toRes(u.res) +// assert.Equal(t, u.ecpu, cpu) +// assert.Equal(t, u.emem, mem) +// }) +// } +// } -func TestToState(t *testing.T) { - uu := map[string]struct { - state v1.ContainerState - e string - }{ - "empty": {v1.ContainerState{}, - MissingValue}, - "running": { - v1.ContainerState{Running: &v1.ContainerStateRunning{}}, - "Running", - }, - "waiting": { - v1.ContainerState{Waiting: &v1.ContainerStateWaiting{}}, - "Waiting", - }, - "waitingReason": { - v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: "blee"}}, - "blee", - }, - "terminating": { - v1.ContainerState{Terminated: &v1.ContainerStateTerminated{}}, - "Terminating", - }, - "terminatedReason": { - v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "blee"}}, - "blee", - }, - } +// func TestToState(t *testing.T) { +// uu := map[string]struct { +// state v1.ContainerState +// e string +// }{ +// "empty": {v1.ContainerState{}, +// MissingValue}, +// "running": { +// v1.ContainerState{Running: &v1.ContainerStateRunning{}}, +// "Running", +// }, +// "waiting": { +// v1.ContainerState{Waiting: &v1.ContainerStateWaiting{}}, +// "Waiting", +// }, +// "waitingReason": { +// v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: "blee"}}, +// "blee", +// }, +// "terminating": { +// v1.ContainerState{Terminated: &v1.ContainerStateTerminated{}}, +// "Terminating", +// }, +// "terminatedReason": { +// v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "blee"}}, +// "blee", +// }, +// } - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, toState(u.state)) - }) - } -} +// for k := range uu { +// u := uu[k] +// t.Run(k, func(t *testing.T) { +// assert.Equal(t, u.e, toState(u.state)) +// }) +// } +// } -// ---------------------------------------------------------------------------- -// Helpers... +// // ---------------------------------------------------------------------------- +// // Helpers... -func toQty(s string) resource.Quantity { - q, _ := resource.ParseQuantity(s) +// func toQty(s string) resource.Quantity { +// q, _ := resource.ParseQuantity(s) - return q -} +// return q +// } diff --git a/internal/resource/context.go b/internal/resource/context.go index 836194d0..6201c13a 100644 --- a/internal/resource/context.go +++ b/internal/resource/context.go @@ -1,88 +1,89 @@ package resource -import ( - "fmt" +// BOZO!! +// import ( +// "fmt" - "github.com/derailed/k9s/internal/k8s" -) +// "github.com/derailed/k9s/internal/k8s" +// ) -type ( - // Switchable represents a switchable resource. - Switchable interface { - Switch(ctx string) error - MustCurrentContextName() string - } +// type ( +// // Switchable represents a switchable resource. +// Switchable interface { +// Switch(ctx string) error +// MustCurrentContextName() string +// } - // SwitchableCruder represents a resource that can be switched. - SwitchableCruder interface { - Cruder - Switchable - } +// // SwitchableCruder represents a resource that can be switched. +// SwitchableCruder interface { +// Cruder +// Switchable +// } - // Context tracks a kubernetes resource. - Context struct { - *Base - instance *k8s.NamedContext - } -) +// // Context tracks a kubernetes resource. +// Context struct { +// *Base +// instance *k8s.NamedContext +// } +// ) -// NewContextList returns a new resource list. -func NewContextList(c Connection, ns string) List { - return NewList(NotNamespaced, "ctx", NewContext(c), SwitchAccess) -} +// // NewContextList returns a new resource list. +// func NewContextList(c Connection, ns string) List { +// return NewList(NotNamespaced, "ctx", NewContext(c), SwitchAccess) +// } -// NewContext instantiates a new Context. -func NewContext(c Connection) *Context { - ctx := &Context{Base: NewBase(c, k8s.NewContext(c))} - ctx.Factory = ctx +// // NewContext instantiates a new Context. +// func NewContext(c Connection) *Context { +// ctx := &Context{Base: NewBase(c, k8s.NewContext(c))} +// ctx.Factory = ctx - return ctx -} +// return ctx +// } -// New builds a new Context instance from a k8s resource. -func (r *Context) New(i interface{}) (Columnar, error) { - c := NewContext(r.Connection) - switch instance := i.(type) { - case *k8s.NamedContext: - c.instance = instance - case k8s.NamedContext: - c.instance = &instance - default: - return nil, fmt.Errorf("unknown context type %T", instance) - } - c.path = c.instance.Name +// // New builds a new Context instance from a k8s resource. +// func (r *Context) New(i interface{}) (Columnar, error) { +// c := NewContext(r.Connection) +// switch instance := i.(type) { +// case *k8s.NamedContext: +// c.instance = instance +// case k8s.NamedContext: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("unknown context type %T", instance) +// } +// c.path = c.instance.Name - return c, nil -} +// return c, nil +// } -// Switch out current context. -func (r *Context) Switch(c string) error { - return r.Resource.(Switchable).Switch(c) -} +// // Switch out current context. +// func (r *Context) Switch(c string) error { +// return r.Resource.(Switchable).Switch(c) +// } -// Marshal the resource to yaml. -func (r *Context) Marshal(path string) (string, error) { - return "", nil -} +// // Marshal the resource to yaml. +// func (r *Context) Marshal(path string) (string, error) { +// return "", nil +// } -// Header return resource header. -func (*Context) Header(string) Row { - return append(Row{}, "NAME", "CLUSTER", "AUTHINFO", "NAMESPACE") -} +// // Header return resource header. +// func (*Context) Header(string) Row { +// return append(Row{}, "NAME", "CLUSTER", "AUTHINFO", "NAMESPACE") +// } -// Fields retrieves displayable fields. -func (r *Context) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) +// // Fields retrieves displayable fields. +// func (r *Context) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) - i := r.instance - if i.MustCurrentContextName() == i.Name { - i.Name += "*" - } +// i := r.instance +// if i.MustCurrentContextName() == i.Name { +// i.Name += "*" +// } - return append(ff, - i.Name, - i.Context.Cluster, - i.Context.AuthInfo, - i.Context.Namespace, - ) -} +// return append(ff, +// i.Name, +// i.Context.Cluster, +// i.Context.AuthInfo, +// i.Context.Namespace, +// ) +// } diff --git a/internal/resource/context_test.go b/internal/resource/context_test.go index b2562c8c..b0f63ebb 100644 --- a/internal/resource/context_test.go +++ b/internal/resource/context_test.go @@ -1,136 +1,137 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - "k8s.io/cli-runtime/pkg/genericclioptions" - api "k8s.io/client-go/tools/clientcmd/api" -) - -func NewContextListWithArgs(ns string, ctx *resource.Context) resource.List { - return resource.NewList(resource.NotNamespaced, "ctx", ctx, resource.SwitchAccess) -} - -func NewContextWithArgs(c k8s.Connection, s resource.SwitchableCruder) *resource.Context { - ctx := &resource.Context{Base: resource.NewBase(c, s)} - ctx.Factory = ctx - return ctx -} - -func TestCTXSwitch(t *testing.T) { - mc := NewMockConnection() - mr := NewMockSwitchableCruder() - m.When(mr.Switch("fred")).ThenReturn(nil) - - ctx := NewContextWithArgs(mc, mr) - err := ctx.Switch("fred") - - assert.Nil(t, err) - mr.VerifyWasCalledOnce().Switch("fred") -} - // BOZO!! -// func TestCTXList(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockSwitchableCruder() -// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sNamedCTX()}, nil) +// import ( +// "testing" -// ctx := NewContextWithArgs(mc, mr) -// cc, err := ctx.List("blee", metav1.ListOptions{}) +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// "k8s.io/cli-runtime/pkg/genericclioptions" +// api "k8s.io/client-go/tools/clientcmd/api" +// ) -// assert.Nil(t, err) -// c, err := ctx.New(k8sNamedCTX()) -// assert.Nil(t, err) -// assert.Equal(t, resource.Columnars{c}, cc) -// mr.VerifyWasCalledOnce().List("blee", metav1.ListOptions{}) +// func NewContextListWithArgs(ns string, ctx *resource.Context) resource.List { +// return resource.NewList(resource.NotNamespaced, "ctx", ctx, resource.SwitchAccess) // } -func TestCTXDelete(t *testing.T) { - mc := NewMockConnection() - mr := NewMockSwitchableCruder() - m.When(mr.Delete("", "fred", true, true)).ThenReturn(nil) +// func NewContextWithArgs(c k8s.Connection, s resource.SwitchableCruder) *resource.Context { +// ctx := &resource.Context{Base: resource.NewBase(c, s)} +// ctx.Factory = ctx +// return ctx +// } - ctx := NewContextWithArgs(mc, mr) +// func TestCTXSwitch(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockSwitchableCruder() +// m.When(mr.Switch("fred")).ThenReturn(nil) - assert.Nil(t, ctx.Delete("fred", true, true)) - mr.VerifyWasCalledOnce().Delete("", "fred", true, true) -} +// ctx := NewContextWithArgs(mc, mr) +// err := ctx.Switch("fred") -func TestCTXListHasName(t *testing.T) { - mc := NewMockConnection() - mr := NewMockSwitchableCruder() +// assert.Nil(t, err) +// mr.VerifyWasCalledOnce().Switch("fred") +// } - ctx := NewContextWithArgs(mc, mr) - l := NewContextListWithArgs("blee", ctx) +// // BOZO!! +// // func TestCTXList(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockSwitchableCruder() +// // m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sNamedCTX()}, nil) - assert.Equal(t, "ctx", l.GetName()) -} +// // ctx := NewContextWithArgs(mc, mr) +// // cc, err := ctx.List("blee", metav1.ListOptions{}) -func TestCTXListHasNamespace(t *testing.T) { - mc := NewMockConnection() - mr := NewMockSwitchableCruder() +// // assert.Nil(t, err) +// // c, err := ctx.New(k8sNamedCTX()) +// // assert.Nil(t, err) +// // assert.Equal(t, resource.Columnars{c}, cc) +// // mr.VerifyWasCalledOnce().List("blee", metav1.ListOptions{}) +// // } - ctx := NewContextWithArgs(mc, mr) - l := NewContextListWithArgs("blee", ctx) +// func TestCTXDelete(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockSwitchableCruder() +// m.When(mr.Delete("", "fred", true, true)).ThenReturn(nil) - assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) -} +// ctx := NewContextWithArgs(mc, mr) -func TestCTXListHasResource(t *testing.T) { - mc := NewMockConnection() - mr := NewMockSwitchableCruder() +// assert.Nil(t, ctx.Delete("fred", true, true)) +// mr.VerifyWasCalledOnce().Delete("", "fred", true, true) +// } - ctx := NewContextWithArgs(mc, mr) - l := NewContextListWithArgs("blee", ctx) +// func TestCTXListHasName(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockSwitchableCruder() - assert.NotNil(t, l.Resource()) -} +// ctx := NewContextWithArgs(mc, mr) +// l := NewContextListWithArgs("blee", ctx) -func TestCTXHeader(t *testing.T) { - mc := NewMockConnection() - mr := NewMockSwitchableCruder() +// assert.Equal(t, "ctx", l.GetName()) +// } - ctx := NewContextWithArgs(mc, mr) +// func TestCTXListHasNamespace(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockSwitchableCruder() - assert.Equal(t, 4, len(ctx.Header(""))) -} +// ctx := NewContextWithArgs(mc, mr) +// l := NewContextListWithArgs("blee", ctx) -func TestCTXFields(t *testing.T) { - mc := NewMockConnection() - m.When(mc.Config()).ThenReturn(k8sConfig()) - mr := NewMockSwitchableCruder() - m.When(mr.MustCurrentContextName()).ThenReturn("test") +// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) +// } - ctx := NewContextWithArgs(mc, mr) - c, err := ctx.New(k8sNamedCTX()) - assert.Nil(t, err) +// func TestCTXListHasResource(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockSwitchableCruder() - assert.Equal(t, 4, len(c.Fields(""))) - assert.Equal(t, "test*", c.Fields("")[0]) -} +// ctx := NewContextWithArgs(mc, mr) +// l := NewContextListWithArgs("blee", ctx) -// Helpers... +// assert.NotNil(t, l.Resource()) +// } -func k8sConfig() *k8s.Config { - ctx := "test" - f := genericclioptions.ConfigFlags{ - Context: &ctx, - } - return k8s.NewConfig(&f) -} +// func TestCTXHeader(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockSwitchableCruder() -func k8sNamedCTX() *k8s.NamedContext { - return k8s.NewNamedContext( - k8sConfig(), - "test", - &api.Context{ - LocationOfOrigin: "fred", - Cluster: "blee", - AuthInfo: "secret", - }, - ) -} +// ctx := NewContextWithArgs(mc, mr) + +// assert.Equal(t, 4, len(ctx.Header(""))) +// } + +// func TestCTXFields(t *testing.T) { +// mc := NewMockConnection() +// m.When(mc.Config()).ThenReturn(k8sConfig()) +// mr := NewMockSwitchableCruder() +// m.When(mr.MustCurrentContextName()).ThenReturn("test") + +// ctx := NewContextWithArgs(mc, mr) +// c, err := ctx.New(k8sNamedCTX()) +// assert.Nil(t, err) + +// assert.Equal(t, 4, len(c.Fields(""))) +// assert.Equal(t, "test*", c.Fields("")[0]) +// } + +// // Helpers... + +// func k8sConfig() *k8s.Config { +// ctx := "test" +// f := genericclioptions.ConfigFlags{ +// Context: &ctx, +// } +// return k8s.NewConfig(&f) +// } + +// func k8sNamedCTX() *k8s.NamedContext { +// return k8s.NewNamedContext( +// k8sConfig(), +// "test", +// &api.Context{ +// LocationOfOrigin: "fred", +// Cluster: "blee", +// AuthInfo: "secret", +// }, +// ) +// } diff --git a/internal/resource/cr.go b/internal/resource/cr.go deleted file mode 100644 index dadf63ef..00000000 --- a/internal/resource/cr.go +++ /dev/null @@ -1,83 +0,0 @@ -package resource - -import ( - "errors" - "fmt" - - "github.com/derailed/k9s/internal/k8s" - v1 "k8s.io/api/rbac/v1" -) - -// ClusterRole tracks a kubernetes resource. -type ClusterRole struct { - *Base - instance *v1.ClusterRole -} - -// NewClusterRoleList returns a new resource list. -func NewClusterRoleList(c Connection, ns string) List { - return NewList( - NotNamespaced, - "clusterrole", - NewClusterRole(c), - CRUDAccess|DescribeAccess, - ) -} - -// NewClusterRole instantiates a new ClusterRole. -func NewClusterRole(c Connection) *ClusterRole { - cr := &ClusterRole{&Base{Connection: c, Resource: k8s.NewClusterRole(c)}, nil} - cr.Factory = cr - - return cr -} - -// New builds a new ClusterRole instance from a k8s resource. -func (r *ClusterRole) New(i interface{}) (Columnar, error) { - c := NewClusterRole(r.Connection) - switch instance := i.(type) { - case *v1.ClusterRole: - c.instance = instance - case v1.ClusterRole: - c.instance = &instance - default: - return nil, fmt.Errorf("unknown context type %T", instance) - } - c.path = c.instance.Name - - return c, nil -} - -// Marshal resource to yaml. -func (r *ClusterRole) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } - - cr, ok := i.(*v1.ClusterRole) - if !ok { - return "", errors.New("Expecting a cr resource") - } - cr.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" - cr.TypeMeta.Kind = "ClusterRole" - - return r.marshalObject(cr) -} - -// Header return resource header. -func (*ClusterRole) Header(ns string) Row { - return append(Row{}, "NAME", "AGE") -} - -// Fields retrieves displayable fields. -func (r *ClusterRole) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - i := r.instance - - return append(ff, - i.Name, - toAge(i.ObjectMeta.CreationTimestamp), - ) -} diff --git a/internal/resource/cr_binding.go b/internal/resource/cr_binding.go deleted file mode 100644 index 7145fef3..00000000 --- a/internal/resource/cr_binding.go +++ /dev/null @@ -1,122 +0,0 @@ -package resource - -import ( - "errors" - "fmt" - "strings" - - "github.com/derailed/k9s/internal/k8s" - v1 "k8s.io/api/rbac/v1" -) - -// ClusterRoleBinding tracks a kubernetes resource. -type ClusterRoleBinding struct { - *Base - instance *v1.ClusterRoleBinding -} - -// NewClusterRoleBindingList returns a new resource list. -func NewClusterRoleBindingList(c Connection, _ string) List { - return NewList( - NotNamespaced, - "clusterrolebinding", - NewClusterRoleBinding(c), - ViewAccess|DeleteAccess|DescribeAccess, - ) -} - -// NewClusterRoleBinding instantiates a new ClusterRoleBinding. -func NewClusterRoleBinding(c Connection) *ClusterRoleBinding { - crb := &ClusterRoleBinding{&Base{Connection: c, Resource: k8s.NewClusterRoleBinding(c)}, nil} - crb.Factory = crb - - return crb -} - -// New builds a new tabular instance from a k8s resource. -func (r *ClusterRoleBinding) New(i interface{}) (Columnar, error) { - crb := NewClusterRoleBinding(r.Connection) - switch instance := i.(type) { - case *v1.ClusterRoleBinding: - crb.instance = instance - case v1.ClusterRoleBinding: - crb.instance = &instance - default: - return nil, fmt.Errorf("unknown context type %T", instance) - } - crb.path = crb.instance.Name - - return crb, nil -} - -// Marshal resource to yaml. -func (r *ClusterRoleBinding) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } - - crb, ok := i.(*v1.ClusterRoleBinding) - if !ok { - return "", errors.New("Expecting a crb resource") - } - crb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" - crb.TypeMeta.Kind = "ClusterRoleBinding" - - return r.marshalObject(crb) -} - -// Header return resource header. -func (*ClusterRoleBinding) Header(_ string) Row { - return append(Row{}, "NAME", "ROLE", "KIND", "SUBJECTS", "AGE") -} - -// Fields retrieves displayable fields. -func (r *ClusterRoleBinding) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - - i := r.instance - kind, ss := renderSubjects(i.Subjects) - - return append(ff, - i.Name, - i.RoleRef.Name, - kind, - ss, - toAge(i.ObjectMeta.CreationTimestamp), - ) -} - -// ---------------------------------------------------------------------------- -// Helpers... - -func renderSubjects(ss []v1.Subject) (kind string, subjects string) { - if len(ss) == 0 { - return NAValue, "" - } - - var tt []string - for _, s := range ss { - kind = toSubjectAlias(s.Kind) - tt = append(tt, s.Name) - } - return kind, strings.Join(tt, ",") -} - -func toSubjectAlias(s string) string { - if len(s) == 0 { - return s - } - - switch s { - case v1.UserKind: - return "USR" - case v1.GroupKind: - return "GRP" - case v1.ServiceAccountKind: - return "SA" - default: - return strings.ToUpper(s) - } -} diff --git a/internal/resource/cr_binding_test.go b/internal/resource/cr_binding_test.go deleted file mode 100644 index a9c982ff..00000000 --- a/internal/resource/cr_binding_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package resource_test - -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewClusterRoleBindingListWithArgs(ns string, r *resource.ClusterRoleBinding) resource.List { - return resource.NewList(resource.NotNamespaced, "clusterrolebinding", r, resource.ViewAccess|resource.DeleteAccess|resource.DescribeAccess) -} - -func NewClusterRoleBindingWithArgs(conn k8s.Connection, res resource.Cruder) *resource.ClusterRoleBinding { - r := &resource.ClusterRoleBinding{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestCRBFields(t *testing.T) { - conn := NewMockConnection() - - r := newCRB(conn).Fields(resource.AllNamespaces) - - assert.Equal(t, "fred", r[0]) -} - -func TestCRBMarshal(t *testing.T) { - conn := NewMockConnection() - ca := NewMockCruder() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sCRB(), nil) - - cm := NewClusterRoleBindingWithArgs(conn, ca) - ma, err := cm.Marshal("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, crbYaml(), ma) -} - -// BOZO!! -// func TestCRBListData(t *testing.T) { -// conn := NewMockConnection() -// ca := NewMockCruder() -// m.When(ca.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sCRB()}, nil) - -// l := NewClusterRoleBindingListWithArgs("-", NewClusterRoleBindingWithArgs(conn, ca)) -// // Make sure we can get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } - -// ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) -// row := td.Rows["fred"] -// assert.Equal(t, 5, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) -// } - -// Helpers... - -func k8sCRB() *rbacv1.ClusterRoleBinding { - return &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "fred", - Namespace: "blee", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Subjects: []rbacv1.Subject{ - {Kind: "test", Name: "fred", Namespace: "blee"}, - }, - } -} - -func newCRB(c resource.Connection) resource.Columnar { - co, _ := resource.NewClusterRoleBinding(c).New(k8sCRB()) - return co -} - -func crbYaml() string { - return `apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -roleRef: - apiGroup: "" - kind: "" - name: "" -subjects: -- kind: test - name: fred - namespace: blee -` -} diff --git a/internal/resource/cr_test.go b/internal/resource/cr_test.go deleted file mode 100644 index 0d4d62c7..00000000 --- a/internal/resource/cr_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package resource_test - -import ( - "fmt" - "testing" - "time" - - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewClusterRoleListWithArgs(ns string, r *resource.ClusterRole) resource.List { - return resource.NewList(resource.NotNamespaced, "clusterrole", r, resource.CRUDAccess|resource.DescribeAccess) -} - -func NewClusterRoleWithArgs(mc resource.Connection, res resource.Cruder) *resource.ClusterRole { - r := &resource.ClusterRole{Base: resource.NewBase(mc, res)} - r.Factory = r - return r -} - -func TestCRListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - - ns := "blee" - r := NewClusterRoleWithArgs(mc, mr) - l := NewClusterRoleListWithArgs(resource.AllNamespaces, r) - l.SetNamespace(ns) - - assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.Equal(t, "clusterrole", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestCRFields(t *testing.T) { - r := newClusterRole().Fields("blee") - assert.Equal(t, "fred", r[0]) -} - -func TestCRFieldsAllNS(t *testing.T) { - r := newClusterRole().Fields(resource.AllNamespaces) - assert.Equal(t, "fred", r[0]) -} - -func TestCRMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sCR(), nil) - - cr := NewClusterRoleWithArgs(mc, mr) - ma, err := cr.Marshal("blee/fred") - - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, mrYaml(), ma) -} - -// BOZO!! -// func TestCRListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sCR()}, nil) - -// l := NewClusterRoleListWithArgs("-", NewClusterRoleWithArgs(mc, mr)) -// // Make sure we mcn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } - -// mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) - -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) -// row := td.Rows["fred"] -// assert.Equal(t, 2, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) -// } - -// Helpers... - -func k8sCR() *rbacv1.ClusterRole { - return &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: "fred", - Namespace: "blee", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Rules: []rbacv1.PolicyRule{ - { - Verbs: []string{"get", "list"}, - APIGroups: []string{""}, - ResourceNames: []string{"pod"}, - }, - }, - } -} - -func newClusterRole() resource.Columnar { - conn := NewMockConnection() - c, _ := resource.NewClusterRole(conn).New(k8sCR()) - return c -} - -func testTime() time.Time { - t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00") - if err != nil { - fmt.Println("TestTime Failed", err) - } - return t -} - -func mrYaml() string { - return `apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -rules: -- apiGroups: - - "" - resourceNames: - - pod - verbs: - - get - - list -` -} diff --git a/internal/resource/cronjob.go b/internal/resource/cronjob.go index f07f965f..99d3a390 100644 --- a/internal/resource/cronjob.go +++ b/internal/resource/cronjob.go @@ -1,124 +1,125 @@ package resource -import ( - "errors" - "fmt" - "strconv" +// BOZO!! +// import ( +// "errors" +// "fmt" +// "strconv" - "github.com/derailed/k9s/internal/k8s" - batchv1beta1 "k8s.io/api/batch/v1beta1" -) +// "github.com/derailed/k9s/internal/k8s" +// batchv1beta1 "k8s.io/api/batch/v1beta1" +// ) -type ( - // CronJob tracks a kubernetes resource. - CronJob struct { - *Base - instance *batchv1beta1.CronJob - } +// type ( +// // CronJob tracks a kubernetes resource. +// CronJob struct { +// *Base +// instance *batchv1beta1.CronJob +// } - // Runner can run jobs. - Runner interface { - Run(path string) error - } +// // Runner can run jobs. +// Runner interface { +// Run(path string) error +// } - // Runnable can run jobs. - Runnable interface { - Run(ns, n string) error - } -) +// // Runnable can run jobs. +// Runnable interface { +// Run(ns, n string) error +// } +// ) -// NewCronJobList returns a new resource list. -func NewCronJobList(c Connection, ns string) List { - return NewList( - ns, - "cronjob", - NewCronJob(c), - AllVerbsAccess|DescribeAccess, - ) -} +// // NewCronJobList returns a new resource list. +// func NewCronJobList(c Connection, ns string) List { +// return NewList( +// ns, +// "cronjob", +// NewCronJob(c), +// AllVerbsAccess|DescribeAccess, +// ) +// } -// NewCronJob instantiates a new CronJob. -func NewCronJob(c Connection) *CronJob { - cj := &CronJob{&Base{Connection: c, Resource: k8s.NewCronJob(c)}, nil} - cj.Factory = cj +// // NewCronJob instantiates a new CronJob. +// func NewCronJob(c Connection) *CronJob { +// cj := &CronJob{&Base{Connection: c, Resource: k8s.NewCronJob(c)}, nil} +// cj.Factory = cj - return cj -} +// return cj +// } -// New builds a new CronJob instance from a k8s resource. -func (r *CronJob) New(i interface{}) (Columnar, error) { - c := NewCronJob(r.Connection) - switch instance := i.(type) { - case *batchv1beta1.CronJob: - c.instance = instance - case batchv1beta1.CronJob: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting CronJob but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) +// // New builds a new CronJob instance from a k8s resource. +// func (r *CronJob) New(i interface{}) (Columnar, error) { +// c := NewCronJob(r.Connection) +// switch instance := i.(type) { +// case *batchv1beta1.CronJob: +// c.instance = instance +// case batchv1beta1.CronJob: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting CronJob but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) - return c, nil -} +// return c, nil +// } -// Marshal resource to yaml. -func (r *CronJob) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } +// // Marshal resource to yaml. +// func (r *CronJob) Marshal(path string) (string, error) { +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// return "", err +// } - cj, ok := i.(*batchv1beta1.CronJob) - if !ok { - return "", errors.New("expecting cronjob resource") - } - cj.TypeMeta.APIVersion = "extensions/batchv1beta1" - cj.TypeMeta.Kind = "CronJob" +// cj, ok := i.(*batchv1beta1.CronJob) +// if !ok { +// return "", errors.New("expecting cronjob resource") +// } +// cj.TypeMeta.APIVersion = "extensions/batchv1beta1" +// cj.TypeMeta.Kind = "CronJob" - return r.marshalObject(cj) -} +// return r.marshalObject(cj) +// } -// Run a given cronjob. -func (r *CronJob) Run(pa string) error { - ns, n := Namespaced(pa) - if c, ok := r.Resource.(Runnable); ok { - return c.Run(ns, n) - } +// // Run a given cronjob. +// func (r *CronJob) Run(pa string) error { +// ns, n := Namespaced(pa) +// if c, ok := r.Resource.(Runnable); ok { +// return c.Run(ns, n) +// } - return fmt.Errorf("unable to run cronjob %s", pa) -} +// return fmt.Errorf("unable to run cronjob %s", pa) +// } -// Header return resource header. -func (*CronJob) Header(ns string) Row { - hh := Row{} - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } +// // Header return resource header. +// func (*CronJob) Header(ns string) Row { +// hh := Row{} +// if ns == AllNamespaces { +// hh = append(hh, "NAMESPACE") +// } - return append(hh, "NAME", "SCHEDULE", "SUSPEND", "ACTIVE", "LAST_SCHEDULE", "AGE") -} +// return append(hh, "NAME", "SCHEDULE", "SUSPEND", "ACTIVE", "LAST_SCHEDULE", "AGE") +// } -// Fields retrieves displayable fields. -func (r *CronJob) Fields(ns string) Row { - ff := make([]string, 0, len(r.Header(ns))) +// // Fields retrieves displayable fields. +// func (r *CronJob) Fields(ns string) Row { +// ff := make([]string, 0, len(r.Header(ns))) - i := r.instance - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } +// i := r.instance +// if ns == AllNamespaces { +// ff = append(ff, i.Namespace) +// } - lastScheduled := MissingValue - if i.Status.LastScheduleTime != nil { - lastScheduled = toAgeHuman(toAge(*i.Status.LastScheduleTime)) - } +// lastScheduled := MissingValue +// if i.Status.LastScheduleTime != nil { +// lastScheduled = toAgeHuman(toAge(*i.Status.LastScheduleTime)) +// } - return append(ff, - i.Name, - i.Spec.Schedule, - boolPtrToStr(i.Spec.Suspend), - strconv.Itoa(len(i.Status.Active)), - lastScheduled, - toAge(i.ObjectMeta.CreationTimestamp), - ) -} +// return append(ff, +// i.Name, +// i.Spec.Schedule, +// boolPtrToStr(i.Spec.Suspend), +// strconv.Itoa(len(i.Status.Active)), +// lastScheduled, +// toAge(i.ObjectMeta.CreationTimestamp), +// ) +// } diff --git a/internal/resource/cronjob_test.go b/internal/resource/cronjob_test.go index 734d3c86..526868a4 100644 --- a/internal/resource/cronjob_test.go +++ b/internal/resource/cronjob_test.go @@ -1,131 +1,132 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - batchv1beta1 "k8s.io/api/batch/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewCronJobListWithArgs(ns string, r *resource.CronJob) resource.List { - return resource.NewList(ns, "cj", r, resource.AllVerbsAccess|resource.DescribeAccess) -} - -func NewCronJobWithArgs(conn k8s.Connection, res resource.Cruder) *resource.CronJob { - r := &resource.CronJob{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestCronJobListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - - ns := "blee" - r := NewCronJobWithArgs(mc, mr) - l := NewCronJobListWithArgs(resource.AllNamespaces, r) - l.SetNamespace(ns) - - assert.Equal(t, ns, l.GetNamespace()) - assert.Equal(t, "cj", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestCronJobFields(t *testing.T) { - r := newCronJob().Fields("blee") - assert.Equal(t, "fred", r[0]) -} - -func TestCronJobMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sCronJob(), nil) - - cm := NewCronJobWithArgs(mc, mr) - ma, err := cm.Marshal("blee/fred") - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, cronjobYaml(), ma) -} - // BOZO!! +// import ( +// "testing" -// func TestCronJobListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sCronJob()}, nil) +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// batchv1beta1 "k8s.io/api/batch/v1beta1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -// l := NewCronJobListWithArgs("-", NewCronJobWithArgs(mc, mr)) -// // Make sure we can get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } - -// mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 6, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// func NewCronJobListWithArgs(ns string, r *resource.CronJob) resource.List { +// return resource.NewList(ns, "cj", r, resource.AllVerbsAccess|resource.DescribeAccess) // } -// Helpers... +// func NewCronJobWithArgs(conn k8s.Connection, res resource.Cruder) *resource.CronJob { +// r := &resource.CronJob{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } -func k8sCronJob() *batchv1beta1.CronJob { - var b bool - return &batchv1beta1.CronJob{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "blee", - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: batchv1beta1.CronJobSpec{ - Schedule: "*/1 * * * *", - Suspend: &b, - }, - Status: batchv1beta1.CronJobStatus{ - LastScheduleTime: &metav1.Time{Time: testTime()}, - }, - } -} +// func TestCronJobListAccess(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() -func newCronJob() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewCronJob(mc).New(k8sCronJob()) - return c -} +// ns := "blee" +// r := NewCronJobWithArgs(mc, mr) +// l := NewCronJobListWithArgs(resource.AllNamespaces, r) +// l.SetNamespace(ns) -func cronjobYaml() string { - return `apiVersion: extensions/batchv1beta1 -kind: CronJob -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -spec: - jobTemplate: - metadata: - creationTimestamp: null - spec: - template: - metadata: - creationTimestamp: null - spec: - containers: null - schedule: '*/1 * * * *' - suspend: false -status: - lastScheduleTime: "2018-12-14T17:36:43Z" -` -} +// assert.Equal(t, ns, l.GetNamespace()) +// assert.Equal(t, "cj", l.GetName()) +// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { +// assert.True(t, l.Access(a)) +// } +// } + +// func TestCronJobFields(t *testing.T) { +// r := newCronJob().Fields("blee") +// assert.Equal(t, "fred", r[0]) +// } + +// func TestCronJobMarshal(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// m.When(mr.Get("blee", "fred")).ThenReturn(k8sCronJob(), nil) + +// cm := NewCronJobWithArgs(mc, mr) +// ma, err := cm.Marshal("blee/fred") +// mr.VerifyWasCalledOnce().Get("blee", "fred") +// assert.Nil(t, err) +// assert.Equal(t, cronjobYaml(), ma) +// } + +// // BOZO!! + +// // func TestCronJobListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sCronJob()}, nil) + +// // l := NewCronJobListWithArgs("-", NewCronJobWithArgs(mc, mr)) +// // // Make sure we can get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } + +// // mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) +// // row := td.Rows["blee/fred"] +// // assert.Equal(t, 6, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// // } + +// // Helpers... + +// func k8sCronJob() *batchv1beta1.CronJob { +// var b bool +// return &batchv1beta1.CronJob{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "blee", +// Name: "fred", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: batchv1beta1.CronJobSpec{ +// Schedule: "*/1 * * * *", +// Suspend: &b, +// }, +// Status: batchv1beta1.CronJobStatus{ +// LastScheduleTime: &metav1.Time{Time: testTime()}, +// }, +// } +// } + +// func newCronJob() resource.Columnar { +// mc := NewMockConnection() +// c, _ := resource.NewCronJob(mc).New(k8sCronJob()) +// return c +// } + +// func cronjobYaml() string { +// return `apiVersion: extensions/batchv1beta1 +// kind: CronJob +// metadata: +// creationTimestamp: "2018-12-14T17:36:43Z" +// name: fred +// namespace: blee +// spec: +// jobTemplate: +// metadata: +// creationTimestamp: null +// spec: +// template: +// metadata: +// creationTimestamp: null +// spec: +// containers: null +// schedule: '*/1 * * * *' +// suspend: false +// status: +// lastScheduleTime: "2018-12-14T17:36:43Z" +// ` +// } diff --git a/internal/resource/custom.go b/internal/resource/custom.go index 45e87e2c..baa6f0a0 100644 --- a/internal/resource/custom.go +++ b/internal/resource/custom.go @@ -1,177 +1,178 @@ package resource -import ( - "encoding/json" - "fmt" - "path" - "strings" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - "github.com/derailed/k9s/internal/k8s" - "github.com/rs/zerolog/log" - "gopkg.in/yaml.v2" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" -) - -// Custom tracks a kubernetes resource. -type Custom struct { - *Base - - instance *metav1beta1.TableRow - gvr k8s.GVR - headers Row -} - -// NewCustomList returns a new resource list. -func NewCustomList(c k8s.Connection, namespaced bool, ns, gvr string) List { - if !namespaced { - ns = NotNamespaced - } - g := k8s.GVR(gvr) - return NewList( - ns, - g.ToR(), - NewCustom(c, g), AllVerbsAccess|DescribeAccess, - ) -} - -// NewCustom instantiates a new Kubernetes Resource. -func NewCustom(c k8s.Connection, gvr k8s.GVR) *Custom { - cr := &Custom{Base: &Base{Connection: c, Resource: k8s.NewResource(c, gvr)}} - cr.Factory = cr - cr.gvr = gvr - - return cr -} - -// New builds a new Custom instance from a k8s resource. -func (r *Custom) New(i interface{}) (Columnar, error) { - cr := NewCustom(r.Connection, "") - switch instance := i.(type) { - case *metav1beta1.TableRow: - cr.instance = instance - case metav1beta1.TableRow: - cr.instance = &instance - default: - return nil, fmt.Errorf("Expecting TableRow but got %T", instance) - } - var obj map[string]interface{} - err := json.Unmarshal(cr.instance.Object.Raw, &obj) - if err != nil { - return nil, err - } - meta, err := extractMeta(obj) - if err != nil { - return nil, err - } - ns, err := extractString(meta, "namespace") - if err != nil { - return nil, err - } - n, err := extractString(meta, "name") - if err != nil { - return nil, err - } - cr.path = path.Join(ns, n) - cr.gvr = k8s.NewGVR(obj["kind"].(string), obj["apiVersion"].(string), n) - - return cr, nil -} - -// Marshal resource to yaml. -func (r *Custom) Marshal(path string) (string, error) { - panic("NYI") - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } - switch v := i.(type) { - case *unstructured.Unstructured: - i = v.Object - } - - raw, err := yaml.Marshal(i) - if err != nil { - return "", err - } - - return string(raw), nil -} - // BOZO!! -// List all resources -// func (r *Custom) List(ns string, opts v1.ListOptions) (Columnars, error) { -// ii, err := r.Resource.List(ns, opts) +// import ( +// "encoding/json" +// "fmt" +// "path" +// "strings" + +// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + +// "github.com/derailed/k9s/internal/k8s" +// "github.com/rs/zerolog/log" +// "gopkg.in/yaml.v2" +// metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" +// ) + +// // Custom tracks a kubernetes resource. +// type Custom struct { +// *Base + +// instance *metav1beta1.TableRow +// gvr k8s.GVR +// headers Row +// } + +// // NewCustomList returns a new resource list. +// func NewCustomList(c k8s.Connection, namespaced bool, ns, gvr string) List { +// if !namespaced { +// ns = NotNamespaced +// } +// g := k8s.GVR(gvr) +// return NewList( +// ns, +// g.ToR(), +// NewCustom(c, g), AllVerbsAccess|DescribeAccess, +// ) +// } + +// // NewCustom instantiates a new Kubernetes Resource. +// func NewCustom(c k8s.Connection, gvr k8s.GVR) *Custom { +// cr := &Custom{Base: &Base{Connection: c, Resource: k8s.NewResource(c, gvr)}} +// cr.Factory = cr +// cr.gvr = gvr + +// return cr +// } + +// // New builds a new Custom instance from a k8s resource. +// func (r *Custom) New(i interface{}) (Columnar, error) { +// cr := NewCustom(r.Connection, "") +// switch instance := i.(type) { +// case *metav1beta1.TableRow: +// cr.instance = instance +// case metav1beta1.TableRow: +// cr.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting TableRow but got %T", instance) +// } +// var obj map[string]interface{} +// err := json.Unmarshal(cr.instance.Object.Raw, &obj) // if err != nil { // return nil, err // } +// meta, err := extractMeta(obj) +// if err != nil { +// return nil, err +// } +// ns, err := extractString(meta, "namespace") +// if err != nil { +// return nil, err +// } +// n, err := extractString(meta, "name") +// if err != nil { +// return nil, err +// } +// cr.path = path.Join(ns, n) +// cr.gvr = k8s.NewGVR(obj["kind"].(string), obj["apiVersion"].(string), n) -// if len(ii) == 0 { -// return Columnars{}, errors.New("no resources found") -// } - -// table, ok := ii[0].(*metav1beta1.Table) -// if !ok { -// return nil, errors.New("expecting a table resource") -// } -// r.headers = make(Row, len(table.ColumnDefinitions)) -// for i, h := range table.ColumnDefinitions { -// r.headers[i] = h.Name -// } -// rows := table.Rows -// cc := make(Columnars, 0, len(rows)) -// for i := 0; i < len(rows); i++ { -// res, err := r.New(rows[i]) -// if err != nil { -// return nil, err -// } -// cc = append(cc, res) -// } - -// return cc, nil +// return cr, nil // } -// Header return resource header. -func (r *Custom) Header(ns string) Row { - hh := make(Row, 0, len(r.headers)+1) +// // Marshal resource to yaml. +// func (r *Custom) Marshal(path string) (string, error) { +// panic("NYI") +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// return "", err +// } +// switch v := i.(type) { +// case *unstructured.Unstructured: +// i = v.Object +// } - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } - for _, h := range r.headers { - hh = append(hh, strings.ToUpper(h)) - } +// raw, err := yaml.Marshal(i) +// if err != nil { +// return "", err +// } - return hh -} +// return string(raw), nil +// } -// Fields retrieves displayable fields. -func (r *Custom) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) +// // BOZO!! +// // List all resources +// // func (r *Custom) List(ns string, opts v1.ListOptions) (Columnars, error) { +// // ii, err := r.Resource.List(ns, opts) +// // if err != nil { +// // return nil, err +// // } - var obj map[string]interface{} - err := json.Unmarshal(r.instance.Object.Raw, &obj) - if err != nil { - log.Error().Err(err) - return Row{} - } +// // if len(ii) == 0 { +// // return Columnars{}, errors.New("no resources found") +// // } - meta, ok := obj["metadata"].(map[string]interface{}) - if !ok { - log.Fatal().Msg("expecting interface map meta") - } - rns, ok := meta["namespace"].(string) - if ns == AllNamespaces { - if ok { - ff = append(ff, rns) - } - } +// // table, ok := ii[0].(*metav1beta1.Table) +// // if !ok { +// // return nil, errors.New("expecting a table resource") +// // } +// // r.headers = make(Row, len(table.ColumnDefinitions)) +// // for i, h := range table.ColumnDefinitions { +// // r.headers[i] = h.Name +// // } +// // rows := table.Rows +// // cc := make(Columnars, 0, len(rows)) +// // for i := 0; i < len(rows); i++ { +// // res, err := r.New(rows[i]) +// // if err != nil { +// // return nil, err +// // } +// // cc = append(cc, res) +// // } - for _, c := range r.instance.Cells { - ff = append(ff, fmt.Sprintf("%v", c)) - } +// // return cc, nil +// // } - return ff -} +// // Header return resource header. +// func (r *Custom) Header(ns string) Row { +// hh := make(Row, 0, len(r.headers)+1) + +// if ns == AllNamespaces { +// hh = append(hh, "NAMESPACE") +// } +// for _, h := range r.headers { +// hh = append(hh, strings.ToUpper(h)) +// } + +// return hh +// } + +// // Fields retrieves displayable fields. +// func (r *Custom) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) + +// var obj map[string]interface{} +// err := json.Unmarshal(r.instance.Object.Raw, &obj) +// if err != nil { +// log.Error().Err(err) +// return Row{} +// } + +// meta, ok := obj["metadata"].(map[string]interface{}) +// if !ok { +// log.Fatal().Msg("expecting interface map meta") +// } +// rns, ok := meta["namespace"].(string) +// if ns == AllNamespaces { +// if ok { +// ff = append(ff, rns) +// } +// } + +// for _, c := range r.instance.Cells { +// ff = append(ff, fmt.Sprintf("%v", c)) +// } + +// return ff +// } diff --git a/internal/resource/custom_test.go b/internal/resource/custom_test.go index 75c3386b..a11b310c 100644 --- a/internal/resource/custom_test.go +++ b/internal/resource/custom_test.go @@ -1,353 +1,354 @@ package resource_test -import ( - "testing" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" - "k8s.io/apimachinery/pkg/runtime" -) - -func NewCustomListWithArgs(ns, name string, r *resource.Custom) resource.List { - return resource.NewList(ns, name, r, resource.AllVerbsAccess) -} - -func NewCustomWithArgs(conn k8s.Connection, res resource.Cruder) *resource.Custom { - r := &resource.Custom{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestCustomListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - - ns := "blee" - r := NewCustomWithArgs(mc, mr) - l := NewCustomListWithArgs(resource.AllNamespaces, "fred", r) - l.SetNamespace(ns) - - assert.Equal(t, ns, l.GetNamespace()) - assert.Equal(t, "fred", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestCustomFields(t *testing.T) { - r := newCustom().Fields("blee") - assert.Equal(t, "a", r[0]) -} - // BOZO!! -// func TestCustomMarshal(t *testing.T) { +// import ( +// "testing" + +// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" +// "k8s.io/apimachinery/pkg/runtime" +// ) + +// func NewCustomListWithArgs(ns, name string, r *resource.Custom) resource.List { +// return resource.NewList(ns, name, r, resource.AllVerbsAccess) +// } + +// func NewCustomWithArgs(conn k8s.Connection, res resource.Cruder) *resource.Custom { +// r := &resource.Custom{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } + +// func TestCustomListAccess(t *testing.T) { // mc := NewMockConnection() // mr := NewMockCruder() -// m.When(mr.Get("blee", "fred")).ThenReturn(k8sCustomTable(), nil) + +// ns := "blee" +// r := NewCustomWithArgs(mc, mr) +// l := NewCustomListWithArgs(resource.AllNamespaces, "fred", r) +// l.SetNamespace(ns) + +// assert.Equal(t, ns, l.GetNamespace()) +// assert.Equal(t, "fred", l.GetName()) +// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { +// assert.True(t, l.Access(a)) +// } +// } + +// func TestCustomFields(t *testing.T) { +// r := newCustom().Fields("blee") +// assert.Equal(t, "a", r[0]) +// } + +// // BOZO!! +// // func TestCustomMarshal(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.Get("blee", "fred")).ThenReturn(k8sCustomGetTable(), nil) + +// // cm := NewCustomWithArgs(mc, mr) +// // ma, err := cm.Marshal("blee/fred") +// // mr.VerifyWasCalledOnce().Get("blee", "fred") + +// // assert.Nil(t, err) +// // assert.Equal(t, customYaml(), ma) +// // } + +// func TestCustomMarshalWithUnstructured(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// m.When(mr.Get("blee", "fred")).ThenReturn(k8sUnstructured(), nil) // cm := NewCustomWithArgs(mc, mr) // ma, err := cm.Marshal("blee/fred") // mr.VerifyWasCalledOnce().Get("blee", "fred") // assert.Nil(t, err) -// assert.Equal(t, customYaml(), ma) +// assert.Equal(t, unstructuredYAML(), ma) // } -func TestCustomMarshalWithUnstructured(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sUnstructured(), nil) +// // BOZO!! +// // func TestCustomListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{k8sCustomGetTable()}, nil) - cm := NewCustomWithArgs(mc, mr) - ma, err := cm.Marshal("blee/fred") - mr.VerifyWasCalledOnce().Get("blee", "fred") +// // l := NewCustomListWithArgs("blee", "fred", NewCustomWithArgs(mc, mr)) +// // // Make sure we can get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } - assert.Nil(t, err) - assert.Equal(t, unstructuredYAML(), ma) -} +// // mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, "blee", l.GetNamespace()) +// // row := td.Rows["blee/fred"] +// // assert.Equal(t, 3, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, resource.Row{"a"}, row.Fields[:1]) +// // } -// BOZO!! -// func TestCustomListData(t *testing.T) { +// // Helpers... + +// func k8sCustomGetTable() *metav1beta1.Table { +// return &metav1beta1.Table{ +// ColumnDefinitions: []metav1beta1.TableColumnDefinition{ +// {Name: "A"}, +// {Name: "B"}, +// {Name: "C"}, +// }, +// Rows: []metav1beta1.TableRow{ +// { +// Object: runtime.RawExtension{ +// Raw: []byte(`{ +// "kind": "fred", +// "apiVersion": "v1", +// "metadata": { +// "namespace": "blee", +// "name": "fred" +// }}`), +// }, +// Cells: []interface{}{ +// "a", +// "b", +// "c", +// }, +// }, +// }, +// } +// } + +// func k8sUnstructured() *unstructured.Unstructured { +// return &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "kind": "fred", +// "apiVersion": "v1", +// "metadata": map[string]interface{}{ +// "namespace": "blee", +// "name": "fred", +// }, +// }, +// } +// } + +// func unstructuredYAML() string { +// return `apiVersion: v1 +// kind: fred +// metadata: +// name: fred +// namespace: blee +// ` +// } + +// func k8sCustomRow() *metav1beta1.TableRow { +// return &metav1beta1.TableRow{ +// Object: runtime.RawExtension{ +// Raw: []byte(`{ +// "kind": "fred", +// "apiVersion": "v1", +// "metadata": { +// "namespace": "blee", +// "name": "fred" +// }}`), +// }, +// Cells: []interface{}{ +// "a", +// "b", +// "c", +// }, +// } +// } + +// func newCustom() resource.Columnar { // mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{k8sCustomTable()}, nil) - -// l := NewCustomListWithArgs("blee", "fred", NewCustomWithArgs(mc, mr)) -// // Make sure we can get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } - -// mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, "blee", l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 3, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"a"}, row.Fields[:1]) +// c, _ := resource.NewCustom(mc, "g/v1/fred").New(k8sCustomRow()) +// return c // } -// Helpers... - -func k8sCustomTable() *metav1beta1.Table { - return &metav1beta1.Table{ - ColumnDefinitions: []metav1beta1.TableColumnDefinition{ - {Name: "A"}, - {Name: "B"}, - {Name: "C"}, - }, - Rows: []metav1beta1.TableRow{ - { - Object: runtime.RawExtension{ - Raw: []byte(`{ - "kind": "fred", - "apiVersion": "v1", - "metadata": { - "namespace": "blee", - "name": "fred" - }}`), - }, - Cells: []interface{}{ - "a", - "b", - "c", - }, - }, - }, - } -} - -func k8sUnstructured() *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "kind": "fred", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "namespace": "blee", - "name": "fred", - }, - }, - } -} - -func unstructuredYAML() string { - return `apiVersion: v1 -kind: fred -metadata: - name: fred - namespace: blee -` -} - -func k8sCustomRow() *metav1beta1.TableRow { - return &metav1beta1.TableRow{ - Object: runtime.RawExtension{ - Raw: []byte(`{ - "kind": "fred", - "apiVersion": "v1", - "metadata": { - "namespace": "blee", - "name": "fred" - }}`), - }, - Cells: []interface{}{ - "a", - "b", - "c", - }, - } -} - -func newCustom() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewCustom(mc, "g/v1/fred").New(k8sCustomRow()) - return c -} - -func customYaml() string { - return `typemeta: - kind: "" - apiversion: "" -listmeta: - selflink: "" - resourceversion: "" - continue: "" - remainingitemcount: null -columndefinitions: -- name: A - type: "" - format: "" - description: "" - priority: 0 -- name: B - type: "" - format: "" - description: "" - priority: 0 -- name: C - type: "" - format: "" - description: "" - priority: 0 -rows: -- cells: - - a - - b - - c - conditions: [] - object: - raw: - - 123 - - 10 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 34 - - 107 - - 105 - - 110 - - 100 - - 34 - - 58 - - 32 - - 34 - - 102 - - 114 - - 101 - - 100 - - 34 - - 44 - - 10 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 34 - - 97 - - 112 - - 105 - - 86 - - 101 - - 114 - - 115 - - 105 - - 111 - - 110 - - 34 - - 58 - - 32 - - 34 - - 118 - - 49 - - 34 - - 44 - - 10 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 34 - - 109 - - 101 - - 116 - - 97 - - 100 - - 97 - - 116 - - 97 - - 34 - - 58 - - 32 - - 123 - - 10 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 34 - - 110 - - 97 - - 109 - - 101 - - 115 - - 112 - - 97 - - 99 - - 101 - - 34 - - 58 - - 32 - - 34 - - 98 - - 108 - - 101 - - 101 - - 34 - - 44 - - 10 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 34 - - 110 - - 97 - - 109 - - 101 - - 34 - - 58 - - 32 - - 34 - - 102 - - 114 - - 101 - - 100 - - 34 - - 10 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 32 - - 125 - - 125 - object: null -` -} +// func customYaml() string { +// return `typemeta: +// kind: "" +// apiversion: "" +// listmeta: +// selflink: "" +// resourceversion: "" +// continue: "" +// remainingitemcount: null +// columndefinitions: +// - name: A +// type: "" +// format: "" +// description: "" +// priority: 0 +// - name: B +// type: "" +// format: "" +// description: "" +// priority: 0 +// - name: C +// type: "" +// format: "" +// description: "" +// priority: 0 +// rows: +// - cells: +// - a +// - b +// - c +// conditions: [] +// object: +// raw: +// - 123 +// - 10 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 34 +// - 107 +// - 105 +// - 110 +// - 100 +// - 34 +// - 58 +// - 32 +// - 34 +// - 102 +// - 114 +// - 101 +// - 100 +// - 34 +// - 44 +// - 10 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 34 +// - 97 +// - 112 +// - 105 +// - 86 +// - 101 +// - 114 +// - 115 +// - 105 +// - 111 +// - 110 +// - 34 +// - 58 +// - 32 +// - 34 +// - 118 +// - 49 +// - 34 +// - 44 +// - 10 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 34 +// - 109 +// - 101 +// - 116 +// - 97 +// - 100 +// - 97 +// - 116 +// - 97 +// - 34 +// - 58 +// - 32 +// - 123 +// - 10 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 34 +// - 110 +// - 97 +// - 109 +// - 101 +// - 115 +// - 112 +// - 97 +// - 99 +// - 101 +// - 34 +// - 58 +// - 32 +// - 34 +// - 98 +// - 108 +// - 101 +// - 101 +// - 34 +// - 44 +// - 10 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 34 +// - 110 +// - 97 +// - 109 +// - 101 +// - 34 +// - 58 +// - 32 +// - 34 +// - 102 +// - 114 +// - 101 +// - 100 +// - 34 +// - 10 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 32 +// - 125 +// - 125 +// object: null +// ` +// } diff --git a/internal/resource/dp.go b/internal/resource/dp.go index 3f994ec4..8716f1f2 100644 --- a/internal/resource/dp.go +++ b/internal/resource/dp.go @@ -1,146 +1,147 @@ package resource -import ( - "context" - "errors" - "fmt" - "strconv" +// BOZO!! +// import ( +// "context" +// "errors" +// "fmt" +// "strconv" - "github.com/derailed/k9s/internal/k8s" - appsv1 "k8s.io/api/apps/v1" -) +// "github.com/derailed/k9s/internal/k8s" +// appsv1 "k8s.io/api/apps/v1" +// ) -// Compile time checks to ensure type satisfies interface -var _ Restartable = (*Deployment)(nil) -var _ Scalable = (*Deployment)(nil) +// // Compile time checks to ensure type satisfies interface +// var _ Restartable = (*Deployment)(nil) +// var _ Scalable = (*Deployment)(nil) -// Deployment tracks a kubernetes resource. -type Deployment struct { - *Base - instance *appsv1.Deployment -} +// // Deployment tracks a kubernetes resource. +// type Deployment struct { +// *Base +// instance *appsv1.Deployment +// } -// NewDeploymentList returns a new resource list. -func NewDeploymentList(c Connection, ns string) List { - return NewList( - ns, - "deploy", - NewDeployment(c), - AllVerbsAccess|DescribeAccess, - ) -} +// // NewDeploymentList returns a new resource list. +// func NewDeploymentList(c Connection, ns string) List { +// return NewList( +// ns, +// "deploy", +// NewDeployment(c), +// AllVerbsAccess|DescribeAccess, +// ) +// } -// NewDeployment instantiates a new Deployment. -func NewDeployment(c Connection) *Deployment { - d := &Deployment{&Base{Connection: c, Resource: k8s.NewDeployment(c)}, nil} - d.Factory = d +// // NewDeployment instantiates a new Deployment. +// func NewDeployment(c Connection) *Deployment { +// d := &Deployment{&Base{Connection: c, Resource: k8s.NewDeployment(c)}, nil} +// d.Factory = d - return d -} +// return d +// } -// New builds a new Deployment instance from a k8s resource. -func (r *Deployment) New(i interface{}) (Columnar, error) { - c := NewDeployment(r.Connection) - switch instance := i.(type) { - case *appsv1.Deployment: - c.instance = instance - case appsv1.Deployment: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting Deployment but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) +// // New builds a new Deployment instance from a k8s resource. +// func (r *Deployment) New(i interface{}) (Columnar, error) { +// c := NewDeployment(r.Connection) +// switch instance := i.(type) { +// case *appsv1.Deployment: +// c.instance = instance +// case appsv1.Deployment: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting Deployment but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) - return c, nil -} +// return c, nil +// } -// Marshal resource to yaml. -func (r *Deployment) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } +// // Marshal resource to yaml. +// func (r *Deployment) Marshal(path string) (string, error) { +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// return "", err +// } - dp, ok := i.(*appsv1.Deployment) - if !ok { - return "", errors.New("expecting dp resource") - } - dp.TypeMeta.APIVersion = "apps/v1" - dp.TypeMeta.Kind = "Deployment" +// dp, ok := i.(*appsv1.Deployment) +// if !ok { +// return "", errors.New("expecting dp resource") +// } +// dp.TypeMeta.APIVersion = "apps/v1" +// dp.TypeMeta.Kind = "Deployment" - return r.marshalObject(dp) -} +// return r.marshalObject(dp) +// } -// Logs tail logs for all pods represented by this deployment. -func (r *Deployment) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { - instance, err := r.Resource.Get(opts.Namespace, opts.Name) - if err != nil { - return err - } - dp, ok := instance.(*appsv1.Deployment) - if !ok { - return errors.New("Expecting valid deployment") - } - if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 { - return fmt.Errorf("No valid selector found on deployment %s", opts.Name) - } +// // Logs tail logs for all pods represented by this deployment. +// func (r *Deployment) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { +// instance, err := r.Resource.Get(opts.Namespace, opts.Name) +// if err != nil { +// return err +// } +// dp, ok := instance.(*appsv1.Deployment) +// if !ok { +// return errors.New("Expecting valid deployment") +// } +// if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 { +// return fmt.Errorf("No valid selector found on deployment %s", opts.Name) +// } - return r.podLogs(ctx, c, dp.Spec.Selector.MatchLabels, opts) -} +// return r.podLogs(ctx, c, dp.Spec.Selector.MatchLabels, opts) +// } -// Header return resource header. -func (*Deployment) Header(ns string) Row { - var hh Row - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } +// // Header return resource header. +// func (*Deployment) Header(ns string) Row { +// var hh Row +// if ns == AllNamespaces { +// hh = append(hh, "NAMESPACE") +// } - return append(hh, - "NAME", - "DESIRED", - "CURRENT", - "UP-TO-DATE", - "AVAILABLE", - "AGE", - ) -} +// return append(hh, +// "NAME", +// "DESIRED", +// "CURRENT", +// "UP-TO-DATE", +// "AVAILABLE", +// "AGE", +// ) +// } -// NumCols designates if column is numerical. -func (*Deployment) NumCols(n string) map[string]bool { - return map[string]bool{ - "DESIRED": true, - "CURRENT": true, - "UP-TO-DATE": true, - "AVAILABLE": true, - } -} +// // NumCols designates if column is numerical. +// func (*Deployment) NumCols(n string) map[string]bool { +// return map[string]bool{ +// "DESIRED": true, +// "CURRENT": true, +// "UP-TO-DATE": true, +// "AVAILABLE": true, +// } +// } -// Fields retrieves displayable fields. -func (r *Deployment) Fields(ns string) Row { - ff := make([]string, 0, len(r.Header(ns))) +// // Fields retrieves displayable fields. +// func (r *Deployment) Fields(ns string) Row { +// ff := make([]string, 0, len(r.Header(ns))) - i := r.instance - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } +// i := r.instance +// if ns == AllNamespaces { +// ff = append(ff, i.Namespace) +// } - return append(ff, - i.Name, - strconv.Itoa(int(*i.Spec.Replicas)), - strconv.Itoa(int(i.Status.Replicas)), - strconv.Itoa(int(i.Status.UpdatedReplicas)), - strconv.Itoa(int(i.Status.AvailableReplicas)), - toAge(i.ObjectMeta.CreationTimestamp), - ) -} +// return append(ff, +// i.Name, +// strconv.Itoa(int(*i.Spec.Replicas)), +// strconv.Itoa(int(i.Status.Replicas)), +// strconv.Itoa(int(i.Status.UpdatedReplicas)), +// strconv.Itoa(int(i.Status.AvailableReplicas)), +// toAge(i.ObjectMeta.CreationTimestamp), +// ) +// } -// Scale the specified resource. -func (r *Deployment) Scale(ns, n string, replicas int32) error { - return r.Resource.(Scalable).Scale(ns, n, replicas) -} +// // Scale the specified resource. +// func (r *Deployment) Scale(ns, n string, replicas int32) error { +// return r.Resource.(Scalable).Scale(ns, n, replicas) +// } -// Restart the rollout of the specified resource. -func (r *Deployment) Restart(ns, n string) error { - return r.Resource.(Restartable).Restart(ns, n) -} +// // Restart the rollout of the specified resource. +// func (r *Deployment) Restart(ns, n string) error { +// return r.Resource.(Restartable).Restart(ns, n) +// } diff --git a/internal/resource/dp_test.go b/internal/resource/dp_test.go index c788f858..1dda8b36 100644 --- a/internal/resource/dp_test.go +++ b/internal/resource/dp_test.go @@ -1,122 +1,123 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewDeploymentListWithArgs(ns string, r *resource.Deployment) resource.List { - return resource.NewList(ns, "deploy", r, resource.AllVerbsAccess|resource.DescribeAccess) -} - -func NewDeploymentWithArgs(conn k8s.Connection, res resource.Cruder) *resource.Deployment { - r := &resource.Deployment{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestDeploymentListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - - ns := "blee" - l := NewDeploymentListWithArgs(resource.AllNamespaces, NewDeploymentWithArgs(mc, mr)) - l.SetNamespace(ns) - - assert.Equal(t, "blee", l.GetNamespace()) - assert.Equal(t, "deploy", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestDeploymentFields(t *testing.T) { - r := newDeployment().Fields("blee") - assert.Equal(t, "fred", r[0]) -} - -func TestDeploymentMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sDeployment(), nil) - - cm := NewDeploymentWithArgs(mc, mr) - ma, err := cm.Marshal("blee/fred") - - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, dpYaml(), ma) -} - // BOZO!! -// func TestDeploymentListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sDeployment()}, nil) +// import ( +// "testing" -// l := NewDeploymentListWithArgs("-", NewDeploymentWithArgs(mc, mr)) -// // Make sure we can get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// appsv1 "k8s.io/api/apps/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -// mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 6, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// func NewDeploymentListWithArgs(ns string, r *resource.Deployment) resource.List { +// return resource.NewList(ns, "deploy", r, resource.AllVerbsAccess|resource.DescribeAccess) // } -// Helpers... +// func NewDeploymentWithArgs(conn k8s.Connection, res resource.Cruder) *resource.Deployment { +// r := &resource.Deployment{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } -func k8sDeployment() *appsv1.Deployment { - var i int32 = 1 - return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "blee", - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: &i, - }, - } -} +// func TestDeploymentListAccess(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() -func newDeployment() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewDeployment(mc).New(k8sDeployment()) - return c -} +// ns := "blee" +// l := NewDeploymentListWithArgs(resource.AllNamespaces, NewDeploymentWithArgs(mc, mr)) +// l.SetNamespace(ns) -func dpYaml() string { - return `apiVersion: apps/v1 -kind: Deployment -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -spec: - replicas: 1 - selector: null - strategy: {} - template: - metadata: - creationTimestamp: null - spec: - containers: null -status: {} -` -} +// assert.Equal(t, "blee", l.GetNamespace()) +// assert.Equal(t, "deploy", l.GetName()) +// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { +// assert.True(t, l.Access(a)) +// } +// } + +// func TestDeploymentFields(t *testing.T) { +// r := newDeployment().Fields("blee") +// assert.Equal(t, "fred", r[0]) +// } + +// func TestDeploymentMarshal(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// m.When(mr.Get("blee", "fred")).ThenReturn(k8sDeployment(), nil) + +// cm := NewDeploymentWithArgs(mc, mr) +// ma, err := cm.Marshal("blee/fred") + +// mr.VerifyWasCalledOnce().Get("blee", "fred") +// assert.Nil(t, err) +// assert.Equal(t, dpYaml(), ma) +// } + +// // BOZO!! +// // func TestDeploymentListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sDeployment()}, nil) + +// // l := NewDeploymentListWithArgs("-", NewDeploymentWithArgs(mc, mr)) +// // // Make sure we can get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } + +// // mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) +// // row := td.Rows["blee/fred"] +// // assert.Equal(t, 6, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// // } + +// // Helpers... + +// func k8sDeployment() *appsv1.Deployment { +// var i int32 = 1 +// return &appsv1.Deployment{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "blee", +// Name: "fred", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: appsv1.DeploymentSpec{ +// Replicas: &i, +// }, +// } +// } + +// func newDeployment() resource.Columnar { +// mc := NewMockConnection() +// c, _ := resource.NewDeployment(mc).New(k8sDeployment()) +// return c +// } + +// func dpYaml() string { +// return `apiVersion: apps/v1 +// kind: Deployment +// metadata: +// creationTimestamp: "2018-12-14T17:36:43Z" +// name: fred +// namespace: blee +// spec: +// replicas: 1 +// selector: null +// strategy: {} +// template: +// metadata: +// creationTimestamp: null +// spec: +// containers: null +// status: {} +// ` +// } diff --git a/internal/resource/job.go b/internal/resource/job.go index f7eae386..534daf5c 100644 --- a/internal/resource/job.go +++ b/internal/resource/job.go @@ -1,195 +1,196 @@ package resource -import ( - "context" - "errors" - "fmt" - "strconv" - "strings" - "time" +// BOZO!! +// import ( +// "context" +// "errors" +// "fmt" +// "strconv" +// "strings" +// "time" - "github.com/derailed/k9s/internal/k8s" - batchv1 "k8s.io/api/batch/v1" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/duration" -) +// "github.com/derailed/k9s/internal/k8s" +// batchv1 "k8s.io/api/batch/v1" +// v1 "k8s.io/api/core/v1" +// "k8s.io/apimachinery/pkg/util/duration" +// ) -// Job tracks a kubernetes resource. -type Job struct { - *Base +// // Job tracks a kubernetes resource. +// type Job struct { +// *Base - instance *batchv1.Job -} +// instance *batchv1.Job +// } -// NewJobList returns a new resource list. -func NewJobList(c Connection, ns string) List { - return NewList( - ns, - "job", - NewJob(c), - AllVerbsAccess|DescribeAccess, - ) -} +// // NewJobList returns a new resource list. +// func NewJobList(c Connection, ns string) List { +// return NewList( +// ns, +// "job", +// NewJob(c), +// AllVerbsAccess|DescribeAccess, +// ) +// } -// NewJob instantiates a new Job. -func NewJob(c Connection) *Job { - j := &Job{ - Base: &Base{Connection: c, Resource: k8s.NewJob(c)}, - } - j.Factory = j +// // NewJob instantiates a new Job. +// func NewJob(c Connection) *Job { +// j := &Job{ +// Base: &Base{Connection: c, Resource: k8s.NewJob(c)}, +// } +// j.Factory = j - return j -} +// return j +// } -// New builds a new Job instance from a k8s resource. -func (r *Job) New(i interface{}) (Columnar, error) { - c := NewJob(r.Connection) - switch instance := i.(type) { - case *batchv1.Job: - c.instance = instance - case batchv1.Job: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting Job but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) +// // New builds a new Job instance from a k8s resource. +// func (r *Job) New(i interface{}) (Columnar, error) { +// c := NewJob(r.Connection) +// switch instance := i.(type) { +// case *batchv1.Job: +// c.instance = instance +// case batchv1.Job: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting Job but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) - return c, nil -} +// return c, nil +// } -// Marshal resource to yaml. -func (r *Job) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } +// // Marshal resource to yaml. +// func (r *Job) Marshal(path string) (string, error) { +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// return "", err +// } - jo, ok := i.(*batchv1.Job) - if !ok { - return "", errors.New("expecting job resource") - } - jo.TypeMeta.APIVersion = "extensions/v1beta1" - jo.TypeMeta.Kind = "Job" +// jo, ok := i.(*batchv1.Job) +// if !ok { +// return "", errors.New("expecting job resource") +// } +// jo.TypeMeta.APIVersion = "extensions/v1beta1" +// jo.TypeMeta.Kind = "Job" - return r.marshalObject(jo) -} +// return r.marshalObject(jo) +// } -// Containers fetch all the containers on this job, may include init containers. -func (r *Job) Containers(path string, includeInit bool) ([]string, error) { - ns, n := Namespaced(path) +// // Containers fetch all the containers on this job, may include init containers. +// func (r *Job) Containers(path string, includeInit bool) ([]string, error) { +// ns, n := Namespaced(path) - return r.Resource.(k8s.Loggable).Containers(ns, n, includeInit) -} +// return r.Resource.(k8s.Loggable).Containers(ns, n, includeInit) +// } -// Logs retrieves logs for a given container. -func (r *Job) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { - instance, err := r.Resource.Get(opts.Namespace, opts.Name) - if err != nil { - return err - } - jo, ok := instance.(*batchv1.Job) - if !ok { - return errors.New("expecting job resource") - } - if jo.Spec.Selector == nil || len(jo.Spec.Selector.MatchLabels) == 0 { - return fmt.Errorf("No valid selector found on job %s", opts.FQN()) - } +// // Logs retrieves logs for a given container. +// func (r *Job) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { +// instance, err := r.Resource.Get(opts.Namespace, opts.Name) +// if err != nil { +// return err +// } +// jo, ok := instance.(*batchv1.Job) +// if !ok { +// return errors.New("expecting job resource") +// } +// if jo.Spec.Selector == nil || len(jo.Spec.Selector.MatchLabels) == 0 { +// return fmt.Errorf("No valid selector found on job %s", opts.FQN()) +// } - return r.podLogs(ctx, c, jo.Spec.Selector.MatchLabels, opts) -} +// return r.podLogs(ctx, c, jo.Spec.Selector.MatchLabels, opts) +// } -// Header return resource header. -func (*Job) Header(ns string) Row { - hh := Row{} - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } +// // Header return resource header. +// func (*Job) Header(ns string) Row { +// hh := Row{} +// if ns == AllNamespaces { +// hh = append(hh, "NAMESPACE") +// } - return append(hh, "NAME", "COMPLETIONS", "DURATION", "CONTAINERS", "IMAGES", "AGE") -} +// return append(hh, "NAME", "COMPLETIONS", "DURATION", "CONTAINERS", "IMAGES", "AGE") +// } -// Fields retrieves displayable fields. -func (r *Job) Fields(ns string) Row { - ff := make([]string, 0, len(r.Header(ns))) +// // Fields retrieves displayable fields. +// func (r *Job) Fields(ns string) Row { +// ff := make([]string, 0, len(r.Header(ns))) - i := r.instance - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } +// i := r.instance +// if ns == AllNamespaces { +// ff = append(ff, i.Namespace) +// } - cc, ii := r.toContainers(i.Spec.Template.Spec) +// cc, ii := r.toContainers(i.Spec.Template.Spec) - return append(ff, - i.Name, - r.toCompletion(i.Spec, i.Status), - r.toDuration(i.Status), - cc, - ii, - toAge(i.ObjectMeta.CreationTimestamp), - ) -} +// return append(ff, +// i.Name, +// r.toCompletion(i.Spec, i.Status), +// r.toDuration(i.Status), +// cc, +// ii, +// toAge(i.ObjectMeta.CreationTimestamp), +// ) +// } -// ---------------------------------------------------------------------------- -// Helpers... +// // ---------------------------------------------------------------------------- +// // Helpers... -const maxShow = 2 +// const maxShow = 2 -func (*Job) toContainers(p v1.PodSpec) (string, string) { - cc, ii := parseContainers(p.InitContainers) - cn, ci := parseContainers(p.Containers) +// func (*Job) toContainers(p v1.PodSpec) (string, string) { +// cc, ii := parseContainers(p.InitContainers) +// cn, ci := parseContainers(p.Containers) - cc, ii = append(cc, cn...), append(ii, ci...) +// cc, ii = append(cc, cn...), append(ii, ci...) - // Limit to 2 of each... - if len(cc) > maxShow { - cc = append(cc[:2], "(+"+strconv.Itoa(len(cc)-maxShow)+")...") - } - if len(ii) > maxShow { - ii = append(ii[:2], "(+"+strconv.Itoa(len(ii)-maxShow)+")...") - } +// // Limit to 2 of each... +// if len(cc) > maxShow { +// cc = append(cc[:2], "(+"+strconv.Itoa(len(cc)-maxShow)+")...") +// } +// if len(ii) > maxShow { +// ii = append(ii[:2], "(+"+strconv.Itoa(len(ii)-maxShow)+")...") +// } - return strings.Join(cc, ","), strings.Join(ii, ",") -} +// return strings.Join(cc, ","), strings.Join(ii, ",") +// } -func parseContainers(cos []v1.Container) (nn, ii []string) { - for _, co := range cos { - nn = append(nn, co.Name) - ii = append(ii, co.Image) - } +// func parseContainers(cos []v1.Container) (nn, ii []string) { +// for _, co := range cos { +// nn = append(nn, co.Name) +// ii = append(ii, co.Image) +// } - return nn, ii -} +// return nn, ii +// } -func (*Job) toCompletion(spec batchv1.JobSpec, status batchv1.JobStatus) (s string) { - if spec.Completions != nil { - return strconv.Itoa(int(status.Succeeded)) + "/" + strconv.Itoa(int(*spec.Completions)) - } +// func (*Job) toCompletion(spec batchv1.JobSpec, status batchv1.JobStatus) (s string) { +// if spec.Completions != nil { +// return strconv.Itoa(int(status.Succeeded)) + "/" + strconv.Itoa(int(*spec.Completions)) +// } - if spec.Parallelism == nil { - return strconv.Itoa(int(status.Succeeded)) + "/1" - } +// if spec.Parallelism == nil { +// return strconv.Itoa(int(status.Succeeded)) + "/1" +// } - p := *spec.Parallelism - if p > 1 { - return strconv.Itoa(int(status.Succeeded)) + "/1 of " + strconv.Itoa(int(p)) - } +// p := *spec.Parallelism +// if p > 1 { +// return strconv.Itoa(int(status.Succeeded)) + "/1 of " + strconv.Itoa(int(p)) +// } - return strconv.Itoa(int(status.Succeeded)) + "/1" -} +// return strconv.Itoa(int(status.Succeeded)) + "/1" +// } -func (*Job) toDuration(status batchv1.JobStatus) string { - if status.StartTime == nil { - return MissingValue - } +// func (*Job) toDuration(status batchv1.JobStatus) string { +// if status.StartTime == nil { +// return MissingValue +// } - var d time.Duration - switch { - case status.CompletionTime == nil: - d = time.Since(status.StartTime.Time) - default: - d = status.CompletionTime.Sub(status.StartTime.Time) - } +// var d time.Duration +// switch { +// case status.CompletionTime == nil: +// d = time.Since(status.StartTime.Time) +// default: +// d = status.CompletionTime.Sub(status.StartTime.Time) +// } - return duration.HumanDuration(d) -} +// return duration.HumanDuration(d) +// } diff --git a/internal/resource/job_int_test.go b/internal/resource/job_int_test.go index 51ebc252..427224f9 100644 --- a/internal/resource/job_int_test.go +++ b/internal/resource/job_int_test.go @@ -1,153 +1,154 @@ package resource -import ( - "testing" - "time" +// BOZO!! +// import ( +// "testing" +// "time" - "github.com/stretchr/testify/assert" - batchv1 "k8s.io/api/batch/v1" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) +// "github.com/stretchr/testify/assert" +// batchv1 "k8s.io/api/batch/v1" +// v1 "k8s.io/api/core/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -func TestJobToCompletion(t *testing.T) { - t0 := testTime() - t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)} - var c, p int32 = 10, 20 +// func TestJobToCompletion(t *testing.T) { +// t0 := testTime() +// t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)} +// var c, p int32 = 10, 20 - uu := []struct { - j batchv1.JobSpec - s batchv1.JobStatus - e string - }{ - { - batchv1.JobSpec{ - Completions: &c, - Parallelism: &p, - }, - batchv1.JobStatus{ - Succeeded: 1, - Active: 1, - Failed: 0, - StartTime: &t1, - CompletionTime: &t2, - }, - "1/10", - }, - { - batchv1.JobSpec{ - Parallelism: &p, - }, - batchv1.JobStatus{ - Succeeded: 1, - Active: 1, - Failed: 0, - StartTime: &t1, - CompletionTime: &t2, - }, - "1/1 of 20", - }, - { - batchv1.JobSpec{ - Completions: &c, - }, - batchv1.JobStatus{ - Succeeded: 1, - Active: 1, - Failed: 0, - StartTime: &t1, - CompletionTime: &t2, - }, - "1/10", - }, - { - batchv1.JobSpec{}, - batchv1.JobStatus{ - Succeeded: 1, - Active: 1, - Failed: 0, - StartTime: &t1, - CompletionTime: &t2, - }, - "1/1", - }, - } +// uu := []struct { +// j batchv1.JobSpec +// s batchv1.JobStatus +// e string +// }{ +// { +// batchv1.JobSpec{ +// Completions: &c, +// Parallelism: &p, +// }, +// batchv1.JobStatus{ +// Succeeded: 1, +// Active: 1, +// Failed: 0, +// StartTime: &t1, +// CompletionTime: &t2, +// }, +// "1/10", +// }, +// { +// batchv1.JobSpec{ +// Parallelism: &p, +// }, +// batchv1.JobStatus{ +// Succeeded: 1, +// Active: 1, +// Failed: 0, +// StartTime: &t1, +// CompletionTime: &t2, +// }, +// "1/1 of 20", +// }, +// { +// batchv1.JobSpec{ +// Completions: &c, +// }, +// batchv1.JobStatus{ +// Succeeded: 1, +// Active: 1, +// Failed: 0, +// StartTime: &t1, +// CompletionTime: &t2, +// }, +// "1/10", +// }, +// { +// batchv1.JobSpec{}, +// batchv1.JobStatus{ +// Succeeded: 1, +// Active: 1, +// Failed: 0, +// StartTime: &t1, +// CompletionTime: &t2, +// }, +// "1/1", +// }, +// } - var j *Job - for _, u := range uu { - assert.Equal(t, u.e, j.toCompletion(u.j, u.s)) - } -} +// var j *Job +// for _, u := range uu { +// assert.Equal(t, u.e, j.toCompletion(u.j, u.s)) +// } +// } -func TestJobToDuration(t *testing.T) { - t0 := testTime().UTC() - t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)} +// func TestJobToDuration(t *testing.T) { +// t0 := testTime().UTC() +// t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)} - uu := []struct { - s batchv1.JobStatus - e string - }{ - { - batchv1.JobStatus{ - StartTime: &t1, - CompletionTime: &t2, - }, - "10s", - }, - { - batchv1.JobStatus{ - StartTime: &metav1.Time{Time: time.Now().Add(-10 * time.Second)}, - }, - "10s", - }, - { - batchv1.JobStatus{ - CompletionTime: &t2, - }, - MissingValue, - }, - } +// uu := []struct { +// s batchv1.JobStatus +// e string +// }{ +// { +// batchv1.JobStatus{ +// StartTime: &t1, +// CompletionTime: &t2, +// }, +// "10s", +// }, +// { +// batchv1.JobStatus{ +// StartTime: &metav1.Time{Time: time.Now().Add(-10 * time.Second)}, +// }, +// "10s", +// }, +// { +// batchv1.JobStatus{ +// CompletionTime: &t2, +// }, +// MissingValue, +// }, +// } - var j *Job - for _, u := range uu { - assert.Equal(t, u.e, j.toDuration(u.s)) - } -} +// var j *Job +// for _, u := range uu { +// assert.Equal(t, u.e, j.toDuration(u.s)) +// } +// } -func TestJobToContainers(t *testing.T) { - uu := []struct { - s v1.PodSpec - c, i string - }{ - { - v1.PodSpec{ - InitContainers: []v1.Container{ - {Name: "i1", Image: "fred"}, - }, - Containers: []v1.Container{ - {Name: "c1", Image: "blee"}, - }, - }, - "i1,c1", "fred,blee", - }, - { - v1.PodSpec{ - InitContainers: []v1.Container{ - {Name: "i1", Image: "fred"}, - }, - Containers: []v1.Container{ - {Name: "c1", Image: "blee"}, - {Name: "c2", Image: "duh"}, - }, - }, - "i1,c1,(+1)...", "fred,blee,(+1)...", - }, - } +// func TestJobToContainers(t *testing.T) { +// uu := []struct { +// s v1.PodSpec +// c, i string +// }{ +// { +// v1.PodSpec{ +// InitContainers: []v1.Container{ +// {Name: "i1", Image: "fred"}, +// }, +// Containers: []v1.Container{ +// {Name: "c1", Image: "blee"}, +// }, +// }, +// "i1,c1", "fred,blee", +// }, +// { +// v1.PodSpec{ +// InitContainers: []v1.Container{ +// {Name: "i1", Image: "fred"}, +// }, +// Containers: []v1.Container{ +// {Name: "c1", Image: "blee"}, +// {Name: "c2", Image: "duh"}, +// }, +// }, +// "i1,c1,(+1)...", "fred,blee,(+1)...", +// }, +// } - var j *Job - for _, u := range uu { - c, i := j.toContainers(u.s) - assert.Equal(t, u.c, c) - assert.Equal(t, u.i, i) - } -} +// var j *Job +// for _, u := range uu { +// c, i := j.toContainers(u.s) +// assert.Equal(t, u.c, c) +// assert.Equal(t, u.i, i) +// } +// } diff --git a/internal/resource/job_test.go b/internal/resource/job_test.go index 4dcf2957..85044db8 100644 --- a/internal/resource/job_test.go +++ b/internal/resource/job_test.go @@ -1,127 +1,128 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/batch/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewJobListWithArgs(ns string, r *resource.Job) resource.List { - return resource.NewList(ns, "job", r, resource.AllVerbsAccess|resource.DescribeAccess) -} - -func NewJobWithArgs(conn k8s.Connection, res resource.Cruder) *resource.Job { - r := &resource.Job{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestJobListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - - ns := "blee" - l := NewJobListWithArgs(resource.AllNamespaces, NewJobWithArgs(mc, mr)) - l.SetNamespace(ns) - - assert.Equal(t, "blee", l.GetNamespace()) - assert.Equal(t, "job", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestJobFields(t *testing.T) { - r := newJob().Fields("blee") - assert.Equal(t, "fred", r[0]) -} - -func TestJobMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sJob(), nil) - - cm := NewJobWithArgs(mc, mr) - ma, err := cm.Marshal("blee/fred") - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, jobYaml(), ma) -} - // BOZO!! -// func TestJobListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sJob()}, nil) +// import ( +// "testing" -// l := NewJobListWithArgs("blee", NewJobWithArgs(mc, mr)) -// // Make sure we mrn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/batch/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -// mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, "blee", l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 6, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// func NewJobListWithArgs(ns string, r *resource.Job) resource.List { +// return resource.NewList(ns, "job", r, resource.AllVerbsAccess|resource.DescribeAccess) // } -// Helpers... +// func NewJobWithArgs(conn k8s.Connection, res resource.Cruder) *resource.Job { +// r := &resource.Job{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } -func k8sJob() *v1.Job { - var i int32 - return &v1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "blee", - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: v1.JobSpec{ - Completions: &i, - Parallelism: &i, - }, - Status: v1.JobStatus{ - StartTime: &metav1.Time{Time: testTime()}, - CompletionTime: &metav1.Time{Time: testTime()}, - }, - } -} +// func TestJobListAccess(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() -func newJob() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewJob(mc).New(k8sJob()) - return c -} +// ns := "blee" +// l := NewJobListWithArgs(resource.AllNamespaces, NewJobWithArgs(mc, mr)) +// l.SetNamespace(ns) -func jobYaml() string { - return `apiVersion: extensions/v1beta1 -kind: Job -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -spec: - completions: 0 - parallelism: 0 - template: - metadata: - creationTimestamp: null - spec: - containers: null -status: - completionTime: "2018-12-14T17:36:43Z" - startTime: "2018-12-14T17:36:43Z" -` -} +// assert.Equal(t, "blee", l.GetNamespace()) +// assert.Equal(t, "job", l.GetName()) +// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { +// assert.True(t, l.Access(a)) +// } +// } + +// func TestJobFields(t *testing.T) { +// r := newJob().Fields("blee") +// assert.Equal(t, "fred", r[0]) +// } + +// func TestJobMarshal(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// m.When(mr.Get("blee", "fred")).ThenReturn(k8sJob(), nil) + +// cm := NewJobWithArgs(mc, mr) +// ma, err := cm.Marshal("blee/fred") +// mr.VerifyWasCalledOnce().Get("blee", "fred") +// assert.Nil(t, err) +// assert.Equal(t, jobYaml(), ma) +// } + +// // BOZO!! +// // func TestJobListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sJob()}, nil) + +// // l := NewJobListWithArgs("blee", NewJobWithArgs(mc, mr)) +// // // Make sure we mrn get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } + +// // mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, "blee", l.GetNamespace()) +// // row := td.Rows["blee/fred"] +// // assert.Equal(t, 6, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// // } + +// // Helpers... + +// func k8sJob() *v1.Job { +// var i int32 +// return &v1.Job{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "blee", +// Name: "fred", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: v1.JobSpec{ +// Completions: &i, +// Parallelism: &i, +// }, +// Status: v1.JobStatus{ +// StartTime: &metav1.Time{Time: testTime()}, +// CompletionTime: &metav1.Time{Time: testTime()}, +// }, +// } +// } + +// func newJob() resource.Columnar { +// mc := NewMockConnection() +// c, _ := resource.NewJob(mc).New(k8sJob()) +// return c +// } + +// func jobYaml() string { +// return `apiVersion: extensions/v1beta1 +// kind: Job +// metadata: +// creationTimestamp: "2018-12-14T17:36:43Z" +// name: fred +// namespace: blee +// spec: +// completions: 0 +// parallelism: 0 +// template: +// metadata: +// creationTimestamp: null +// spec: +// containers: null +// status: +// completionTime: "2018-12-14T17:36:43Z" +// startTime: "2018-12-14T17:36:43Z" +// ` +// } diff --git a/internal/resource/list.go b/internal/resource/list.go index 94724b81..3208eb04 100644 --- a/internal/resource/list.go +++ b/internal/resource/list.go @@ -238,7 +238,7 @@ func (l *list) update(ns string, rows render.Rows) { continue } if index, ok := l.cache.FindIndex(row.ID); ok { - delta := render.NewDeltaRow(l.cache[index].Row, row) + delta := render.NewDeltaRow(l.cache[index].Row, row, true) if delta.IsBlank() { l.cache[index].Kind, l.cache[index].Deltas = render.EventUnchanged, delta } else { diff --git a/internal/resource/node.go b/internal/resource/node.go index 6242f73f..d7c3f22a 100644 --- a/internal/resource/node.go +++ b/internal/resource/node.go @@ -1,278 +1,279 @@ package resource -import ( - "errors" - "fmt" - "strings" - - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/derailed/k9s/internal/k8s" - "github.com/rs/zerolog/log" - v1 "k8s.io/api/core/v1" - mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" -) - -const ( - labelNodeRolePrefix = "node-role.kubernetes.io/" - nodeLabelRole = "kubernetes.io/role" -) - -// Node tracks a kubernetes resource. -type Node struct { - *Base - instance *v1.Node - metrics *mv1beta1.NodeMetrics -} - -// NewNodeList returns a new resource list. -func NewNodeList(c Connection, _ string) List { - return NewList( - NotNamespaced, - "nodes", - NewNode(c), - ViewAccess|DescribeAccess, - ) -} - -// NewNode instantiates a new Node. -func NewNode(c Connection) *Node { - n := &Node{ - Base: &Base{ - Connection: c, - Resource: k8s.NewNode(c), - }, - } - n.Factory = n - - return n -} - -// New builds a new Node instance from a k8s resource. -func (r *Node) New(i interface{}) (Columnar, error) { - c := NewNode(r.Connection) - switch instance := i.(type) { - case *v1.Node: - c.instance = instance - case v1.Node: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting Node but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) - - return c, nil -} - -// SetNodeMetrics set the current k8s resource metrics on a given node. -func (r *Node) SetNodeMetrics(m *mv1beta1.NodeMetrics) { - r.metrics = m -} - // BOZO!! -// // List all resources for a given namespace. -// func (r *Node) List(ns string, opts metav1.ListOptions) (Columnars, error) { -// nn, err := r.Resource.List(ns, opts) -// if err != nil { -// return nil, err -// } +// import ( +// "errors" +// "fmt" +// "strings" -// cc := make(Columnars, 0, len(nn)) -// for i := range nn { -// node, ok := nn[i].(v1.Node) -// if !ok { -// return nil, errors.New("Expecting a node resource") -// } -// no, err := r.New(&node) -// if err != nil { -// return nil, err -// } -// cc = append(cc, no) -// } +// "k8s.io/apimachinery/pkg/util/sets" -// return cc, nil +// "github.com/derailed/k9s/internal/k8s" +// "github.com/rs/zerolog/log" +// v1 "k8s.io/api/core/v1" +// mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" +// ) + +// const ( +// labelNodeRolePrefix = "node-role.kubernetes.io/" +// nodeLabelRole = "kubernetes.io/role" +// ) + +// // Node tracks a kubernetes resource. +// type Node struct { +// *Base +// instance *v1.Node +// metrics *mv1beta1.NodeMetrics // } -// Marshal a resource to yaml. -func (r *Node) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - log.Error().Err(err) - return "", err - } +// // NewNodeList returns a new resource list. +// func NewNodeList(c Connection, _ string) List { +// return NewList( +// NotNamespaced, +// "nodes", +// NewNode(c), +// ViewAccess|DescribeAccess, +// ) +// } - no, ok := i.(*v1.Node) - if !ok { - return "", errors.New("Expecting a node resource") - } - no.TypeMeta.APIVersion = "v1" - no.TypeMeta.Kind = "Node" +// // NewNode instantiates a new Node. +// func NewNode(c Connection) *Node { +// n := &Node{ +// Base: &Base{ +// Connection: c, +// Resource: k8s.NewNode(c), +// }, +// } +// n.Factory = n - return r.marshalObject(no) -} +// return n +// } -// Header returns resource header. -func (*Node) Header(ns string) Row { - return Row{ - "NAME", - "STATUS", - "ROLE", - "VERSION", - "KERNEL", - "INTERNAL-IP", - "EXTERNAL-IP", - "CPU", - "MEM", - "%CPU", - "%MEM", - "ACPU", - "AMEM", - "AGE", - } -} +// // New builds a new Node instance from a k8s resource. +// func (r *Node) New(i interface{}) (Columnar, error) { +// c := NewNode(r.Connection) +// switch instance := i.(type) { +// case *v1.Node: +// c.instance = instance +// case v1.Node: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting Node but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) -// NumCols designates if column is numerical. -func (*Node) NumCols(n string) map[string]bool { - return map[string]bool{ - "CPU": true, - "MEM": true, - "%CPU": true, - "%MEM": true, - "ACPU": true, - "AMEM": true, - } -} +// return c, nil +// } -// Fields returns displayable fields. -func (r *Node) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) +// // SetNodeMetrics set the current k8s resource metrics on a given node. +// func (r *Node) SetNodeMetrics(m *mv1beta1.NodeMetrics) { +// r.metrics = m +// } - no := r.instance - iIP, eIP := r.getIPs(no.Status.Addresses) - iIP, eIP = missing(iIP), missing(eIP) +// // BOZO!! +// // // List all resources for a given namespace. +// // func (r *Node) List(ns string, opts metav1.ListOptions) (Columnars, error) { +// // nn, err := r.Resource.List(ns, opts) +// // if err != nil { +// // return nil, err +// // } - c, a, p := gatherNodeMX(no, r.metrics) +// // cc := make(Columnars, 0, len(nn)) +// // for i := range nn { +// // node, ok := nn[i].(v1.Node) +// // if !ok { +// // return nil, errors.New("Expecting a node resource") +// // } +// // no, err := r.New(&node) +// // if err != nil { +// // return nil, err +// // } +// // cc = append(cc, no) +// // } - sta := make([]string, 10) - r.status(no.Status, no.Spec.Unschedulable, sta) - ro := sets.NewString() - r.findNodeRoles(no, &ro) +// // return cc, nil +// // } - return append(ff, - no.Name, - join(sta), - join(ro.List()), - no.Status.NodeInfo.KubeletVersion, - no.Status.NodeInfo.KernelVersion, - iIP, - eIP, - c.cpu, - c.mem, - p.cpu, - p.mem, - a.cpu, - a.mem, - toAge(no.ObjectMeta.CreationTimestamp), - ) -} +// // Marshal a resource to yaml. +// func (r *Node) Marshal(path string) (string, error) { +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// log.Error().Err(err) +// return "", err +// } -// ---------------------------------------------------------------------------- -// Helpers... +// no, ok := i.(*v1.Node) +// if !ok { +// return "", errors.New("Expecting a node resource") +// } +// no.TypeMeta.APIVersion = "v1" +// no.TypeMeta.Kind = "Node" -type metric struct { - cpu, mem string -} +// return r.marshalObject(no) +// } -func noMetric() metric { - return metric{cpu: NAValue, mem: NAValue} -} +// // Header returns resource header. +// func (*Node) Header(ns string) Row { +// return Row{ +// "NAME", +// "STATUS", +// "ROLE", +// "VERSION", +// "KERNEL", +// "INTERNAL-IP", +// "EXTERNAL-IP", +// "CPU", +// "MEM", +// "%CPU", +// "%MEM", +// "ACPU", +// "AMEM", +// "AGE", +// } +// } -func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c metric, a metric, p metric) { - c, a, p = noMetric(), noMetric(), noMetric() - if mx == nil { - return - } +// // NumCols designates if column is numerical. +// func (*Node) NumCols(n string) map[string]bool { +// return map[string]bool{ +// "CPU": true, +// "MEM": true, +// "%CPU": true, +// "%MEM": true, +// "ACPU": true, +// "AMEM": true, +// } +// } - cpu := mx.Usage.Cpu().MilliValue() - mem := k8s.ToMB(mx.Usage.Memory().Value()) - c = metric{ - cpu: ToMillicore(cpu), - mem: ToMi(mem), - } +// // Fields returns displayable fields. +// func (r *Node) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) - acpu := no.Status.Allocatable.Cpu().MilliValue() - amem := k8s.ToMB(no.Status.Allocatable.Memory().Value()) - a = metric{ - cpu: ToMillicore(acpu), - mem: ToMi(amem), - } +// no := r.instance +// iIP, eIP := r.getIPs(no.Status.Addresses) +// iIP, eIP = missing(iIP), missing(eIP) - p = metric{ - cpu: AsPerc(toPerc(float64(cpu), float64(acpu))), - mem: AsPerc(toPerc(mem, amem)), - } +// c, a, p := gatherNodeMX(no, r.metrics) - return -} +// sta := make([]string, 10) +// r.status(no.Status, no.Spec.Unschedulable, sta) +// ro := sets.NewString() +// r.findNodeRoles(no, &ro) -func (_ *Node) findNodeRoles(no *v1.Node, roles *sets.String) { - for k, v := range no.Labels { - switch { - case strings.HasPrefix(k, labelNodeRolePrefix): - if role := strings.TrimPrefix(k, labelNodeRolePrefix); len(role) > 0 { - roles.Insert(role) - } - case k == nodeLabelRole && v != "": - roles.Insert(v) - } - } +// return append(ff, +// no.Name, +// join(sta), +// join(ro.List()), +// no.Status.NodeInfo.KubeletVersion, +// no.Status.NodeInfo.KernelVersion, +// iIP, +// eIP, +// c.cpu, +// c.mem, +// p.cpu, +// p.mem, +// a.cpu, +// a.mem, +// toAge(no.ObjectMeta.CreationTimestamp), +// ) +// } - if roles.Len() == 0 { - roles.Insert(MissingValue) - } -} +// // ---------------------------------------------------------------------------- +// // Helpers... -func (*Node) getIPs(addrs []v1.NodeAddress) (iIP, eIP string) { - for _, a := range addrs { - switch a.Type { - case v1.NodeExternalIP: - eIP = a.Address - case v1.NodeInternalIP: - iIP = a.Address - } - } +// type metric struct { +// cpu, mem string +// } - return -} +// func noMetric() metric { +// return metric{cpu: NAValue, mem: NAValue} +// } -func (*Node) status(status v1.NodeStatus, exempt bool, res []string) { - var index int - conditions := make(map[v1.NodeConditionType]*v1.NodeCondition) - for n := range status.Conditions { - cond := status.Conditions[n] - conditions[cond.Type] = &cond - } +// func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c metric, a metric, p metric) { +// c, a, p = noMetric(), noMetric(), noMetric() +// if mx == nil { +// return +// } - validConditions := []v1.NodeConditionType{v1.NodeReady} - for _, validCondition := range validConditions { - condition, ok := conditions[validCondition] - if !ok { - continue - } - neg := "" - if condition.Status != v1.ConditionTrue { - neg = "Not" - } - res[index] = neg + string(condition.Type) - index++ +// cpu := mx.Usage.Cpu().MilliValue() +// mem := k8s.ToMB(mx.Usage.Memory().Value()) +// c = metric{ +// cpu: ToMillicore(cpu), +// mem: ToMi(mem), +// } - } - if len(res) == 0 { - res[index] = "Unknown" - index++ - } - if exempt { - res[index] = "SchedulingDisabled" - } -} +// acpu := no.Status.Allocatable.Cpu().MilliValue() +// amem := k8s.ToMB(no.Status.Allocatable.Memory().Value()) +// a = metric{ +// cpu: ToMillicore(acpu), +// mem: ToMi(amem), +// } + +// p = metric{ +// cpu: AsPerc(toPerc(float64(cpu), float64(acpu))), +// mem: AsPerc(toPerc(mem, amem)), +// } + +// return +// } + +// func (_ *Node) findNodeRoles(no *v1.Node, roles *sets.String) { +// for k, v := range no.Labels { +// switch { +// case strings.HasPrefix(k, labelNodeRolePrefix): +// if role := strings.TrimPrefix(k, labelNodeRolePrefix); len(role) > 0 { +// roles.Insert(role) +// } +// case k == nodeLabelRole && v != "": +// roles.Insert(v) +// } +// } + +// if roles.Len() == 0 { +// roles.Insert(MissingValue) +// } +// } + +// func (*Node) getIPs(addrs []v1.NodeAddress) (iIP, eIP string) { +// for _, a := range addrs { +// switch a.Type { +// case v1.NodeExternalIP: +// eIP = a.Address +// case v1.NodeInternalIP: +// iIP = a.Address +// } +// } + +// return +// } + +// func (*Node) status(status v1.NodeStatus, exempt bool, res []string) { +// var index int +// conditions := make(map[v1.NodeConditionType]*v1.NodeCondition) +// for n := range status.Conditions { +// cond := status.Conditions[n] +// conditions[cond.Type] = &cond +// } + +// validConditions := []v1.NodeConditionType{v1.NodeReady} +// for _, validCondition := range validConditions { +// condition, ok := conditions[validCondition] +// if !ok { +// continue +// } +// neg := "" +// if condition.Status != v1.ConditionTrue { +// neg = "Not" +// } +// res[index] = neg + string(condition.Type) +// index++ + +// } +// if len(res) == 0 { +// res[index] = "Unknown" +// index++ +// } +// if exempt { +// res[index] = "SchedulingDisabled" +// } +// } diff --git a/internal/resource/node_int_test.go b/internal/resource/node_int_test.go index 19187c5d..0c2c125f 100644 --- a/internal/resource/node_int_test.go +++ b/internal/resource/node_int_test.go @@ -1,121 +1,122 @@ package resource -import ( - "testing" +// BOZO!! +// import ( +// "testing" - "k8s.io/apimachinery/pkg/util/sets" +// "k8s.io/apimachinery/pkg/util/sets" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/core/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -func TestNodeStatus(t *testing.T) { - uu := []struct { - s v1.NodeStatus - e string - }{ - { - v1.NodeStatus{ - Conditions: []v1.NodeCondition{ - { - Type: v1.NodeReady, - Status: v1.ConditionTrue, - }, - }, - }, - "Ready", - }, - } +// func TestNodeStatus(t *testing.T) { +// uu := []struct { +// s v1.NodeStatus +// e string +// }{ +// { +// v1.NodeStatus{ +// Conditions: []v1.NodeCondition{ +// { +// Type: v1.NodeReady, +// Status: v1.ConditionTrue, +// }, +// }, +// }, +// "Ready", +// }, +// } - no := NewNode(nil) - for _, u := range uu { - res := make([]string, 5) - no.status(u.s, false, res) - assert.Equal(t, "Ready", join(res)) - } -} +// no := NewNode(nil) +// for _, u := range uu { +// res := make([]string, 5) +// no.status(u.s, false, res) +// assert.Equal(t, "Ready", join(res)) +// } +// } -func TestNodeRoles(t *testing.T) { - uu := []struct { - node v1.Node - roles []string - }{ - { - node: v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "kubernetes.io/role": "master", - "node-role.kubernetes.io/worker": "true", - }, - }, - }, - roles: []string{"master", "worker"}, - }, +// func TestNodeRoles(t *testing.T) { +// uu := []struct { +// node v1.Node +// roles []string +// }{ +// { +// node: v1.Node{ +// ObjectMeta: metav1.ObjectMeta{ +// Labels: map[string]string{ +// "kubernetes.io/role": "master", +// "node-role.kubernetes.io/worker": "true", +// }, +// }, +// }, +// roles: []string{"master", "worker"}, +// }, - { - node: v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "node-role.kubernetes.io/worker": "true", - "kubernetes.io/role": "master", - }, - }, - }, - roles: []string{"master", "worker"}, - }, +// { +// node: v1.Node{ +// ObjectMeta: metav1.ObjectMeta{ +// Labels: map[string]string{ +// "node-role.kubernetes.io/worker": "true", +// "kubernetes.io/role": "master", +// }, +// }, +// }, +// roles: []string{"master", "worker"}, +// }, - { - node: v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "kubernetes.io/role": "worker", - }, - }, - }, - roles: []string{"worker"}, - }, +// { +// node: v1.Node{ +// ObjectMeta: metav1.ObjectMeta{ +// Labels: map[string]string{ +// "kubernetes.io/role": "worker", +// }, +// }, +// }, +// roles: []string{"worker"}, +// }, - { - node: v1.Node{}, - roles: []string{""}, - }, - } +// { +// node: v1.Node{}, +// roles: []string{""}, +// }, +// } - no := NewNode(nil) - for _, u := range uu { - roles := sets.NewString() - no.findNodeRoles(&u.node, &roles) - assert.Equal(t, u.roles, roles.List()) - } -} +// no := NewNode(nil) +// for _, u := range uu { +// roles := sets.NewString() +// no.findNodeRoles(&u.node, &roles) +// assert.Equal(t, u.roles, roles.List()) +// } +// } -func BenchmarkNodeFields(b *testing.B) { - n := NewNode(nil) - no := makeNode() +// func BenchmarkNodeFields(b *testing.B) { +// n := NewNode(nil) +// no := makeNode() - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - node, _ := n.New(no) - node.Fields("") - } -} +// b.ResetTimer() +// b.ReportAllocs() +// for i := 0; i < b.N; i++ { +// node, _ := n.New(no) +// node.Fields("") +// } +// } -// ---------------------------------------------------------------------------- -// Helpers... +// // ---------------------------------------------------------------------------- +// // Helpers... -func makeNode() *v1.Node { - return &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: v1.NodeSpec{}, - Status: v1.NodeStatus{ - Addresses: []v1.NodeAddress{ - {Address: "1.1.1.1"}, - }, - }, - } -} +// func makeNode() *v1.Node { +// return &v1.Node{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: "fred", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: v1.NodeSpec{}, +// Status: v1.NodeStatus{ +// Addresses: []v1.NodeAddress{ +// {Address: "1.1.1.1"}, +// }, +// }, +// } +// } diff --git a/internal/resource/node_test.go b/internal/resource/node_test.go index 5e0bb2ec..3c9e2292 100644 --- a/internal/resource/node_test.go +++ b/internal/resource/node_test.go @@ -1,161 +1,162 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - res "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" - v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" -) - -func NewNodeListWithArgs(ns string, r *resource.Node) resource.List { - return resource.NewList(resource.NotNamespaced, "no", r, resource.ViewAccess|resource.DescribeAccess) -} - -func NewNodeWithArgs(conn k8s.Connection, res resource.Cruder, mx resource.MetricsServer) *resource.Node { - r := &resource.Node{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestNodeListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - mx := NewMockMetricsServer() - - ns := "blee" - l := NewNodeListWithArgs(resource.AllNamespaces, NewNodeWithArgs(mc, mr, mx)) - l.SetNamespace(ns) - - assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.Equal(t, "no", l.GetName()) - for _, a := range []int{resource.ViewAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestNodeFields(t *testing.T) { - r := newNode().Fields("blee") - assert.Equal(t, "fred", r[0]) -} - -func TestNodeMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sNode(), nil) - mx := NewMockMetricsServer() - - cm := NewNodeWithArgs(mc, mr, mx) - ma, err := cm.Marshal("blee/fred") - - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, noYaml(), ma) -} - // BOZO!! -// func TestNodeListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List("-", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sNode()}, nil) -// mx := NewMockMetricsServer() -// m.When(mx.HasMetrics()).ThenReturn(true) -// m.When(mx.FetchNodesMetrics()). -// ThenReturn(&mv1beta1.NodeMetricsList{Items: []mv1beta1.NodeMetrics{makeMxNode("fred", "100m", "100Mi")}}, nil) +// import ( +// "testing" -// l := NewNodeListWithArgs("-", NewNodeWithArgs(mc, mr, mx)) -// // Make sure we mrn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/core/v1" +// res "k8s.io/apimachinery/pkg/api/resource" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" +// v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" +// ) -// mr.VerifyWasCalled(m.Times(2)).List("-", metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) -// row, ok := td.Rows["fred"] -// assert.True(t, ok) -// assert.Equal(t, 14, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// func NewNodeListWithArgs(ns string, r *resource.Node) resource.List { +// return resource.NewList(resource.NotNamespaced, "no", r, resource.ViewAccess|resource.DescribeAccess) // } -// ---------------------------------------------------------------------------- -// Helpers... +// func NewNodeWithArgs(conn k8s.Connection, res resource.Cruder, mx resource.MetricsServer) *resource.Node { +// r := &resource.Node{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } -func k8sNode() *v1.Node { - return &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: v1.NodeSpec{}, - Status: v1.NodeStatus{ - Addresses: []v1.NodeAddress{ - {Address: "1.1.1.1"}, - }, - }, - } -} +// func TestNodeListAccess(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// mx := NewMockMetricsServer() -func makeMxNode(name, cpu, mem string) mv1beta1.NodeMetrics { - return v1beta1.NodeMetrics{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Usage: makeRes(cpu, mem), - } -} +// ns := "blee" +// l := NewNodeListWithArgs(resource.AllNamespaces, NewNodeWithArgs(mc, mr, mx)) +// l.SetNamespace(ns) -func makeRes(c, m string) v1.ResourceList { - cpu, _ := res.ParseQuantity(c) - mem, _ := res.ParseQuantity(m) +// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) +// assert.Equal(t, "no", l.GetName()) +// for _, a := range []int{resource.ViewAccess} { +// assert.True(t, l.Access(a)) +// } +// } - return v1.ResourceList{ - v1.ResourceCPU: cpu, - v1.ResourceMemory: mem, - } -} +// func TestNodeFields(t *testing.T) { +// r := newNode().Fields("blee") +// assert.Equal(t, "fred", r[0]) +// } -func newNode() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewNode(mc).New(k8sNode()) - return c -} +// func TestNodeMarshal(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// m.When(mr.Get("blee", "fred")).ThenReturn(k8sNode(), nil) +// mx := NewMockMetricsServer() -func noYaml() string { - return `apiVersion: v1 -kind: Node -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred -spec: {} -status: - addresses: - - address: 1.1.1.1 - type: "" - daemonEndpoints: - kubeletEndpoint: - Port: 0 - nodeInfo: - architecture: "" - bootID: "" - containerRuntimeVersion: "" - kernelVersion: "" - kubeProxyVersion: "" - kubeletVersion: "" - machineID: "" - operatingSystem: "" - osImage: "" - systemUUID: "" -` -} +// cm := NewNodeWithArgs(mc, mr, mx) +// ma, err := cm.Marshal("blee/fred") + +// mr.VerifyWasCalledOnce().Get("blee", "fred") +// assert.Nil(t, err) +// assert.Equal(t, noYaml(), ma) +// } + +// // BOZO!! +// // func TestNodeListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List("-", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sNode()}, nil) +// // mx := NewMockMetricsServer() +// // m.When(mx.HasMetrics()).ThenReturn(true) +// // m.When(mx.FetchNodesMetrics()). +// // ThenReturn(&mv1beta1.NodeMetricsList{Items: []mv1beta1.NodeMetrics{makeMxNode("fred", "100m", "100Mi")}}, nil) + +// // l := NewNodeListWithArgs("-", NewNodeWithArgs(mc, mr, mx)) +// // // Make sure we mrn get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } + +// // mr.VerifyWasCalled(m.Times(2)).List("-", metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) +// // row, ok := td.Rows["fred"] +// // assert.True(t, ok) +// // assert.Equal(t, 14, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// // } + +// // ---------------------------------------------------------------------------- +// // Helpers... + +// func k8sNode() *v1.Node { +// return &v1.Node{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: "fred", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: v1.NodeSpec{}, +// Status: v1.NodeStatus{ +// Addresses: []v1.NodeAddress{ +// {Address: "1.1.1.1"}, +// }, +// }, +// } +// } + +// func makeMxNode(name, cpu, mem string) mv1beta1.NodeMetrics { +// return v1beta1.NodeMetrics{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: name, +// }, +// Usage: makeRes(cpu, mem), +// } +// } + +// func makeRes(c, m string) v1.ResourceList { +// cpu, _ := res.ParseQuantity(c) +// mem, _ := res.ParseQuantity(m) + +// return v1.ResourceList{ +// v1.ResourceCPU: cpu, +// v1.ResourceMemory: mem, +// } +// } + +// func newNode() resource.Columnar { +// mc := NewMockConnection() +// c, _ := resource.NewNode(mc).New(k8sNode()) +// return c +// } + +// func noYaml() string { +// return `apiVersion: v1 +// kind: Node +// metadata: +// creationTimestamp: "2018-12-14T17:36:43Z" +// name: fred +// spec: {} +// status: +// addresses: +// - address: 1.1.1.1 +// type: "" +// daemonEndpoints: +// kubeletEndpoint: +// Port: 0 +// nodeInfo: +// architecture: "" +// bootID: "" +// containerRuntimeVersion: "" +// kernelVersion: "" +// kubeProxyVersion: "" +// kubeletVersion: "" +// machineID: "" +// operatingSystem: "" +// osImage: "" +// systemUUID: "" +// ` +// } diff --git a/internal/resource/ns.go b/internal/resource/ns.go index b46a5376..75f5e597 100644 --- a/internal/resource/ns.go +++ b/internal/resource/ns.go @@ -1,86 +1,87 @@ package resource -import ( - "errors" - "fmt" +// BOZO!! +// import ( +// "errors" +// "fmt" - "github.com/derailed/k9s/internal/k8s" - "github.com/rs/zerolog/log" - v1 "k8s.io/api/core/v1" -) +// "github.com/derailed/k9s/internal/k8s" +// "github.com/rs/zerolog/log" +// v1 "k8s.io/api/core/v1" +// ) -// Namespace tracks a kubernetes resource. -type Namespace struct { - *Base - instance *v1.Namespace -} +// // Namespace tracks a kubernetes resource. +// type Namespace struct { +// *Base +// instance *v1.Namespace +// } -// NewNamespaceList returns a new resource list. -func NewNamespaceList(c Connection, ns string) List { - return NewList( - NotNamespaced, - "ns", - NewNamespace(c), - CRUDAccess|DescribeAccess, - ) -} +// // NewNamespaceList returns a new resource list. +// func NewNamespaceList(c Connection, ns string) List { +// return NewList( +// NotNamespaced, +// "ns", +// NewNamespace(c), +// CRUDAccess|DescribeAccess, +// ) +// } -// NewNamespace instantiates a new Namespace. -func NewNamespace(c Connection) *Namespace { - n := &Namespace{&Base{Connection: c, Resource: k8s.NewNamespace(c)}, nil} - n.Factory = n +// // NewNamespace instantiates a new Namespace. +// func NewNamespace(c Connection) *Namespace { +// n := &Namespace{&Base{Connection: c, Resource: k8s.NewNamespace(c)}, nil} +// n.Factory = n - return n -} +// return n +// } -// New builds a new Namespace instance from a k8s resource. -func (r *Namespace) New(i interface{}) (Columnar, error) { - c := NewNamespace(r.Connection) - switch instance := i.(type) { - case *v1.Namespace: - c.instance = instance - case v1.Namespace: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting Namespace but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) +// // New builds a new Namespace instance from a k8s resource. +// func (r *Namespace) New(i interface{}) (Columnar, error) { +// c := NewNamespace(r.Connection) +// switch instance := i.(type) { +// case *v1.Namespace: +// c.instance = instance +// case v1.Namespace: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting Namespace but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) - return c, nil -} +// return c, nil +// } -// Marshal a resource to yaml. -func (r *Namespace) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - log.Error().Err(err) - return "", err - } +// // Marshal a resource to yaml. +// func (r *Namespace) Marshal(path string) (string, error) { +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// log.Error().Err(err) +// return "", err +// } - nss, ok := i.(*v1.Namespace) - if !ok { - return "", errors.New("Expecting a ns resource") - } - nss.TypeMeta.APIVersion = "v1" - nss.TypeMeta.Kind = "Namespace" +// nss, ok := i.(*v1.Namespace) +// if !ok { +// return "", errors.New("Expecting a ns resource") +// } +// nss.TypeMeta.APIVersion = "v1" +// nss.TypeMeta.Kind = "Namespace" - return r.marshalObject(nss) -} +// return r.marshalObject(nss) +// } -// Header returns resource header. -func (*Namespace) Header(ns string) Row { - return Row{"NAME", "STATUS", "AGE"} -} +// // Header returns resource header. +// func (*Namespace) Header(ns string) Row { +// return Row{"NAME", "STATUS", "AGE"} +// } -// Fields returns displayable fields. -func (r *Namespace) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - i := r.instance +// // Fields returns displayable fields. +// func (r *Namespace) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) +// i := r.instance - return append(ff, - i.Name, - string(i.Status.Phase), - toAge(i.ObjectMeta.CreationTimestamp), - ) -} +// return append(ff, +// i.Name, +// string(i.Status.Phase), +// toAge(i.ObjectMeta.CreationTimestamp), +// ) +// } diff --git a/internal/resource/ns_test.go b/internal/resource/ns_test.go index 5a929764..b2149b31 100644 --- a/internal/resource/ns_test.go +++ b/internal/resource/ns_test.go @@ -1,110 +1,111 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewNamespaceListWithArgs(ns string, r *resource.Namespace) resource.List { - return resource.NewList(resource.NotNamespaced, "ns", r, resource.CRUDAccess|resource.DescribeAccess) -} - -func NewNamespaceWithArgs(conn k8s.Connection, res resource.Cruder) *resource.Namespace { - r := &resource.Namespace{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestNamespaceListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - - ns := "blee" - l := NewNamespaceListWithArgs(resource.AllNamespaces, NewNamespaceWithArgs(mc, mr)) - l.SetNamespace(ns) - - assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.Equal(t, "ns", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestNamespaceFields(t *testing.T) { - r := newNamespace().Fields("blee") - assert.Equal(t, "fred", r[0]) -} - -func TestNamespaceMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("", "fred")).ThenReturn(k8sNamespace(), nil) - - cm := NewNamespaceWithArgs(mc, mr) - ma, err := cm.Marshal("fred") - - mr.VerifyWasCalledOnce().Get("", "fred") - assert.Nil(t, err) - assert.Equal(t, nsYaml(), ma) -} - // BOZO!! -// func TestNamespaceListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sNamespace()}, nil) +// import ( +// "testing" -// l := NewNamespaceListWithArgs("-", NewNamespaceWithArgs(mc, mr)) -// // Make sure we mrn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/core/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -// mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 3, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// func NewNamespaceListWithArgs(ns string, r *resource.Namespace) resource.List { +// return resource.NewList(resource.NotNamespaced, "ns", r, resource.CRUDAccess|resource.DescribeAccess) // } -// Helpers... +// func NewNamespaceWithArgs(conn k8s.Connection, res resource.Cruder) *resource.Namespace { +// r := &resource.Namespace{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } -func k8sNamespace() *v1.Namespace { - return &v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "blee", - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - } -} +// func TestNamespaceListAccess(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() -func newNamespace() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewNamespace(mc).New(k8sNamespace()) - return c -} +// ns := "blee" +// l := NewNamespaceListWithArgs(resource.AllNamespaces, NewNamespaceWithArgs(mc, mr)) +// l.SetNamespace(ns) -func nsYaml() string { - return `apiVersion: v1 -kind: Namespace -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -spec: {} -status: {} -` -} +// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) +// assert.Equal(t, "ns", l.GetName()) +// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { +// assert.True(t, l.Access(a)) +// } +// } + +// func TestNamespaceFields(t *testing.T) { +// r := newNamespace().Fields("blee") +// assert.Equal(t, "fred", r[0]) +// } + +// func TestNamespaceMarshal(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// m.When(mr.Get("", "fred")).ThenReturn(k8sNamespace(), nil) + +// cm := NewNamespaceWithArgs(mc, mr) +// ma, err := cm.Marshal("fred") + +// mr.VerifyWasCalledOnce().Get("", "fred") +// assert.Nil(t, err) +// assert.Equal(t, nsYaml(), ma) +// } + +// // BOZO!! +// // func TestNamespaceListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sNamespace()}, nil) + +// // l := NewNamespaceListWithArgs("-", NewNamespaceWithArgs(mc, mr)) +// // // Make sure we mrn get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } + +// // mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) +// // row := td.Rows["blee/fred"] +// // assert.Equal(t, 3, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// // } + +// // Helpers... + +// func k8sNamespace() *v1.Namespace { +// return &v1.Namespace{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "blee", +// Name: "fred", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// } +// } + +// func newNamespace() resource.Columnar { +// mc := NewMockConnection() +// c, _ := resource.NewNamespace(mc).New(k8sNamespace()) +// return c +// } + +// func nsYaml() string { +// return `apiVersion: v1 +// kind: Namespace +// metadata: +// creationTimestamp: "2018-12-14T17:36:43Z" +// name: fred +// namespace: blee +// spec: {} +// status: {} +// ` +// } diff --git a/internal/resource/pod.go b/internal/resource/pod.go index 8ebdac8a..ede8aa84 100644 --- a/internal/resource/pod.go +++ b/internal/resource/pod.go @@ -1,484 +1,484 @@ package resource -import ( - "bufio" - "context" - "errors" - "fmt" - "io" - "strconv" - "sync/atomic" - "time" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/color" - "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/resource" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" -) - -const ( - defaultTimeout = 1 * time.Second - // BOZO!! - Terminating = "Terminating" - Running = "Running" - Initialized = "Initialized" - Completed = "Completed" -) - -// Pod that can be displayed in a table and interacted with. -type Pod struct { - *Base - instance *v1.Pod - metrics *mv1beta1.PodMetrics -} - -// NewPodList returns a new resource list. -func NewPodList(c Connection, ns string) List { - return NewList( - ns, - "pods", - NewPod(c), - AllVerbsAccess|DescribeAccess, - ) -} - -// NewPod instantiates a new Pod. -func NewPod(c Connection) *Pod { - p := &Pod{ - Base: &Base{Connection: c, Resource: k8s.NewPod(c)}, - } - p.Factory = p - - return p -} - -// New builds a new Pod instance from a k8s resource. -func (r *Pod) New(i interface{}) (Columnar, error) { - c := NewPod(r.Connection) - switch instance := i.(type) { - case *v1.Pod: - c.instance = instance - case v1.Pod: - c.instance = &instance - case *interface{}: - ptr := *instance - po, ok := ptr.(v1.Pod) - if !ok { - return nil, fmt.Errorf("Expecting Pod but got %T", ptr) - } - c.instance = &po - default: - return nil, fmt.Errorf("Expecting Pod but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) - - return c, nil -} - -// SetPodMetrics set the current k8s resource metrics on a given pod. -func (r *Pod) SetPodMetrics(m *mv1beta1.PodMetrics) { - r.metrics = m -} - -// Marshal resource to yaml. -func (r *Pod) Marshal(path string) (string, error) { - panic("Should not be called") - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } - po, ok := i.(*v1.Pod) - if !ok { - return "", errors.New("Expecting a pod resource") - } - po.TypeMeta.APIVersion = "v1" - po.TypeMeta.Kind = "Pod" - - return r.marshalObject(po) -} - -// Containers lists out all the docker containers name contained in a pod. -func (r *Pod) Containers(path string, includeInit bool) ([]string, error) { - ns, po := Namespaced(path) - - return r.Resource.(k8s.Loggable).Containers(ns, po, includeInit) -} - -// PodLogs tail logs for all containers in a running Pod. -func (r *Pod) PodLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) - if !ok { - return errors.New("Expecting an informer") - } - ns, n := Namespaced(opts.FQN()) - o, err := fac.Get(ns, "v1/pods", n, labels.Everything()) - if err != nil { - return err - } - - var po v1.Pod - if runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil { - return err - } - opts.Color = asColor(po.Name) - if len(po.Spec.InitContainers)+len(po.Spec.Containers) == 1 { - opts.SingleContainer = true - } - - for _, co := range po.Spec.InitContainers { - opts.Container = co.Name - if err := r.Logs(ctx, c, opts); err != nil { - return err - } - } - rcos := r.loggableContainers(po.Status) - for _, co := range po.Spec.Containers { - if in(rcos, co.Name) { - opts.Container = co.Name - if err := r.Logs(ctx, c, opts); err != nil { - log.Error().Err(err).Msgf("Getting logs for %s failed", co.Name) - return err - } - } - } - - return nil -} - -// Logs tails a given container logs -func (r *Pod) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { - if !opts.HasContainer() { - return r.PodLogs(ctx, c, opts) - } - res, ok := r.Resource.(k8s.Loggable) - if !ok { - return fmt.Errorf("Resource %T is not Loggable", r.Resource) - } - - return tailLogs(ctx, res, c, opts) -} - -func tailLogs(ctx context.Context, res k8s.Loggable, c chan<- string, opts LogOptions) error { - log.Debug().Msgf("Tailing logs for %q/%q:%q", opts.Namespace, opts.Name, opts.Container) - o := v1.PodLogOptions{ - Container: opts.Container, - Follow: true, - TailLines: &opts.Lines, - Previous: opts.Previous, - } - req := res.Logs(opts.Namespace, opts.Name, &o) - ctxt, cancelFunc := context.WithCancel(ctx) - req.Context(ctxt) - - var blocked int32 = 1 - go logsTimeout(cancelFunc, &blocked) - - // This call will block if nothing is in the stream!! - stream, err := req.Stream() - atomic.StoreInt32(&blocked, 0) - if err != nil { - log.Error().Err(err).Msgf("Log stream failed for `%s", opts.Path()) - return fmt.Errorf("Unable to obtain log stream for %s", opts.Path()) - } - go readLogs(ctx, stream, c, opts) - - return nil -} - -func logsTimeout(cancel context.CancelFunc, blocked *int32) { - <-time.After(defaultTimeout) - if atomic.LoadInt32(blocked) == 1 { - log.Debug().Msg("Timed out reading the log stream") - cancel() - } -} - -func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) { - defer func() { - log.Debug().Msgf(">>> Closing stream `%s", opts.Path()) - if err := stream.Close(); err != nil { - log.Error().Err(err).Msg("Cloing stream") - } - }() - - scanner := bufio.NewScanner(stream) - for scanner.Scan() { - select { - case <-ctx.Done(): - return - default: - c <- opts.DecorateLog(scanner.Text()) - } - } -} - // BOZO!! -// // List resources for a given namespace. -// func (r *Pod) List(ns string, opts metav1.ListOptions) (Columnars, error) { -// pods, err := r.Resource.List(ns, opts) -// if err != nil { -// return nil, err -// } +// import ( +// "bufio" +// "context" +// "errors" +// "fmt" +// "io" +// "strconv" +// "sync/atomic" +// "time" -// cc := make(Columnars, 0, len(pods)) -// for i := range pods { -// po, err := r.New(&pods[i]) -// if err != nil { -// return nil, errors.New("Expecting a pod resource") -// } -// cc = append(cc, po) -// } +// "github.com/derailed/k9s/internal" +// "github.com/derailed/k9s/internal/color" +// "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/resource" +// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +// "k8s.io/apimachinery/pkg/labels" +// "k8s.io/apimachinery/pkg/runtime" +// mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" +// ) -// return cc, nil +// const ( +// defaultTimeout = 1 * time.Second +// // BOZO!! +// Terminating = "Terminating" +// Running = "Running" +// Initialized = "Initialized" +// Completed = "Completed" +// ) + +// // Pod that can be displayed in a table and interacted with. +// type Pod struct { +// *Base +// instance *v1.Pod +// metrics *mv1beta1.PodMetrics // } -// Header return resource header. -func (*Pod) Header(ns string) Row { - hh := Row{} - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } - return append(hh, - "NAME", - "READY", - "STATUS", - "RS", - "CPU", - "MEM", - "%CPU", - "%MEM", - "IP", - "NODE", - "QOS", - "AGE", - ) -} +// // NewPodList returns a new resource list. +// func NewPodList(c Connection, ns string) List { +// return NewList( +// ns, +// "pods", +// NewPod(c), +// AllVerbsAccess|DescribeAccess, +// ) +// } -// NumCols designates if column is numerical. -func (*Pod) NumCols(n string) map[string]bool { - return map[string]bool{ - "CPU": true, - "MEM": true, - "%CPU": true, - "%MEM": true, - "RS": true, - } -} +// // NewPod instantiates a new Pod. +// func NewPod(c Connection) *Pod { +// p := &Pod{ +// Base: &Base{Connection: c, Resource: k8s.NewPod(c)}, +// } +// p.Factory = p -// Fields retrieves displayable fields. -func (r *Pod) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - i := r.instance +// return p +// } - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } +// // New builds a new Pod instance from a k8s resource. +// func (r *Pod) New(i interface{}) (Columnar, error) { +// c := NewPod(r.Connection) +// switch instance := i.(type) { +// case *v1.Pod: +// c.instance = instance +// case v1.Pod: +// c.instance = &instance +// case *interface{}: +// ptr := *instance +// po, ok := ptr.(v1.Pod) +// if !ok { +// return nil, fmt.Errorf("Expecting Pod but got %T", ptr) +// } +// c.instance = &po +// default: +// return nil, fmt.Errorf("Expecting Pod but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) - ss := i.Status.ContainerStatuses - cr, _, rc := r.statuses(ss) +// return c, nil +// } - c, p := r.gatherPodMX(i) +// // SetPodMetrics set the current k8s resource metrics on a given pod. +// func (r *Pod) SetPodMetrics(m *mv1beta1.PodMetrics) { +// r.metrics = m +// } - return append(ff, - i.ObjectMeta.Name, - strconv.Itoa(cr)+"/"+strconv.Itoa(len(ss)), - r.phase(i), - strconv.Itoa(rc), - c.cpu, - c.mem, - p.cpu, - p.mem, - na(i.Status.PodIP), - na(i.Spec.NodeName), - r.mapQOS(i.Status.QOSClass), - toAge(i.ObjectMeta.CreationTimestamp), - ) -} +// // Marshal resource to yaml. +// func (r *Pod) Marshal(path string) (string, error) { +// panic("Should not be called") +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// return "", err +// } +// po, ok := i.(*v1.Pod) +// if !ok { +// return "", errors.New("Expecting a pod resource") +// } +// po.TypeMeta.APIVersion = "v1" +// po.TypeMeta.Kind = "Pod" -// ---------------------------------------------------------------------------- -// Helpers... +// return r.marshalObject(po) +// } -func (r *Pod) gatherPodMX(po *v1.Pod) (c, p metric) { - c, p = noMetric(), noMetric() - if r.metrics == nil { - return - } +// // Containers lists out all the docker containers name contained in a pod. +// func (r *Pod) Containers(path string, includeInit bool) ([]string, error) { +// ns, po := Namespaced(path) - cpu, mem := r.currentRes(r.metrics) - c = metric{ - cpu: ToMillicore(cpu.MilliValue()), - mem: ToMi(k8s.ToMB(mem.Value())), - } +// return r.Resource.(k8s.Loggable).Containers(ns, po, includeInit) +// } - rc, rm := r.requestedRes(po) - p = metric{ - cpu: AsPerc(toPerc(float64(cpu.MilliValue()), float64(rc.MilliValue()))), - mem: AsPerc(toPerc(k8s.ToMB(mem.Value()), k8s.ToMB(rm.Value()))), - } +// // PodLogs tail logs for all containers in a running Pod. +// func (r *Pod) PodLogs(ctx context.Context, c chan<- string, opts LogOptions) error { +// fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) +// if !ok { +// return errors.New("Expecting an informer") +// } +// o, err := fac.Get("v1/pods", opts.FQN(), labels.Everything()) +// if err != nil { +// return err +// } - return -} +// var po v1.Pod +// if runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil { +// return err +// } +// opts.Color = asColor(po.Name) +// if len(po.Spec.InitContainers)+len(po.Spec.Containers) == 1 { +// opts.SingleContainer = true +// } -func containerResources(co v1.Container) (cpu, mem *resource.Quantity) { - req, limit := co.Resources.Requests, co.Resources.Limits - switch { - case len(req) != 0: - cpu, mem = req.Cpu(), req.Memory() - case len(limit) != 0: - cpu, mem = limit.Cpu(), limit.Memory() - } - return -} +// for _, co := range po.Spec.InitContainers { +// opts.Container = co.Name +// if err := r.Logs(ctx, c, opts); err != nil { +// return err +// } +// } +// rcos := r.loggableContainers(po.Status) +// for _, co := range po.Spec.Containers { +// if in(rcos, co.Name) { +// opts.Container = co.Name +// if err := r.Logs(ctx, c, opts); err != nil { +// log.Error().Err(err).Msgf("Getting logs for %s failed", co.Name) +// return err +// } +// } +// } -func (r *Pod) requestedRes(po *v1.Pod) (cpu, mem resource.Quantity) { - for _, co := range po.Spec.Containers { - c, m := containerResources(co) - if c != nil { - cpu.Add(*c) - } - if m != nil { - mem.Add(*m) - } - } - return -} +// return nil +// } -func (*Pod) currentRes(mx *mv1beta1.PodMetrics) (cpu, mem resource.Quantity) { - for _, co := range mx.Containers { - c, m := co.Usage.Cpu(), co.Usage.Memory() - cpu.Add(*c) - mem.Add(*m) - } - return -} +// // Logs tails a given container logs +// func (r *Pod) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { +// if !opts.HasContainer() { +// return r.PodLogs(ctx, c, opts) +// } +// res, ok := r.Resource.(k8s.Loggable) +// if !ok { +// return fmt.Errorf("Resource %T is not Loggable", r.Resource) +// } -func (*Pod) mapQOS(class v1.PodQOSClass) string { - switch class { - case v1.PodQOSGuaranteed: - return "GA" - case v1.PodQOSBurstable: - return "BU" - default: - return "BE" - } -} +// return tailLogs(ctx, res, c, opts) +// } -func (r *Pod) statuses(ss []v1.ContainerStatus) (cr, ct, rc int) { - for _, c := range ss { - if c.State.Terminated != nil { - ct++ - } - if c.Ready { - cr = cr + 1 - } - rc += int(c.RestartCount) - } +// func tailLogs(ctx context.Context, res k8s.Loggable, c chan<- string, opts LogOptions) error { +// log.Debug().Msgf("Tailing logs for %q/%q:%q", opts.Namespace, opts.Name, opts.Container) +// o := v1.PodLogOptions{ +// Container: opts.Container, +// Follow: true, +// TailLines: &opts.Lines, +// Previous: opts.Previous, +// } +// req := res.Logs(opts.Namespace, opts.Name, &o) +// ctxt, cancelFunc := context.WithCancel(ctx) +// req.Context(ctxt) - return -} +// var blocked int32 = 1 +// go logsTimeout(cancelFunc, &blocked) -func (r *Pod) phase(po *v1.Pod) string { - status := string(po.Status.Phase) - if po.Status.Reason != "" { - if po.DeletionTimestamp != nil && po.Status.Reason == "NodeLost" { - return "Unknown" - } - status = po.Status.Reason - } +// // This call will block if nothing is in the stream!! +// stream, err := req.Stream() +// atomic.StoreInt32(&blocked, 0) +// if err != nil { +// log.Error().Err(err).Msgf("Log stream failed for `%s", opts.Path()) +// return fmt.Errorf("Unable to obtain log stream for %s", opts.Path()) +// } +// go readLogs(ctx, stream, c, opts) - init, status := r.initContainerPhase(po.Status, len(po.Spec.InitContainers), status) - if init { - return status - } +// return nil +// } - running, status := r.containerPhase(po.Status, status) - if running && status == "Completed" { - status = "Running" - } - if po.DeletionTimestamp == nil { - return status - } +// func logsTimeout(cancel context.CancelFunc, blocked *int32) { +// <-time.After(defaultTimeout) +// if atomic.LoadInt32(blocked) == 1 { +// log.Debug().Msg("Timed out reading the log stream") +// cancel() +// } +// } - return Terminating -} +// func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) { +// defer func() { +// log.Debug().Msgf(">>> Closing stream `%s", opts.Path()) +// if err := stream.Close(); err != nil { +// log.Error().Err(err).Msg("Cloing stream") +// } +// }() -func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) { - var running bool - for i := len(st.ContainerStatuses) - 1; i >= 0; i-- { - cs := st.ContainerStatuses[i] - switch { - case cs.State.Waiting != nil && cs.State.Waiting.Reason != "": - status = cs.State.Waiting.Reason - case cs.State.Terminated != nil && cs.State.Terminated.Reason != "": - status = cs.State.Terminated.Reason - case cs.State.Terminated != nil: - if cs.State.Terminated.Signal != 0 { - status = "Signal:" + strconv.Itoa(int(cs.State.Terminated.Signal)) - } else { - status = "ExitCode:" + strconv.Itoa(int(cs.State.Terminated.ExitCode)) - } - case cs.Ready && cs.State.Running != nil: - running = true - } - } +// scanner := bufio.NewScanner(stream) +// for scanner.Scan() { +// select { +// case <-ctx.Done(): +// return +// default: +// c <- opts.DecorateLog(scanner.Text()) +// } +// } +// } - return running, status -} +// // BOZO!! +// // // List resources for a given namespace. +// // func (r *Pod) List(ns string, opts metav1.ListOptions) (Columnars, error) { +// // pods, err := r.Resource.List(ns, opts) +// // if err != nil { +// // return nil, err +// // } -func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (bool, string) { - for i, cs := range st.InitContainerStatuses { - if state := checkContainerStatus(cs, i, initCount); state == "" { - continue - } else { - return true, state - } - } +// // cc := make(Columnars, 0, len(pods)) +// // for i := range pods { +// // po, err := r.New(&pods[i]) +// // if err != nil { +// // return nil, errors.New("Expecting a pod resource") +// // } +// // cc = append(cc, po) +// // } - return false, status -} +// // return cc, nil +// // } -func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string { - switch { - case cs.State.Terminated != nil: - if cs.State.Terminated.ExitCode == 0 { - return "" - } - if cs.State.Terminated.Reason != "" { - return "Init:" + cs.State.Terminated.Reason - } - if cs.State.Terminated.Signal != 0 { - return "Init:Signal:" + strconv.Itoa(int(cs.State.Terminated.Signal)) - } - return "Init:ExitCode:" + strconv.Itoa(int(cs.State.Terminated.ExitCode)) - case cs.State.Waiting != nil && cs.State.Waiting.Reason != "" && cs.State.Waiting.Reason != "PodInitializing": - return "Init:" + cs.State.Waiting.Reason - default: - return "Init:" + strconv.Itoa(i) + "/" + strconv.Itoa(initCount) - } -} +// // Header return resource header. +// func (*Pod) Header(ns string) Row { +// hh := Row{} +// if ns == AllNamespaces { +// hh = append(hh, "NAMESPACE") +// } +// return append(hh, +// "NAME", +// "READY", +// "STATUS", +// "RS", +// "CPU", +// "MEM", +// "%CPU", +// "%MEM", +// "IP", +// "NODE", +// "QOS", +// "AGE", +// ) +// } -func (r *Pod) loggableContainers(s v1.PodStatus) []string { - var rcos []string - for _, c := range s.ContainerStatuses { - rcos = append(rcos, c.Name) - } - return rcos -} +// // NumCols designates if column is numerical. +// func (*Pod) NumCols(n string) map[string]bool { +// return map[string]bool{ +// "CPU": true, +// "MEM": true, +// "%CPU": true, +// "%MEM": true, +// "RS": true, +// } +// } -// Helpers.. +// // Fields retrieves displayable fields. +// func (r *Pod) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) +// i := r.instance -func asColor(n string) color.Paint { - var sum int - for _, r := range n { - sum += int(r) - } - return color.Paint(30 + 2 + sum%6) -} +// if ns == AllNamespaces { +// ff = append(ff, i.Namespace) +// } + +// ss := i.Status.ContainerStatuses +// cr, _, rc := r.statuses(ss) + +// c, p := r.gatherPodMX(i) + +// return append(ff, +// i.ObjectMeta.Name, +// strconv.Itoa(cr)+"/"+strconv.Itoa(len(ss)), +// r.phase(i), +// strconv.Itoa(rc), +// c.cpu, +// c.mem, +// p.cpu, +// p.mem, +// na(i.Status.PodIP), +// na(i.Spec.NodeName), +// r.mapQOS(i.Status.QOSClass), +// toAge(i.ObjectMeta.CreationTimestamp), +// ) +// } + +// // ---------------------------------------------------------------------------- +// // Helpers... + +// func (r *Pod) gatherPodMX(po *v1.Pod) (c, p metric) { +// c, p = noMetric(), noMetric() +// if r.metrics == nil { +// return +// } + +// cpu, mem := r.currentRes(r.metrics) +// c = metric{ +// cpu: ToMillicore(cpu.MilliValue()), +// mem: ToMi(k8s.ToMB(mem.Value())), +// } + +// rc, rm := r.requestedRes(po) +// p = metric{ +// cpu: AsPerc(toPerc(float64(cpu.MilliValue()), float64(rc.MilliValue()))), +// mem: AsPerc(toPerc(k8s.ToMB(mem.Value()), k8s.ToMB(rm.Value()))), +// } + +// return +// } + +// func containerResources(co v1.Container) (cpu, mem *resource.Quantity) { +// req, limit := co.Resources.Requests, co.Resources.Limits +// switch { +// case len(req) != 0: +// cpu, mem = req.Cpu(), req.Memory() +// case len(limit) != 0: +// cpu, mem = limit.Cpu(), limit.Memory() +// } +// return +// } + +// func (r *Pod) requestedRes(po *v1.Pod) (cpu, mem resource.Quantity) { +// for _, co := range po.Spec.Containers { +// c, m := containerResources(co) +// if c != nil { +// cpu.Add(*c) +// } +// if m != nil { +// mem.Add(*m) +// } +// } +// return +// } + +// func (*Pod) currentRes(mx *mv1beta1.PodMetrics) (cpu, mem resource.Quantity) { +// for _, co := range mx.Containers { +// c, m := co.Usage.Cpu(), co.Usage.Memory() +// cpu.Add(*c) +// mem.Add(*m) +// } +// return +// } + +// func (*Pod) mapQOS(class v1.PodQOSClass) string { +// switch class { +// case v1.PodQOSGuaranteed: +// return "GA" +// case v1.PodQOSBurstable: +// return "BU" +// default: +// return "BE" +// } +// } + +// func (r *Pod) statuses(ss []v1.ContainerStatus) (cr, ct, rc int) { +// for _, c := range ss { +// if c.State.Terminated != nil { +// ct++ +// } +// if c.Ready { +// cr = cr + 1 +// } +// rc += int(c.RestartCount) +// } + +// return +// } + +// func (r *Pod) phase(po *v1.Pod) string { +// status := string(po.Status.Phase) +// if po.Status.Reason != "" { +// if po.DeletionTimestamp != nil && po.Status.Reason == "NodeLost" { +// return "Unknown" +// } +// status = po.Status.Reason +// } + +// init, status := r.initContainerPhase(po.Status, len(po.Spec.InitContainers), status) +// if init { +// return status +// } + +// running, status := r.containerPhase(po.Status, status) +// if running && status == "Completed" { +// status = "Running" +// } +// if po.DeletionTimestamp == nil { +// return status +// } + +// return Terminating +// } + +// func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) { +// var running bool +// for i := len(st.ContainerStatuses) - 1; i >= 0; i-- { +// cs := st.ContainerStatuses[i] +// switch { +// case cs.State.Waiting != nil && cs.State.Waiting.Reason != "": +// status = cs.State.Waiting.Reason +// case cs.State.Terminated != nil && cs.State.Terminated.Reason != "": +// status = cs.State.Terminated.Reason +// case cs.State.Terminated != nil: +// if cs.State.Terminated.Signal != 0 { +// status = "Signal:" + strconv.Itoa(int(cs.State.Terminated.Signal)) +// } else { +// status = "ExitCode:" + strconv.Itoa(int(cs.State.Terminated.ExitCode)) +// } +// case cs.Ready && cs.State.Running != nil: +// running = true +// } +// } + +// return running, status +// } + +// func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (bool, string) { +// for i, cs := range st.InitContainerStatuses { +// if state := checkContainerStatus(cs, i, initCount); state == "" { +// continue +// } else { +// return true, state +// } +// } + +// return false, status +// } + +// func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string { +// switch { +// case cs.State.Terminated != nil: +// if cs.State.Terminated.ExitCode == 0 { +// return "" +// } +// if cs.State.Terminated.Reason != "" { +// return "Init:" + cs.State.Terminated.Reason +// } +// if cs.State.Terminated.Signal != 0 { +// return "Init:Signal:" + strconv.Itoa(int(cs.State.Terminated.Signal)) +// } +// return "Init:ExitCode:" + strconv.Itoa(int(cs.State.Terminated.ExitCode)) +// case cs.State.Waiting != nil && cs.State.Waiting.Reason != "" && cs.State.Waiting.Reason != "PodInitializing": +// return "Init:" + cs.State.Waiting.Reason +// default: +// return "Init:" + strconv.Itoa(i) + "/" + strconv.Itoa(initCount) +// } +// } + +// func (r *Pod) loggableContainers(s v1.PodStatus) []string { +// var rcos []string +// for _, c := range s.ContainerStatuses { +// rcos = append(rcos, c.Name) +// } +// return rcos +// } + +// // Helpers.. + +// func asColor(n string) color.Paint { +// var sum int +// for _, r := range n { +// sum += int(r) +// } +// return color.Paint(30 + 2 + sum%6) +// } diff --git a/internal/resource/pod_int_test.go b/internal/resource/pod_int_test.go index 40f8714d..da495b1d 100644 --- a/internal/resource/pod_int_test.go +++ b/internal/resource/pod_int_test.go @@ -1,182 +1,183 @@ package resource -import ( - "testing" - "time" +// BOZO!! +// import ( +// "testing" +// "time" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/core/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -func TestPodStatuses(t *testing.T) { - type counts struct { - ready, terminated, restarts int - } +// func TestPodStatuses(t *testing.T) { +// type counts struct { +// ready, terminated, restarts int +// } - uu := []struct { - s []v1.ContainerStatus - e counts - }{ - { - []v1.ContainerStatus{ - { - Name: "c1", - Ready: true, - State: v1.ContainerState{ - Running: &v1.ContainerStateRunning{}, - }, - }, - { - Name: "c2", - Ready: false, - RestartCount: 10, - State: v1.ContainerState{ - Terminated: &v1.ContainerStateTerminated{}, - }, - }, - }, - counts{1, 1, 10}, - }, - } +// uu := []struct { +// s []v1.ContainerStatus +// e counts +// }{ +// { +// []v1.ContainerStatus{ +// { +// Name: "c1", +// Ready: true, +// State: v1.ContainerState{ +// Running: &v1.ContainerStateRunning{}, +// }, +// }, +// { +// Name: "c2", +// Ready: false, +// RestartCount: 10, +// State: v1.ContainerState{ +// Terminated: &v1.ContainerStateTerminated{}, +// }, +// }, +// }, +// counts{1, 1, 10}, +// }, +// } - var p Pod - for _, u := range uu { - cr, ct, cs := p.statuses(u.s) - assert.Equal(t, u.e.ready, cr) - assert.Equal(t, u.e.terminated, ct) - assert.Equal(t, u.e.restarts, cs) - } -} +// var p Pod +// for _, u := range uu { +// cr, ct, cs := p.statuses(u.s) +// assert.Equal(t, u.e.ready, cr) +// assert.Equal(t, u.e.terminated, ct) +// assert.Equal(t, u.e.restarts, cs) +// } +// } -func TestPodPhase(t *testing.T) { - uu := []struct { - p *v1.Pod - e string - }{ - {makePodStatus("p1", v1.PodRunning, ""), "Running"}, - {makePodStatus("p2", v1.PodRunning, "Evicted"), "Evicted"}, - {makePodStatus("p1", v1.PodPending, ""), "Pending"}, - {makePodStatus("p1", v1.PodSucceeded, ""), "Succeeded"}, - {makePodStatus("p1", v1.PodFailed, ""), "Failed"}, - {makePodStatus("p1", v1.PodUnknown, ""), "Unknown"}, - {makePodCoInitTerminated("p1"), "Init:OOMKilled"}, - {makePodCoInitWaiting("p1", ""), "Init:0/1"}, - {makePodCoInitWaiting("p2", "Waiting"), "Init:Waiting"}, - {makePodCoInitWaiting("p1", "PodInitializing"), "Init:0/1"}, - {makePodCoWaiting("p1", "Waiting"), "Waiting"}, - {makePodCoWaiting("p1", ""), ""}, - {makePodCoTerminated("p1", "OOMKilled", 0, true), Terminating}, - {makePodCoTerminated("p2", "OOMKilled", 0, false), "OOMKilled"}, - {makePodCoTerminated("p1", "", 0, true), Terminating}, - {makePodCoTerminated("p1", "", 0, false), "ExitCode:1"}, - {makePodCoTerminated("p1", "", 1, true), Terminating}, - {makePodCoTerminated("p1", "", 1, false), "Signal:1"}, - } +// func TestPodPhase(t *testing.T) { +// uu := []struct { +// p *v1.Pod +// e string +// }{ +// {makePodStatus("p1", v1.PodRunning, ""), "Running"}, +// {makePodStatus("p2", v1.PodRunning, "Evicted"), "Evicted"}, +// {makePodStatus("p1", v1.PodPending, ""), "Pending"}, +// {makePodStatus("p1", v1.PodSucceeded, ""), "Succeeded"}, +// {makePodStatus("p1", v1.PodFailed, ""), "Failed"}, +// {makePodStatus("p1", v1.PodUnknown, ""), "Unknown"}, +// {makePodCoInitTerminated("p1"), "Init:OOMKilled"}, +// {makePodCoInitWaiting("p1", ""), "Init:0/1"}, +// {makePodCoInitWaiting("p2", "Waiting"), "Init:Waiting"}, +// {makePodCoInitWaiting("p1", "PodInitializing"), "Init:0/1"}, +// {makePodCoWaiting("p1", "Waiting"), "Waiting"}, +// {makePodCoWaiting("p1", ""), ""}, +// {makePodCoTerminated("p1", "OOMKilled", 0, true), Terminating}, +// {makePodCoTerminated("p2", "OOMKilled", 0, false), "OOMKilled"}, +// {makePodCoTerminated("p1", "", 0, true), Terminating}, +// {makePodCoTerminated("p1", "", 0, false), "ExitCode:1"}, +// {makePodCoTerminated("p1", "", 1, true), Terminating}, +// {makePodCoTerminated("p1", "", 1, false), "Signal:1"}, +// } - var p Pod - for _, u := range uu { - assert.Equal(t, u.e, p.phase(u.p)) - } -} +// var p Pod +// for _, u := range uu { +// assert.Equal(t, u.e, p.phase(u.p)) +// } +// } -func makePodStatus(n string, phase v1.PodPhase, reason string) *v1.Pod { - po := makePod(n) - po.Status = v1.PodStatus{ - Phase: phase, - Reason: reason, - } +// func makePodStatus(n string, phase v1.PodPhase, reason string) *v1.Pod { +// po := makePod(n) +// po.Status = v1.PodStatus{ +// Phase: phase, +// Reason: reason, +// } - return po -} +// return po +// } -func makePodCoInitTerminated(n string) *v1.Pod { - po := makePod(n) +// func makePodCoInitTerminated(n string) *v1.Pod { +// po := makePod(n) - po.Status.InitContainerStatuses = []v1.ContainerStatus{ - { - State: v1.ContainerState{ - Terminated: &v1.ContainerStateTerminated{ - Reason: "OOMKilled", - ExitCode: 1, - }, - }, - }, - } +// po.Status.InitContainerStatuses = []v1.ContainerStatus{ +// { +// State: v1.ContainerState{ +// Terminated: &v1.ContainerStateTerminated{ +// Reason: "OOMKilled", +// ExitCode: 1, +// }, +// }, +// }, +// } - return po -} +// return po +// } -func makePodCoInitWaiting(n, reason string) *v1.Pod { - po := makePod(n) +// func makePodCoInitWaiting(n, reason string) *v1.Pod { +// po := makePod(n) - po.Status.InitContainerStatuses = []v1.ContainerStatus{ - { - State: v1.ContainerState{ - Waiting: &v1.ContainerStateWaiting{ - Reason: reason, - }, - }, - }, - } +// po.Status.InitContainerStatuses = []v1.ContainerStatus{ +// { +// State: v1.ContainerState{ +// Waiting: &v1.ContainerStateWaiting{ +// Reason: reason, +// }, +// }, +// }, +// } - return po -} +// return po +// } -func makePodCoTerminated(n, reason string, signal int32, deleted bool) *v1.Pod { - po := makePod(n) +// func makePodCoTerminated(n, reason string, signal int32, deleted bool) *v1.Pod { +// po := makePod(n) - if deleted { - po.DeletionTimestamp = &metav1.Time{Time: time.Now()} - } - po.Status.ContainerStatuses = []v1.ContainerStatus{ - { - State: v1.ContainerState{ - Terminated: &v1.ContainerStateTerminated{ - Reason: reason, - Signal: signal, - ExitCode: 1, - }, - }, - }, - } +// if deleted { +// po.DeletionTimestamp = &metav1.Time{Time: time.Now()} +// } +// po.Status.ContainerStatuses = []v1.ContainerStatus{ +// { +// State: v1.ContainerState{ +// Terminated: &v1.ContainerStateTerminated{ +// Reason: reason, +// Signal: signal, +// ExitCode: 1, +// }, +// }, +// }, +// } - return po -} +// return po +// } -func makePodCoWaiting(n, reason string) *v1.Pod { - po := makePod(n) +// func makePodCoWaiting(n, reason string) *v1.Pod { +// po := makePod(n) - po.Status.ContainerStatuses = []v1.ContainerStatus{ - { - State: v1.ContainerState{ - Waiting: &v1.ContainerStateWaiting{ - Reason: reason, - }, - }, - }, - } +// po.Status.ContainerStatuses = []v1.ContainerStatus{ +// { +// State: v1.ContainerState{ +// Waiting: &v1.ContainerStateWaiting{ +// Reason: reason, +// }, +// }, +// }, +// } - return po -} +// return po +// } -func makePod(n string) *v1.Pod { - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: n, - Namespace: "default", - }, - Spec: v1.PodSpec{ - InitContainers: []v1.Container{ - { - Name: "ic1", - }, - }, - Containers: []v1.Container{ - { - Name: "c1", - }, - }, - }, - } -} +// func makePod(n string) *v1.Pod { +// return &v1.Pod{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: n, +// Namespace: "default", +// }, +// Spec: v1.PodSpec{ +// InitContainers: []v1.Container{ +// { +// Name: "ic1", +// }, +// }, +// Containers: []v1.Container{ +// { +// Name: "c1", +// }, +// }, +// }, +// } +// } diff --git a/internal/resource/pod_test.go b/internal/resource/pod_test.go index 5f91c7bc..ea1aa221 100644 --- a/internal/resource/pod_test.go +++ b/internal/resource/pod_test.go @@ -1,276 +1,277 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" -) - -func NewPodListWithArgs(ns string, r *resource.Pod) resource.List { - return resource.NewList(ns, "po", r, resource.AllVerbsAccess|resource.DescribeAccess) -} - -func NewPodWithArgs(conn k8s.Connection, res resource.Cruder, mx resource.MetricsServer) *resource.Pod { - r := &resource.Pod{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestPodListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - mx := NewMockMetricsServer() - - ns := "blee" - l := NewPodListWithArgs(resource.AllNamespaces, NewPodWithArgs(mc, mr, mx)) - l.SetNamespace(ns) - - assert.Equal(t, "blee", l.GetNamespace()) - assert.Equal(t, "po", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestPodFields(t *testing.T) { - r := newPod().Fields("blee") - assert.Equal(t, "fred", r[0]) -} - -func TestPodGatherMX(t *testing.T) { - uu := map[string]struct { - resources v1.ResourceRequirements - metrics mv1beta1.PodMetrics - expectedCpuPercentage string - expectedMemPercentage string - }{ - "request": { - v1.ResourceRequirements{ - Requests: makeRes("500m", "512Mi"), - }, - makeMxPod("p1", "250m", "256Mi"), - "150", - "150", - }, - "limit": { - v1.ResourceRequirements{ - Limits: makeRes("1000m", "1024Mi"), - }, - makeMxPod("p2", "250m", "256Mi"), - "75", - "75", - }, - "both": { - v1.ResourceRequirements{ - Requests: makeRes("500m", "512Mi"), - Limits: makeRes("1000m", "1024Mi"), - }, - makeMxPod("p3", "250m", "256Mi"), - "150", - "150", - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - r := NewPodWithMetrics(u.metrics, u.resources).Fields("blee") - - assert.Equal(t, u.expectedCpuPercentage, r[6]) - assert.Equal(t, u.expectedMemPercentage, r[7]) - }) - } -} - // BOZO!! -// func TestPodMarshal(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.Get("blee", "fred")).ThenReturn(makePod(), nil) -// mx := NewMockMetricsServer() +// import ( +// "testing" -// cm := NewPodWithArgs(mc, mr, mx) -// ma, err := cm.Marshal("blee/fred") +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/core/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" +// ) -// mr.VerifyWasCalledOnce().Get("blee", "fred") -// assert.Nil(t, err) -// assert.Equal(t, poYaml(), ma) +// func NewPodListWithArgs(ns string, r *resource.Pod) resource.List { +// return resource.NewList(ns, "po", r, resource.AllVerbsAccess|resource.DescribeAccess) // } -// BOZO!! -// func TestPodListData(t *testing.T) { +// func NewPodWithArgs(conn k8s.Connection, res resource.Cruder, mx resource.MetricsServer) *resource.Pod { +// r := &resource.Pod{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } + +// func TestPodListAccess(t *testing.T) { // mc := NewMockConnection() // mr := NewMockCruder() -// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*makePod()}, nil) // mx := NewMockMetricsServer() -// m.When(mx.HasMetrics()).ThenReturn(true) -// m.When(mx.FetchPodsMetrics("blee")). -// ThenReturn(&mv1beta1.PodMetricsList{Items: []mv1beta1.PodMetrics{makeMxPod("p1", "100m", "20Mi")}}, nil) -// l := NewPodListWithArgs("blee", NewPodWithArgs(mc, mr, mx)) -// // Make sure we mcn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } +// ns := "blee" +// l := NewPodListWithArgs(resource.AllNamespaces, NewPodWithArgs(mc, mr, mx)) +// l.SetNamespace(ns) -// mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) // assert.Equal(t, "blee", l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 12, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) +// assert.Equal(t, "po", l.GetName()) +// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { +// assert.True(t, l.Access(a)) // } -// assert.Equal(t, "fred", strings.TrimSpace(row.Fields[:1][0])) // } -func BenchmarkPodFields(b *testing.B) { - p := resource.NewPod(nil) - po := makePod() +// func TestPodFields(t *testing.T) { +// r := newPod().Fields("blee") +// assert.Equal(t, "fred", r[0]) +// } - b.ResetTimer() - b.ReportAllocs() +// func TestPodGatherMX(t *testing.T) { +// uu := map[string]struct { +// resources v1.ResourceRequirements +// metrics mv1beta1.PodMetrics +// expectedCpuPercentage string +// expectedMemPercentage string +// }{ +// "request": { +// v1.ResourceRequirements{ +// Requests: makeRes("500m", "512Mi"), +// }, +// makeMxPod("p1", "250m", "256Mi"), +// "150", +// "150", +// }, +// "limit": { +// v1.ResourceRequirements{ +// Limits: makeRes("1000m", "1024Mi"), +// }, +// makeMxPod("p2", "250m", "256Mi"), +// "75", +// "75", +// }, +// "both": { +// v1.ResourceRequirements{ +// Requests: makeRes("500m", "512Mi"), +// Limits: makeRes("1000m", "1024Mi"), +// }, +// makeMxPod("p3", "250m", "256Mi"), +// "150", +// "150", +// }, +// } - for n := 0; n < b.N; n++ { - pod, _ := p.New(po) - pod.Fields("") - } -} +// for k := range uu { +// u := uu[k] +// t.Run(k, func(t *testing.T) { +// r := NewPodWithMetrics(u.metrics, u.resources).Fields("blee") -// ---------------------------------------------------------------------------- -// Helpers... -func makePodWithContainerSpec(resources v1.ResourceRequirements) *v1.Pod { - pod := makePod() - pod.Spec.Containers[0].Resources = resources - return pod -} +// assert.Equal(t, u.expectedCpuPercentage, r[6]) +// assert.Equal(t, u.expectedMemPercentage, r[7]) +// }) +// } +// } -func makePod() *v1.Pod { - var i int32 = 1 - var t = v1.HostPathDirectory - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "blee", - Name: "fred", - Labels: map[string]string{"blee": "duh"}, - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: v1.PodSpec{ - Priority: &i, - PriorityClassName: "bozo", - Containers: []v1.Container{ - { - Name: "fred", - Image: "blee", - Env: []v1.EnvVar{ - { - Name: "fred", - Value: "1", - ValueFrom: &v1.EnvVarSource{ - ConfigMapKeyRef: &v1.ConfigMapKeySelector{Key: "blee"}, - }, - }, - }, - }, - }, - Volumes: []v1.Volume{ - { - Name: "fred", - VolumeSource: v1.VolumeSource{ - HostPath: &v1.HostPathVolumeSource{ - Path: "/blee", - Type: &t, - }, - }, - }, - }, - }, - Status: v1.PodStatus{ - Phase: "Running", - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "fred", - State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, - RestartCount: 0, - }, - }, - }, - } -} +// // BOZO!! +// // func TestPodMarshal(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.Get("blee", "fred")).ThenReturn(makePod(), nil) +// // mx := NewMockMetricsServer() -func newPod() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewPod(mc).New(makePod()) - return c -} +// // cm := NewPodWithArgs(mc, mr, mx) +// // ma, err := cm.Marshal("blee/fred") -func NewPodWithMetrics(metrics mv1beta1.PodMetrics, resources v1.ResourceRequirements) resource.Columnar { - mc := NewMockConnection() - p := resource.NewPod(mc) - r, _ := p.New(makePodWithContainerSpec(resources)) - r.SetPodMetrics(&metrics) - return r -} +// // mr.VerifyWasCalledOnce().Get("blee", "fred") +// // assert.Nil(t, err) +// // assert.Equal(t, poYaml(), ma) +// // } -func poYaml() string { - return `apiVersion: v1 -kind: Pod -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - labels: - blee: duh - name: fred - namespace: blee -spec: - containers: - - env: - - name: fred - value: "1" - valueFrom: - configMapKeyRef: - key: blee - image: blee - name: fred - resources: {} - priority: 1 - priorityClassName: bozo - volumes: - - hostPath: - path: /blee - type: Directory - name: fred -status: - containerStatuses: - - image: "" - imageID: "" - lastState: {} - name: fred - ready: false - restartCount: 0 - state: - running: - startedAt: null - phase: Running -` -} +// // BOZO!! +// // func TestPodListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*makePod()}, nil) +// // mx := NewMockMetricsServer() +// // m.When(mx.HasMetrics()).ThenReturn(true) +// // m.When(mx.FetchPodsMetrics("blee")). +// // ThenReturn(&mv1beta1.PodMetricsList{Items: []mv1beta1.PodMetrics{makeMxPod("p1", "100m", "20Mi")}}, nil) -func makeMxPod(name, cpu, mem string) mv1beta1.PodMetrics { - return mv1beta1.PodMetrics{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "default", - }, - Containers: []mv1beta1.ContainerMetrics{ - {Usage: makeRes(cpu, mem)}, - {Usage: makeRes(cpu, mem)}, - {Usage: makeRes(cpu, mem)}, - }, - } -} +// // l := NewPodListWithArgs("blee", NewPodWithArgs(mc, mr, mx)) +// // // Make sure we mcn get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } + +// // mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, "blee", l.GetNamespace()) +// // row := td.Rows["blee/fred"] +// // assert.Equal(t, 12, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, "fred", strings.TrimSpace(row.Fields[:1][0])) +// // } + +// func BenchmarkPodFields(b *testing.B) { +// p := resource.NewPod(nil) +// po := makePod() + +// b.ResetTimer() +// b.ReportAllocs() + +// for n := 0; n < b.N; n++ { +// pod, _ := p.New(po) +// pod.Fields("") +// } +// } + +// // ---------------------------------------------------------------------------- +// // Helpers... +// func makePodWithContainerSpec(resources v1.ResourceRequirements) *v1.Pod { +// pod := makePod() +// pod.Spec.Containers[0].Resources = resources +// return pod +// } + +// func makePod() *v1.Pod { +// var i int32 = 1 +// var t = v1.HostPathDirectory +// return &v1.Pod{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "blee", +// Name: "fred", +// Labels: map[string]string{"blee": "duh"}, +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: v1.PodSpec{ +// Priority: &i, +// PriorityClassName: "bozo", +// Containers: []v1.Container{ +// { +// Name: "fred", +// Image: "blee", +// Env: []v1.EnvVar{ +// { +// Name: "fred", +// Value: "1", +// ValueFrom: &v1.EnvVarSource{ +// ConfigMapKeyRef: &v1.ConfigMapKeySelector{Key: "blee"}, +// }, +// }, +// }, +// }, +// }, +// Volumes: []v1.Volume{ +// { +// Name: "fred", +// VolumeSource: v1.VolumeSource{ +// HostPath: &v1.HostPathVolumeSource{ +// Path: "/blee", +// Type: &t, +// }, +// }, +// }, +// }, +// }, +// Status: v1.PodStatus{ +// Phase: "Running", +// ContainerStatuses: []v1.ContainerStatus{ +// { +// Name: "fred", +// State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, +// RestartCount: 0, +// }, +// }, +// }, +// } +// } + +// func newPod() resource.Columnar { +// mc := NewMockConnection() +// c, _ := resource.NewPod(mc).New(makePod()) +// return c +// } + +// func NewPodWithMetrics(metrics mv1beta1.PodMetrics, resources v1.ResourceRequirements) resource.Columnar { +// mc := NewMockConnection() +// p := resource.NewPod(mc) +// r, _ := p.New(makePodWithContainerSpec(resources)) +// r.SetPodMetrics(&metrics) +// return r +// } + +// func poYaml() string { +// return `apiVersion: v1 +// kind: Pod +// metadata: +// creationTimestamp: "2018-12-14T17:36:43Z" +// labels: +// blee: duh +// name: fred +// namespace: blee +// spec: +// containers: +// - env: +// - name: fred +// value: "1" +// valueFrom: +// configMapKeyRef: +// key: blee +// image: blee +// name: fred +// resources: {} +// priority: 1 +// priorityClassName: bozo +// volumes: +// - hostPath: +// path: /blee +// type: Directory +// name: fred +// status: +// containerStatuses: +// - image: "" +// imageID: "" +// lastState: {} +// name: fred +// ready: false +// restartCount: 0 +// state: +// running: +// startedAt: null +// phase: Running +// ` +// } + +// func makeMxPod(name, cpu, mem string) mv1beta1.PodMetrics { +// return mv1beta1.PodMetrics{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: name, +// Namespace: "default", +// }, +// Containers: []mv1beta1.ContainerMetrics{ +// {Usage: makeRes(cpu, mem)}, +// {Usage: makeRes(cpu, mem)}, +// {Usage: makeRes(cpu, mem)}, +// }, +// } +// } diff --git a/internal/resource/pv.go b/internal/resource/pv.go index bee1c9a2..d12e9afc 100644 --- a/internal/resource/pv.go +++ b/internal/resource/pv.go @@ -1,158 +1,161 @@ package resource -import ( - "errors" - "fmt" - "path" - "strings" +// BOZO!! +// import ( +// "errors" +// "fmt" +// "path" +// "strings" - "github.com/derailed/k9s/internal/k8s" - v1 "k8s.io/api/core/v1" -) +// "github.com/derailed/k9s/internal/k8s" +// v1 "k8s.io/api/core/v1" +// ) -// PersistentVolume tracks a kubernetes resource. -type PersistentVolume struct { - *Base - instance *v1.PersistentVolume -} +// const Terminating = "Terminating" -// NewPersistentVolumeList returns a new resource list. -func NewPersistentVolumeList(c Connection, ns string) List { - return NewList( - NotNamespaced, - "pv", - NewPersistentVolume(c), - CRUDAccess|DescribeAccess, - ) -} +// // PersistentVolume tracks a kubernetes resource. +// type PersistentVolume struct { +// *Base +// instance *v1.PersistentVolume +// } -// NewPersistentVolume instantiates a new PersistentVolume. -func NewPersistentVolume(c Connection) *PersistentVolume { - p := &PersistentVolume{&Base{Connection: c, Resource: k8s.NewPersistentVolume(c)}, nil} - p.Factory = p +// // NewPersistentVolumeList returns a new resource list. +// func NewPersistentVolumeList(c Connection, ns string) List { +// return NewList( +// NotNamespaced, +// "pv", +// NewPersistentVolume(c), +// CRUDAccess|DescribeAccess, +// ) +// } - return p -} +// // NewPersistentVolume instantiates a new PersistentVolume. +// func NewPersistentVolume(c Connection) *PersistentVolume { +// p := &PersistentVolume{&Base{Connection: c, Resource: k8s.NewPersistentVolume(c)}, nil} +// p.Factory = p -// New builds a new PersistentVolume instance from a k8s resource. -func (r *PersistentVolume) New(i interface{}) (Columnar, error) { - c := NewPersistentVolume(r.Connection) - switch instance := i.(type) { - case *v1.PersistentVolume: - c.instance = instance - case v1.PersistentVolume: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting PV but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) +// return p +// } - return c, nil -} +// // New builds a new PersistentVolume instance from a k8s resource. +// func (r *PersistentVolume) New(i interface{}) (Columnar, error) { +// c := NewPersistentVolume(r.Connection) +// switch instance := i.(type) { +// case *v1.PersistentVolume: +// c.instance = instance +// case v1.PersistentVolume: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting PV but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) -// Marshal resource to yaml. -func (r *PersistentVolume) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } +// return c, nil +// } - pv, ok := i.(*v1.PersistentVolume) - if !ok { - return "", errors.New("Expecting a pv resource") - } - pv.TypeMeta.APIVersion = "v1" - pv.TypeMeta.Kind = "PersistentVolume" +// // Marshal resource to yaml. +// func (r *PersistentVolume) Marshal(path string) (string, error) { +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// return "", err +// } - return r.marshalObject(pv) -} +// pv, ok := i.(*v1.PersistentVolume) +// if !ok { +// return "", errors.New("Expecting a pv resource") +// } +// pv.TypeMeta.APIVersion = "v1" +// pv.TypeMeta.Kind = "PersistentVolume" -// Header return resource header. -func (*PersistentVolume) Header(ns string) Row { - hh := Row{} - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } +// return r.marshalObject(pv) +// } - return append(hh, "NAME", "CAPACITY", "ACCESS MODES", "RECLAIM POLICY", "STATUS", "CLAIM", "STORAGECLASS", "REASON", "AGE") -} +// // Header return resource header. +// func (*PersistentVolume) Header(ns string) Row { +// hh := Row{} +// if ns == AllNamespaces { +// hh = append(hh, "NAMESPACE") +// } -// Fields retrieves displayable fields. -func (r *PersistentVolume) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - i := r.instance - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } +// return append(hh, "NAME", "CAPACITY", "ACCESS MODES", "RECLAIM POLICY", "STATUS", "CLAIM", "STORAGECLASS", "REASON", "AGE") +// } - phase := i.Status.Phase - if i.ObjectMeta.DeletionTimestamp != nil { - phase = Terminating - } +// // Fields retrieves displayable fields. +// func (r *PersistentVolume) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) +// i := r.instance +// if ns == AllNamespaces { +// ff = append(ff, i.Namespace) +// } - var claim string - if i.Spec.ClaimRef != nil { - claim = path.Join(i.Spec.ClaimRef.Namespace, i.Spec.ClaimRef.Name) - } +// phase := i.Status.Phase +// if i.ObjectMeta.DeletionTimestamp != nil { +// phase = Terminating +// } - class, found := i.Annotations[v1.BetaStorageClassAnnotation] - if !found { - class = i.Spec.StorageClassName - } +// var claim string +// if i.Spec.ClaimRef != nil { +// claim = path.Join(i.Spec.ClaimRef.Namespace, i.Spec.ClaimRef.Name) +// } - size := i.Spec.Capacity[v1.ResourceStorage] +// class, found := i.Annotations[v1.BetaStorageClassAnnotation] +// if !found { +// class = i.Spec.StorageClassName +// } - return append(ff, - i.Name, - size.String(), - r.accessMode(i.Spec.AccessModes), - string(i.Spec.PersistentVolumeReclaimPolicy), - string(phase), - claim, - class, - i.Status.Reason, - toAge(i.ObjectMeta.CreationTimestamp), - ) -} +// size := i.Spec.Capacity[v1.ResourceStorage] -// ---------------------------------------------------------------------------- -// Helpers... +// return append(ff, +// i.Name, +// size.String(), +// r.accessMode(i.Spec.AccessModes), +// string(i.Spec.PersistentVolumeReclaimPolicy), +// string(phase), +// claim, +// class, +// i.Status.Reason, +// toAge(i.ObjectMeta.CreationTimestamp), +// ) +// } -func (r *PersistentVolume) accessMode(aa []v1.PersistentVolumeAccessMode) string { - dd := r.accessDedup(aa) - s := make([]string, 0, len(dd)) - for i := 0; i < len(aa); i++ { - switch { - case r.accessContains(dd, v1.ReadWriteOnce): - s = append(s, "RWO") - case r.accessContains(dd, v1.ReadOnlyMany): - s = append(s, "ROX") - case r.accessContains(dd, v1.ReadWriteMany): - s = append(s, "RWX") - } - } +// // ---------------------------------------------------------------------------- +// // Helpers... - return strings.Join(s, ",") -} +// func (r *PersistentVolume) accessMode(aa []v1.PersistentVolumeAccessMode) string { +// dd := r.accessDedup(aa) +// s := make([]string, 0, len(dd)) +// for i := 0; i < len(aa); i++ { +// switch { +// case r.accessContains(dd, v1.ReadWriteOnce): +// s = append(s, "RWO") +// case r.accessContains(dd, v1.ReadOnlyMany): +// s = append(s, "ROX") +// case r.accessContains(dd, v1.ReadWriteMany): +// s = append(s, "RWX") +// } +// } -func (r *PersistentVolume) accessContains(cc []v1.PersistentVolumeAccessMode, a v1.PersistentVolumeAccessMode) bool { - for _, c := range cc { - if c == a { - return true - } - } +// return strings.Join(s, ",") +// } - return false -} +// func (r *PersistentVolume) accessContains(cc []v1.PersistentVolumeAccessMode, a v1.PersistentVolumeAccessMode) bool { +// for _, c := range cc { +// if c == a { +// return true +// } +// } -func (r *PersistentVolume) accessDedup(cc []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode { - set := []v1.PersistentVolumeAccessMode{} - for _, c := range cc { - if !r.accessContains(set, c) { - set = append(set, c) - } - } +// return false +// } - return set -} +// func (r *PersistentVolume) accessDedup(cc []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode { +// set := []v1.PersistentVolumeAccessMode{} +// for _, c := range cc { +// if !r.accessContains(set, c) { +// set = append(set, c) +// } +// } + +// return set +// } diff --git a/internal/resource/pv_test.go b/internal/resource/pv_test.go index 604e5f37..f81a7f94 100644 --- a/internal/resource/pv_test.go +++ b/internal/resource/pv_test.go @@ -1,110 +1,111 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewPVListWithArgs(ns string, r *resource.PersistentVolume) resource.List { - return resource.NewList(resource.NotNamespaced, "pv", r, resource.CRUDAccess|resource.DescribeAccess) -} - -func NewPVWithArgs(conn k8s.Connection, res resource.Cruder) *resource.PersistentVolume { - r := &resource.PersistentVolume{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestPVListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - - ns := "blee" - l := NewPVListWithArgs(resource.AllNamespaces, NewPVWithArgs(mc, mr)) - l.SetNamespace(ns) - - assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.Equal(t, "pv", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestPVFields(t *testing.T) { - r := newPV().Fields("blee") - assert.Equal(t, "fred", r[0]) -} - -func TestPVMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sPV(), nil) - - cm := NewPVWithArgs(mc, mr) - ma, err := cm.Marshal("blee/fred") - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, pvYaml(), ma) -} - // BOZO!! -// func TestPVListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sPV()}, nil) +// import ( +// "testing" -// l := NewPVListWithArgs("-", NewPVWithArgs(mc, mr)) -// // Make sure we mrn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/core/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -// mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 9, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// func NewPVListWithArgs(ns string, r *resource.PersistentVolume) resource.List { +// return resource.NewList(resource.NotNamespaced, "pv", r, resource.CRUDAccess|resource.DescribeAccess) // } -// Helpers... +// func NewPVWithArgs(conn k8s.Connection, res resource.Cruder) *resource.PersistentVolume { +// r := &resource.PersistentVolume{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } -func k8sPV() *v1.PersistentVolume { - return &v1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "blee", - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: v1.PersistentVolumeSpec{}, - } -} +// func TestPVListAccess(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() -func newPV() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewPersistentVolume(mc).New(k8sPV()) - return c -} +// ns := "blee" +// l := NewPVListWithArgs(resource.AllNamespaces, NewPVWithArgs(mc, mr)) +// l.SetNamespace(ns) -func pvYaml() string { - return `apiVersion: v1 -kind: PersistentVolume -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -spec: {} -status: {} -` -} +// assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) +// assert.Equal(t, "pv", l.GetName()) +// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { +// assert.True(t, l.Access(a)) +// } +// } + +// func TestPVFields(t *testing.T) { +// r := newPV().Fields("blee") +// assert.Equal(t, "fred", r[0]) +// } + +// func TestPVMarshal(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// m.When(mr.Get("blee", "fred")).ThenReturn(k8sPV(), nil) + +// cm := NewPVWithArgs(mc, mr) +// ma, err := cm.Marshal("blee/fred") +// mr.VerifyWasCalledOnce().Get("blee", "fred") +// assert.Nil(t, err) +// assert.Equal(t, pvYaml(), ma) +// } + +// // BOZO!! +// // func TestPVListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sPV()}, nil) + +// // l := NewPVListWithArgs("-", NewPVWithArgs(mc, mr)) +// // // Make sure we mrn get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } + +// // mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) +// // row := td.Rows["blee/fred"] +// // assert.Equal(t, 9, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// // } + +// // Helpers... + +// func k8sPV() *v1.PersistentVolume { +// return &v1.PersistentVolume{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "blee", +// Name: "fred", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: v1.PersistentVolumeSpec{}, +// } +// } + +// func newPV() resource.Columnar { +// mc := NewMockConnection() +// c, _ := resource.NewPersistentVolume(mc).New(k8sPV()) +// return c +// } + +// func pvYaml() string { +// return `apiVersion: v1 +// kind: PersistentVolume +// metadata: +// creationTimestamp: "2018-12-14T17:36:43Z" +// name: fred +// namespace: blee +// spec: {} +// status: {} +// ` +// } diff --git a/internal/resource/pvc.go b/internal/resource/pvc.go index aba85ed8..26933f0d 100644 --- a/internal/resource/pvc.go +++ b/internal/resource/pvc.go @@ -1,117 +1,118 @@ package resource -import ( - "errors" - "fmt" +// BOZO!! +// import ( +// "errors" +// "fmt" - "github.com/derailed/k9s/internal/k8s" - v1 "k8s.io/api/core/v1" -) +// "github.com/derailed/k9s/internal/k8s" +// v1 "k8s.io/api/core/v1" +// ) -// PersistentVolumeClaim tracks a kubernetes resource. -type PersistentVolumeClaim struct { - *Base - instance *v1.PersistentVolumeClaim -} +// // PersistentVolumeClaim tracks a kubernetes resource. +// type PersistentVolumeClaim struct { +// *Base +// instance *v1.PersistentVolumeClaim +// } -// NewPersistentVolumeClaimList returns a new resource list. -func NewPersistentVolumeClaimList(c Connection, ns string) List { - return NewList( - ns, - "pvc", - NewPersistentVolumeClaim(c), - AllVerbsAccess|DescribeAccess, - ) -} +// // NewPersistentVolumeClaimList returns a new resource list. +// func NewPersistentVolumeClaimList(c Connection, ns string) List { +// return NewList( +// ns, +// "pvc", +// NewPersistentVolumeClaim(c), +// AllVerbsAccess|DescribeAccess, +// ) +// } -// NewPersistentVolumeClaim instantiates a new PersistentVolumeClaim. -func NewPersistentVolumeClaim(c Connection) *PersistentVolumeClaim { - p := &PersistentVolumeClaim{&Base{Connection: c, Resource: k8s.NewPersistentVolumeClaim(c)}, nil} - p.Factory = p +// // NewPersistentVolumeClaim instantiates a new PersistentVolumeClaim. +// func NewPersistentVolumeClaim(c Connection) *PersistentVolumeClaim { +// p := &PersistentVolumeClaim{&Base{Connection: c, Resource: k8s.NewPersistentVolumeClaim(c)}, nil} +// p.Factory = p - return p -} +// return p +// } -// New builds a new PersistentVolumeClaim instance from a k8s resource. -func (r *PersistentVolumeClaim) New(i interface{}) (Columnar, error) { - c := NewPersistentVolumeClaim(r.Connection) - switch instance := i.(type) { - case *v1.PersistentVolumeClaim: - c.instance = instance - case v1.PersistentVolumeClaim: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting PVC but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) +// // New builds a new PersistentVolumeClaim instance from a k8s resource. +// func (r *PersistentVolumeClaim) New(i interface{}) (Columnar, error) { +// c := NewPersistentVolumeClaim(r.Connection) +// switch instance := i.(type) { +// case *v1.PersistentVolumeClaim: +// c.instance = instance +// case v1.PersistentVolumeClaim: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting PVC but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) - return c, nil -} +// return c, nil +// } -// Marshal resource to yaml. -func (r *PersistentVolumeClaim) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } +// // Marshal resource to yaml. +// func (r *PersistentVolumeClaim) Marshal(path string) (string, error) { +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// return "", err +// } - pvc, ok := i.(*v1.PersistentVolumeClaim) - if !ok { - return "", errors.New("Expecting a pvc resource") - } - pvc.TypeMeta.APIVersion = "v1" - pvc.TypeMeta.Kind = "PersistentVolumeClaim" +// pvc, ok := i.(*v1.PersistentVolumeClaim) +// if !ok { +// return "", errors.New("Expecting a pvc resource") +// } +// pvc.TypeMeta.APIVersion = "v1" +// pvc.TypeMeta.Kind = "PersistentVolumeClaim" - return r.marshalObject(pvc) -} +// return r.marshalObject(pvc) +// } -// Header return resource header. -func (*PersistentVolumeClaim) Header(ns string) Row { - hh := Row{} - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } +// // Header return resource header. +// func (*PersistentVolumeClaim) Header(ns string) Row { +// hh := Row{} +// if ns == AllNamespaces { +// hh = append(hh, "NAMESPACE") +// } - return append(hh, "NAME", "STATUS", "VOLUME", "CAPACITY", "ACCESS MODES", "STORAGECLASS", "AGE") -} +// return append(hh, "NAME", "STATUS", "VOLUME", "CAPACITY", "ACCESS MODES", "STORAGECLASS", "AGE") +// } -// Fields retrieves displayable fields. -func (r *PersistentVolumeClaim) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - i := r.instance - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } +// // Fields retrieves displayable fields. +// func (r *PersistentVolumeClaim) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) +// i := r.instance +// if ns == AllNamespaces { +// ff = append(ff, i.Namespace) +// } - phase := i.Status.Phase - if i.ObjectMeta.DeletionTimestamp != nil { - phase = Terminating - } +// phase := i.Status.Phase +// if i.ObjectMeta.DeletionTimestamp != nil { +// phase = Terminating +// } - var pv PersistentVolume - storage := i.Spec.Resources.Requests[v1.ResourceStorage] - var capacity, accessModes string - if i.Spec.VolumeName != "" { - accessModes = pv.accessMode(i.Status.AccessModes) - storage = i.Status.Capacity[v1.ResourceStorage] - capacity = storage.String() - } +// var pv PersistentVolume +// storage := i.Spec.Resources.Requests[v1.ResourceStorage] +// var capacity, accessModes string +// if i.Spec.VolumeName != "" { +// accessModes = pv.accessMode(i.Status.AccessModes) +// storage = i.Status.Capacity[v1.ResourceStorage] +// capacity = storage.String() +// } - class, found := i.Annotations[v1.BetaStorageClassAnnotation] - if !found { - if i.Spec.StorageClassName != nil { - class = *i.Spec.StorageClassName - } - } +// class, found := i.Annotations[v1.BetaStorageClassAnnotation] +// if !found { +// if i.Spec.StorageClassName != nil { +// class = *i.Spec.StorageClassName +// } +// } - return append(ff, - i.Name, - string(phase), - i.Spec.VolumeName, - capacity, - accessModes, - class, - toAge(i.ObjectMeta.CreationTimestamp), - ) -} +// return append(ff, +// i.Name, +// string(phase), +// i.Spec.VolumeName, +// capacity, +// accessModes, +// class, +// toAge(i.ObjectMeta.CreationTimestamp), +// ) +// } diff --git a/internal/resource/pvc_test.go b/internal/resource/pvc_test.go index 638b8ea1..1cc250f0 100644 --- a/internal/resource/pvc_test.go +++ b/internal/resource/pvc_test.go @@ -1,122 +1,123 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - resv1 "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewPVCListWithArgs(ns string, r *resource.PersistentVolumeClaim) resource.List { - return resource.NewList(ns, "pvc", r, resource.AllVerbsAccess|resource.DescribeAccess) -} - -func NewPVCWithArgs(conn k8s.Connection, res resource.Cruder) *resource.PersistentVolumeClaim { - r := &resource.PersistentVolumeClaim{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestPVCListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - - ns := "blee" - l := NewPVCListWithArgs(resource.AllNamespaces, NewPVCWithArgs(mc, mr)) - l.SetNamespace(ns) - - assert.Equal(t, "blee", l.GetNamespace()) - assert.Equal(t, "pvc", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestPVCFields(t *testing.T) { - r := newPVC().Fields("blee") - assert.Equal(t, "fred", r[0]) -} - -func TestPVCMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sPVC(), nil) - - cm := NewPVCWithArgs(mc, mr) - ma, err := cm.Marshal("blee/fred") - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, pvcYaml(), ma) -} - // BOZO!! -// func TestPVCListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sPVC()}, nil) +// import ( +// "testing" -// l := NewPVCListWithArgs("blee", NewPVCWithArgs(mc, mr)) -// // Make sure we mrn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/core/v1" +// resv1 "k8s.io/apimachinery/pkg/api/resource" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -// mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, "blee", l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 7, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// func NewPVCListWithArgs(ns string, r *resource.PersistentVolumeClaim) resource.List { +// return resource.NewList(ns, "pvc", r, resource.AllVerbsAccess|resource.DescribeAccess) // } -// Helpers... +// func NewPVCWithArgs(conn k8s.Connection, res resource.Cruder) *resource.PersistentVolumeClaim { +// r := &resource.PersistentVolumeClaim{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } -func k8sPVC() *v1.PersistentVolumeClaim { - return &v1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "blee", - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: v1.PersistentVolumeClaimSpec{ - VolumeName: "duh", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceStorage: resv1.Quantity{}, - }, - }, - }, - } -} +// func TestPVCListAccess(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() -func newPVC() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewPersistentVolumeClaim(mc).New(k8sPVC()) - return c -} +// ns := "blee" +// l := NewPVCListWithArgs(resource.AllNamespaces, NewPVCWithArgs(mc, mr)) +// l.SetNamespace(ns) -func pvcYaml() string { - return `apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -spec: - resources: - requests: - storage: "0" - volumeName: duh -status: {} -` -} +// assert.Equal(t, "blee", l.GetNamespace()) +// assert.Equal(t, "pvc", l.GetName()) +// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { +// assert.True(t, l.Access(a)) +// } +// } + +// func TestPVCFields(t *testing.T) { +// r := newPVC().Fields("blee") +// assert.Equal(t, "fred", r[0]) +// } + +// func TestPVCMarshal(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// m.When(mr.Get("blee", "fred")).ThenReturn(k8sPVC(), nil) + +// cm := NewPVCWithArgs(mc, mr) +// ma, err := cm.Marshal("blee/fred") +// mr.VerifyWasCalledOnce().Get("blee", "fred") +// assert.Nil(t, err) +// assert.Equal(t, pvcYaml(), ma) +// } + +// // BOZO!! +// // func TestPVCListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sPVC()}, nil) + +// // l := NewPVCListWithArgs("blee", NewPVCWithArgs(mc, mr)) +// // // Make sure we mrn get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } + +// // mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, "blee", l.GetNamespace()) +// // row := td.Rows["blee/fred"] +// // assert.Equal(t, 7, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// // } + +// // Helpers... + +// func k8sPVC() *v1.PersistentVolumeClaim { +// return &v1.PersistentVolumeClaim{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "blee", +// Name: "fred", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: v1.PersistentVolumeClaimSpec{ +// VolumeName: "duh", +// Resources: v1.ResourceRequirements{ +// Requests: v1.ResourceList{ +// v1.ResourceStorage: resv1.Quantity{}, +// }, +// }, +// }, +// } +// } + +// func newPVC() resource.Columnar { +// mc := NewMockConnection() +// c, _ := resource.NewPersistentVolumeClaim(mc).New(k8sPVC()) +// return c +// } + +// func pvcYaml() string { +// return `apiVersion: v1 +// kind: PersistentVolumeClaim +// metadata: +// creationTimestamp: "2018-12-14T17:36:43Z" +// name: fred +// namespace: blee +// spec: +// resources: +// requests: +// storage: "0" +// volumeName: duh +// status: {} +// ` +// } diff --git a/internal/resource/ro.go b/internal/resource/ro.go deleted file mode 100644 index 05668f27..00000000 --- a/internal/resource/ro.go +++ /dev/null @@ -1,110 +0,0 @@ -package resource - -import ( - "errors" - "fmt" - "strings" - - "github.com/derailed/k9s/internal/k8s" - v1 "k8s.io/api/rbac/v1" -) - -// Role tracks a kubernetes resource. -type Role struct { - *Base - instance *v1.Role -} - -// NewRoleList returns a new resource list. -func NewRoleList(c Connection, ns string) List { - return NewList( - ns, - "role", - NewRole(c), - AllVerbsAccess|DescribeAccess, - ) -} - -// NewRole instantiates a new Role. -func NewRole(c Connection) *Role { - r := &Role{&Base{Connection: c, Resource: k8s.NewRole(c)}, nil} - r.Factory = r - - return r -} - -// New builds a new Role instance from a k8s resource. -func (r *Role) New(i interface{}) (Columnar, error) { - c := NewRole(r.Connection) - switch instance := i.(type) { - case *v1.Role: - c.instance = instance - case v1.Role: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting Role but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) - - return c, nil -} - -// Marshal resource to yaml. -func (r *Role) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } - - role, ok := i.(*v1.Role) - if !ok { - return "", errors.New("Expecting a role resource") - } - role.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" - role.TypeMeta.Kind = "Role" - - return r.marshalObject(role) -} - -// Header return resource header. -func (*Role) Header(ns string) Row { - hh := Row{} - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } - - return append(hh, "NAME", "AGE") -} - -// Fields retrieves displayable fields. -func (r *Role) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - - i := r.instance - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } - - return append(ff, - i.Name, - toAge(i.ObjectMeta.CreationTimestamp), - ) -} - -// ---------------------------------------------------------------------------- -// Helpers... - -func (r *Role) parseRules(pp []v1.PolicyRule) []Row { - acc := make([]Row, len(pp)) - - for i, p := range pp { - acc[i] = make(Row, 0, 4) - acc[i] = append(acc[i], strings.Join(p.Resources, ", ")) - acc[i] = append(acc[i], strings.Join(p.NonResourceURLs, ", ")) - acc[i] = append(acc[i], strings.Join(p.ResourceNames, ", ")) - acc[i] = append(acc[i], strings.Join(p.Verbs, ", ")) - } - - return acc -} diff --git a/internal/resource/ro_binding.go b/internal/resource/ro_binding.go deleted file mode 100644 index f96f3405..00000000 --- a/internal/resource/ro_binding.go +++ /dev/null @@ -1,97 +0,0 @@ -package resource - -import ( - "errors" - "fmt" - - "github.com/derailed/k9s/internal/k8s" - v1 "k8s.io/api/rbac/v1" -) - -// RoleBinding tracks a kubernetes resource. -type RoleBinding struct { - *Base - instance *v1.RoleBinding -} - -// NewRoleBindingList returns a new resource list. -func NewRoleBindingList(c Connection, ns string) List { - return NewList( - ns, - "rolebinding", - NewRoleBinding(c), - AllVerbsAccess|DescribeAccess, - ) -} - -// NewRoleBinding instantiates a new RoleBinding. -func NewRoleBinding(c Connection) *RoleBinding { - r := &RoleBinding{&Base{Connection: c, Resource: k8s.NewRoleBinding(c)}, nil} - r.Factory = r - - return r -} - -// New builds a new RoleBinding instance from a k8s resource. -func (r *RoleBinding) New(i interface{}) (Columnar, error) { - c := NewRoleBinding(r.Connection) - switch instance := i.(type) { - case *v1.RoleBinding: - c.instance = instance - case v1.RoleBinding: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting RoleBinding but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) - - return c, nil -} - -// Marshal resource to yaml. -func (r *RoleBinding) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } - - rb, ok := i.(*v1.RoleBinding) - if !ok { - return "", errors.New("Expecting a rb resource") - } - rb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" - rb.TypeMeta.Kind = "RoleBinding" - - return r.marshalObject(rb) -} - -// Header return resource header. -func (*RoleBinding) Header(ns string) Row { - hh := Row{} - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } - - return append(hh, "NAME", "ROLE", "KIND", "SUBJECTS", "AGE") -} - -// Fields retrieves displayable fields. -func (r *RoleBinding) Fields(ns string) Row { - i := r.instance - - ff := make(Row, 0, len(r.Header(ns))) - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } - - kind, ss := renderSubjects(i.Subjects) - - return append(ff, - i.Name, - i.RoleRef.Name, - kind, - ss, - toAge(i.ObjectMeta.CreationTimestamp), - ) -} diff --git a/internal/resource/ro_binding_int_test.go b/internal/resource/ro_binding_int_test.go deleted file mode 100644 index e1a47e74..00000000 --- a/internal/resource/ro_binding_int_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package resource - -import ( - "testing" - - "github.com/stretchr/testify/assert" - rbacv1 "k8s.io/api/rbac/v1" -) - -func TestToSubjectAlias(t *testing.T) { - uu := []struct { - i string - e string - }{ - {rbacv1.UserKind, "USR"}, - {rbacv1.GroupKind, "GRP"}, - {rbacv1.ServiceAccountKind, "SA"}, - {"fred", "FRED"}, - } - for _, u := range uu { - assert.Equal(t, u.e, toSubjectAlias(u.i)) - } -} - -func TestRenderSubjects(t *testing.T) { - uu := []struct { - ss []rbacv1.Subject - ek string - e string - }{ - { - []rbacv1.Subject{ - {Name: "blee", Kind: rbacv1.UserKind}, - }, - "USR", - "blee", - }, - { - []rbacv1.Subject{}, - NAValue, - "", - }, - } - for _, u := range uu { - kind, ss := renderSubjects(u.ss) - assert.Equal(t, u.e, ss) - assert.Equal(t, u.ek, kind) - } -} - -func BenchmarkToSubjects(b *testing.B) { - ss := []rbacv1.Subject{ - {Name: "blee", Kind: rbacv1.UserKind}, - } - - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - renderSubjects(ss) - } -} diff --git a/internal/resource/ro_binding_test.go b/internal/resource/ro_binding_test.go deleted file mode 100644 index c71cb8b9..00000000 --- a/internal/resource/ro_binding_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package resource_test - -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewRBListWithArgs(ns string, r *resource.RoleBinding) resource.List { - return resource.NewList(ns, "rb", r, resource.AllVerbsAccess|resource.DescribeAccess) -} - -func NewRBWithArgs(conn k8s.Connection, res resource.Cruder) *resource.RoleBinding { - r := &resource.RoleBinding{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestRBMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sRB(), nil) - - cm := NewRBWithArgs(mc, mr) - ma, err := cm.Marshal("blee/fred") - - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, rbYaml(), ma) -} - -// BOZO!! -// func TestRBListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sRB()}, nil) - -// l := NewRBListWithArgs("blee", NewRBWithArgs(mc, mr)) -// // Make sure we mrn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } - -// mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, "blee", l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 5, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) -// } - -// Helpers... - -func k8sRB() *v1.RoleBinding { - return &v1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "blee", - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Subjects: []v1.Subject{ - { - Kind: v1.UserKind, - Name: "fred", - Namespace: "blee", - }, - }, - RoleRef: v1.RoleRef{ - Kind: v1.UserKind, - Name: "duh", - }, - } -} - -func rbYaml() string { - return `apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -roleRef: - apiGroup: "" - kind: User - name: duh -subjects: -- kind: User - name: fred - namespace: blee -` -} diff --git a/internal/resource/ro_int_test.go b/internal/resource/ro_int_test.go deleted file mode 100644 index 8b251030..00000000 --- a/internal/resource/ro_int_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package resource - -import ( - "testing" - - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/rbac/v1" -) - -func TestRoleParseRules(t *testing.T) { - rules := []v1.PolicyRule{ - { - Resources: []string{"", "apps"}, - NonResourceURLs: []string{"/fred"}, - ResourceNames: []string{"pods", "deployments"}, - Verbs: []string{"get", "list"}, - }, - } - - var r Role - rows := r.parseRules(rules) - - assert.Equal(t, 1, len(rows)) - assert.Equal(t, 1, len(rows)) -} diff --git a/internal/resource/ro_test.go b/internal/resource/ro_test.go deleted file mode 100644 index d7a94c34..00000000 --- a/internal/resource/ro_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package resource_test - -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewRoleListWithArgs(ns string, r *resource.Role) resource.List { - return resource.NewList(ns, "ro", r, resource.AllVerbsAccess|resource.DescribeAccess) -} - -func NewRoleWithArgs(conn k8s.Connection, res resource.Cruder) *resource.Role { - r := &resource.Role{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestRoleMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sRole(), nil) - - cm := NewRoleWithArgs(mc, mr) - ma, err := cm.Marshal("blee/fred") - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, roleYaml(), ma) -} - -// BOZO!! -// func TestRoleListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sRole()}, nil) - -// l := NewRoleListWithArgs("blee", NewRoleWithArgs(mc, mr)) -// // Make sure we mrn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } - -// mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, "blee", l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 2, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) -// } - -// Helpers... - -func k8sRole() *v1.Role { - return &v1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "blee", - Name: "fred", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - } -} - -func roleYaml() string { - return `apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -rules: null -` -} diff --git a/internal/resource/secret.go b/internal/resource/secret.go index 26adbb1d..95519a14 100644 --- a/internal/resource/secret.go +++ b/internal/resource/secret.go @@ -1,6 +1,7 @@ package resource -// NewSecretList returns a new resource list. -func NewSecretList(c Connection, ns string) List { - return NewCustomList(c, true, "", "v1/secrets") -} +// BOZO!! +// // NewSecretList returns a new resource list. +// func NewSecretList(c Connection, ns string) List { +// return NewCustomList(c, true, "", "v1/secrets") +// } diff --git a/internal/resource/sts.go b/internal/resource/sts.go index 91e126ea..9ca9fcef 100644 --- a/internal/resource/sts.go +++ b/internal/resource/sts.go @@ -1,135 +1,136 @@ package resource -import ( - "context" - "errors" - "fmt" - "strconv" +// BOZO!! +// import ( +// "context" +// "errors" +// "fmt" +// "strconv" - "github.com/derailed/k9s/internal/k8s" - appsv1 "k8s.io/api/apps/v1" -) +// "github.com/derailed/k9s/internal/k8s" +// appsv1 "k8s.io/api/apps/v1" +// ) -// Compile time checks to ensure type satisfies interface -var _ Restartable = (*StatefulSet)(nil) -var _ Scalable = (*StatefulSet)(nil) +// // Compile time checks to ensure type satisfies interface +// var _ Restartable = (*StatefulSet)(nil) +// var _ Scalable = (*StatefulSet)(nil) -// StatefulSet tracks a kubernetes resource. -type StatefulSet struct { - *Base - instance *appsv1.StatefulSet -} +// // StatefulSet tracks a kubernetes resource. +// type StatefulSet struct { +// *Base +// instance *appsv1.StatefulSet +// } -// NewStatefulSetList returns a new resource list. -func NewStatefulSetList(c Connection, ns string) List { - return NewList( - ns, - "sts", - NewStatefulSet(c), - AllVerbsAccess|DescribeAccess, - ) -} +// // NewStatefulSetList returns a new resource list. +// func NewStatefulSetList(c Connection, ns string) List { +// return NewList( +// ns, +// "sts", +// NewStatefulSet(c), +// AllVerbsAccess|DescribeAccess, +// ) +// } -// NewStatefulSet instantiates a new StatefulSet. -func NewStatefulSet(c Connection) *StatefulSet { - s := &StatefulSet{&Base{Connection: c, Resource: k8s.NewStatefulSet(c)}, nil} - s.Factory = s +// // NewStatefulSet instantiates a new StatefulSet. +// func NewStatefulSet(c Connection) *StatefulSet { +// s := &StatefulSet{&Base{Connection: c, Resource: k8s.NewStatefulSet(c)}, nil} +// s.Factory = s - return s -} +// return s +// } -// New builds a new StatefulSet instance from a k8s resource. -func (r *StatefulSet) New(i interface{}) (Columnar, error) { - c := NewStatefulSet(r.Connection) - switch instance := i.(type) { - case *appsv1.StatefulSet: - c.instance = instance - case appsv1.StatefulSet: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting STS but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) +// // New builds a new StatefulSet instance from a k8s resource. +// func (r *StatefulSet) New(i interface{}) (Columnar, error) { +// c := NewStatefulSet(r.Connection) +// switch instance := i.(type) { +// case *appsv1.StatefulSet: +// c.instance = instance +// case appsv1.StatefulSet: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting STS but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) - return c, nil -} +// return c, nil +// } -// Marshal resource to yaml. -func (r *StatefulSet) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } +// // Marshal resource to yaml. +// func (r *StatefulSet) Marshal(path string) (string, error) { +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// return "", err +// } - sts, ok := i.(*appsv1.StatefulSet) - if !ok { - return "", errors.New("Expecting an sts resource") - } - sts.TypeMeta.APIVersion = "apps/v1" - sts.TypeMeta.Kind = "StatefulSet" +// sts, ok := i.(*appsv1.StatefulSet) +// if !ok { +// return "", errors.New("Expecting an sts resource") +// } +// sts.TypeMeta.APIVersion = "apps/v1" +// sts.TypeMeta.Kind = "StatefulSet" - return r.marshalObject(sts) -} +// return r.marshalObject(sts) +// } -// Logs tail logs for all pods represented by this statefulset. -func (r *StatefulSet) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { - instance, err := r.Resource.Get(opts.Namespace, opts.Name) - if err != nil { - return err - } +// // Logs tail logs for all pods represented by this statefulset. +// func (r *StatefulSet) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { +// instance, err := r.Resource.Get(opts.Namespace, opts.Name) +// if err != nil { +// return err +// } - sts, ok := instance.(*appsv1.StatefulSet) - if !ok { - return errors.New("Expecting an sts resource") - } - if sts.Spec.Selector == nil || len(sts.Spec.Selector.MatchLabels) == 0 { - return fmt.Errorf("No valid selector found on statefulset %s", opts.FQN()) - } +// sts, ok := instance.(*appsv1.StatefulSet) +// if !ok { +// return errors.New("Expecting an sts resource") +// } +// if sts.Spec.Selector == nil || len(sts.Spec.Selector.MatchLabels) == 0 { +// return fmt.Errorf("No valid selector found on statefulset %s", opts.FQN()) +// } - return r.podLogs(ctx, c, sts.Spec.Selector.MatchLabels, opts) -} +// return r.podLogs(ctx, c, sts.Spec.Selector.MatchLabels, opts) +// } -// Header return resource header. -func (*StatefulSet) Header(ns string) Row { - hh := Row{} - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } +// // Header return resource header. +// func (*StatefulSet) Header(ns string) Row { +// hh := Row{} +// if ns == AllNamespaces { +// hh = append(hh, "NAMESPACE") +// } - return append(hh, "NAME", "DESIRED", "CURRENT", "AGE") -} +// return append(hh, "NAME", "DESIRED", "CURRENT", "AGE") +// } -// NumCols designates if column is numerical. -func (*StatefulSet) NumCols(n string) map[string]bool { - return map[string]bool{ - "DESIRED": true, - "CURRENT": true, - } -} +// // NumCols designates if column is numerical. +// func (*StatefulSet) NumCols(n string) map[string]bool { +// return map[string]bool{ +// "DESIRED": true, +// "CURRENT": true, +// } +// } -// Fields retrieves displayable fields. -func (r *StatefulSet) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - i := r.instance - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } +// // Fields retrieves displayable fields. +// func (r *StatefulSet) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) +// i := r.instance +// if ns == AllNamespaces { +// ff = append(ff, i.Namespace) +// } - return append(ff, - i.Name, - strconv.Itoa(int(*i.Spec.Replicas)), - strconv.Itoa(int(i.Status.ReadyReplicas)), - toAge(i.ObjectMeta.CreationTimestamp), - ) -} +// return append(ff, +// i.Name, +// strconv.Itoa(int(*i.Spec.Replicas)), +// strconv.Itoa(int(i.Status.ReadyReplicas)), +// toAge(i.ObjectMeta.CreationTimestamp), +// ) +// } -// Scale the specified resource. -func (r *StatefulSet) Scale(ns, n string, replicas int32) error { - return r.Resource.(Scalable).Scale(ns, n, replicas) -} +// // Scale the specified resource. +// func (r *StatefulSet) Scale(ns, n string, replicas int32) error { +// return r.Resource.(Scalable).Scale(ns, n, replicas) +// } -// Restart the rollout of the specified resource. -func (r *StatefulSet) Restart(ns, n string) error { - return r.Resource.(Restartable).Restart(ns, n) -} +// // Restart the rollout of the specified resource. +// func (r *StatefulSet) Restart(ns, n string) error { +// return r.Resource.(Restartable).Restart(ns, n) +// } diff --git a/internal/resource/sts_test.go b/internal/resource/sts_test.go index 712840d3..48faa448 100644 --- a/internal/resource/sts_test.go +++ b/internal/resource/sts_test.go @@ -1,148 +1,149 @@ package resource_test -import ( - "testing" - - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/apps/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func NewStatefulSetListWithArgs(ns string, r *resource.StatefulSet) resource.List { - return resource.NewList(ns, "sts", r, resource.AllVerbsAccess|resource.DescribeAccess) -} - -func NewStatefulSetWithArgs(conn k8s.Connection, res resource.Cruder) *resource.StatefulSet { - r := &resource.StatefulSet{Base: resource.NewBase(conn, res)} - r.Factory = r - return r -} - -func TestStsListAccess(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - - ns := "blee" - l := NewStatefulSetListWithArgs(resource.AllNamespaces, NewStatefulSetWithArgs(mc, mr)) - l.SetNamespace(ns) - - assert.Equal(t, l.GetNamespace(), ns) - assert.Equal(t, "sts", l.GetName()) - for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { - assert.True(t, l.Access(a)) - } -} - -func TestStsHeader(t *testing.T) { - s := newSts() - e := append(resource.Row{"NAMESPACE"}, stsHeader()...) - assert.Equal(t, e, s.Header(resource.AllNamespaces)) - assert.Equal(t, stsHeader(), s.Header("fred")) -} - -func TestStsFields(t *testing.T) { - uu := []struct { - i resource.Columnar - e resource.Row - }{ - {i: newSts(), e: resource.Row{"blee", "fred", "0", "1"}}, - } - - for _, u := range uu { - assert.Equal(t, "blee/fred", u.i.Name()) - assert.Equal(t, u.e, u.i.Fields(resource.AllNamespaces)[:4]) - assert.Equal(t, u.e[1:4], u.i.Fields("blee")[:3]) - } -} - -func TestSTSMarshal(t *testing.T) { - mc := NewMockConnection() - mr := NewMockCruder() - m.When(mr.Get("blee", "fred")).ThenReturn(k8sSTS(), nil) - - cm := NewStatefulSetWithArgs(mc, mr) - ma, err := cm.Marshal("blee/fred") - - mr.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, stsYaml(), ma) -} - // BOZO!! -// func TestSTSListData(t *testing.T) { -// mc := NewMockConnection() -// mr := NewMockCruder() -// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sSTS()}, nil) +// import ( +// "testing" -// l := NewStatefulSetListWithArgs("blee", NewStatefulSetWithArgs(mc, mr)) -// // Make sure we mrn get deltas! -// for i := 0; i < 2; i++ { -// err := l.Reconcile(nil, "", "") -// assert.Nil(t, err) -// } +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// m "github.com/petergtz/pegomock" +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/apps/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -// mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) -// td := l.Data() -// assert.Equal(t, 1, len(td.Rows)) -// assert.Equal(t, "blee", l.GetNamespace()) -// row := td.Rows["blee/fred"] -// assert.Equal(t, 4, len(row.Deltas)) -// for _, d := range row.Deltas { -// assert.Equal(t, "", d) -// } -// assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// func NewStatefulSetListWithArgs(ns string, r *resource.StatefulSet) resource.List { +// return resource.NewList(ns, "sts", r, resource.AllVerbsAccess|resource.DescribeAccess) // } -// Helpers... +// func NewStatefulSetWithArgs(conn k8s.Connection, res resource.Cruder) *resource.StatefulSet { +// r := &resource.StatefulSet{Base: resource.NewBase(conn, res)} +// r.Factory = r +// return r +// } -func k8sSTS() *v1.StatefulSet { - return &v1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "fred", - Namespace: "blee", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: v1.StatefulSetSpec{ - Replicas: new(int32), - }, - Status: v1.StatefulSetStatus{ - ReadyReplicas: 1, - }, - } -} +// func TestStsListAccess(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() -func newSts() resource.Columnar { - mc := NewMockConnection() - c, _ := resource.NewStatefulSet(mc).New(k8sSTS()) - return c -} +// ns := "blee" +// l := NewStatefulSetListWithArgs(resource.AllNamespaces, NewStatefulSetWithArgs(mc, mr)) +// l.SetNamespace(ns) -func stsHeader() resource.Row { - return resource.Row{"NAME", "DESIRED", "CURRENT", "AGE"} -} +// assert.Equal(t, l.GetNamespace(), ns) +// assert.Equal(t, "sts", l.GetName()) +// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} { +// assert.True(t, l.Access(a)) +// } +// } -func stsYaml() string { - return `apiVersion: apps/v1 -kind: StatefulSet -metadata: - creationTimestamp: "2018-12-14T17:36:43Z" - name: fred - namespace: blee -spec: - replicas: 0 - selector: null - serviceName: "" - template: - metadata: - creationTimestamp: null - spec: - containers: null - updateStrategy: {} -status: - readyReplicas: 1 - replicas: 0 -` -} +// func TestStsHeader(t *testing.T) { +// s := newSts() +// e := append(resource.Row{"NAMESPACE"}, stsHeader()...) +// assert.Equal(t, e, s.Header(resource.AllNamespaces)) +// assert.Equal(t, stsHeader(), s.Header("fred")) +// } + +// func TestStsFields(t *testing.T) { +// uu := []struct { +// i resource.Columnar +// e resource.Row +// }{ +// {i: newSts(), e: resource.Row{"blee", "fred", "0", "1"}}, +// } + +// for _, u := range uu { +// assert.Equal(t, "blee/fred", u.i.Name()) +// assert.Equal(t, u.e, u.i.Fields(resource.AllNamespaces)[:4]) +// assert.Equal(t, u.e[1:4], u.i.Fields("blee")[:3]) +// } +// } + +// func TestSTSMarshal(t *testing.T) { +// mc := NewMockConnection() +// mr := NewMockCruder() +// m.When(mr.Get("blee", "fred")).ThenReturn(k8sSTS(), nil) + +// cm := NewStatefulSetWithArgs(mc, mr) +// ma, err := cm.Marshal("blee/fred") + +// mr.VerifyWasCalledOnce().Get("blee", "fred") +// assert.Nil(t, err) +// assert.Equal(t, stsYaml(), ma) +// } + +// // BOZO!! +// // func TestSTSListData(t *testing.T) { +// // mc := NewMockConnection() +// // mr := NewMockCruder() +// // m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sSTS()}, nil) + +// // l := NewStatefulSetListWithArgs("blee", NewStatefulSetWithArgs(mc, mr)) +// // // Make sure we mrn get deltas! +// // for i := 0; i < 2; i++ { +// // err := l.Reconcile(nil, "", "") +// // assert.Nil(t, err) +// // } + +// // mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{}) +// // td := l.Data() +// // assert.Equal(t, 1, len(td.Rows)) +// // assert.Equal(t, "blee", l.GetNamespace()) +// // row := td.Rows["blee/fred"] +// // assert.Equal(t, 4, len(row.Deltas)) +// // for _, d := range row.Deltas { +// // assert.Equal(t, "", d) +// // } +// // assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) +// // } + +// // Helpers... + +// func k8sSTS() *v1.StatefulSet { +// return &v1.StatefulSet{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: "fred", +// Namespace: "blee", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: v1.StatefulSetSpec{ +// Replicas: new(int32), +// }, +// Status: v1.StatefulSetStatus{ +// ReadyReplicas: 1, +// }, +// } +// } + +// func newSts() resource.Columnar { +// mc := NewMockConnection() +// c, _ := resource.NewStatefulSet(mc).New(k8sSTS()) +// return c +// } + +// func stsHeader() resource.Row { +// return resource.Row{"NAME", "DESIRED", "CURRENT", "AGE"} +// } + +// func stsYaml() string { +// return `apiVersion: apps/v1 +// kind: StatefulSet +// metadata: +// creationTimestamp: "2018-12-14T17:36:43Z" +// name: fred +// namespace: blee +// spec: +// replicas: 0 +// selector: null +// serviceName: "" +// template: +// metadata: +// creationTimestamp: null +// spec: +// containers: null +// updateStrategy: {} +// status: +// readyReplicas: 1 +// replicas: 0 +// ` +// } diff --git a/internal/resource/svc.go b/internal/resource/svc.go index afa0a144..45e4572b 100644 --- a/internal/resource/svc.go +++ b/internal/resource/svc.go @@ -1,202 +1,203 @@ package resource -import ( - "context" - "errors" - "fmt" - "sort" - "strconv" - "strings" +// BOZO!! +// import ( +// "context" +// "errors" +// "fmt" +// "sort" +// "strconv" +// "strings" - "github.com/derailed/k9s/internal/k8s" - "github.com/rs/zerolog/log" - v1 "k8s.io/api/core/v1" -) +// "github.com/derailed/k9s/internal/k8s" +// "github.com/rs/zerolog/log" +// v1 "k8s.io/api/core/v1" +// ) -// Service tracks a kubernetes resource. -type Service struct { - *Base - instance *v1.Service -} +// // Service tracks a kubernetes resource. +// type Service struct { +// *Base +// instance *v1.Service +// } -// NewServiceList returns a new resource list. -func NewServiceList(c Connection, ns string) List { - return NewList( - ns, - "svc", - NewService(c), - AllVerbsAccess|DescribeAccess, - ) -} +// // NewServiceList returns a new resource list. +// func NewServiceList(c Connection, ns string) List { +// return NewList( +// ns, +// "svc", +// NewService(c), +// AllVerbsAccess|DescribeAccess, +// ) +// } -// NewService instantiates a new Service. -func NewService(c Connection) *Service { - s := &Service{&Base{Connection: c, Resource: k8s.NewService(c)}, nil} - s.Factory = s +// // NewService instantiates a new Service. +// func NewService(c Connection) *Service { +// s := &Service{&Base{Connection: c, Resource: k8s.NewService(c)}, nil} +// s.Factory = s - return s -} +// return s +// } -// New builds a new Service instance from a k8s resource. -func (r *Service) New(i interface{}) (Columnar, error) { - c := NewService(r.Connection) - switch instance := i.(type) { - case *v1.Service: - c.instance = instance - case v1.Service: - c.instance = &instance - default: - return nil, fmt.Errorf("Expecting Service but got %T", instance) - } - c.path = c.namespacedName(c.instance.ObjectMeta) +// // New builds a new Service instance from a k8s resource. +// func (r *Service) New(i interface{}) (Columnar, error) { +// c := NewService(r.Connection) +// switch instance := i.(type) { +// case *v1.Service: +// c.instance = instance +// case v1.Service: +// c.instance = &instance +// default: +// return nil, fmt.Errorf("Expecting Service but got %T", instance) +// } +// c.path = c.namespacedName(c.instance.ObjectMeta) - return c, nil -} +// return c, nil +// } -// Marshal resource to yaml. -// BOZO!! Why you need to fill type info?? -func (r *Service) Marshal(path string) (string, error) { - ns, n := Namespaced(path) - i, err := r.Resource.Get(ns, n) - if err != nil { - return "", err - } +// // Marshal resource to yaml. +// // BOZO!! Why you need to fill type info?? +// func (r *Service) Marshal(path string) (string, error) { +// ns, n := Namespaced(path) +// i, err := r.Resource.Get(ns, n) +// if err != nil { +// return "", err +// } - svc, ok := i.(*v1.Service) - if !ok { - return "", errors.New("Expecting a service resource") - } - svc.TypeMeta.APIVersion = "v1" - svc.TypeMeta.Kind = "Service" +// svc, ok := i.(*v1.Service) +// if !ok { +// return "", errors.New("Expecting a service resource") +// } +// svc.TypeMeta.APIVersion = "v1" +// svc.TypeMeta.Kind = "Service" - return r.marshalObject(svc) -} +// return r.marshalObject(svc) +// } -// Logs tail logs for all pods represented by this service. -func (r *Service) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { - instance, err := r.Resource.Get(opts.Namespace, opts.Name) - if err != nil { - return err - } +// // Logs tail logs for all pods represented by this service. +// func (r *Service) Logs(ctx context.Context, c chan<- string, opts LogOptions) error { +// instance, err := r.Resource.Get(opts.Namespace, opts.Name) +// if err != nil { +// return err +// } - svc, ok := instance.(*v1.Service) - if !ok { - return errors.New("Expecting a service resource") - } - log.Debug().Msgf("Service %s--%s", svc.Name, svc.Spec.Selector) - if len(svc.Spec.Selector) == 0 { - return errors.New("No logs for headless service") - } +// svc, ok := instance.(*v1.Service) +// if !ok { +// return errors.New("Expecting a service resource") +// } +// log.Debug().Msgf("Service %s--%s", svc.Name, svc.Spec.Selector) +// if len(svc.Spec.Selector) == 0 { +// return errors.New("No logs for headless service") +// } - return r.podLogs(ctx, c, svc.Spec.Selector, opts) -} +// return r.podLogs(ctx, c, svc.Spec.Selector, opts) +// } -// Header returns resource header. -func (*Service) Header(ns string) Row { - hh := Row{} - if ns == AllNamespaces { - hh = append(hh, "NAMESPACE") - } +// // Header returns resource header. +// func (*Service) Header(ns string) Row { +// hh := Row{} +// if ns == AllNamespaces { +// hh = append(hh, "NAMESPACE") +// } - return append(hh, - "NAME", - "TYPE", - "CLUSTER-IP", - "EXTERNAL-IP", - "SELECTOR", - "PORTS", - "AGE", - ) -} +// return append(hh, +// "NAME", +// "TYPE", +// "CLUSTER-IP", +// "EXTERNAL-IP", +// "SELECTOR", +// "PORTS", +// "AGE", +// ) +// } -// Fields retrieves displayable fields. -func (r *Service) Fields(ns string) Row { - ff := make(Row, 0, len(r.Header(ns))) - i := r.instance +// // Fields retrieves displayable fields. +// func (r *Service) Fields(ns string) Row { +// ff := make(Row, 0, len(r.Header(ns))) +// i := r.instance - if ns == AllNamespaces { - ff = append(ff, i.Namespace) - } +// if ns == AllNamespaces { +// ff = append(ff, i.Namespace) +// } - return append(ff, - i.ObjectMeta.Name, - string(i.Spec.Type), - i.Spec.ClusterIP, - r.toIPs(i.Spec.Type, r.getSvcExtIPS(i)), - mapToStr(i.Spec.Selector), - r.toPorts(i.Spec.Ports), - toAge(i.ObjectMeta.CreationTimestamp), - ) -} +// return append(ff, +// i.ObjectMeta.Name, +// string(i.Spec.Type), +// i.Spec.ClusterIP, +// r.toIPs(i.Spec.Type, r.getSvcExtIPS(i)), +// mapToStr(i.Spec.Selector), +// r.toPorts(i.Spec.Ports), +// toAge(i.ObjectMeta.CreationTimestamp), +// ) +// } -// ---------------------------------------------------------------------------- -// Helpers... +// // ---------------------------------------------------------------------------- +// // Helpers... -func (r *Service) getSvcExtIPS(svc *v1.Service) []string { - results := []string{} +// func (r *Service) getSvcExtIPS(svc *v1.Service) []string { +// results := []string{} - switch svc.Spec.Type { - case v1.ServiceTypeClusterIP: - fallthrough - case v1.ServiceTypeNodePort: - return svc.Spec.ExternalIPs - case v1.ServiceTypeLoadBalancer: - lbIps := r.lbIngressIP(svc.Status.LoadBalancer) - if len(svc.Spec.ExternalIPs) > 0 { - if len(lbIps) > 0 { - results = append(results, lbIps) - } - return append(results, svc.Spec.ExternalIPs...) - } - if len(lbIps) > 0 { - results = append(results, lbIps) - } - case v1.ServiceTypeExternalName: - results = append(results, svc.Spec.ExternalName) - } +// switch svc.Spec.Type { +// case v1.ServiceTypeClusterIP: +// fallthrough +// case v1.ServiceTypeNodePort: +// return svc.Spec.ExternalIPs +// case v1.ServiceTypeLoadBalancer: +// lbIps := r.lbIngressIP(svc.Status.LoadBalancer) +// if len(svc.Spec.ExternalIPs) > 0 { +// if len(lbIps) > 0 { +// results = append(results, lbIps) +// } +// return append(results, svc.Spec.ExternalIPs...) +// } +// if len(lbIps) > 0 { +// results = append(results, lbIps) +// } +// case v1.ServiceTypeExternalName: +// results = append(results, svc.Spec.ExternalName) +// } - return results -} +// return results +// } -func (*Service) lbIngressIP(s v1.LoadBalancerStatus) string { - ingress := s.Ingress - result := []string{} - for i := range ingress { - if len(ingress[i].IP) > 0 { - result = append(result, ingress[i].IP) - } else if len(ingress[i].Hostname) > 0 { - result = append(result, ingress[i].Hostname) - } - } +// func (*Service) lbIngressIP(s v1.LoadBalancerStatus) string { +// ingress := s.Ingress +// result := []string{} +// for i := range ingress { +// if len(ingress[i].IP) > 0 { +// result = append(result, ingress[i].IP) +// } else if len(ingress[i].Hostname) > 0 { +// result = append(result, ingress[i].Hostname) +// } +// } - return strings.Join(result, ",") -} +// return strings.Join(result, ",") +// } -func (*Service) toIPs(svcType v1.ServiceType, ips []string) string { - if len(ips) == 0 { - if svcType == v1.ServiceTypeLoadBalancer { - return "" - } - return MissingValue - } - sort.Strings(ips) +// func (*Service) toIPs(svcType v1.ServiceType, ips []string) string { +// if len(ips) == 0 { +// if svcType == v1.ServiceTypeLoadBalancer { +// return "" +// } +// return MissingValue +// } +// sort.Strings(ips) - return strings.Join(ips, ",") -} +// return strings.Join(ips, ",") +// } -func (*Service) toPorts(pp []v1.ServicePort) string { - ports := make([]string, len(pp)) - for i, p := range pp { - if len(p.Name) > 0 { - ports[i] = p.Name + ":" - } - ports[i] += strconv.Itoa(int(p.Port)) + - "►" + - strconv.Itoa(int(p.NodePort)) - if p.Protocol != "TCP" { - ports[i] += "╱" + string(p.Protocol) - } - } +// func (*Service) toPorts(pp []v1.ServicePort) string { +// ports := make([]string, len(pp)) +// for i, p := range pp { +// if len(p.Name) > 0 { +// ports[i] = p.Name + ":" +// } +// ports[i] += strconv.Itoa(int(p.Port)) + +// "►" + +// strconv.Itoa(int(p.NodePort)) +// if p.Protocol != "TCP" { +// ports[i] += "╱" + string(p.Protocol) +// } +// } - return strings.Join(ports, " ") -} +// return strings.Join(ports, " ") +// } diff --git a/internal/resource/svc_int_test.go b/internal/resource/svc_int_test.go index ca83a760..93f0b763 100644 --- a/internal/resource/svc_int_test.go +++ b/internal/resource/svc_int_test.go @@ -1,123 +1,124 @@ package resource -import ( - "fmt" - "testing" - "time" +// BOZO!! +// import ( +// "fmt" +// "testing" +// "time" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) +// "github.com/stretchr/testify/assert" +// v1 "k8s.io/api/core/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// ) -func TestSvcExtIPs(t *testing.T) { - i := k8sSVCLb() +// func TestSvcExtIPs(t *testing.T) { +// i := k8sSVCLb() - var s Service - ips := s.getSvcExtIPS(i) +// var s Service +// ips := s.getSvcExtIPS(i) - assert.Equal(t, "10.0.0.0,2.2.2.2", s.toIPs(i.Spec.Type, ips)) -} +// assert.Equal(t, "10.0.0.0,2.2.2.2", s.toIPs(i.Spec.Type, ips)) +// } -func TestLbIngressIP(t *testing.T) { - lb := v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {IP: "10.0.0.0", Hostname: "fred"}, - {IP: "10.0.0.1", Hostname: "blee"}, - }, - } +// func TestLbIngressIP(t *testing.T) { +// lb := v1.LoadBalancerStatus{ +// Ingress: []v1.LoadBalancerIngress{ +// {IP: "10.0.0.0", Hostname: "fred"}, +// {IP: "10.0.0.1", Hostname: "blee"}, +// }, +// } - var s Service - assert.Equal(t, "10.0.0.0,10.0.0.1", s.lbIngressIP(lb)) -} +// var s Service +// assert.Equal(t, "10.0.0.0,10.0.0.1", s.lbIngressIP(lb)) +// } -func TestToIPs(t *testing.T) { - uu := []struct { - t v1.ServiceType - ii []string - e string - }{ - {v1.ServiceTypeLoadBalancer, []string{"2.2.2.2", "1.1.1.1"}, "1.1.1.1,2.2.2.2"}, - {v1.ServiceTypeLoadBalancer, []string{}, ""}, - {v1.ServiceTypeClusterIP, []string{}, MissingValue}, - } +// func TestToIPs(t *testing.T) { +// uu := []struct { +// t v1.ServiceType +// ii []string +// e string +// }{ +// {v1.ServiceTypeLoadBalancer, []string{"2.2.2.2", "1.1.1.1"}, "1.1.1.1,2.2.2.2"}, +// {v1.ServiceTypeLoadBalancer, []string{}, ""}, +// {v1.ServiceTypeClusterIP, []string{}, MissingValue}, +// } - var s Service - for _, u := range uu { - assert.Equal(t, u.e, s.toIPs(u.t, u.ii)) - } -} +// var s Service +// for _, u := range uu { +// assert.Equal(t, u.e, s.toIPs(u.t, u.ii)) +// } +// } -func TestToPorts(t *testing.T) { - uu := []struct { - pp []v1.ServicePort - e string - }{ - {[]v1.ServicePort{ - {Name: "http", Port: 80, NodePort: 90, Protocol: "TCP"}}, - "http:80►90", - }, - {[]v1.ServicePort{ - {Port: 80, NodePort: 30080, Protocol: "UDP"}}, - "80►30080╱UDP", - }, - } +// func TestToPorts(t *testing.T) { +// uu := []struct { +// pp []v1.ServicePort +// e string +// }{ +// {[]v1.ServicePort{ +// {Name: "http", Port: 80, NodePort: 90, Protocol: "TCP"}}, +// "http:80►90", +// }, +// {[]v1.ServicePort{ +// {Port: 80, NodePort: 30080, Protocol: "UDP"}}, +// "80►30080╱UDP", +// }, +// } - var s Service - for _, u := range uu { - assert.Equal(t, u.e, s.toPorts(u.pp)) - } -} +// var s Service +// for _, u := range uu { +// assert.Equal(t, u.e, s.toPorts(u.pp)) +// } +// } -func BenchmarkToPorts(b *testing.B) { - sp := []v1.ServicePort{ - {Name: "http", Port: 80, NodePort: 90, Protocol: "TCP"}, - {Port: 80, NodePort: 90, Protocol: "TCP"}, - {Name: "http", Port: 80, NodePort: 90, Protocol: "TCP"}, - } - b.ResetTimer() - b.ReportAllocs() +// func BenchmarkToPorts(b *testing.B) { +// sp := []v1.ServicePort{ +// {Name: "http", Port: 80, NodePort: 90, Protocol: "TCP"}, +// {Port: 80, NodePort: 90, Protocol: "TCP"}, +// {Name: "http", Port: 80, NodePort: 90, Protocol: "TCP"}, +// } +// b.ResetTimer() +// b.ReportAllocs() - var s Service - for i := 0; i < b.N; i++ { - s.toPorts(sp) - } -} +// var s Service +// for i := 0; i < b.N; i++ { +// s.toPorts(sp) +// } +// } -func k8sSVCLb() *v1.Service { - return &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "fred", - Namespace: "blee", - CreationTimestamp: metav1.Time{Time: testTime()}, - }, - Spec: v1.ServiceSpec{ - Type: v1.ServiceTypeLoadBalancer, - ClusterIP: "1.1.1.1", - ExternalIPs: []string{"2.2.2.2"}, - Selector: map[string]string{"fred": "blee"}, - Ports: []v1.ServicePort{ - { - Name: "http", - Port: 90, - Protocol: "TCP", - }, - }, - }, - Status: v1.ServiceStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ - {IP: "10.0.0.0", Hostname: "fred"}, - }, - }, - }, - } -} +// func k8sSVCLb() *v1.Service { +// return &v1.Service{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: "fred", +// Namespace: "blee", +// CreationTimestamp: metav1.Time{Time: testTime()}, +// }, +// Spec: v1.ServiceSpec{ +// Type: v1.ServiceTypeLoadBalancer, +// ClusterIP: "1.1.1.1", +// ExternalIPs: []string{"2.2.2.2"}, +// Selector: map[string]string{"fred": "blee"}, +// Ports: []v1.ServicePort{ +// { +// Name: "http", +// Port: 90, +// Protocol: "TCP", +// }, +// }, +// }, +// Status: v1.ServiceStatus{ +// LoadBalancer: v1.LoadBalancerStatus{ +// Ingress: []v1.LoadBalancerIngress{ +// {IP: "10.0.0.0", Hostname: "fred"}, +// }, +// }, +// }, +// } +// } -func testTime() time.Time { - t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00") - if err != nil { - fmt.Println("TestTime Failed", err) - } - return t -} +// func testTime() time.Time { +// t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00") +// if err != nil { +// fmt.Println("TestTime Failed", err) +// } +// return t +// } diff --git a/internal/ui/pages.go b/internal/ui/pages.go index c7461121..4514b3db 100644 --- a/internal/ui/pages.go +++ b/internal/ui/pages.go @@ -66,6 +66,7 @@ func (p *Pages) StackPushed(c model.Component) { } func (p *Pages) StackPopped(o, top model.Component) { + log.Debug().Msgf("UI STACK POPPED!!!") p.delete(o) } @@ -79,5 +80,8 @@ func (p *Pages) StackTop(top model.Component) { // Helpers... func componentID(c model.Component) string { + if c.Name() == "" { + panic("Component has no name") + } return fmt.Sprintf("%s-%p", c.Name(), c) } diff --git a/internal/ui/table.go b/internal/ui/table.go index d31b9463..38b4cf7e 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -57,15 +57,15 @@ func (t *Table) Init(ctx context.Context) { t.SetFixed(1, 0) t.SetBorder(true) - t.SetBackgroundColor(config.AsColor(t.styles.Table().BgColor)) - t.SetBorderColor(config.AsColor(t.styles.Table().FgColor)) + t.SetBackgroundColor(config.AsColor(t.styles.GetTable().BgColor)) + t.SetBorderColor(config.AsColor(t.styles.GetTable().FgColor)) t.SetBorderFocusColor(config.AsColor(t.styles.Frame().Border.FocusColor)) t.SetBorderAttributes(tcell.AttrBold) t.SetBorderPadding(0, 0, 1, 1) t.SetSelectable(true, false) t.SetSelectedStyle( tcell.ColorBlack, - config.AsColor(t.styles.Table().CursorColor), + config.AsColor(t.styles.GetTable().CursorColor), tcell.AttrBold, ) t.SetSelectionChangedFunc(t.selChanged) @@ -123,9 +123,6 @@ func (t *Table) SetDecorateFn(f DecorateFunc) { // SetColorerFn specifies the default colorer. func (t *Table) SetColorerFn(f render.ColorerFunc) { - if f == nil { - return - } t.colorerFn = f } @@ -146,6 +143,7 @@ func (t *Table) Update(data render.TableData) { } else { t.doUpdate(t.filtered()) } + t.UpdateTitle() t.updateSelection(true) } @@ -161,8 +159,8 @@ func (t *Table) doUpdate(data render.TableData) { t.adjustSorter(data) var row int - fg := config.AsColor(t.styles.Table().Header.FgColor) - bg := config.AsColor(t.styles.Table().Header.BgColor) + fg := config.AsColor(t.styles.GetTable().Header.FgColor) + bg := config.AsColor(t.styles.GetTable().Header.BgColor) for col, h := range data.Header { t.AddHeaderCell(col, h) c := t.GetCell(0, col) @@ -250,7 +248,8 @@ func (t *Table) buildRow(ns string, r int, re render.RowEvent, header render.Hea c.SetAlign(header[col].Align) c.SetTextColor(color(ns, re)) if marked { - c.SetTextColor(config.AsColor(t.styles.Table().MarkColor)) + log.Debug().Msgf("Marked!") + c.SetTextColor(config.AsColor(t.styles.GetTable().MarkColor)) } t.SetCell(r, col, c) } @@ -277,7 +276,7 @@ func (t *Table) NameColIndex() int { // AddHeaderCell configures a table cell header. func (t *Table) AddHeaderCell(col int, h render.Header) { - c := tview.NewTableCell(sortIndicator(t.sortCol, t.styles.Table(), col, h.Name)) + c := tview.NewTableCell(sortIndicator(t.sortCol, t.styles.GetTable(), col, h.Name)) c.SetExpansion(1) c.SetAlign(h.Align) t.SetCell(0, col, c) diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 3c311781..8f312a11 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -8,7 +8,6 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/resource" "github.com/rs/zerolog/log" "github.com/sahilm/fuzzy" ) @@ -192,31 +191,34 @@ func fuzzyFilter(q string, index int, data render.TableData) render.TableData { // UpdateTitle refreshes the table title. func styleTitle(rc int, ns, base, path, buff string, styles *config.Styles) string { - var title string - if rc > 0 { rc-- } - if path == "" { - path = "all" + if ns == render.AllNamespaces { + ns = render.NamespaceAll } - switch ns { - case resource.NotNamespaced, "*": + info := ns + if path != "" { + info = path + cns, n := render.Namespaced(path) + if cns == render.ClusterWide { + info = n + } + } + + var title string + if info == "" || info == render.ClusterWide { title = SkinTitle(fmt.Sprintf(titleFmt, base, rc), styles.Frame()) - default: - if ns == resource.AllNamespaces { - ns = resource.AllNamespace - } - title = SkinTitle(fmt.Sprintf(nsTitleFmt, base, ns, rc), styles.Frame()) + } else { + title = SkinTitle(fmt.Sprintf(nsTitleFmt, base, info, rc), styles.Frame()) + } + if buff == "" { + return title } - if buff != "" { - if IsLabelSelector(buff) { - buff = TrimLabelSelector(buff) - } - title += SkinTitle(fmt.Sprintf(SearchFmt, buff), styles.Frame()) + if IsLabelSelector(buff) { + buff = TrimLabelSelector(buff) } - - return title + return title + SkinTitle(fmt.Sprintf(SearchFmt, buff), styles.Frame()) } diff --git a/internal/view/alias.go b/internal/view/alias.go index 6bcf49bd..651ef394 100644 --- a/internal/view/alias.go +++ b/internal/view/alias.go @@ -2,12 +2,11 @@ package view import ( "context" - "fmt" "strings" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" @@ -20,115 +19,121 @@ const ( // Alias represents a command alias view. type Alias struct { - *Table + ResourceViewer } // NewAlias returns a new alias view. -func NewAlias() *Alias { - return &Alias{ - Table: NewTable(aliasTitle), +func NewAlias(gvr dao.GVR) ResourceViewer { + a := Alias{ + ResourceViewer: NewBrowser(gvr), } + a.GetTable().SetColorerFn(render.Alias{}.ColorerFunc()) + a.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen) + a.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone) + a.SetBindKeysFn(a.bindKeys) + a.SetContextFn(a.aliasContext) + // a.GetTable().SetEnterFn(a.gotoCmd) + + return &a } -// Init the view. -func (a *Alias) Init(ctx context.Context) error { - if err := a.Table.Init(ctx); err != nil { - return err - } - - a.SetColorerFn(render.Alias{}.ColorerFunc()) - a.SetBorderFocusColor(tcell.ColorMediumSpringGreen) - a.SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone) - a.registerActions() - a.Update(a.hydrate()) - a.resetTitle() - - return nil +func (a *Alias) aliasContext(ctx context.Context) context.Context { + return context.WithValue(ctx, internal.KeyAliases, aliases.Alias) } -func (a *Alias) registerActions() { - a.Actions().Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) - a.Actions().Add(ui.KeyActions{ - tcell.KeyEnter: ui.NewKeyAction("Goto Resource", a.gotoCmd, true), - tcell.KeyEscape: ui.NewKeyAction("Reset", a.resetCmd, false), - ui.KeySlash: ui.NewKeyAction("Filter", a.activateCmd, false), - ui.KeyShiftR: ui.NewKeyAction("Sort Resource", a.SortColCmd(0, true), false), - ui.KeyShiftC: ui.NewKeyAction("Sort Command", a.SortColCmd(1, true), false), - ui.KeyShiftA: ui.NewKeyAction("Sort ApiGroup", a.SortColCmd(2, true), false), +func (a *Alias) bindKeys(aa ui.KeyActions) { + aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) + aa.Add(ui.KeyActions{ + tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, true), + // BOZO!! + // tcell.KeyEscape: ui.NewKeyAction("Reset", a.resetCmd, false), + // ui.KeySlash: ui.NewKeyAction("Filter", a.GetTable().activateCmd, false), + ui.KeyShiftR: ui.NewKeyAction("Sort Resource", a.GetTable().SortColCmd(0, true), false), + ui.KeyShiftC: ui.NewKeyAction("Sort Command", a.GetTable().SortColCmd(1, true), false), + ui.KeyShiftA: ui.NewKeyAction("Sort ApiGroup", a.GetTable().SortColCmd(2, true), false), }) } -func (a *Alias) resetCmd(evt *tcell.EventKey) *tcell.EventKey { - if !a.SearchBuff().Empty() { - a.SearchBuff().Reset() - return nil - } - - return a.backCmd(evt) -} - func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { - r, _ := a.GetSelection() + log.Debug().Msgf("GOTO CMD") + r, _ := a.GetTable().GetSelection() if r != 0 { - s := ui.TrimCell(a.Table.SelectTable, r, 1) + s := ui.TrimCell(a.GetTable().SelectTable, r, 1) tokens := strings.Split(s, ",") - a.app.Content.Pop() - if !a.app.gotoResource(tokens[0]) { - a.app.Flash().Err(fmt.Errorf("Goto %s failed", tokens[0])) - } + a.App().gotoResource(tokens[0]) return nil } - if a.SearchBuff().IsActive() { - return a.activateCmd(evt) + if a.GetTable().SearchBuff().IsActive() { + return a.GetTable().activateCmd(evt) } - return evt } -func (a *Alias) backCmd(_ *tcell.EventKey) *tcell.EventKey { - if a.SearchBuff().IsActive() { - a.SearchBuff().Reset() - } else { - a.app.Content.Pop() +func (a *Alias) resetCmd(evt *tcell.EventKey) *tcell.EventKey { + if !a.GetTable().SearchBuff().Empty() { + a.GetTable().SearchBuff().Reset() + return nil } - return nil + return a.App().PrevCmd(evt) } -func (a *Alias) hydrate() render.TableData { - var re render.Alias +func (a *Alias) gotoCmd1(app *App, ns, res, path string) { + log.Debug().Msgf("GOTO %q -- %q -- %q", ns, res, path) + app.gotoResource(dao.GVR(path).ToR()) + // r, _ := a.GetTable().GetSelection() + // if r != 0 { + // s := ui.TrimCell(a.GetTable().SelectTable, r, 1) + // tokens := strings.Split(s, ",") + // a.App().Content.Pop() + // if err := a.App().gotoResource(tokens[0]); err != nil { + // a.App().Flash().Err(err) + // } + // return nil + // } - data := render.TableData{ - Header: re.Header(render.AllNamespaces), - RowEvents: make(render.RowEvents, 0, len(aliases.Alias)), - Namespace: resource.NotNamespaced, - } + // if a.GetTable().SearchBuff().IsActive() { + // return a.GetTable().activateCmd(evt) + // } - aa := make(config.ShortNames, len(aliases.Alias)) - for alias, gvr := range aliases.Alias { - if _, ok := aa[gvr]; ok { - aa[gvr] = append(aa[gvr], alias) - } else { - aa[gvr] = []string{alias} - } - } - - for gvr, aliases := range aa { - var row render.Row - if err := re.Render(aliases, gvr, &row); err != nil { - log.Error().Err(err).Msgf("Alias render failed") - continue - } - data.RowEvents = append(data.RowEvents, render.RowEvent{ - Kind: render.EventAdd, - Row: row, - }) - } - - return data + // return evt } -func (a *Alias) resetTitle() { - a.SetTitle(fmt.Sprintf(aliasTitleFmt, aliasTitle, a.GetRowCount()-1)) -} +// BOZO!! +// func (a *Alias) hydrate() render.TableData { +// var re render.Alias + +// data := render.TableData{ +// Header: re.Header(render.AllNamespaces), +// RowEvents: make(render.RowEvents, 0, len(aliases.Alias)), +// Namespace: resource.NotNamespaced, +// } + +// aa := make(config.ShortNames, len(aliases.Alias)) +// for alias, gvr := range aliases.Alias { +// if _, ok := aa[gvr]; ok { +// aa[gvr] = append(aa[gvr], alias) +// } else { +// aa[gvr] = []string{alias} +// } +// } + +// for gvr, aliases := range aa { +// var row render.Row +// if err := re.Render(aliases, gvr, &row); err != nil { +// log.Error().Err(err).Msgf("Alias render failed") +// continue +// } +// data.RowEvents = append(data.RowEvents, render.RowEvent{ +// Kind: render.EventAdd, +// Row: row, +// }) +// } + +// return data +// } + +// func (a *Alias) resetTitle() { +// a.SetTitle(fmt.Sprintf(aliasTitleFmt, aliasTitle, a.GetRowCount()-1)) +// } diff --git a/internal/view/app.go b/internal/view/app.go index 57762dfc..6d5039b8 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -30,7 +30,6 @@ type App struct { Content *PageStack command *command factory *watch.Factory - forwarders model.Forwarders version string showHeader bool cancelFn context.CancelFunc @@ -39,9 +38,8 @@ type App struct { // NewApp returns a K9s app instance. func NewApp(cfg *config.Config) *App { v := App{ - App: ui.NewApp(), - Content: NewPageStack(), - forwarders: model.NewForwarders(), + App: ui.NewApp(), + Content: NewPageStack(), } v.Config = cfg v.InitBench(cfg.K9s.CurrentCluster) @@ -59,9 +57,14 @@ func (a *App) ActiveView() model.Component { } func (a *App) PrevCmd(evt *tcell.EventKey) *tcell.EventKey { + log.Debug().Msgf("PREVIOUS!!!") + a.Content.DumpStack() + a.Content.DumpPages() if !a.Content.IsLast() { a.Content.Pop() } + a.Content.DumpStack() + a.Content.DumpPages() return nil } @@ -252,10 +255,9 @@ func (a *App) switchCtx(name string, loadPods bool) error { a.Halt() defer a.Resume() { - a.forwarders.DeleteAll() ns, err := a.Conn().Config().CurrentNamespaceName() if err != nil { - log.Info().Err(err).Msg("No namespace specified in context. Using K9s config") + log.Warn().Msg("No namespace specified in context. Using K9s config") } a.initFactory(ns) @@ -264,8 +266,8 @@ func (a *App) switchCtx(name string, loadPods bool) error { log.Error().Err(err).Msg("Config save failed!") } a.Flash().Infof("Switching context to %s", name) - if loadPods && !a.gotoResource("pods") { - a.Flash().Err(errors.New("Goto pods failed")) + if err := a.gotoResource("pods"); loadPods && err != nil { + a.Flash().Err(err) } a.refreshClusterInfo() } @@ -282,7 +284,6 @@ func (a *App) initFactory(ns string) { // BailOut exists the application. func (a *App) BailOut() { a.factory.Terminate() - a.forwarders.DeleteAll() a.App.BailOut() } @@ -306,7 +307,9 @@ func (a *App) Run() { }) }() - a.command.defaultCmd() + if err := a.command.defaultCmd(); err != nil { + panic(err) + } if err := a.Application.Run(); err != nil { panic(err) } @@ -361,7 +364,9 @@ func (a *App) toggleHeaderCmd(evt *tcell.EventKey) *tcell.EventKey { func (a *App) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { if a.CmdBuff().IsActive() && !a.CmdBuff().Empty() { - if !a.gotoResource(a.GetCmd()) { + if err := a.gotoResource(a.GetCmd()); err != nil { + log.Error().Err(err).Msgf("Goto resource for %q failed", a.GetCmd()) + a.Flash().Err(err) return nil } a.ResetCmd() @@ -378,8 +383,11 @@ func (a *App) helpCmd(evt *tcell.EventKey) *tcell.EventKey { } if a.Content.Top() != nil && a.Content.Top().Name() == helpTitle { a.Content.Pop() - } else { - a.inject(NewHelp()) + return nil + } + + if err := a.inject(NewHelp()); err != nil { + a.Flash().Err(err) } return nil @@ -392,19 +400,28 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey { if a.Content.Top() != nil && a.Content.Top().Name() == aliasTitle { a.Content.Pop() - } else { - a.inject(NewAlias()) + return nil + } + + if err := a.inject(NewAlias("aliases")); err != nil { + a.Flash().Err(err) } return nil } -func (a *App) gotoResource(res string) bool { +func (a *App) gotoResource(res string) error { return a.command.run(res) } -func (a *App) inject(c model.Component) { +func (a *App) inject(c model.Component) error { + ctx := context.WithValue(context.Background(), ui.KeyApp, a) + if err := c.Init(ctx); err != nil { + return fmt.Errorf("component init failed for %q %v", c.Name(), err) + } a.Content.Push(c) + + return nil } func (a *App) clusterInfo() *clusterInfoView { diff --git a/internal/view/bench.go b/internal/view/bench.go deleted file mode 100644 index 45209c66..00000000 --- a/internal/view/bench.go +++ /dev/null @@ -1,234 +0,0 @@ -package view - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/perf" - "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/resource" - "github.com/derailed/k9s/internal/ui" - "github.com/fsnotify/fsnotify" - "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" -) - -const ( - benchTitle = "Benchmarks" - resultTitle = "Benchmark Results" -) - -// Bench represents a service benchmark results view. -type Bench struct { - *Table - - details *Details -} - -// NewBench returns a new viewer. -func NewBench(title, _ string, _ resource.List) ResourceViewer { - return &Bench{ - Table: NewTable(benchTitle), - details: NewDetails(resultTitle), - } -} - -func (*Bench) SetContextFn(ContextFunc) {} - -// Init initializes the viewer. -func (b *Bench) Init(ctx context.Context) error { - log.Debug().Msgf(">>> Bench INIT") - if err := b.Table.Init(ctx); err != nil { - return err - } - b.SetBorderFocusColor(tcell.ColorSeaGreen) - b.SetSelectedStyle(tcell.ColorWhite, tcell.ColorSeaGreen, tcell.AttrNone) - b.SetColorerFn(render.Bench{}.ColorerFunc()) - b.bindKeys() - - b.details.SetTextColor(tcell.ColorSeaGreen) - if err := b.details.Init(ctx); err != nil { - return nil - } - - b.Start() - b.refresh() - b.SetSortCol(b.NameColIndex()+7, 0, true) - b.Refresh() - b.Select(1, 0) - - return nil -} - -// GVR returns a resource descriptor. -func (b *Bench) GVR() string { - return "n/a" -} - -// SetEnvFn sets k9s env vars. -func (b *Bench) SetEnvFn(EnvFunc) {} - -// GetTable returns the table view. -func (b *Bench) GetTable() *Table { return b.Table } - -// SetPath sets parent selector. -func (b *Bench) SetPath(s string) {} - -// Start runs the refresh loop -func (b *Bench) Start() { - log.Debug().Msgf(">>>> Bench START") - var ctx context.Context - - ctx, b.cancelFn = context.WithCancel(context.Background()) - if err := b.watchBenchDir(ctx); err != nil { - b.app.Flash().Errf("Unable to watch benchmarks directory %s", err) - } -} - -// List returns a resource list. -func (b *Bench) List() resource.List { - return nil -} - -func (b *Bench) refresh() { - b.Update(b.hydrate()) - b.UpdateTitle() -} - -func (b *Bench) bindKeys() { - b.Actions().Add(ui.KeyActions{ - tcell.KeyEnter: ui.NewKeyAction("Enter", b.enterCmd, false), - tcell.KeyCtrlD: ui.NewKeyAction("Delete", b.deleteCmd, false), - }) -} - -func (b *Bench) enterCmd(evt *tcell.EventKey) *tcell.EventKey { - if b.SearchBuff().IsActive() { - return b.filterCmd(evt) - } - - if !b.RowSelected() { - return nil - } - - data, err := readBenchFile(b.app.Config, b.benchFile()) - if err != nil { - b.app.Flash().Errf("Unable to load bench file %s", err) - return nil - } - - b.details.SetText(data) - b.details.SetSubject(b.GetSelectedItem()) - b.app.inject(b.details) - - return nil -} - -func (b *Bench) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { - if !b.RowSelected() { - return nil - } - - sel, file := b.GetSelectedItem(), b.benchFile() - dir := filepath.Join(perf.K9sBenchDir, b.app.Config.K9s.CurrentCluster) - showModal(b.app.Content.Pages, fmt.Sprintf("Delete benchmark `%s?", file), func() { - if err := os.Remove(filepath.Join(dir, file)); err != nil { - b.app.Flash().Errf("Unable to delete file %s", err) - return - } - b.app.Flash().Infof("Benchmark %s deleted!", sel) - }) - - return nil -} - -func (b *Bench) benchFile() string { - r := b.GetSelectedRowIndex() - return ui.TrimCell(b.SelectTable, r, 7) -} - -func (b *Bench) hydrate() render.TableData { - ff, err := loadBenchDir(b.app.Config) - if err != nil { - b.app.Flash().Errf("Unable to read bench directory %s", err) - } - - var re render.Bench - data := render.TableData{ - Header: re.Header(render.AllNamespaces), - RowEvents: make(render.RowEvents, 0, 10), - Namespace: render.AllNamespaces, - } - - for _, f := range ff { - bench := render.BenchInfo{ - File: f, - Path: filepath.Join(benchDir(b.app.Config), f.Name()), - } - - var row render.Row - if err := re.Render(bench, render.AllNamespaces, &row); err != nil { - log.Error().Err(err).Msg("Bench render failed") - continue - } - data.RowEvents = append(data.RowEvents, render.RowEvent{ - Kind: render.EventAdd, - Row: row, - }) - } - - return data -} - -func (b *Bench) watchBenchDir(ctx context.Context) error { - w, err := fsnotify.NewWatcher() - if err != nil { - return err - } - - go func() { - for { - select { - case evt := <-w.Events: - log.Debug().Msgf("Bench event %#v", evt) - b.app.QueueUpdateDraw(func() { - b.refresh() - }) - case err := <-w.Errors: - log.Info().Err(err).Msg("Dir Watcher failed") - return - case <-ctx.Done(): - log.Debug().Msg("!!!! FS WATCHER DONE!!") - if err := w.Close(); err != nil { - log.Error().Err(err).Msg("Closing bench watched") - } - return - } - } - }() - - return w.Add(benchDir(b.app.Config)) -} - -// ---------------------------------------------------------------------------- -// Helpers... - -func benchDir(cfg *config.Config) string { - return filepath.Join(perf.K9sBenchDir, cfg.K9s.CurrentCluster) -} - -func loadBenchDir(cfg *config.Config) ([]os.FileInfo, error) { - return ioutil.ReadDir(benchDir(cfg)) -} - -func readBenchFile(cfg *config.Config, n string) (string, error) { - data, err := ioutil.ReadFile(filepath.Join(benchDir(cfg), n)) - if err != nil { - return "", err - } - return string(data), nil -} diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go new file mode 100644 index 00000000..923ac552 --- /dev/null +++ b/internal/view/benchmark.go @@ -0,0 +1,158 @@ +package view + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/derailed/k9s/internal" + "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" + "github.com/gdamore/tcell" + "github.com/rs/zerolog/log" +) + +const ( + benchTitle = "Benchmarks" + resultTitle = "Benchmark Results" +) + +// Benchmark represents a service benchmark results view. +type Benchmark struct { + ResourceViewer + + details *Details +} + +// NewBench returns a new viewer. +func NewBenchmark(gvr dao.GVR) ResourceViewer { + b := Benchmark{ + ResourceViewer: NewBrowser(gvr), + details: NewDetails(resultTitle), + } + b.GetTable().SetBorderFocusColor(tcell.ColorSeaGreen) + b.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorSeaGreen, tcell.AttrNone) + b.GetTable().SetColorerFn(render.Benchmark{}.ColorerFunc()) + b.GetTable().SetSortCol(b.GetTable().NameColIndex()+7, 0, true) + b.SetContextFn(b.benchContext) + b.GetTable().SetEnterFn(b.viewBench) + + return &b +} + +func (b *Benchmark) benchContext(ctx context.Context) context.Context { + return context.WithValue(ctx, internal.KeyDir, benchDir(b.App().Config)) +} + +// BOZO!! +// // Start runs the refresh loop +// func (b *Bench) Start() { +// log.Debug().Msgf(">>>> Bench START") +// var ctx context.Context + +// ctx, b.cancelFn = context.WithCancel(context.Background()) +// if err := b.watchBenchDir(ctx); err != nil { +// b.App().Flash().Errf("Unable to watch benchmarks directory %s", err) +// } +// } + +func (b *Benchmark) viewBench(app *App, ns, res, path string) { + log.Debug().Msgf("VIEWBENCH %q -- %q -- %q", ns, res, path) + data, err := readBenchFile(app.Config, b.benchFile()) + if err != nil { + b.App().Flash().Errf("Unable to load bench file %s", err) + return + } + + b.details.SetText(data) + b.details.SetSubject(fileToSubject(path)) + b.App().inject(b.details) + + return +} + +func fileToSubject(path string) string { + tokens := strings.Split(path, "/") + log.Debug().Msgf("TOKENS %v", tokens) + ee := strings.Split(tokens[len(tokens)-1], "_") + return ee[0] + "/" + ee[1] +} + +func (b *Benchmark) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { + if !b.GetTable().RowSelected() { + return nil + } + + sel, file := b.GetTable().GetSelectedItem(), b.benchFile() + dir := filepath.Join(perf.K9sBenchDir, b.App().Config.K9s.CurrentCluster) + showModal(b.App().Content.Pages, fmt.Sprintf("Delete benchmark `%s?", file), func() { + if err := os.Remove(filepath.Join(dir, file)); err != nil { + b.App().Flash().Errf("Unable to delete file %s", err) + return + } + b.App().Flash().Infof("Benchmark %s deleted!", sel) + }) + + return nil +} + +func (b *Benchmark) benchFile() string { + r := b.GetTable().GetSelectedRowIndex() + return ui.TrimCell(b.GetTable().SelectTable, r, 7) +} + +// BOZO!! +// func (b *Benchmark) watchBenchDir(ctx context.Context) error { +// w, err := fsnotify.NewWatcher() +// if err != nil { +// return err +// } + +// go func() { +// for { +// select { +// case evt := <-w.Events: +// log.Debug().Msgf("Bench event %#v", evt) +// b.App().QueueUpdateDraw(func() { +// b.Refresh() +// }) +// case err := <-w.Errors: +// log.Info().Err(err).Msg("Dir Watcher failed") +// return +// case <-ctx.Done(): +// log.Debug().Msg("!!!! FS WATCHER DONE!!") +// if err := w.Close(); err != nil { +// log.Error().Err(err).Msg("Closing bench watched") +// } +// return +// } +// } +// }() + +// return w.Add(benchDir(b.App().Config)) +// } + +// ---------------------------------------------------------------------------- +// Helpers... + +func benchDir(cfg *config.Config) string { + return filepath.Join(perf.K9sBenchDir, cfg.K9s.CurrentCluster) +} + +func loadBenchDir(cfg *config.Config) ([]os.FileInfo, error) { + return ioutil.ReadDir(benchDir(cfg)) +} + +func readBenchFile(cfg *config.Config, n string) (string, error) { + data, err := ioutil.ReadFile(filepath.Join(benchDir(cfg), n)) + if err != nil { + return "", err + } + return string(data), nil +} diff --git a/internal/view/browser.go b/internal/view/browser.go new file mode 100644 index 00000000..7cd29d97 --- /dev/null +++ b/internal/view/browser.go @@ -0,0 +1,548 @@ +package view + +import ( + "bytes" + "context" + "errors" + "fmt" + "strconv" + "time" + + "github.com/atotto/clipboard" + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/k8s" + "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/resource" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/k9s/internal/ui/dialog" + "github.com/gdamore/tcell" + "github.com/rs/zerolog/log" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/printers" +) + +// ContextFunc enhances a given context. +type ContextFunc func(context.Context) context.Context + +type BindKeysFunc func(ui.KeyActions) + +// Browser represents a generic resource browser. +type Browser struct { + *Table + + namespaces map[int]string + gvr dao.GVR + envFn EnvFunc + meta metav1.APIResource + accessor dao.Accessor + contextFn ContextFunc + bindKeysFn BindKeysFunc + cancelFn context.CancelFunc +} + +// NewBrowser returns a new browser. +func NewBrowser(gvr dao.GVR) ResourceViewer { + return &Browser{ + Table: NewTable(string(gvr)), + gvr: gvr, + } +} + +// Init watches all running pods in given namespace +func (b *Browser) Init(ctx context.Context) error { + log.Debug().Msgf("BROWSER INIT %s", b.gvr) + var err error + b.meta, err = dao.MetaFor(b.gvr) + if err != nil { + return err + } + + if err := b.Table.Init(ctx); err != nil { + return err + } + if !dao.IsK9sMeta(b.meta) { + _ = b.app.factory.ForResource(b.app.Config.ActiveNamespace(), b.GVR()) + b.app.factory.WaitForCacheSync() + } + + if b.bindKeysFn != nil { + b.bindKeysFn(b.Actions()) + } + b.Table.BaseTitle = b.meta.Kind + b.accessor, err = dao.AccessorFor(b.app.factory, b.gvr) + if err != nil { + return err + } + log.Debug().Msgf("ACCESSOR FOR %s -- %#v", b.gvr, b.accessor) + + b.envFn = b.defaultK9sEnv + b.Table.setFilterFn(b.filterBrowser) + b.setNamespace(b.App().Config.ActiveNamespace()) + b.refresh() + row, _ := b.GetSelection() + if row == 0 && b.GetRowCount() > 0 { + b.Select(1, 0) + } + + return nil +} + +// Start initializes updates. +func (b *Browser) Start() { + b.Stop() + + log.Debug().Msgf("BROWSER START %s", b.gvr) + b.Table.Start() + + var ctx context.Context + ctx, b.cancelFn = context.WithCancel(context.Background()) + go b.update(ctx) +} + +func (b *Browser) Stop() { + if b.cancelFn != nil { + b.cancelFn() + b.cancelFn = nil + log.Debug().Msgf("BROWSER %s", b.BaseTitle) + } +} + +func (b *Browser) Refresh() { + // BOZO!! + // b.app.QueueUpdateDraw(func() { + b.refresh() + // }) +} + +// Name returns the component name. +func (b *Browser) Name() string { + return b.meta.Kind +} + +// SetContextFn populates a custom context. +func (b *Browser) SetContextFn(f ContextFunc) { + b.contextFn = f +} + +// SetBindKeysFn adds additional key bindings. +func (b *Browser) SetBindKeysFn(f BindKeysFunc) { + b.bindKeysFn = f +} + +// List returns a resource List. +func (b *Browser) List() resource.List { return nil } + +// SetEnvFn sets a function to pull viewer env vars for plugins. +func (b *Browser) SetEnvFn(f EnvFunc) { b.envFn = f } + +// GVR returns a resource descriptor. +func (b *Browser) GVR() string { return string(b.gvr) } + +func (b *Browser) GetTable() *Table { + return b.Table +} + +func (b *Browser) filterBrowser(sel string) { + panic("NYI") + // b.list.SetLabelSelector(sel) + b.refresh() +} + +func (b *Browser) update(ctx context.Context) { + for { + select { + case <-ctx.Done(): + log.Debug().Msgf("BROWSER <> -- %s", b.gvr) + return + case <-time.After(time.Duration(b.app.Config.K9s.GetRefreshRate()) * time.Second): + b.app.QueueUpdateDraw(func() { + b.refresh() + }) + } + } +} + +// ---------------------------------------------------------------------------- +// Actions()... + +func (b *Browser) cpCmd(evt *tcell.EventKey) *tcell.EventKey { + if !b.RowSelected() { + return evt + } + + _, n := k8s.Namespaced(b.GetSelectedItem()) + log.Debug().Msgf("Copied selection to clipboard %q", n) + b.app.Flash().Info("Current selection copied to clipboard...") + if err := clipboard.WriteAll(n); err != nil { + b.app.Flash().Err(err) + } + + return nil +} + +func (b *Browser) enterCmd(evt *tcell.EventKey) *tcell.EventKey { + log.Debug().Msgf("GENERIC RES ENTER CMD FOR %q...", b.gvr) + // If in command mode run filter otherwise enter function. + if b.filterCmd(evt) == nil || !b.RowSelected() { + return nil + } + + f := b.defaultEnter + if b.enterFn != nil { + log.Debug().Msgf("Found custom enter") + f = b.enterFn + } + f(b.app, b.Data.Namespace, string(b.gvr), b.GetSelectedItem()) + + return nil +} + +func (b *Browser) refreshCmd(*tcell.EventKey) *tcell.EventKey { + b.app.Flash().Info("Refreshinb...") + b.refresh() + return nil +} + +func (b *Browser) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { + selections := b.GetSelectedItems() + if len(selections) == 0 { + return evt + } + log.Debug().Msgf("DEL SELECTIONS %#v", selections) + + b.Stop() + defer b.Start() + { + msg := fmt.Sprintf("Delete %s %s?", b.gvr, selections[0]) + if len(selections) > 1 { + msg = fmt.Sprintf("Delete %d marked %s?", len(selections), b.gvr) + } + + cancelFn := func() {} + if dao.IsK9sMeta(b.meta) { + dialog.ShowConfirm(b.app.Content.Pages, "Confirm Delete", msg, func() { + b.ShowDeleted() + if len(selections) > 1 { + b.app.Flash().Infof("Delete %d marked %s", len(selections), b.gvr) + } else { + b.app.Flash().Infof("Delete resource %s %s", b.gvr, selections[0]) + } + for _, sel := range selections { + if err := b.accessor.(dao.Nuker).Delete(sel, true, true); err != nil { + b.app.Flash().Errf("Delete failed with `%s", err) + } else { + b.GetTable().DeleteMark(sel) + } + } + b.refresh() + b.SelectRow(1, true) + }, cancelFn) + return nil + } + + dialog.ShowDelete(b.app.Content.Pages, msg, func(cascade, force bool) { + b.ShowDeleted() + if len(selections) > 1 { + b.app.Flash().Infof("Delete %d marked %s", len(selections), b.gvr) + } else { + b.app.Flash().Infof("Delete resource %s %s", b.gvr, selections[0]) + } + for _, sel := range selections { + if err := b.accessor.(dao.Nuker).Delete(sel, cascade, force); err != nil { + b.app.Flash().Errf("Delete failed with `%s", err) + } else { + b.app.factory.DeleteForwarder(sel) + b.GetTable().DeleteMark(sel) + } + } + b.refresh() + b.SelectRow(1, true) + }, cancelFn) + } + + return nil +} + +func (b *Browser) defaultEnter(app *App, ns, _, sel string) { + log.Debug().Msgf("--------- Resource %q Verbs %v", sel, b.meta.Verbs) + ns, n := k8s.Namespaced(sel) + yaml, err := dao.Describe(b.app.Conn(), b.gvr, ns, n) + if err != nil { + b.app.Flash().Errf("Describe command failed: %s", err) + return + } + + details := NewDetails("Describe") + details.SetSubject(sel) + details.SetTextColor(b.app.Styles.FgColor()) + details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, yaml)) + details.ScrollToBeginning() + + if err := b.app.inject(details); err != nil { + b.app.Flash().Err(err) + } +} + +func (b *Browser) describeCmd(evt *tcell.EventKey) *tcell.EventKey { + log.Debug().Msgf("DESCRIBE %t -- %#v", b.RowSelected(), b.GetSelectedItems()) + if !b.RowSelected() { + return evt + } + b.defaultEnter(b.app, b.Data.Namespace, string(b.gvr), b.GetSelectedItem()) + + return nil +} + +func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey { + if !b.RowSelected() { + return evt + } + + path := b.GetSelectedItem() + log.Debug().Msgf("------ NAMESPACES %q vs %q", path, b.Data.Namespace) + o, err := b.app.factory.Get(string(b.gvr), path, labels.Everything()) + if err != nil { + b.app.Flash().Errf("Unable to get resource %q -- %s", b.gvr, err) + return nil + } + + raw, err := toYAML(o) + if err != nil { + b.app.Flash().Errf("Unable to marshal resource %s", err) + return nil + } + + details := NewDetails("YAML") + details.SetSubject(path) + details.SetTextColor(b.app.Styles.FgColor()) + details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, raw)) + details.ScrollToBeginning() + b.app.inject(details) + + return nil +} + +func toYAML(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 *Browser) editCmd(evt *tcell.EventKey) *tcell.EventKey { + if !b.RowSelected() { + return evt + } + + b.Stop() + defer b.Start() + { + ns, po := k8s.Namespaced(b.GetSelectedItem()) + args := make([]string, 0, 10) + args = append(args, "edit") + args = append(args, b.meta.Kind) + args = append(args, "-n", ns) + args = append(args, "--context", b.app.Config.K9s.CurrentContext) + if cfg := b.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { + args = append(args, "--kubeconfig", *cfg) + } + if !runK(true, b.app, append(args, po)...) { + b.app.Flash().Err(errors.New("Edit exec failed")) + } + } + + return evt +} + +func (b *Browser) setNamespace(ns string) { + if !b.meta.Namespaced { + b.Data.Namespace = render.ClusterWide + return + } + if b.Data.Namespace == ns { + return + } + + if ns == render.NamespaceAll { + ns = render.AllNamespaces + } + log.Debug().Msgf("!!!!!! SETTING NS %q", ns) + b.Data.Namespace = ns + b.Data.RowEvents = b.Data.RowEvents.Clear() +} + +func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { + i, _ := strconv.Atoi(string(evt.Rune())) + ns := b.namespaces[i] + if ns == "" { + ns = render.NamespaceAll + } + + b.app.switchNS(ns) + b.setNamespace(ns) + b.app.Flash().Infof("Viewing namespace `%s`...", ns) + b.refresh() + b.UpdateTitle() + b.SelectRow(1, true) + b.app.CmdBuff().Reset() + if err := b.app.Config.SetActiveNamespace(b.Data.Namespace); err != nil { + log.Error().Err(err).Msg("Config save NS failed!") + } + if err := b.app.Config.Save(); err != nil { + log.Error().Err(err).Msg("Config save failed!") + } + + return nil +} + +func (b *Browser) refresh() { + log.Debug().Msgf("REFRESHING (%q) in ns %q -- %q", b.gvr, b.Data.Namespace, b.Path) + + if b.app.Conn() == nil { + log.Error().Msg("No api connection") + return + } + + ctx := b.defaultContext() + if b.contextFn != nil { + log.Debug().Msgf("GOT CUSTOM CTX") + ctx = b.contextFn(ctx) + } + if path, ok := ctx.Value(internal.KeyPath).(string); ok && path != "" { + b.Path = path + } + data, err := dao.Reconcile(ctx, b.Table.Data, b.gvr) + if err != nil { + b.app.Flash().Err(err) + } + b.refreshActions() + b.Update(data) +} + +func (b *Browser) defaultContext() context.Context { + ctx := context.WithValue(context.Background(), internal.KeyFactory, b.app.factory) + ctx = context.WithValue(ctx, internal.KeyGVR, string(b.gvr)) + ctx = context.WithValue(ctx, internal.KeyPath, b.Path) + ctx = context.WithValue(ctx, internal.KeyLabels, "") + ctx = context.WithValue(ctx, internal.KeyFields, "") + + return ctx +} + +func (b *Browser) namespaceActions(aa ui.KeyActions) { + if b.app.Conn() == nil || !b.meta.Namespaced || b.GetTable().Path != "" { + return + } + b.namespaces = make(map[int]string, config.MaxFavoritesNS) + aa[tcell.Key(ui.NumKeys[0])] = ui.NewKeyAction(resource.AllNamespace, b.switchNamespaceCmd, true) + b.namespaces[0] = resource.AllNamespace + index := 1 + for _, n := range b.app.Config.FavNamespaces() { + if n == resource.AllNamespace { + continue + } + aa[tcell.Key(ui.NumKeys[index])] = ui.NewKeyAction(n, b.switchNamespaceCmd, true) + b.namespaces[index] = n + index++ + } +} + +func (b *Browser) refreshActions() { + aa := ui.KeyActions{ + ui.KeyC: ui.NewKeyAction("Copy", b.cpCmd, false), + tcell.KeyEnter: ui.NewKeyAction("View", b.enterCmd, false), + tcell.KeyCtrlR: ui.NewKeyAction("Refresh", b.refreshCmd, false), + } + b.namespaceActions(aa) + + if dao.Can(b.meta.Verbs, "edit") { + aa[ui.KeyE] = ui.NewKeyAction("Edit", b.editCmd, true) + } + if dao.Can(b.meta.Verbs, "delete") { + aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", b.deleteCmd, true) + } + if dao.Can(b.meta.Verbs, "view") { + aa[ui.KeyY] = ui.NewKeyAction("YAML", b.viewCmd, true) + } + if dao.Can(b.meta.Verbs, "describe") { + aa[ui.KeyD] = ui.NewKeyAction("Describe", b.describeCmd, true) + } + b.customActions(aa) + b.Actions().Add(aa) + + if b.bindKeysFn != nil { + b.bindKeysFn(b.Actions()) + } +} + +func (b *Browser) customActions(aa ui.KeyActions) { + pp := config.NewPlugins() + if err := pp.Load(); err != nil { + log.Warn().Msgf("No plugin configuration found") + return + } + + for k, plugin := range pp.Plugin { + if !in(plugin.Scopes, b.meta.Name) { + continue + } + key, err := asKey(plugin.ShortCut) + if err != nil { + log.Error().Err(err).Msg("Unable to map shortcut to a key") + continue + } + _, ok := aa[key] + if ok { + log.Error().Err(fmt.Errorf("Doh! you are trying to overide an existing command `%s", k)).Msg("Invalid shortcut") + continue + } + aa[key] = ui.NewKeyAction( + plugin.Description, + b.execCmd(plugin.Command, plugin.Background, plugin.Args...), + true) + } +} + +func (b *Browser) execCmd(bin string, bg bool, args ...string) ui.ActionHandler { + return func(evt *tcell.EventKey) *tcell.EventKey { + if !b.RowSelected() { + + return evt + } + + var ( + env = b.envFn() + aa = make([]string, len(args)) + err error + ) + for i, a := range args { + aa[i], err = env.envFor(a) + if err != nil { + log.Error().Err(err).Msg("Args match failed") + return nil + } + } + + if run(true, b.app, bin, bg, aa...) { + b.app.Flash().Info("Custom CMD launched!") + } else { + b.app.Flash().Info("Custom CMD failed!") + } + return nil + } +} + +func (b *Browser) defaultK9sEnv() K9sEnv { + return defaultK9sEnv(b.app, b.GetSelectedItem(), b.GetRow()) +} diff --git a/internal/view/command.go b/internal/view/command.go index 1c1be055..eb3224fc 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -8,7 +8,6 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/resource" "github.com/rs/zerolog/log" ) @@ -34,11 +33,8 @@ func (c *command) Init() error { return nil } -func (c *command) defaultCmd() { - cmd := c.app.Config.ActiveView() - if !c.run(cmd) { - log.Error().Err(fmt.Errorf("Unable to load command %s", cmd)).Msg("Command failed") - } +func (c *command) defaultCmd() error { + return c.run(c.app.Config.ActiveView()) } var authRX = regexp.MustCompile(`\Apol\s([u|g|s]):([\w-:]+)\b`) @@ -61,46 +57,42 @@ func (c *command) isK9sCmd(cmd string) bool { } tokens := authRX.FindAllStringSubmatch(cmd, -1) if len(tokens) == 1 && len(tokens[0]) == 3 { - c.app.inject(NewPolicy(c.app, tokens[0][1], tokens[0][2])) + // BOZO!! + // c.app.inject(NewPolicy(c.app, tokens[0][1], tokens[0][2])) return true } } return false } -func (c *command) viewMetaFor(cmd string) (string, *MetaViewer) { +func (c *command) viewMetaFor(cmd string) (string, *MetaViewer, error) { gvr, ok := aliases.Get(cmd) if !ok { - log.Error().Err(fmt.Errorf("Huh? `%s` command not found", cmd)).Msg("Command Failed") - c.app.Flash().Warnf("Huh? `%s` command not found", cmd) - return "", nil + return "", nil, fmt.Errorf("Huh? `%s` command not found", cmd) } v, ok := customViewers[dao.GVR(gvr)] if !ok { - log.Error().Err(fmt.Errorf("Huh? `%s` viewer not found", gvr)).Msg("MetaViewer Failed") - c.app.Flash().Warnf("Huh? viewer for %s not found", cmd) - return "", nil + return gvr, &MetaViewer{viewerFn: NewBrowser}, nil } - return gvr, &v + return gvr, &v, nil } // Exec the command by showing associated display. -func (c *command) run(cmd string) bool { +func (c *command) run(cmd string) error { if c.isK9sCmd(cmd) { - return true + return nil } cmds := strings.Split(cmd, " ") - gvr, v := c.viewMetaFor(cmds[0]) - if v == nil { - return false + gvr, v, err := c.viewMetaFor(cmds[0]) + if err != nil { + return err } switch cmds[0] { case "ctx", "context", "contexts": if len(cmds) == 2 && c.app.switchCtx(cmds[1], true) != nil { - log.Error().Msg("Context switch failed!") - return false + return fmt.Errorf("context switch failed!") } view := c.componentFor(gvr, v) return c.exec(gvr, view) @@ -111,42 +103,33 @@ func (c *command) run(cmd string) bool { ns = cmds[1] } if !c.app.switchNS(ns) { - return false + return fmt.Errorf("namespace switch failed for ns %q", ns) } return c.exec(gvr, c.componentFor(gvr, v)) } } func (c *command) componentFor(gvr string, v *MetaViewer) ResourceViewer { - var r resource.List - if v.listFn != nil { - r = v.listFn(c.app.Conn(), resource.DefaultNamespace) - } - var view ResourceViewer if v.viewerFn != nil { log.Debug().Msgf("Custom viewer for %s", gvr) view = v.viewerFn(dao.GVR(gvr)) - // view = NewGeneric(dao.GVR(gvr)) } else { - log.Debug().Msgf("Standard viewer for %s", gvr) - view = NewResource("BLAH", gvr, r) + log.Debug().Msgf("Generic viewer for %s", gvr) + view = NewBrowser(dao.GVR(gvr)) } - switch o := view.(type) { - case TableViewer: - if v.enterFn != nil { - o.GetTable().SetEnterFn(v.enterFn) - } + if v.enterFn != nil { + log.Debug().Msgf("SETTING CUSTOM ENTER ON %s", gvr) + view.GetTable().SetEnterFn(v.enterFn) } return view } -func (c *command) exec(gvr string, comp model.Component) bool { +func (c *command) exec(gvr string, comp model.Component) error { if comp == nil { - log.Error().Err(fmt.Errorf("No component given for %s", gvr)) - return false + return fmt.Errorf("No component given for %s", gvr) } g := k8s.GVR(gvr) @@ -157,7 +140,7 @@ func (c *command) exec(gvr string, comp model.Component) bool { log.Error().Err(err).Msg("Config save failed!") } c.app.Content.Stack.ClearHistory() - c.app.inject(comp) + return c.app.inject(comp) - return true + return nil } diff --git a/internal/view/container.go b/internal/view/container.go index 41029c8c..ca387761 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -1,14 +1,13 @@ package view import ( - "context" "errors" "fmt" "strings" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/gdamore/tcell" @@ -21,52 +20,38 @@ const containerTitle = "Containers" // Container represents a container view. type Container struct { ResourceViewer - - podPath string } // New Container returns a new container view. -func NewContainer(path string, list resource.List) ResourceViewer { - return &Container{ - ResourceViewer: NewResource(containerTitle, "containers", list), - podPath: path, - } -} - -// Init initializes the viewer. -func (c *Container) Init(ctx context.Context) error { - c.ResourceViewer = NewLogsExtender(c.ResourceViewer, c.selectedContainer) - c.ResourceViewer.SetPath(c.podPath) - c.GetTable().Path = c.podPath - if err := c.ResourceViewer.Init(ctx); err != nil { - return err - } +func NewContainer(gvr dao.GVR) ResourceViewer { + c := Container{} + c.ResourceViewer = NewLogsExtender(NewBrowser(gvr), c.selectedContainer) c.SetEnvFn(c.k9sEnv) c.GetTable().SetEnterFn(c.viewLogs) c.GetTable().SetColorerFn(render.Container{}.ColorerFunc()) - c.bindKeys() + c.SetBindKeysFn(c.bindKeys) - return nil + return &c } // Name returns the component name. func (c *Container) Name() string { return containerTitle } -func (c *Container) bindKeys() { - c.Actions().Delete(tcell.KeyCtrlSpace, ui.KeySpace) - c.Actions().Add(ui.KeyActions{ - tcell.KeyCtrlF: ui.NewKeyAction("PortForward", c.portFwdCmd, true), - ui.KeyS: ui.NewKeyAction("Shell", c.shellCmd, true), - ui.KeyShiftC: ui.NewKeyAction("Sort CPU", c.GetTable().SortColCmd(6, false), false), - ui.KeyShiftM: ui.NewKeyAction("Sort MEM", c.GetTable().SortColCmd(7, false), false), - ui.KeyShiftX: ui.NewKeyAction("Sort CPU%", c.GetTable().SortColCmd(8, false), false), - ui.KeyShiftZ: ui.NewKeyAction("Sort MEM%", c.GetTable().SortColCmd(9, false), false), +func (c *Container) bindKeys(aa ui.KeyActions) { + aa.Delete(tcell.KeyCtrlSpace, ui.KeySpace) + aa.Add(ui.KeyActions{ + ui.KeyShiftF: ui.NewKeyAction("PortForward", c.portFwdCmd, true), + ui.KeyS: ui.NewKeyAction("Shell", c.shellCmd, true), + ui.KeyShiftC: ui.NewKeyAction("Sort CPU", c.GetTable().SortColCmd(6, false), false), + ui.KeyShiftM: ui.NewKeyAction("Sort MEM", c.GetTable().SortColCmd(7, false), false), + ui.KeyShiftX: ui.NewKeyAction("Sort CPU%", c.GetTable().SortColCmd(8, false), false), + ui.KeyShiftZ: ui.NewKeyAction("Sort MEM%", c.GetTable().SortColCmd(9, false), false), }) } func (c *Container) k9sEnv() K9sEnv { env := defaultK9sEnv(c.App(), c.GetTable().GetSelectedItem(), c.GetTable().GetRow()) - ns, n := namespaced(c.podPath) + ns, n := k8s.Namespaced(c.GetTable().Path) env["POD"] = n env["NAMESPACE"] = ns @@ -80,14 +65,14 @@ func (c *Container) selectedContainer() string { return tokens[0] } -func (c *Container) viewLogs(_ *App, ns, res, path string) { +func (c *Container) viewLogs(app *App, ns, res, path string) { log.Debug().Msgf(">>>>>>>> ViewLOgs %q -- %q -- %q", ns, res, path) status := c.GetTable().GetSelectedCell(3) if status != "Running" && status != "Completed" { - c.App().Flash().Err(errors.New("No logs available")) + app.Flash().Err(errors.New("No logs available")) return } - c.ResourceViewer.(*LogsExtender).showLogs(c.podPath, false) + c.ResourceViewer.(*LogsExtender).showLogs(c.GetTable().Path, false) } // Handlers... @@ -100,7 +85,7 @@ func (c *Container) shellCmd(evt *tcell.EventKey) *tcell.EventKey { c.Stop() defer c.Start() - shellIn(c.App(), c.podPath, sel) + shellIn(c.App(), c.GetTable().Path, sel) return nil } @@ -111,8 +96,8 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - if _, ok := c.App().forwarders[fwFQN(c.podPath, sel)]; ok { - c.App().Flash().Err(fmt.Errorf("A PortForward already exist on container %s", c.podPath)) + if _, ok := c.App().factory.ForwarderFor(fwFQN(c.GetTable().Path, sel)); ok { + c.App().Flash().Err(fmt.Errorf("A PortForward already exist on container %s", c.GetTable().Path)) return nil } @@ -136,6 +121,10 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { continue } port = strings.TrimSpace(p) + tokens := strings.Split(port, ":") + if len(tokens) == 2 { + port = tokens[1] + } break } if port == "" { @@ -151,19 +140,19 @@ func (c *Container) portForward(lport, cport string) { co := c.GetTable().GetSelectedCell(0) pf := k8s.NewPortForward(c.App().Conn(), &log.Logger) ports := []string{lport + ":" + cport} - fw, err := pf.Start(c.podPath, co, ports) + fw, err := pf.Start(c.GetTable().Path, co, ports) if err != nil { c.App().Flash().Err(err) return } - log.Debug().Msgf(">>> Starting port forward %q %v", c.podPath, ports) + log.Debug().Msgf(">>> Starting port forward %q %v", c.GetTable().Path, ports) go c.runForward(pf, fw) } func (c *Container) runForward(pf *k8s.PortForward, f *portforward.PortForwarder) { c.App().QueueUpdateDraw(func() { - c.App().forwarders[pf.FQN()] = pf + c.App().factory.RegisterForwarder(pf) c.App().Flash().Infof("PortForward activated %s:%s", pf.Path(), pf.Ports()[0]) dialog.DismissPortForward(c.App().Content.Pages) }) @@ -174,7 +163,7 @@ func (c *Container) runForward(pf *k8s.PortForward, f *portforward.PortForwarder return } c.App().QueueUpdateDraw(func() { - delete(c.App().forwarders, pf.FQN()) + c.App().factory.DeleteForwarder(pf.FQN()) pf.SetActive(false) }) } diff --git a/internal/view/context.go b/internal/view/context.go index 7226ed0e..cd4bc823 100644 --- a/internal/view/context.go +++ b/internal/view/context.go @@ -2,7 +2,6 @@ package view import ( "errors" - "strings" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" @@ -16,44 +15,33 @@ type Context struct { ResourceViewer } -// NewContext return a new context viewer. +// NewContext returns a new viewer. func NewContext(gvr dao.GVR) ResourceViewer { c := Context{ - ResourceViewer: NewGeneric(gvr), + ResourceViewer: NewBrowser(gvr), } c.GetTable().SetEnterFn(c.useCtx) - c.GetTable().SetSelectedFn(c.cleanser) c.GetTable().SetColorerFn(render.Context{}.ColorerFunc()) - c.BindKeys() + c.SetBindKeysFn(c.bindKeys) return &c } -func (c *Context) BindKeys() { - c.Actions().Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) +func (c *Context) bindKeys(aa ui.KeyActions) { + aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) } -func (c *Context) useCtx(app *App, _, res, sel string) { - if err := c.useContext(sel); err != nil { +func (c *Context) useCtx(app *App, _, res, path string) { + log.Debug().Msgf("SWITCH CTX %q--%q", res, path) + if err := c.useContext(path); err != nil { app.Flash().Err(err) return } - if !app.gotoResource("po") { - app.Flash().Err(errors.New("goto pod failed")) + if err := app.gotoResource("po"); err != nil { + app.Flash().Err(err) } } -func (*Context) cleanser(s string) string { - name := strings.TrimSpace(s) - if strings.HasSuffix(name, "(*)") { - name = strings.TrimRight(name, "(*)") - } - if strings.HasSuffix(name, "(𝜟)") { - name = strings.TrimRight(name, "(𝜟)") - } - return name -} - func (c *Context) useContext(name string) error { res, err := dao.AccessorFor(c.App().factory, dao.GVR(c.GVR())) if err != nil { @@ -64,15 +52,10 @@ func (c *Context) useContext(name string) error { if !ok { return errors.New("Expecting a switchable resource") } - - log.Debug().Msgf("Context %q", name) - ctx, _ := namespaced(name) - ctx = c.cleanser(ctx) - if err := switcher.Switch(ctx); err != nil { + if err := switcher.Switch(name); err != nil { return err } - - if err := c.App().switchCtx(ctx, false); err != nil { + if err := c.App().switchCtx(name, false); err != nil { return err } c.Refresh() diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go index df689c48..04a051f3 100644 --- a/internal/view/cronjob.go +++ b/internal/view/cronjob.go @@ -2,35 +2,64 @@ package view import ( "context" + "fmt" - "github.com/derailed/k9s/internal/resource" + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" + "github.com/rs/zerolog/log" + batchv1beta1 "k8s.io/api/batch/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" ) -// CronJob presents a cronjob viewer. +// CronJob represents a cronjob viewer. type CronJob struct { ResourceViewer } // NewCronJob returns a new viewer. -func NewCronJob(title, gvr string, list resource.List) ResourceViewer { - return &CronJob{ - ResourceViewer: NewResource(title, gvr, list).(ResourceViewer), +func NewCronJob(gvr dao.GVR) ResourceViewer { + c := CronJob{ResourceViewer: NewBrowser(gvr)} + c.SetBindKeysFn(c.bindKeys) + c.GetTable().SetEnterFn(c.showJobs) + c.GetTable().SetColorerFn(render.CronJob{}.ColorerFunc()) + + return &c +} + +func (c *CronJob) showJobs(app *App, ns, res, path string) { + log.Debug().Msgf("Showing Jobs %q:%q -- %q", ns, res, path) + o, err := app.factory.Get("batch/v1beta1/cronjobs", path, labels.Everything()) + if err != nil { + app.Flash().Err(err) + return + } + + var cj batchv1beta1.CronJob + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj) + if err != nil { + app.Flash().Err(err) + return + } + + v := NewJob(dao.GVR("batch/v1/jobs")) + v.SetContextFn(jobCtx(path, string(cj.UID))) + app.inject(v) +} + +func jobCtx(path, uid string) ContextFunc { + return func(ctx context.Context) context.Context { + ctx = context.WithValue(ctx, internal.KeyPath, path) + return context.WithValue(ctx, internal.KeyUID, uid) } } -func (c *CronJob) Init(ctx context.Context) error { - if err := c.ResourceViewer.Init(ctx); err != nil { - return err - } - c.bindKeys() - - return nil -} - -func (c *CronJob) bindKeys() { - c.Actions().Add(ui.KeyActions{ +func (c *CronJob) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ tcell.KeyCtrlT: ui.NewKeyAction("Trigger", c.trigger, true), }) } @@ -41,11 +70,21 @@ func (c *CronJob) trigger(evt *tcell.EventKey) *tcell.EventKey { return evt } - if err := c.List().Resource().(resource.Runner).Run(sel); err != nil { + res, err := dao.AccessorFor(c.App().factory, dao.GVR(c.GVR())) + if err != nil { + return nil + } + runner, ok := res.(dao.Runnable) + if !ok { + c.App().Flash().Err(fmt.Errorf("expecting a jobrunner resource for %q", c.GVR())) + return nil + } + + if err := runner.Run(sel); err != nil { c.App().Flash().Errf("Cronjob trigger failed %v", err) return evt } - c.App().Flash().Infof("Triggering %s %s", c.List().GetName(), sel) + c.App().Flash().Infof("Triggering Job %s %s", c.GVR(), sel) return nil } diff --git a/internal/view/dp.go b/internal/view/dp.go index 7cd1701b..48f520c4 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -22,31 +22,25 @@ type Deploy struct { func NewDeploy(gvr dao.GVR) ResourceViewer { d := Deploy{ ResourceViewer: NewRestartExtender( - NewScaleExtender( - NewLogsExtender( - NewGeneric(gvr), - func() string { return "" }, - ), - ), + NewScaleExtender(NewLogsExtender(NewBrowser(gvr), nil)), ), } - d.BindKeys() + d.SetBindKeysFn(d.bindKeys) d.GetTable().SetEnterFn(d.showPods) d.GetTable().SetColorerFn(render.Deployment{}.ColorerFunc()) return &d } -func (d *Deploy) BindKeys() { - d.Actions().Add(ui.KeyActions{ +func (d *Deploy) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ ui.KeyShiftD: ui.NewKeyAction("Sort Desired", d.GetTable().SortColCmd(1, true), false), ui.KeyShiftC: ui.NewKeyAction("Sort Current", d.GetTable().SortColCmd(2, true), false), }) } -func (d *Deploy) showPods(app *App, _, res, sel string) { - ns, n := namespaced(sel) - o, err := app.factory.Get(ns, d.GVR(), n, labels.Everything()) +func (d *Deploy) showPods(app *App, _, _, path string) { + o, err := app.factory.Get(d.GVR(), path, labels.Everything()) if err != nil { app.Flash().Err(err) return @@ -58,17 +52,17 @@ func (d *Deploy) showPods(app *App, _, res, sel string) { app.Flash().Err(err) } - showPodsFromSelector(app, ns, dp.Spec.Selector) + showPodsFromSelector(app, path, dp.Spec.Selector) } // Helpers... -func showPodsFromSelector(app *App, ns string, sel *metav1.LabelSelector) { +func showPodsFromSelector(app *App, path string, sel *metav1.LabelSelector) { l, err := metav1.LabelSelectorAsSelector(sel) if err != nil { app.Flash().Err(err) return } - showPods(app, ns, l.String(), "") + showPods(app, path, l.String(), "") } diff --git a/internal/view/ds.go b/internal/view/ds.go index c655bcd5..91dd8420 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -17,29 +17,25 @@ type DaemonSet struct { func NewDaemonSet(gvr dao.GVR) ResourceViewer { d := DaemonSet{ ResourceViewer: NewRestartExtender( - NewLogsExtender( - NewGeneric(gvr), - func() string { return "" }, - ), + NewLogsExtender(NewBrowser(gvr), nil), ), } - d.BindKeys() + d.SetBindKeysFn(d.bindKeys) d.GetTable().SetEnterFn(d.showPods) d.GetTable().SetColorerFn(render.DaemonSet{}.ColorerFunc()) return &d } -func (d *DaemonSet) BindKeys() { - d.Actions().Add(ui.KeyActions{ +func (d *DaemonSet) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ ui.KeyShiftD: ui.NewKeyAction("Sort Desired", d.GetTable().SortColCmd(1, true), false), ui.KeyShiftC: ui.NewKeyAction("Sort Current", d.GetTable().SortColCmd(2, true), false), }) } -func (d *DaemonSet) showPods(app *App, _, res, sel string) { - ns, n := namespaced(sel) - o, err := app.factory.Get(ns, d.GVR(), n, labels.Everything()) +func (d *DaemonSet) showPods(app *App, _, _, path string) { + o, err := app.factory.Get(d.GVR(), path, labels.Everything()) if err != nil { d.App().Flash().Err(err) return @@ -51,5 +47,5 @@ func (d *DaemonSet) showPods(app *App, _, res, sel string) { d.App().Flash().Err(err) } - showPodsFromSelector(app, ns, ds.Spec.Selector) + showPodsFromSelector(app, path, ds.Spec.Selector) } diff --git a/internal/view/generic.go b/internal/view/generic.go deleted file mode 100644 index 488d82fa..00000000 --- a/internal/view/generic.go +++ /dev/null @@ -1,512 +0,0 @@ -package view - -import ( - "bytes" - "context" - "errors" - "fmt" - "strconv" - "time" - - "github.com/atotto/clipboard" - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/dao" - "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/resource" - "github.com/derailed/k9s/internal/ui" - "github.com/derailed/k9s/internal/ui/dialog" - "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/printers" -) - -// ContextFunc enhances a given context. -type ContextFunc func(context.Context) context.Context - -// Generic represents a generic resource vieweg. -type Generic struct { - *Table - - namespaces map[int]string - path string - gvr dao.GVR - envFn EnvFunc - meta metav1.APIResource - accessor dao.Accessor - contextFn ContextFunc -} - -// NewGeneric returns a new vieweg. -func NewGeneric(gvr dao.GVR) *Generic { - return &Generic{ - Table: NewTable(string(gvr)), - gvr: gvr, - } -} - -// Init watches all running pods in given namespace -func (g *Generic) Init(ctx context.Context) error { - log.Debug().Msgf(">>> GENERIC VIEW INIT %s", g.gvr) - var err error - g.meta, err = dao.MetaFor(g.gvr) - if err != nil { - return err - } - - if err := g.Table.Init(ctx); err != nil { - return err - } - g.Table.BaseTitle = g.meta.Kind - g.accessor, err = dao.AccessorFor(g.app.factory, g.gvr) - if err != nil { - return err - } - - g.envFn = g.defaultK9sEnv - g.Table.setFilterFn(g.filterGeneric) - g.setNamespace(g.App().Config.ActiveNamespace()) - g.refresh() - row, _ := g.GetSelection() - if row == 0 && g.GetRowCount() > 0 { - g.Select(1, 0) - } - - return nil -} - -// Start initializes updates. -func (g *Generic) Start() { - g.Stop() - - log.Debug().Msgf(">>>>>>> START %s", g.gvr) - g.Table.Start() - - var ctx context.Context - ctx, g.cancelFn = context.WithCancel(context.Background()) - go g.update(ctx) -} - -func (g *Generic) Refresh() { - g.app.QueueUpdateDraw(func() { - g.refresh() - }) -} - -// Name returns the component name. -func (g *Generic) Name() string { - return g.meta.Kind -} - -func (g *Generic) SetContextFn(f ContextFunc) { - g.contextFn = f -} - -// List returns a resource List. -func (g *Generic) List() resource.List { return nil } - -// SetEnvFn sets a function to pull viewer env vars for plugins. -func (g *Generic) SetEnvFn(f EnvFunc) { g.envFn = f } - -// SetPath set parents selector. -func (g *Generic) SetPath(p string) { g.Path = p } - -// GVR returns a resource descriptor. -func (g *Generic) GVR() string { return string(g.gvr) } - -func (g *Generic) GetTable() *Table { - return g.Table -} -func (g *Generic) filterGeneric(sel string) { - panic("NYI") - // g.list.SetLabelSelector(sel) - g.refresh() -} - -func (g *Generic) update(ctx context.Context) { - for { - select { - case <-ctx.Done(): - log.Debug().Msgf("%s updater canceled!", g.gvr) - return - case <-time.After(time.Duration(g.app.Config.K9s.GetRefreshRate()) * time.Second): - g.app.QueueUpdateDraw(func() { - g.refresh() - }) - } - } -} - -// ---------------------------------------------------------------------------- -// Actions()... - -func (g *Generic) cpCmd(evt *tcell.EventKey) *tcell.EventKey { - if !g.RowSelected() { - return evt - } - - _, n := namespaced(g.GetSelectedItem()) - log.Debug().Msgf("Copied selection to clipboard %q", n) - g.app.Flash().Info("Current selection copied to clipboard...") - if err := clipboard.WriteAll(n); err != nil { - g.app.Flash().Err(err) - } - - return nil -} - -func (g *Generic) enterCmd(evt *tcell.EventKey) *tcell.EventKey { - log.Debug().Msgf("RES ENTER CMD...") - // If in command mode run filter otherwise enter function. - if g.filterCmd(evt) == nil || !g.RowSelected() { - return nil - } - - f := g.defaultEnter - if g.enterFn != nil { - log.Debug().Msgf("Found custom enter") - f = g.enterFn - } - f(g.app, g.Data.Namespace, string(g.gvr), g.GetSelectedItem()) - - return nil -} - -func (g *Generic) refreshCmd(*tcell.EventKey) *tcell.EventKey { - g.app.Flash().Info("Refreshing...") - g.refresh() - return nil -} - -func (g *Generic) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { - selections := g.GetSelectedItems() - if len(selections) == 0 { - return evt - } - log.Debug().Msgf("DEL SELECTIONS %#v", selections) - - var msg string - if len(selections) > 1 { - msg = fmt.Sprintf("Delete %d marked %s?", len(selections), g.gvr) - } else { - msg = fmt.Sprintf("Delete %s %s?", g.gvr, selections[0]) - } - - cancelFn := func() {} - if in(g.meta.Categories, "K9s") { - dialog.ShowConfirm(g.app.Content.Pages, "Confirm Delete", msg, func() { - g.ShowDeleted() - if len(selections) > 1 { - g.app.Flash().Infof("Delete %d marked %s", len(selections), g.gvr) - } else { - g.app.Flash().Infof("Delete resource %s %s", g.gvr, selections[0]) - } - for _, sel := range selections { - ns, n := namespaced(sel) - if err := g.accessor.(dao.Nuker).Delete(ns, n, true, true); err != nil { - g.app.Flash().Errf("Delete failed with %s", err) - } else { - g.GetTable().DeleteMark(sel) - } - } - g.refresh() - g.SelectRow(1, true) - }, cancelFn) - return nil - } - - dialog.ShowDelete(g.app.Content.Pages, msg, func(cascade, force bool) { - g.ShowDeleted() - if len(selections) > 1 { - g.app.Flash().Infof("Delete %d marked %s", len(selections), g.gvr) - } else { - g.app.Flash().Infof("Delete resource %s %s", g.gvr, selections[0]) - } - for _, sel := range selections { - ns, n := namespaced(sel) - if err := g.accessor.(dao.Nuker).Delete(ns, n, cascade, force); err != nil { - g.app.Flash().Errf("Delete failed with %s", err) - } else { - g.app.forwarders.Kill(sel) - g.GetTable().DeleteMark(sel) - } - } - g.refresh() - g.SelectRow(1, true) - }, func() {}) - return nil -} - -func (g *Generic) defaultEnter(app *App, ns, _, sel string) { - log.Debug().Msgf("--------- Resource %q Verbs %v", sel, g.meta.Verbs) - ns, n := namespaced(sel) - yaml, err := dao.Describe(g.app.Conn(), g.gvr, ns, n) - if err != nil { - g.app.Flash().Errf("Describe command failed: %s", err) - return - } - - details := NewDetails("Describe") - details.SetSubject(sel) - details.SetTextColor(g.app.Styles.FgColor()) - details.SetText(colorizeYAML(g.app.Styles.Views().Yaml, yaml)) - details.ScrollToBeginning() - g.app.inject(details) -} - -func (g *Generic) describeCmd(evt *tcell.EventKey) *tcell.EventKey { - log.Debug().Msgf("DESCRIBE %t -- %#v", g.RowSelected(), g.GetSelectedItems()) - if !g.RowSelected() { - return evt - } - g.defaultEnter(g.app, g.Data.Namespace, string(g.gvr), g.GetSelectedItem()) - - return nil -} - -func (g *Generic) viewCmd(evt *tcell.EventKey) *tcell.EventKey { - if !g.RowSelected() { - return evt - } - - sel := g.GetSelectedItem() - ns, n := resource.Namespaced(sel) - if ns == "" { - ns = g.Data.Namespace - } - log.Debug().Msgf("------ NAMESPACES %q vs %q", ns, g.Data.Namespace) - o, err := g.app.factory.Get(ns, string(g.gvr), n, labels.Everything()) - if err != nil { - g.app.Flash().Errf("Unable to get resource %s", err) - return nil - } - - raw, err := toYAML(o) - if err != nil { - g.app.Flash().Errf("Unable to marshal resource %s", err) - return nil - } - - details := NewDetails("YAML") - details.SetSubject(sel) - details.SetTextColor(g.app.Styles.FgColor()) - details.SetText(colorizeYAML(g.app.Styles.Views().Yaml, raw)) - details.ScrollToBeginning() - g.app.inject(details) - - return nil -} - -func toYAML(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 (g *Generic) editCmd(evt *tcell.EventKey) *tcell.EventKey { - if !g.RowSelected() { - return evt - } - - g.Stop() - defer g.Start() - { - ns, po := namespaced(g.GetSelectedItem()) - args := make([]string, 0, 10) - args = append(args, "edit") - args = append(args, g.meta.Kind) - args = append(args, "-n", ns) - args = append(args, "--context", g.app.Config.K9s.CurrentContext) - if cfg := g.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { - args = append(args, "--kubeconfig", *cfg) - } - if !runK(true, g.app, append(args, po)...) { - g.app.Flash().Err(errors.New("Edit exec failed")) - } - } - - return evt -} - -func (g *Generic) setNamespace(ns string) { - if !g.meta.Namespaced { - g.Data.Namespace = render.ClusterWide - return - } - if g.Data.Namespace == ns { - return - } - - if ns == render.NamespaceAll { - ns = render.AllNamespaces - } - log.Debug().Msgf("!!!!!! SETTING NS %q", ns) - g.Data.Namespace = ns - g.Data.RowEvents = g.Data.RowEvents.Clear() -} - -func (g *Generic) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { - i, _ := strconv.Atoi(string(evt.Rune())) - ns := g.namespaces[i] - if ns == "" { - ns = render.NamespaceAll - } - - g.app.switchNS(ns) - g.setNamespace(ns) - g.app.Flash().Infof("Viewing namespace `%s`...", ns) - g.refresh() - g.UpdateTitle() - g.SelectRow(1, true) - g.app.CmdBuff().Reset() - if err := g.app.Config.SetActiveNamespace(g.Data.Namespace); err != nil { - log.Error().Err(err).Msg("Config save NS failed!") - } - if err := g.app.Config.Save(); err != nil { - log.Error().Err(err).Msg("Config save failed!") - } - - return nil -} - -func (g *Generic) refresh() { - if g.app.Conn() == nil { - log.Error().Msg("No api connection") - return - } - - log.Debug().Msgf("REFRESHING (%q) in ns %q", g.gvr, g.Data.Namespace) - ctx := g.defaultContext() - if g.contextFn != nil { - ctx = g.contextFn(ctx) - } - data, err := dao.Reconcile(ctx, g.Table.Data, g.gvr) - if err != nil { - g.app.Flash().Err(err) - } - g.refreshActions() - g.Update(data) -} - -func (g *Generic) defaultContext() context.Context { - ctx := context.WithValue(context.Background(), internal.KeyFactory, g.app.factory) - ctx = context.WithValue(ctx, internal.KeySelection, g.Path) - ctx = context.WithValue(ctx, internal.KeyLabels, "") - ctx = context.WithValue(ctx, internal.KeyFields, "") - - return ctx -} - -func (g *Generic) namespaceActions(aa ui.KeyActions) { - if g.app.Conn() == nil || !g.meta.Namespaced { - return - } - g.namespaces = make(map[int]string, config.MaxFavoritesNS) - aa[tcell.Key(ui.NumKeys[0])] = ui.NewKeyAction(resource.AllNamespace, g.switchNamespaceCmd, true) - g.namespaces[0] = resource.AllNamespace - index := 1 - for _, n := range g.app.Config.FavNamespaces() { - if n == resource.AllNamespace { - continue - } - aa[tcell.Key(ui.NumKeys[index])] = ui.NewKeyAction(n, g.switchNamespaceCmd, true) - g.namespaces[index] = n - index++ - } -} - -func (g *Generic) refreshActions() { - aa := ui.KeyActions{ - ui.KeyC: ui.NewKeyAction("Copy", g.cpCmd, false), - tcell.KeyEnter: ui.NewKeyAction("View", g.enterCmd, false), - tcell.KeyCtrlR: ui.NewKeyAction("Refresh", g.refreshCmd, false), - } - g.namespaceActions(aa) - - if dao.Can(g.meta.Verbs, "edit") { - aa[ui.KeyE] = ui.NewKeyAction("Edit", g.editCmd, true) - } - if dao.Can(g.meta.Verbs, "delete") { - aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", g.deleteCmd, true) - } - if dao.Can(g.meta.Verbs, "view") { - aa[ui.KeyY] = ui.NewKeyAction("YAML", g.viewCmd, true) - } - if dao.Can(g.meta.Verbs, "describe") { - aa[ui.KeyD] = ui.NewKeyAction("Describe", g.describeCmd, true) - } - g.customActions(aa) - g.Actions().Set(aa) -} - -func (g *Generic) customActions(aa ui.KeyActions) { - pp := config.NewPlugins() - if err := pp.Load(); err != nil { - log.Warn().Msgf("No plugin configuration found") - return - } - - for k, plugin := range pp.Plugin { - if !in(plugin.Scopes, g.meta.Name) { - continue - } - key, err := asKey(plugin.ShortCut) - if err != nil { - log.Error().Err(err).Msg("Unable to map shortcut to a key") - continue - } - _, ok := aa[key] - if ok { - log.Error().Err(fmt.Errorf("Doh! you are trying to overide an existing command `%s", k)).Msg("Invalid shortcut") - continue - } - aa[key] = ui.NewKeyAction( - plugin.Description, - g.execCmd(plugin.Command, plugin.Background, plugin.Args...), - true) - } -} - -func (g *Generic) execCmd(bin string, bg bool, args ...string) ui.ActionHandler { - return func(evt *tcell.EventKey) *tcell.EventKey { - if !g.RowSelected() { - - return evt - } - - var ( - env = g.envFn() - aa = make([]string, len(args)) - err error - ) - for i, a := range args { - aa[i], err = env.envFor(a) - if err != nil { - log.Error().Err(err).Msg("Args match failed") - return nil - } - } - - if run(true, g.app, bin, bg, aa...) { - g.app.Flash().Info("Custom CMD launched!") - } else { - g.app.Flash().Info("Custom CMD failed!") - } - return nil - } -} - -func (g *Generic) defaultK9sEnv() K9sEnv { - return defaultK9sEnv(g.app, g.GetSelectedItem(), g.GetRow()) -} diff --git a/internal/view/help.go b/internal/view/help.go index 01f12afb..39e7db2a 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" @@ -41,12 +42,13 @@ func (v *Help) Init(ctx context.Context) (err error) { v.SetBorder(true) v.SetBorderPadding(0, 0, 1, 1) v.bindKeys() - v.build(v.app.Content.Previous().Hints()) + v.build(v.app.Content.Top().Hints()) return nil } func (v *Help) bindKeys() { + v.Actions().Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlS) v.Actions().Set(ui.KeyActions{ tcell.KeyEsc: ui.NewKeyAction("Back", v.app.PrevCmd, true), tcell.KeyEnter: ui.NewKeyAction("Back", v.app.PrevCmd, false), @@ -202,7 +204,7 @@ func keyConv(s string) string { } func defaultK9sEnv(app *App, sel string, row resource.Row) K9sEnv { - ns, n := namespaced(sel) + ns, n := k8s.Namespaced(sel) ctx, err := app.Conn().Config().CurrentContextName() if err != nil { ctx = resource.NAValue diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 157666fd..f4d164b3 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -4,16 +4,52 @@ import ( "context" "errors" "fmt" - "path" "strings" + "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/k8s" + "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" + "github.com/rs/zerolog/log" "golang.org/x/text/language" "golang.org/x/text/message" ) +func showPodsWithLabels(app *App, path string, sel map[string]string) { + log.Debug().Msgf("SHOWING POD FOR %#v", sel) + var labels []string + for k, v := range sel { + labels = append(labels, fmt.Sprintf("%s=%s", k, v)) + } + showPods(app, path, strings.Join(labels, ","), "") +} + +func showPods(app *App, path, labelSel, fieldSel string) { + log.Debug().Msgf("SHOW PODS %q -- %q -- %q", path, labelSel, fieldSel) + app.switchNS("") + + v := NewPod(dao.GVR("v1/pods")) + v.SetContextFn(podCtx(path, labelSel, fieldSel)) + v.GetTable().SetColorerFn(render.Pod{}.ColorerFunc()) + + ns, _ := k8s.Namespaced(path) + if err := app.Config.SetActiveNamespace(ns); err != nil { + log.Error().Err(err).Msg("Config NS set failed!") + } + app.inject(v) +} + +func podCtx(path, labelSel, fieldSel string) ContextFunc { + return func(ctx context.Context) context.Context { + ctx = context.WithValue(ctx, internal.KeyPath, path) + ctx = context.WithValue(ctx, internal.KeyLabels, labelSel) + return context.WithValue(ctx, internal.KeyFields, fieldSel) + } +} + func extractApp(ctx context.Context) (*App, error) { app, ok := ctx.Value(ui.KeyApp).(*App) if !ok { @@ -54,15 +90,9 @@ func isTCPPort(p string) bool { return !strings.Contains(p, "UDP") } -// Namespaced converts an fqn resource name to ns and name. -func namespaced(n string) (string, string) { - ns, po := path.Split(n) - return strings.Trim(ns, "/"), po -} - // ContainerID computes container ID based on ns/po/co. func containerID(path, co string) string { - ns, n := namespaced(path) + ns, n := k8s.Namespaced(path) po := strings.Split(n, "-")[0] return ns + "/" + po + ":" + co diff --git a/internal/view/job.go b/internal/view/job.go index ddad480f..3065ba65 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -1,10 +1,12 @@ package view import ( - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/render" batchv1 "k8s.io/api/batch/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" ) // Job represents a job viewer. @@ -13,29 +15,28 @@ type Job struct { } // NewJob returns a new viewer. -func NewJob(title, gvr string, list resource.List) ResourceViewer { - j := Job{ - ResourceViewer: NewLogsExtender( - NewResource(title, gvr, list), - func() string { return "" }, - ), - } +func NewJob(gvr dao.GVR) ResourceViewer { + j := Job{ResourceViewer: NewLogsExtender(NewBrowser(gvr), nil)} j.GetTable().SetEnterFn(j.showPods) + j.GetTable().SetColorerFn(render.Job{}.ColorerFunc()) return &j } -func (j *Job) showPods(app *App, _, res, path string) { - ns, n := namespaced(path) - job, err := k8s.NewJob(app.Conn()).Get(ns, n) +// BOZO!! Change enter signature? +func (*Job) showPods(app *App, _, res, path string) { + o, err := app.factory.Get("batch/v1/jobs", path, labels.Everything()) if err != nil { app.Flash().Err(err) return } - jo, ok := job.(*batchv1.Job) - if !ok { - log.Fatal().Msg("Expecting a valid job") + var job batchv1.Job + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job) + if err != nil { + app.Flash().Err(err) + return } - showPodsFromSelector(app, ns, jo.Spec.Selector) + + showPodsFromSelector(app, path, job.Spec.Selector) } diff --git a/internal/view/log.go b/internal/view/log.go index 8574c772..3163c129 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -13,7 +13,6 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" "github.com/gdamore/tcell" @@ -40,20 +39,18 @@ type Log struct { path, container string cancelFn context.CancelFunc previous bool - list resource.List gvr dao.GVR } var _ model.Component = &Log{} // NewLog returns a new viewer. -func NewLog(gvr dao.GVR, path, co string, l resource.List, prev bool) *Log { +func NewLog(gvr dao.GVR, path, co string, prev bool) *Log { return &Log{ gvr: gvr, Flex: tview.NewFlex(), path: path, container: co, - list: l, previous: prev, } } @@ -94,11 +91,6 @@ func (l *Log) Init(ctx context.Context) (err error) { // Refresh refreshes the viewer. func (l *Log) Refresh() {} -// List returns the resource list. -func (l *Log) List() resource.List { - return l.list -} - // App returns an app handle. func (l *Log) App() *App { return l.app @@ -179,15 +171,11 @@ func (l *Log) doLoad() error { } func (l *Log) logOpts(path, co string, prevLogs bool) dao.LogOptions { - ns, po := namespaced(path) return dao.LogOptions{ - Fqn: dao.Fqn{ - Namespace: ns, - Name: po, - Container: co, - }, - Lines: int64(l.app.Config.K9s.LogRequestSize), - Previous: prevLogs, + Path: path, + Container: co, + Lines: int64(l.app.Config.K9s.LogRequestSize), + Previous: prevLogs, } } diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index b2b8c06e..ccb09f05 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -4,6 +4,7 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" + "github.com/rs/zerolog/log" ) // LogsExtender adds log actions to a given viewer. @@ -14,19 +15,19 @@ type LogsExtender struct { } // NewLogsExtender returns a new extender. -func NewLogsExtender(r ResourceViewer, f ContainerFunc) ResourceViewer { +func NewLogsExtender(v ResourceViewer, f ContainerFunc) ResourceViewer { l := LogsExtender{ - ResourceViewer: r, + ResourceViewer: v, containerFn: f, } - l.BindKeys() + l.bindKeys(l.Actions()) return &l } // BindKeys injects new menu actions. -func (l *LogsExtender) BindKeys() { - l.Actions().Add(ui.KeyActions{ +func (l *LogsExtender) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ ui.KeyL: ui.NewKeyAction("Logs", l.logsCmd(false), true), ui.KeyShiftL: ui.NewKeyAction("Logs Previous", l.logsCmd(true), true), }) @@ -48,10 +49,11 @@ func (l *LogsExtender) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.Event } func (l *LogsExtender) showLogs(path string, prev bool) { + log.Debug().Msgf("SHOWING LOGS path %q", path) co := "" if l.containerFn != nil { + log.Debug().Msgf("CUSTOM CO FUNC") co = l.containerFn() } - log := NewLog(dao.GVR(l.GVR()), path, co, l.List(), prev) - l.App().inject(log) + l.App().inject(NewLog(dao.GVR(l.GVR()), path, co, prev)) } diff --git a/internal/view/node.go b/internal/view/node.go index f1ebeb8d..42d3fd38 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -1,13 +1,11 @@ package view import ( - "context" - - "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/resource" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const nodeTitle = "Nodes" @@ -18,25 +16,20 @@ type Node struct { } // NewNode returns a new node view. -func NewNode(title, gvr string, list resource.List) ResourceViewer { - return &Node{ - ResourceViewer: NewResource(nodeTitle, gvr, list), +func NewNode(gvr dao.GVR) ResourceViewer { + n := Node{ + ResourceViewer: NewBrowser(gvr), } -} - -func (n *Node) Init(ctx context.Context) error { - if err := n.ResourceViewer.Init(ctx); err != nil { - return err - } - n.bindKeys() + n.SetBindKeysFn(n.bindKeys) n.GetTable().SetEnterFn(n.showPods) - return nil + return &n } -func (n *Node) bindKeys() { - n.Actions().Delete(ui.KeySpace, tcell.KeyCtrlSpace) - n.Actions().Add(ui.KeyActions{ +func (n *Node) bindKeys(aa ui.KeyActions) { + aa.Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlD) + aa.Add(ui.KeyActions{ + ui.KeyY: ui.NewKeyAction("YAML", n.viewCmd, true), ui.KeyShiftC: ui.NewKeyAction("Sort CPU", n.GetTable().SortColCmd(7, false), false), ui.KeyShiftM: ui.NewKeyAction("Sort MEM", n.GetTable().SortColCmd(8, false), false), ui.KeyShiftX: ui.NewKeyAction("Sort CPU%", n.GetTable().SortColCmd(9, false), false), @@ -48,20 +41,31 @@ func (n *Node) showPods(app *App, ns, res, sel string) { showPods(app, n.GetTable().GetSelectedItem(), "", "spec.nodeName="+sel) } -func showPods(app *App, path, labelSel, fieldSel string) { - log.Debug().Msgf("NODE show pods %q -- %q -- %q", path, labelSel, fieldSel) - app.switchNS("") - - list := resource.NewPodList(app.Conn(), "") - list.SetLabelSelector(labelSel) - list.SetFieldSelector(fieldSel) - - v := NewPod(path, "v1/pods", list) - v.GetTable().SetColorerFn(render.Pod{}.ColorerFunc()) - - ns, _ := namespaced(path) - if err := app.Config.SetActiveNamespace(ns); err != nil { - log.Error().Err(err).Msg("Config NS set failed!") +func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey { + if !n.GetTable().RowSelected() { + return evt } - app.inject(v) + + sel := n.GetTable().GetSelectedItem() + log.Debug().Msgf("------ VIEW NODE %q", sel) + o, err := n.App().factory.Client().DynDialOrDie().Resource(dao.GVR(n.GVR()).AsGVR()).Get(sel, metav1.GetOptions{}) + if err != nil { + n.App().Flash().Errf("Unable to get resource %q -- %s", n.GVR(), err) + return nil + } + + raw, err := toYAML(o) + if err != nil { + n.App().Flash().Errf("Unable to marshal resource %s", err) + return nil + } + + details := NewDetails("YAML") + details.SetSubject(sel) + details.SetTextColor(n.App().Styles.FgColor()) + details.SetText(colorizeYAML(n.App().Styles.Views().Yaml, raw)) + details.ScrollToBeginning() + n.App().inject(details) + + return nil } diff --git a/internal/view/ns.go b/internal/view/ns.go index e16e4f6a..610d1e79 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -26,19 +26,18 @@ type Namespace struct { // NewNamespace returns a new viewer func NewNamespace(gvr dao.GVR) ResourceViewer { n := Namespace{ - ResourceViewer: NewGeneric(gvr), + ResourceViewer: NewBrowser(gvr), } n.GetTable().SetDecorateFn(n.decorate) n.GetTable().SetColorerFn(render.Namespace{}.ColorerFunc()) n.GetTable().SetEnterFn(n.switchNs) - n.GetTable().SetSelectedFn(n.cleanser) - n.BindKeys() + n.SetBindKeysFn(n.bindKeys) return &n } -func (n *Namespace) BindKeys() { - n.Actions().Add(ui.KeyActions{ +func (n *Namespace) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ ui.KeyU: ui.NewKeyAction("Use", n.useNsCmd, true), }) } @@ -49,16 +48,20 @@ func (n *Namespace) switchNs(app *App, _, res, sel string) { } func (n *Namespace) useNsCmd(evt *tcell.EventKey) *tcell.EventKey { - ns := n.GetTable().GetSelectedItem() - if ns == "" { - return evt + path := n.GetTable().GetSelectedItem() + if path == "" { + return nil } - n.useNamespace(ns) + n.useNamespace(path) + + log.Debug().Msgf("NS TABLE %#v", n.GetTable().Data) return nil } func (n *Namespace) useNamespace(ns string) { + log.Debug().Msgf("SWITCHING NS %q", ns) + n.App().switchNS(ns) if err := n.App().Config.SetActiveNamespace(ns); err != nil { n.App().Flash().Err(err) } else { @@ -67,12 +70,6 @@ func (n *Namespace) useNamespace(ns string) { if err := n.App().Config.Save(); err != nil { log.Error().Err(err).Msg("Config file save failed!") } - n.App().switchNS(ns) -} - -func (*Namespace) cleanser(s string) string { - log.Debug().Msgf("NS CLEANZ %q", s) - return nsCleanser.ReplaceAllString(s, `$1`) } func (n *Namespace) decorate(data render.TableData) render.TableData { @@ -80,12 +77,12 @@ func (n *Namespace) decorate(data render.TableData) render.TableData { return render.TableData{} } - log.Debug().Msgf("CLONING %q", data.Namespace) + // log.Debug().Msgf("CLONING %q", data.Namespace) // don't want to change the cache here thus need to clone!! - res := data.Clone() + // res := data.Clone() // checks if all ns is in the list if not add it. if _, ok := data.RowEvents.FindIndex(render.NamespaceAll); !ok { - res.RowEvents = append(render.RowEvents{ + data.RowEvents = append(data.RowEvents, render.RowEvent{ Kind: render.EventUnchanged, Row: render.Row{ @@ -93,11 +90,10 @@ func (n *Namespace) decorate(data render.TableData) render.TableData { Fields: render.Fields{render.NamespaceAll, "Active", "0"}, }, }, - }, - res.RowEvents...) + ) } - for _, re := range res.RowEvents { + for _, re := range data.RowEvents { if config.InList(n.App().Config.FavNamespaces(), re.Row.ID) { re.Row.Fields[0] += favNSIndicator re.Kind = render.EventUnchanged @@ -108,5 +104,5 @@ func (n *Namespace) decorate(data render.TableData) render.TableData { } } - return res + return data } diff --git a/internal/view/page_stack.go b/internal/view/page_stack.go index 260eca3e..311b7b8b 100644 --- a/internal/view/page_stack.go +++ b/internal/view/page_stack.go @@ -31,17 +31,19 @@ func (p *PageStack) Init(ctx context.Context) (err error) { } func (p *PageStack) StackPushed(c model.Component) { - ctx := context.WithValue(context.Background(), ui.KeyApp, p.app) - if err := c.Init(ctx); err != nil { - log.Error().Err(err).Msgf("Component Init failed!") - p.app.Flash().Err(err) - return - } + log.Debug().Msgf("Stack PUSHED!!!") + // ctx := context.WithValue(context.Background(), ui.KeyApp, p.app) + // if err := c.Init(ctx); err != nil { + // log.Error().Err(err).Msgf("Component Init failed!") + // p.app.Flash().Err(err) + // return + // } c.Start() p.app.SetFocus(c) } func (p *PageStack) StackPopped(o, top model.Component) { + log.Debug().Msgf("PS STACK POPPED!!!") o.Stop() p.StackTop(top) } diff --git a/internal/view/pod.go b/internal/view/pod.go index f208abb0..f7a1ab30 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -1,11 +1,16 @@ package view import ( + "context" "errors" + "fmt" + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" + "github.com/derailed/k9s/internal/watch" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" @@ -25,21 +30,17 @@ type Pod struct { } // NewPod returns a new viewer. -func NewPod(title, gvr string, list resource.List) ResourceViewer { - p := Pod{ - ResourceViewer: NewLogsExtender( - NewResource(podTitle, gvr, list), - func() string { return "" }, - ), - } - p.BindKeys() +func NewPod(gvr dao.GVR) ResourceViewer { + p := Pod{ResourceViewer: NewLogsExtender(NewBrowser(gvr), nil)} + p.SetBindKeysFn(p.bindKeys) p.GetTable().SetEnterFn(p.showContainers) + p.GetTable().SetColorerFn(render.Pod{}.ColorerFunc()) return &p } -func (p *Pod) BindKeys() { - p.Actions().Add(ui.KeyActions{ +func (p *Pod) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ tcell.KeyCtrlK: ui.NewKeyAction("Kill", p.killCmd, true), ui.KeyS: ui.NewKeyAction("Shell", p.shellCmd, true), ui.KeyShiftR: ui.NewKeyAction("Sort Ready", p.GetTable().SortColCmd(1, true), false), @@ -54,26 +55,18 @@ func (p *Pod) BindKeys() { }) } -func (p *Pod) showContainers(app *App, ns, res, sel string) { - ns, n := namespaced(sel) - o, err := p.App().factory.Get(ns, "v1/pods", n, labels.Everything()) - if err != nil { - app.Flash().Err(err) - log.Error().Err(err).Msgf("Pod %s not found", sel) - return - } - - var pod v1.Pod - if runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod); err != nil { - app.Flash().Err(err) - } - list := resource.NewContainerList(app.Conn(), &pod) - - // Spawn child view - p.App().inject(NewContainer(fqn(pod.Namespace, pod.Name), list)) +func (p *Pod) showContainers(app *App, ns, gvr, path string) { + log.Debug().Msgf("SHOW CONTAINERS %q -- %q -- %q", gvr, ns, path) + co := NewContainer(dao.GVR("containers")) + co.SetContextFn(p.podContext) + app.inject(co) } -// Protocol... +func (p *Pod) podContext(ctx context.Context) context.Context { + return context.WithValue(ctx, internal.KeyPath, p.GetTable().GetSelectedItem()) +} + +// Commands... func (p *Pod) killCmd(evt *tcell.EventKey) *tcell.EventKey { sels := p.GetTable().GetSelectedItems() @@ -81,13 +74,23 @@ func (p *Pod) killCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } + res, err := dao.AccessorFor(p.App().factory, dao.GVR(p.GVR())) + if err != nil { + p.App().Flash().Err(err) + return nil + } + nuker, ok := res.(dao.Nuker) + if !ok { + p.App().Flash().Err(fmt.Errorf("expecting a nuker for %q", p.GVR())) + return nil + } p.GetTable().ShowDeleted() for _, res := range sels { - p.App().Flash().Infof("Delete resource %s %s", p.List().GetName(), res) - if err := p.List().Resource().Delete(res, true, false); err != nil { + p.App().Flash().Infof("Delete resource %s -- %s", p.GVR(), res) + if err := nuker.Delete(res, true, false); err != nil { p.App().Flash().Errf("Delete failed with %s", err) } else { - p.App().forwarders.Kill(res) + p.App().factory.DeleteForwarder(res) } } p.Refresh() @@ -107,7 +110,7 @@ func (p *Pod) shellCmd(evt *tcell.EventKey) *tcell.EventKey { p.App().Flash().Errf("%s is not in a running state", sel) return nil } - cc, err := fetchContainers(p.List(), sel, false) + cc, err := fetchContainers(p.App().factory, sel, false) if err != nil { p.App().Flash().Errf("Unable to retrieve containers %s", err) return evt @@ -135,11 +138,28 @@ func (p *Pod) shellIn(path, co string) { // ---------------------------------------------------------------------------- // Helpers... -func fetchContainers(l resource.List, po string, includeInit bool) ([]string, error) { - if len(po) == 0 { - return []string{}, nil +func fetchContainers(f *watch.Factory, path string, includeInit bool) ([]string, error) { + o, err := f.Get("v1/pods", path, labels.Everything()) + if err != nil { + return nil, err } - return l.Resource().(resource.Containers).Containers(po, includeInit) + + var pod v1.Pod + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod) + if err != nil { + return nil, err + } + + nn := make([]string, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers)) + for _, c := range pod.Spec.Containers { + nn = append(nn, c.Name) + } + if includeInit { + for _, c := range pod.Spec.InitContainers { + nn = append(nn, c.Name) + } + } + return nn, nil } func shellIn(a *App, path, co string) { @@ -154,7 +174,7 @@ func computeShellArgs(path, co, context string, kcfg *string) []string { args := make([]string, 0, 15) args = append(args, "exec", "-it") args = append(args, "--context", context) - ns, po := namespaced(path) + ns, po := k8s.Namespaced(path) args = append(args, "-n", ns) args = append(args, po) if kcfg != nil && *kcfg != "" { diff --git a/internal/view/policy.go b/internal/view/policy.go index e46aad76..8088f920 100644 --- a/internal/view/policy.go +++ b/internal/view/policy.go @@ -1,18 +1,12 @@ package view import ( - "context" - "fmt" - "time" + "strings" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" ) const ( @@ -29,290 +23,275 @@ type ( // Policy presents a RBAC policy viewer. Policy struct { - *Table - - cancel context.CancelFunc - subjectKind string - subjectName string - cache render.RowEvents + ResourceViewer } ) // NewPolicy returns a new viewer. -func NewPolicy(app *App, subject, name string) *Policy { - return &Policy{ - Table: NewTable(policyTitle), - subjectKind: mapSubject(subject), - subjectName: name, +func NewPolicy(gvr dao.GVR) *Policy { + p := Policy{ + ResourceViewer: NewBrowser(gvr), } -} + p.GetTable().SetColorerFn(render.Policy{}.ColorerFunc()) + p.SetBindKeysFn(p.bindKeys) + p.GetTable().SetSortCol(1, len(render.Policy{}.Header(render.AllNamespaces)), false) -// Init the view. -func (p *Policy) Init(ctx context.Context) error { - p.Table.Path = p.subjectKind + ":" + p.subjectName - if err := p.Table.Init(ctx); err != nil { - return err - } - p.SetColorerFn(render.Policy{}.ColorerFunc()) - p.bindKeys() - p.SetSortCol(1, len(render.Policy{}.Header(render.AllNamespaces)), false) - p.refresh() - p.SelectRow(1, true) - - return nil + return &p } func (p *Policy) Name() string { return "policy" } -func (p *Policy) Start() { - p.Stop() - ctx, cancel := context.WithCancel(context.Background()) - p.cancel = cancel - go func(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - case <-time.After(time.Duration(p.app.Config.K9s.GetRefreshRate()) * time.Second): - p.refresh() - } - } - }(ctx) -} +// func (p *Policy) Start() { +// p.Stop() +// ctx, cancel := context.WithCancel(context.Background()) +// p.cancel = cancel +// go func(ctx context.Context) { +// for { +// select { +// case <-ctx.Done(): +// return +// case <-time.After(time.Duration(p.app.Config.K9s.GetRefreshRate()) * time.Second): +// p.refresh() +// } +// } +// }(ctx) +// } -func (p *Policy) bindKeys() { - p.Actions().Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) - p.Actions().Add(ui.KeyActions{ - tcell.KeyEscape: ui.NewKeyAction("Back", p.resetCmd, false), - ui.KeySlash: ui.NewKeyAction("Filter", p.activateCmd, false), - ui.KeyShiftP: ui.NewKeyAction("Sort Namespace", p.SortColCmd(0, true), false), - ui.KeyShiftN: ui.NewKeyAction("Sort Name", p.SortColCmd(1, true), false), - ui.KeyShiftO: ui.NewKeyAction("Sort Group", p.SortColCmd(2, true), false), - ui.KeyShiftB: ui.NewKeyAction("Sort Binding", p.SortColCmd(3, true), false), +func (p *Policy) bindKeys(aa ui.KeyActions) { + aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) + aa.Add(ui.KeyActions{ + // tcell.KeyEscape: ui.NewKeyAction("Back", p.resetCmd, false), + // ui.KeySlash: ui.NewKeyAction("Filter", p.activateCmd, false), + ui.KeyShiftP: ui.NewKeyAction("Sort Namespace", p.GetTable().SortColCmd(0, true), false), + ui.KeyShiftN: ui.NewKeyAction("Sort Name", p.GetTable().SortColCmd(1, true), false), + ui.KeyShiftO: ui.NewKeyAction("Sort Group", p.GetTable().SortColCmd(2, true), false), + ui.KeyShiftB: ui.NewKeyAction("Sort Binding", p.GetTable().SortColCmd(3, true), false), }) } func (p *Policy) getTitle() string { - return fmt.Sprintf(rbacTitleFmt, policyTitle, p.subjectKind+":"+p.subjectName, p.GetRowCount()) -} - -func (p *Policy) refresh() { - log.Debug().Msgf(">>>>>>>>>>>>>>> Refreshing Policies") // BOZO!! - defer func(t time.Time) { - log.Debug().Msgf("Policy Refresh elapsed %v", time.Since(t)) - }(time.Now()) - - data, err := p.reconcile() - if err != nil { - log.Error().Err(err).Msgf("Refresh for %s:%s", p.subjectKind, p.subjectName) - p.app.Flash().Err(err) - } - p.app.QueueUpdateDraw(func() { - p.Update(data) - }) + // return fmt.Sprintf(rbacTitleFmt, policyTitle, p.subjectKind+":"+p.subjectName, p.GetRowCount()) + return "" } -func (p *Policy) resetCmd(evt *tcell.EventKey) *tcell.EventKey { - if !p.SearchBuff().Empty() { - p.SearchBuff().Reset() - return nil - } +// func (p *Policy) refresh() { +// log.Debug().Msgf(">>>>>>>>>>>>>>> Refreshing Policies") +// // BOZO!! +// defer func(t time.Time) { +// log.Debug().Msgf("Policy Refresh elapsed %v", time.Since(t)) +// }(time.Now()) - return p.backCmd(evt) -} +// data, err := p.reconcile() +// if err != nil { +// log.Error().Err(err).Msgf("Refresh for %s:%s", p.subjectKind, p.subjectName) +// p.app.Flash().Err(err) +// } +// p.app.QueueUpdateDraw(func() { +// p.Update(data) +// }) +// } -func (p *Policy) backCmd(evt *tcell.EventKey) *tcell.EventKey { - if p.cancel != nil { - p.cancel() - } +// func (p *Policy) resetCmd(evt *tcell.EventKey) *tcell.EventKey { +// if !p.GetTable().SearchBuff().Empty() { +// p.GetTable().SearchBuff().Reset() +// return nil +// } - if p.SearchBuff().IsActive() { - p.SearchBuff().Reset() - return nil - } +// return p.backCmd(evt) +// } - return p.app.PrevCmd(evt) -} +// func (p *Policy) backCmd(evt *tcell.EventKey) *tcell.EventKey { +// if p.cancel != nil { +// p.cancel() +// } -func (p *Policy) reconcile() (render.TableData, error) { - // BOZO!! - defer func(t time.Time) { - log.Debug().Msgf("Policy Reconcile elapsed %v", time.Since(t)) - }(time.Now()) +// if p.SearchBuff().IsActive() { +// p.SearchBuff().Reset() +// return nil +// } - var table render.TableData +// return p.app.PrevCmd(evt) +// } - evts, errs := p.fetchClusterRoleBindings() - if len(errs) > 0 { - for _, err := range errs { - log.Error().Err(err).Msg("Unable to find cluster policies") - } - return table, errs[0] - } +// func (p *Policy) reconcile() (render.TableData, error) { +// // BOZO!! +// defer func(t time.Time) { +// log.Debug().Msgf("Policy Reconcile elapsed %v", time.Since(t)) +// }(time.Now()) - nevts, errs := p.namespacedPolicies() - if len(errs) > 0 { - for _, err := range errs { - log.Error().Err(err).Msg("Unable to find cluster policies") - } - return table, errs[0] - } +// var table render.TableData - for _, v := range nevts { - evts = append(evts, v) - } +// evts, errs := p.fetchClusterRoleBindings() +// if len(errs) > 0 { +// for _, err := range errs { +// log.Error().Err(err).Msg("Unable to find cluster policies") +// } +// return table, errs[0] +// } - return buildTable(p, evts), nil -} +// nevts, errs := p.namespacedPolicies() +// if len(errs) > 0 { +// for _, err := range errs { +// log.Error().Err(err).Msg("Unable to find cluster policies") +// } +// return table, errs[0] +// } + +// for _, v := range nevts { +// evts = append(evts, v) +// } + +// return buildTable(p, evts), nil +// } // Protocol... -func (p *Policy) Header() render.HeaderRow { - return render.Policy{}.Header(render.AllNamespaces) -} +// func (p *Policy) Header() render.HeaderRow { +// return render.Policy{}.Header(render.AllNamespaces) +// } -func (p *Policy) GetCache() render.RowEvents { - return p.cache -} +// func (p *Policy) GetCache() render.RowEvents { +// return p.cache +// } -func (p *Policy) SetCache(evts render.RowEvents) { - p.cache = evts -} +// func (p *Policy) SetCache(evts render.RowEvents) { +// p.cache = evts +// } -func (p *Policy) fetchClusterRoleBindings() (render.Rows, []error) { - var errs []error - oo, err := p.app.factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) - if err != nil { - return nil, append(errs, err) - } +// func (p *Policy) fetchClusterRoleBindings() (render.Rows, []error) { +// var errs []error +// oo, err := p.app.factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) +// if err != nil { +// return nil, append(errs, err) +// } - roles := make([]string, 0, len(oo)) - for _, o := range oo { - var crb rbacv1.ClusterRoleBinding - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) - if err != nil { - errs = append(errs, err) - continue - } - for _, s := range crb.Subjects { - if s.Kind == p.subjectKind && s.Name == p.subjectName { - roles = append(roles, crb.RoleRef.Name) - } - } - } +// roles := make([]string, 0, len(oo)) +// for _, o := range oo { +// var crb rbacv1.ClusterRoleBinding +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) +// if err != nil { +// errs = append(errs, err) +// continue +// } +// for _, s := range crb.Subjects { +// if s.Kind == p.subjectKind && s.Name == p.subjectName { +// roles = append(roles, crb.RoleRef.Name) +// } +// } +// } - rows := make(render.Rows, 0, len(oo)) - for _, role := range roles { - o, err := p.app.factory.Get(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterroles", role, labels.Everything()) - if err != nil { - return nil, append(errs, err) - } - var cr rbacv1.ClusterRole - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) - if err != nil { - errs = append(errs, err) - continue - } +// rows := make(render.Rows, 0, len(oo)) +// for _, role := range roles { +// o, err := p.app.factory.Get(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterroles", role, labels.Everything()) +// if err != nil { +// return nil, append(errs, err) +// } +// var cr rbacv1.ClusterRole +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) +// if err != nil { +// errs = append(errs, err) +// continue +// } - for _, v := range p.parseRules("*", "CR:"+role, cr.Rules) { - rows = append(rows, v) - } - } +// for _, v := range p.parseRules("*", "CR:"+role, cr.Rules) { +// rows = append(rows, v) +// } +// } - return rows, errs -} +// return rows, errs +// } -func (p *Policy) fetchRoleBindings() ([]namespacedRole, error) { - oo, err := p.app.factory.List(render.AllNamespaces, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) - if err != nil { - return nil, err - } +// func (p *Policy) fetchRoleBindings() ([]namespacedRole, error) { +// oo, err := p.app.factory.List(render.AllNamespaces, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) +// if err != nil { +// return nil, err +// } - rr := make([]namespacedRole, 0, len(oo)) - for _, o := range oo { - var rb rbacv1.RoleBinding - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) - if err != nil { - return nil, err - } - for _, s := range rb.Subjects { - if s.Kind == p.subjectKind && s.Name == p.subjectName { - rr = append(rr, namespacedRole{rb.Namespace, rb.RoleRef.Name}) - } - } - } +// rr := make([]namespacedRole, 0, len(oo)) +// for _, o := range oo { +// var rb rbacv1.RoleBinding +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) +// if err != nil { +// return nil, err +// } +// for _, s := range rb.Subjects { +// if s.Kind == p.subjectKind && s.Name == p.subjectName { +// rr = append(rr, namespacedRole{rb.Namespace, rb.RoleRef.Name}) +// } +// } +// } - return rr, nil -} +// return rr, nil +// } -func (p *Policy) fetchClusterRoles(errs []error, rr []namespacedRole) (render.Rows, []error) { - rows := make(render.Rows, 0, len(rr)) - for _, r := range rr { - o, err := p.app.factory.Get(r.ns, "rbac.authorization.k8s.io/v1/clusterroles", r.role, labels.Everything()) - if err != nil { - return nil, append(errs, err) - } +// func (p *Policy) fetchClusterRoles(errs []error, rr []namespacedRole) (render.Rows, []error) { +// rows := make(render.Rows, 0, len(rr)) +// for _, r := range rr { +// o, err := p.app.factory.Get(r.ns, "rbac.authorization.k8s.io/v1/clusterroles", r.role, labels.Everything()) +// if err != nil { +// return nil, append(errs, err) +// } - var cr rbacv1.ClusterRole - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) - if err != nil { - errs = append(errs, err) - continue - } - rows = append(rows, p.parseRules(r.ns, "RO:"+r.role, cr.Rules)...) - } +// var cr rbacv1.ClusterRole +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) +// if err != nil { +// errs = append(errs, err) +// continue +// } +// rows = append(rows, p.parseRules(r.ns, "RO:"+r.role, cr.Rules)...) +// } - return rows, errs -} +// return rows, errs +// } -func (p *Policy) namespacedPolicies() (render.Rows, []error) { - var errs []error - roles, err := p.fetchRoleBindings() - if err != nil { - errs = append(errs, err) - } +// func (p *Policy) namespacedPolicies() (render.Rows, []error) { +// var errs []error +// roles, err := p.fetchRoleBindings() +// if err != nil { +// errs = append(errs, err) +// } - return p.fetchClusterRoles(errs, roles) -} +// return p.fetchClusterRoles(errs, roles) +// } -func (p *Policy) parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Rows { - m := make(render.Rows, 0, len(rules)) - for _, r := range rules { - for _, grp := range r.APIGroups { - for _, res := range r.Resources { - k := res - if grp != "" { - k = res + "." + grp - } - for _, na := range r.ResourceNames { - n := fqn(k, na) - m = append(m, render.Row{ - ID: fqn(ns, n), - Fields: append(policyRow(ns, n, grp, binding), asVerbs(r.Verbs...)...), - }) - } - m = append(m, render.Row{ - ID: fqn(ns, k), - Fields: append(policyRow(ns, k, grp, binding), asVerbs(r.Verbs...)...), - }) - } - } - for _, nres := range r.NonResourceURLs { - if nres[0] != '/' { - nres = "/" + nres - } - m = append(m, render.Row{ - ID: fqn(ns, nres), - Fields: append(policyRow(ns, nres, "", binding), asVerbs(r.Verbs...)...), - }) - } - } +// func (p *Policy) parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Rows { +// m := make(render.Rows, 0, len(rules)) +// for _, r := range rules { +// for _, grp := range r.APIGroups { +// for _, res := range r.Resources { +// k := res +// if grp != "" { +// k = res + "." + grp +// } +// for _, na := range r.ResourceNames { +// n := fqn(k, na) +// m = append(m, render.Row{ +// ID: fqn(ns, n), +// Fields: append(policyRow(ns, n, grp, binding), asVerbs(r.Verbs)...), +// }) +// } +// m = append(m, render.Row{ +// ID: fqn(ns, k), +// Fields: append(policyRow(ns, k, grp, binding), asVerbs(r.Verbs)...), +// }) +// } +// } +// for _, nres := range r.NonResourceURLs { +// if nres[0] != '/' { +// nres = "/" + nres +// } +// m = append(m, render.Row{ +// ID: fqn(ns, nres), +// Fields: append(policyRow(ns, nres, "", binding), asVerbs(r.Verbs)...), +// }) +// } +// } - return m -} +// return m +// } func policyRow(ns, res, grp, binding string) render.Fields { if grp != "" { @@ -334,12 +313,69 @@ func mapSubject(subject string) string { } } -func showSAPolicy(app *App, _, _, selection string) { - _, n := namespaced(selection) - subject, err := mapFuSubject("ServiceAccount") - if err != nil { - app.Flash().Err(err) - return +// func showSAPolicy(app *App, _, _, selection string) { +// _, n := k8s.Namespaced(selection) +// subject, err := mapFuSubject("ServiceAccount") +// if err != nil { +// app.Flash().Err(err) +// return +// } +// app.inject(NewPolicy(app, subject, n)) +// } + +func toGroup(g string) string { + if g == "" { + return "v1" } - app.inject(NewPolicy(app, subject, n)) + return g +} + +func hasVerb(verbs []string, verb string) bool { + if len(verbs) == 1 && verbs[0] == render.ClusterWide { + return true + } + + for _, v := range verbs { + if hv, ok := httpTok8sVerbs[v]; ok { + if hv == verb { + return true + } + } + if v == verb { + return true + } + } + + return false +} + +func toVerbIcon(ok bool) string { + if ok { + return "[green::b] ✓ [::]" + } + return "[orangered::b] 𐄂 [::]" +} + +func asVerbs(verbs []string) []string { + const ( + verbLen = 4 + unknownLen = 30 + ) + + r := make([]string, 0, len(k8sVerbs)+1) + for _, v := range k8sVerbs { + r = append(r, toVerbIcon(hasVerb(verbs, v))) + } + + var unknowns []string + for _, v := range verbs { + if hv, ok := httpTok8sVerbs[v]; ok { + v = hv + } + if !hasVerb(k8sVerbs, v) && v != render.ClusterWide { + unknowns = append(unknowns, v) + } + } + + return append(r, render.Truncate(strings.Join(unknowns, ","), unknownLen)) } diff --git a/internal/view/port_forward.go b/internal/view/port_forward.go index 9bbae4c2..67e05b61 100644 --- a/internal/view/port_forward.go +++ b/internal/view/port_forward.go @@ -6,10 +6,11 @@ import ( "fmt" "time" + "github.com/derailed/k9s/internal" "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/resource" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" "github.com/fsnotify/fsnotify" @@ -24,98 +25,76 @@ const ( // PortForward presents active portforward viewer. type PortForward struct { - *Table + ResourceViewer bench *perf.Benchmark } // NewPortForward returns a new viewer. -func NewPortForward(title, gvr string, list resource.List) ResourceViewer { - return &PortForward{ - Table: NewTable(portForwardTitle), +func NewPortForward(gvr dao.GVR) ResourceViewer { + p := PortForward{ + ResourceViewer: NewBrowser(gvr), } + p.GetTable().SetBorderFocusColor(tcell.ColorDodgerBlue) + p.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorDodgerBlue, tcell.AttrNone) + p.GetTable().SetColorerFn(render.PortForward{}.ColorerFunc()) + p.GetTable().SetSortCol(p.GetTable().NameColIndex()+6, 0, true) + p.SetContextFn(p.portForwardContext) + p.SetBindKeysFn(p.bindKeys) + + return &p } -func (*PortForward) SetContextFn(ContextFunc) {} - -// Init the view. -func (p *PortForward) Init(ctx context.Context) error { - if err := p.Table.Init(ctx); err != nil { - return err - } - p.registerActions() - p.SetBorderFocusColor(tcell.ColorDodgerBlue) - p.SetSelectedStyle(tcell.ColorWhite, tcell.ColorDodgerBlue, tcell.AttrNone) - p.SetColorerFn(render.Forward{}.ColorerFunc()) - p.SetSortCol(p.NameColIndex()+6, 0, true) - p.Select(1, 0) - p.refresh() - - return nil +func (p *PortForward) portForwardContext(ctx context.Context) context.Context { + return context.WithValue(ctx, internal.KeyBenchCfg, p.App().Bench) } -// GVR returns a resource descriptor. -func (p *PortForward) GVR() string { - return "n/a" -} +// BOZO!! +// // Start runs the refresh loop. +// func (p *PortForward) Start() { +// path := ui.BenchConfig(p.App().Config.K9s.CurrentCluster) +// var ctx context.Context +// ctx, p.cancelFn = context.WithCancel(context.Background()) +// if err := watchFS(ctx, p.App(), config.K9sHome, path, p.reload); err != nil { +// p.App().Flash().Errf("RuRoh! Unable to watch benchmarks directory %s : %s", config.K9sHome, err) +// } +// } -// List returns the resource list. -func (p *PortForward) List() resource.List { return nil } +// // Name returns the component name. +// func (p *PortForward) Name() string { +// return portForwardTitle +// } -// GetTable returns the table view. -func (p *PortForward) GetTable() *Table { return p.Table } +// func (p *PortForward) reload() { +// path := ui.BenchConfig(p.App().Config.K9s.CurrentCluster) +// log.Debug().Msgf("Reloading Config %s", path) +// if err := p.App().Bench.Reload(path); err != nil { +// p.App().Flash().Err(err) +// } +// p.refresh() +// } -// SetEnvFn sets the k9s env vars. -func (p *PortForward) SetEnvFn(EnvFunc) {} +// func (p *PortForward) refresh() { +// p.Update(p.hydrate()) +// p.App().SetFocus(p) +// p.UpdateTitle() +// } -// SetPath sets parent selector. -func (p *PortForward) SetPath(s string) {} - -// Start runs the refresh loop. -func (p *PortForward) Start() { - path := ui.BenchConfig(p.app.Config.K9s.CurrentCluster) - var ctx context.Context - ctx, p.cancelFn = context.WithCancel(context.Background()) - if err := watchFS(ctx, p.app, config.K9sHome, path, p.reload); err != nil { - p.app.Flash().Errf("RuRoh! Unable to watch benchmarks directory %s : %s", config.K9sHome, err) - } -} - -// Name returns the component name. -func (p *PortForward) Name() string { - return portForwardTitle -} - -func (p *PortForward) reload() { - path := ui.BenchConfig(p.app.Config.K9s.CurrentCluster) - log.Debug().Msgf("Reloading Config %s", path) - if err := p.app.Bench.Reload(path); err != nil { - p.app.Flash().Err(err) - } - p.refresh() -} - -func (p *PortForward) refresh() { - p.Update(p.hydrate()) - p.app.SetFocus(p) - p.UpdateTitle() -} - -func (p *PortForward) registerActions() { - p.Actions().Add(ui.KeyActions{ +func (p *PortForward) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ tcell.KeyEnter: ui.NewKeyAction("Benchmarks", p.showBenchCmd, true), tcell.KeyCtrlB: ui.NewKeyAction("Bench", p.benchCmd, true), tcell.KeyCtrlK: ui.NewKeyAction("Bench Stop", p.benchStopCmd, true), tcell.KeyCtrlD: ui.NewKeyAction("Delete", p.deleteCmd, true), - ui.KeySlash: ui.NewKeyAction("Filter", p.activateCmd, false), - tcell.KeyEsc: ui.NewKeyAction("Back", p.app.PrevCmd, false), - ui.KeyShiftP: ui.NewKeyAction("Sort Ports", p.SortColCmd(2, true), false), - ui.KeyShiftU: ui.NewKeyAction("Sort URL", p.SortColCmd(4, true), false), + // ui.KeySlash: ui.NewKeyAction("Filter", p.activateCmd, false), + tcell.KeyEsc: ui.NewKeyAction("Back", p.App().PrevCmd, false), + ui.KeyShiftP: ui.NewKeyAction("Sort Ports", p.GetTable().SortColCmd(2, true), false), + ui.KeyShiftU: ui.NewKeyAction("Sort URL", p.GetTable().SortColCmd(4, true), false), }) } func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey { - p.app.inject(NewBench("", "", nil)) + p.App().inject(NewBenchmark("benchmarks")) return nil } @@ -123,10 +102,10 @@ func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey { func (p *PortForward) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey { if p.bench != nil { log.Debug().Msg(">>> Benchmark cancelFned!!") - p.app.status(ui.FlashErr, "Benchmark Camceled!") + p.App().status(ui.FlashErr, "Benchmark Camceled!") p.bench.Cancel() } - p.app.StatusReset() + p.App().StatusReset() return nil } @@ -138,26 +117,26 @@ func (p *PortForward) benchCmd(evt *tcell.EventKey) *tcell.EventKey { } if p.bench != nil { - p.app.Flash().Err(errors.New("Only one benchmark allowed at a time")) + p.App().Flash().Err(errors.New("Only one benchmark allowed at a time")) return nil } - r, _ := p.GetSelection() - cfg, co := defaultConfig(), ui.TrimCell(p.SelectTable, r, 2) - if b, ok := p.app.Bench.Benchmarks.Containers[containerID(sel, co)]; ok { + r, _ := p.GetTable().GetSelection() + cfg, co := defaultConfig(), ui.TrimCell(p.GetTable().SelectTable, r, 2) + if b, ok := p.App().Bench.Benchmarks.Containers[containerID(sel, co)]; ok { cfg = b } cfg.Name = sel - base := ui.TrimCell(p.SelectTable, r, 4) + base := ui.TrimCell(p.GetTable().SelectTable, r, 4) var err error if p.bench, err = perf.NewBenchmark(base, cfg); err != nil { - p.app.Flash().Errf("Bench failed %v", err) - p.app.StatusReset() + p.App().Flash().Errf("Bench failed %v", err) + p.App().StatusReset() return nil } - p.app.status(ui.FlashWarn, "Benchmark in progress...") + p.App().status(ui.FlashWarn, "Benchmark in progress...") log.Debug().Msg("Bench starting...") go p.runBenchmark() @@ -165,38 +144,38 @@ func (p *PortForward) benchCmd(evt *tcell.EventKey) *tcell.EventKey { } func (p *PortForward) runBenchmark() { - p.bench.Run(p.app.Config.K9s.CurrentCluster, func() { + p.bench.Run(p.App().Config.K9s.CurrentCluster, func() { log.Debug().Msg("Bench Completed!") - p.app.QueueUpdate(func() { + p.App().QueueUpdate(func() { if p.bench.Canceled() { - p.app.status(ui.FlashInfo, "Benchmark cancelFned") + p.App().status(ui.FlashInfo, "Benchmark cancelFned") } else { - p.app.status(ui.FlashInfo, "Benchmark Completed!") + p.App().status(ui.FlashInfo, "Benchmark Completed!") p.bench.Cancel() } p.bench = nil go func() { <-time.After(2 * time.Second) - p.app.QueueUpdate(func() { p.app.StatusReset() }) + p.App().QueueUpdate(func() { p.App().StatusReset() }) }() }) }) } func (p *PortForward) getSelectedItem() string { - r, _ := p.GetSelection() + r, _ := p.GetTable().GetSelection() if r == 0 { return "" } return fwFQN( - fqn(ui.TrimCell(p.SelectTable, r, 0), ui.TrimCell(p.SelectTable, r, 1)), - ui.TrimCell(p.SelectTable, r, 2), + fqn(ui.TrimCell(p.GetTable().SelectTable, r, 0), ui.TrimCell(p.GetTable().SelectTable, r, 1)), + ui.TrimCell(p.GetTable().SelectTable, r, 2), ) } func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { - if !p.SearchBuff().Empty() { - p.SearchBuff().Reset() + if !p.GetTable().SearchBuff().Empty() { + p.GetTable().SearchBuff().Reset() return nil } @@ -205,71 +184,70 @@ func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - showModal(p.app.Content.Pages, fmt.Sprintf("Delete PortForward `%s?", sel), func() { - stats := p.app.forwarders.Kill(sel) - log.Debug().Msgf("Deleted %d port-forwards", stats) - p.app.Flash().Infof("PortForward %s(%d) deleted!", sel, stats) - p.Update(p.hydrate()) + showModal(p.App().Content.Pages, fmt.Sprintf("Delete PortForward `%s?", sel), func() { + p.App().factory.DeleteForwarder(sel) + p.App().Flash().Infof("PortForward %s(%d) deleted!", sel) + p.GetTable().Refresh() }) return nil } -func (p *PortForward) hydrate() render.TableData { - var re render.Forward +// func (p *PortForward) hydrate() render.TableData { +// var re render.Forward - data := render.TableData{ - Header: re.Header(render.AllNamespaces), - RowEvents: make(render.RowEvents, 0, len(p.app.forwarders)), - Namespace: render.AllNamespaces, - } +// data := render.TableData{ +// Header: re.Header(render.AllNamespaces), +// RowEvents: make(render.RowEvents, 0, len(p.App().forwarders)), +// Namespace: render.AllNamespaces, +// } - containers := p.app.Bench.Benchmarks.Containers - for _, f := range p.app.forwarders { - fqn := containerID(f.Path(), f.Container()) - cfg := benchCfg{ - c: p.app.Bench.Benchmarks.Defaults.C, - n: p.app.Bench.Benchmarks.Defaults.N, - } - if config, ok := containers[fqn]; ok { - cfg.c, cfg.n = config.C, config.N - cfg.host, cfg.path = config.HTTP.Host, config.HTTP.Path - } +// containers := p.App().Bench.Benchmarks.Containers +// for _, f := range p.App().forwarders { +// fqn := containerID(f.Path(), f.Container()) +// cfg := benchCfg{ +// c: p.App().Bench.Benchmarks.Defaults.C, +// n: p.App().Bench.Benchmarks.Defaults.N, +// } +// if config, ok := containers[fqn]; ok { +// cfg.c, cfg.n = config.C, config.N +// cfg.host, cfg.path = config.HTTP.Host, config.HTTP.Path +// } - var row render.Row - fwd := forwarder{ - Forwarder: f, - BenchConfigurator: cfg, - } - if err := re.Render(fwd, render.AllNamespaces, &row); err != nil { - log.Error().Err(err).Msgf("PortForward render failed") - continue - } - data.RowEvents = append(data.RowEvents, render.RowEvent{Kind: render.EventAdd, Row: row}) - } +// var row render.Row +// fwd := forwarder{ +// Forwarder: f, +// BenchConfigurator: cfg, +// } +// if err := re.Render(fwd, render.AllNamespaces, &row); err != nil { +// log.Error().Err(err).Msgf("PortForward render failed") +// continue +// } +// data.RowEvents = append(data.RowEvents, render.RowEvent{Kind: render.EventAdd, Row: row}) +// } - return data -} +// return data +// } // ---------------------------------------------------------------------------- // Helpers... -var _ render.PortForwarder = forwarder{} +// var _ render.PortForwarder = forwarder{} -type forwarder struct { - render.Forwarder - render.BenchConfigurator -} +// type forwarder struct { +// render.Forwarder +// render.BenchConfigurator +// } -type benchCfg struct { - c, n int - host, path string -} +// type benchCfg struct { +// c, n int +// host, path string +// } -func (b benchCfg) C() int { return b.c } -func (b benchCfg) N() int { return b.n } -func (b benchCfg) Host() string { return b.host } -func (b benchCfg) HttpPath() string { return b.path } +// func (b benchCfg) C() int { return b.c } +// func (b benchCfg) N() int { return b.n } +// func (b benchCfg) Host() string { return b.host } +// func (b benchCfg) HttpPath() string { return b.path } func defaultConfig() config.BenchConfig { return config.BenchConfig{ diff --git a/internal/view/rbac.go b/internal/view/rbac.go index 6f8eaaef..e1120909 100644 --- a/internal/view/rbac.go +++ b/internal/view/rbac.go @@ -2,17 +2,14 @@ package view import ( "context" - "fmt" - "strings" - "time" + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" rbacv1 "k8s.io/api/rbac/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" @@ -23,7 +20,7 @@ const ( Role clusterWide = "*" - rbacTitle = "Rbac" + rbacTitle = "Policies" rbacTitleFmt = " [fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%d[fg:bg:-]][fg:bg:-] " ) @@ -49,288 +46,251 @@ type roleKind = int8 // Rbac presents an RBAC policy viewer. type Rbac struct { - *Table - - roleType roleKind - roleName string - path string - cache render.RowEvents + ResourceViewer } // NewRbac returns a new viewer. -func NewRbac(name string, kind roleKind, path string) *Rbac { - return &Rbac{ - Table: NewTable(rbacTitle), - roleName: name, - roleType: kind, - path: path, +func NewRbac(gvr dao.GVR) ResourceViewer { + log.Debug().Msgf(">>>>> NEWRBAC %v!!!!!", gvr) + r := Rbac{ + ResourceViewer: NewBrowser(gvr), } + r.GetTable().SetColorerFn(render.Rbac{}.ColorerFunc()) + r.SetBindKeysFn(r.bindKeys) + r.GetTable().SetSortCol(1, len(render.Rbac{}.Header(render.ClusterWide)), true) + + return &r } -// Init initializes the view. -func (r *Rbac) Init(ctx context.Context) error { - if err := r.Table.Init(ctx); err != nil { - return err - } - r.SetColorerFn(render.Rbac{}.ColorerFunc()) - r.bindKeys() - r.SetSortCol(1, len(r.Header()), true) - r.refresh() - - return nil +func (r *Rbac) showPolicies(app *App, ns, resource, selection string) { + log.Debug().Msgf("SHOWING!! %q--%q--%q", ns, resource, selection) } func (r *Rbac) UpdateTitle() { - r.SetTitle(ui.SkinTitle(fmt.Sprintf(rbacTitleFmt, rbacTitle, r.path, r.GetRowCount()-1), r.app.Styles.Frame())) + // BOZO!! + // r.GetTable().SetTitle(ui.SkinTitle(fmt.Sprintf(rbacTitleFmt, rbacTitle, r.path, r.GetRowCount()-1), r.app.Styles.Frame())) } -// Start watches for viewer updates -func (r *Rbac) Start() { - if r.app.Conn() == nil { - return - } - - r.Stop() - - var ctx context.Context - ctx, r.cancelFn = context.WithCancel(context.Background()) - - go func(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - case <-time.After(time.Duration(r.app.Config.K9s.GetRefreshRate()) * time.Second): - r.app.QueueUpdateDraw(func() { - r.refresh() - }) - } - } - }(ctx) -} - -// Name returns the component name. -func (r *Rbac) Name() string { - return rbacTitle -} - -func (r *Rbac) bindKeys() { - r.Actions().Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) - r.Actions().Add(ui.KeyActions{ - tcell.KeyEscape: ui.NewKeyAction("Reset", r.resetCmd, false), - ui.KeySlash: ui.NewKeyAction("Filter", r.activateCmd, false), - ui.KeyShiftO: ui.NewKeyAction("Sort APIGroup", r.SortColCmd(1, true), false), +func (r *Rbac) bindKeys(aa ui.KeyActions) { + aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) + aa.Add(ui.KeyActions{ + // BOZO!! + // tcell.KeyEscape: ui.NewKeyAction("Reset", r.resetCmd, false), + // ui.KeySlash: ui.NewKeyAction("Filter", r.activateCmd, false), + ui.KeyShiftO: ui.NewKeyAction("Sort APIGroup", r.GetTable().SortColCmd(1, true), false), }) } -func (r *Rbac) refresh() { - if r.app.Conn() == nil { - return - } - data, err := r.reconcile(r.roleName, r.roleType) - if err != nil { - log.Error().Err(err).Msgf("Refresh for %s:%d", r.roleName, r.roleType) - r.app.Flash().Err(err) - } - r.Update(data) - r.UpdateTitle() -} +// BOZO!! +// func (r *Rbac) refresh() { +// if r.app.Conn() == nil { +// return +// } +// data, err := r.reconcile(r.roleName, r.roleType) +// if err != nil { +// log.Error().Err(err).Msgf("Refresh for %s:%d", r.roleName, r.roleType) +// r.app.Flash().Err(err) +// } +// r.Update(data) +// r.UpdateTitle() +// } -func (r *Rbac) resetCmd(evt *tcell.EventKey) *tcell.EventKey { - if !r.SearchBuff().Empty() { - r.SearchBuff().Reset() - return nil - } +// func (r *Rbac) resetCmd(evt *tcell.EventKey) *tcell.EventKey { +// if !r.GetTable().SearchBuff().Empty() { +// r.GetTable().SearchBuff().Reset() +// return nil +// } - return r.backCmd(evt) -} +// return r.App().PrevCmd(evt) +// } -func (r *Rbac) backCmd(evt *tcell.EventKey) *tcell.EventKey { - if r.cancelFn != nil { - r.cancelFn() - } +// func (r *Rbac) backCmd(evt *tcell.EventKey) *tcell.EventKey { +// if r.cancelFn != nil { +// r.cancelFn() +// } - if r.SearchBuff().IsActive() { - r.SearchBuff().Reset() - return nil - } +// if r.SearchBuff().IsActive() { +// r.SearchBuff().Reset() +// return nil +// } - return r.app.PrevCmd(evt) -} +// return r.app.PrevCmd(evt) +// } -func (r *Rbac) reconcile(name string, kind roleKind) (render.TableData, error) { - var table render.TableData +// func (r *Rbac) reconcile(name string, kind roleKind) (render.TableData, error) { +// var table render.TableData - rows, err := r.fetchRoles(name, kind) - if err != nil { - return table, err - } +// rows, err := r.fetchRoles(name, kind) +// if err != nil { +// return table, err +// } - return buildTable(r, rows), nil -} +// return buildTable(r, rows), nil +// } -func (r *Rbac) Header() render.HeaderRow { - return render.Rbac{}.Header(render.AllNamespaces) -} +// func (r *Rbac) Header() render.HeaderRow { +// return render.Rbac{}.Header(render.AllNamespaces) +// } -func (r *Rbac) GetCache() render.RowEvents { - return r.cache -} +// func (r *Rbac) GetCache() render.RowEvents { +// return r.cache +// } -func (r *Rbac) SetCache(evts render.RowEvents) { - r.cache = evts -} +// func (r *Rbac) SetCache(evts render.RowEvents) { +// r.cache = evts +// } -func (r *Rbac) fetchRoles(name string, kind roleKind) (render.Rows, error) { - switch kind { - case ClusterRole: - return r.loadClusterRoles(name) - case Role: - return r.loadRoles(name) - default: - return nil, fmt.Errorf("Expecting clusterrole/role but found %d", kind) - } -} +// func (r *Rbac) fetchRoles(name string, kind roleKind) (render.Rows, error) { +// switch kind { +// case ClusterRole: +// return r.loadClusterRoles(name) +// case Role: +// return r.loadRoles(name) +// default: +// return nil, fmt.Errorf("Expecting clusterrole/role but found %d", kind) +// } +// } -func (r *Rbac) loadClusterRoles(name string) (render.Rows, error) { - o, err := r.app.factory.Get("-", "rbac.authorization.k8s.io/v1/clusterroles", name, labels.Everything()) - if err != nil { - return nil, err - } +// func (r *Rbac) loadClusterRoles(name string) (render.Rows, error) { +// o, err := r.app.factory.Get("-", "rbac.authorization.k8s.io/v1/clusterroles", name, labels.Everything()) +// if err != nil { +// return nil, err +// } - var cr rbacv1.ClusterRole - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) - if err != nil { - return nil, err - } +// var cr rbacv1.ClusterRole +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) +// if err != nil { +// return nil, err +// } - return r.parseRules(cr.Rules), nil -} +// return r.parseRules(cr.Rules), nil +// } -func (r *Rbac) loadRoles(path string) (render.Rows, error) { - ns, n := namespaced(path) - o, err := r.app.factory.Get(ns, "rbac.authorization.k8s.io/v1/roles", n, labels.Everything()) - if err != nil { - return nil, err - } +// func (r *Rbac) loadRoles(path string) (render.Rows, error) { +// ns, n := k8s.Namespaced(path) +// o, err := r.app.factory.Get(ns, "rbac.authorization.k8s.io/v1/roles", n, labels.Everything()) +// if err != nil { +// return nil, err +// } - var ro rbacv1.Role - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro) - if err != nil { - return nil, err - } +// var ro rbacv1.Role +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro) +// if err != nil { +// return nil, err +// } - return r.parseRules(ro.Rules), nil -} +// return r.parseRules(ro.Rules), nil +// } -func (r *Rbac) parseRules(rules []rbacv1.PolicyRule) render.Rows { - m := make(render.Rows, 0, len(rules)) - for _, rule := range rules { - for _, grp := range rule.APIGroups { - for _, res := range rule.Resources { - k := res - if grp != "" { - k = res + "." + grp - } - for _, na := range rule.ResourceNames { - m = m.Upsert(r.prepRow(fqn(k, na), grp, rule.Verbs)) - } - m = m.Upsert(r.prepRow(k, grp, rule.Verbs)) - } - } - for _, nres := range rule.NonResourceURLs { - if nres[0] != '/' { - nres = "/" + nres - } - m = m.Upsert(r.prepRow(nres, "", rule.Verbs)) - } - } +// func (r *Rbac) parseRules(rules []rbacv1.PolicyRule) render.Rows { +// m := make(render.Rows, 0, len(rules)) +// for _, rule := range rules { +// for _, grp := range rule.APIGroups { +// for _, res := range rule.Resources { +// k := res +// if grp != "" { +// k = res + "." + grp +// } +// for _, na := range rule.ResourceNames { +// m = m.Upsert(r.prepRow(fqn(k, na), grp, rule.Verbs)) +// } +// m = m.Upsert(r.prepRow(k, grp, rule.Verbs)) +// } +// } +// for _, nres := range rule.NonResourceURLs { +// if nres[0] != '/' { +// nres = "/" + nres +// } +// m = m.Upsert(r.prepRow(nres, "", rule.Verbs)) +// } +// } - return m -} +// return m +// } -func (r *Rbac) prepRow(res, grp string, verbs []string) render.Row { - if grp != "" { - grp = toGroup(grp) - } +// func (r *Rbac) prepRow(res, grp string, verbs []string) render.Row { +// if grp != "" { +// grp = toGroup(grp) +// } - fields := make(render.Fields, 0, len(r.Header())) - fields = append(fields, res, group) - return render.Row{ - ID: res, - Fields: append(fields, verbs...), - } -} +// fields := make(render.Fields, 0, len(r.Header())) +// fields = append(fields, res, group) +// return render.Row{ +// ID: res, +// Fields: append(fields, verbs...), +// } +// } -func asVerbs(verbs ...string) []string { - const ( - verbLen = 4 - unknownLen = 30 - ) +// func asVerbs(verbs ...string) []string { +// const ( +// verbLen = 4 +// unknownLen = 30 +// ) - r := make([]string, 0, len(k8sVerbs)+1) - for _, v := range k8sVerbs { - r = append(r, toVerbIcon(hasVerb(verbs, v))) - } +// r := make([]string, 0, len(k8sVerbs)+1) +// for _, v := range k8sVerbs { +// r = append(r, toVerbIcon(hasVerb(verbs, v))) +// } - var unknowns []string - for _, v := range verbs { - if hv, ok := httpTok8sVerbs[v]; ok { - v = hv - } - if !hasVerb(k8sVerbs, v) && v != clusterWide { - unknowns = append(unknowns, v) - } - } +// var unknowns []string +// for _, v := range verbs { +// if hv, ok := httpTok8sVerbs[v]; ok { +// v = hv +// } +// if !hasVerb(k8sVerbs, v) && v != clusterWide { +// unknowns = append(unknowns, v) +// } +// } - return append(r, resource.Truncate(strings.Join(unknowns, ","), unknownLen)) -} +// return append(r, resource.Truncate(strings.Join(unknowns, ","), unknownLen)) +// } -func toVerbIcon(ok bool) string { - if ok { - return "[green::b] ✓ [::]" - } - return "[orangered::b] 𐄂 [::]" -} +// func toVerbIcon(ok bool) string { +// if ok { +// return "[green::b] ✓ [::]" +// } +// return "[orangered::b] 𐄂 [::]" +// } -func hasVerb(verbs []string, verb string) bool { - if len(verbs) == 1 && verbs[0] == clusterWide { - return true - } +// func hasVerb(verbs []string, verb string) bool { +// if len(verbs) == 1 && verbs[0] == clusterWide { +// return true +// } - for _, v := range verbs { - if hv, ok := httpTok8sVerbs[v]; ok { - if hv == verb { - return true - } - } - if v == verb { - return true - } - } +// for _, v := range verbs { +// if hv, ok := httpTok8sVerbs[v]; ok { +// if hv == verb { +// return true +// } +// } +// if v == verb { +// return true +// } +// } - return false -} +// return false +// } -func toGroup(g string) string { - if g == "" { - return "v1" - } - return g -} +// func toGroup(g string) string { +// if g == "" { +// return "v1" +// } +// return g +// } func showRoleBinding(app *App, _, resource, selection string) { - ns, n := namespaced(selection) - rb, err := app.Conn().DialOrDie().RbacV1().RoleBindings(ns).Get(n, metav1.GetOptions{}) - if err != nil { - app.Flash().Errf("Unable to retrieve rolebindings for %s", selection) - return - } - app.inject(NewRbac(fqn(ns, rb.RoleRef.Name), Role, selection)) + // ns, n := k8s.Namespaced(selection) + // rb, err := app.Conn().DialOrDie().RbacV1().RoleBindings(ns).Get(n, metav1.GetOptions{}) + // if err != nil { + // app.Flash().Errf("Unable to retrieve rolebindings for %s", selection) + // return + // } + // BOZO!! + // app.inject(NewRbac(fqn(ns, rb.RoleRef.Name), Role, selection)) } -func showClusterRoleBinding(app *App, ns, resource, selection string) { - o, err := app.factory.Get("-", "rbac.authorization.k8s.io/v1/clusterrolebindings", selection, labels.Everything()) +func showClusterRoleBinding(app *App, ns, gvr, path string) { + o, err := app.factory.Get("rbac.authorization.k8s.io/v1/clusterrolebindings", path, labels.Everything()) if err != nil { app.Flash().Err(err) return @@ -339,7 +299,7 @@ func showClusterRoleBinding(app *App, ns, resource, selection string) { var crb rbacv1.ClusterRoleBinding err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) if err != nil { - app.Flash().Errf("Unable to retrieve clusterrolebindings for %s", selection) + app.Flash().Errf("Unable to retrieve clusterrolebindings for %s", path) return } @@ -347,13 +307,20 @@ func showClusterRoleBinding(app *App, ns, resource, selection string) { app.factory.ForResource("-", "rbac.authorization.k8s.io/v1/clusterroles") app.factory.WaitForCacheSync() - app.inject(NewRbac(crb.RoleRef.Name, ClusterRole, selection)) + // BOZO!! + // app.inject(NewRbac(crb.RoleRef.Name, ClusterRole, selection)) } -func showRBAC(app *App, ns, resource, selection string) { - kind := ClusterRole - if resource == "role" { - kind = Role - } - app.inject(NewRbac(selection, kind, selection)) +func showRBAC(app *App, _, gvr, path string) { + log.Debug().Msgf("Showing RBAC %q--%q", gvr, path) + v := NewRbac(dao.GVR("rbac")) + v.SetContextFn(rbacCtxt(app, gvr, path)) + app.inject(v) +} + +func rbacCtxt(app *App, gvr, path string) ContextFunc { + return func(ctx context.Context) context.Context { + ctx = context.WithValue(ctx, internal.KeyPath, path) + return context.WithValue(ctx, internal.KeyGVR, gvr) + } } diff --git a/internal/view/rc.go b/internal/view/rc.go index 6c92d510..f4041a12 100644 --- a/internal/view/rc.go +++ b/internal/view/rc.go @@ -1,52 +1,53 @@ package view -import ( - "github.com/derailed/k9s/internal/k8s" - "github.com/derailed/k9s/internal/resource" - "github.com/derailed/k9s/internal/ui" - "github.com/rs/zerolog/log" - v1 "k8s.io/api/core/v1" -) +// BOZO!! +// import ( +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// "github.com/derailed/k9s/internal/ui" +// "github.com/rs/zerolog/log" +// v1 "k8s.io/api/core/v1" +// ) -// ReplicationController represents a deployment view. -type ReplicationController struct { - ResourceViewer -} +// // ReplicationController represents a deployment view. +// type ReplicationController struct { +// ResourceViewer +// } -// NewReplicationController returns a new deployment view. -func NewReplicationController(title, gvr string, list resource.List) ResourceViewer { - d := ReplicationController{ - ResourceViewer: NewScaleExtender( - NewLogsExtender( - NewResource(title, gvr, list), - func() string { return "" }, - ), - ), - } - d.BindKeys() - d.GetTable().SetEnterFn(d.showPods) +// // NewReplicationController returns a new deployment view. +// func NewReplicationController(title, gvr string, list resource.List) ResourceViewer { +// d := ReplicationController{ +// ResourceViewer: NewScaleExtender( +// NewLogsExtender( +// NewResource(title, gvr, list), +// func() string { return "" }, +// ), +// ), +// } +// d.SetBindKeysFn(d.bindKeys) +// d.GetTable().SetEnterFn(d.showPods) - return &d -} +// return &d +// } -func (d *ReplicationController) BindKeys() { - d.Actions().Add(ui.KeyActions{ - ui.KeyShiftD: ui.NewKeyAction("Sort Desired", d.GetTable().SortColCmd(1, true), false), - ui.KeyShiftC: ui.NewKeyAction("Sort Current", d.GetTable().SortColCmd(2, true), false), - }) -} +// func (d *ReplicationController) bindKeys(aa ui.KeyActions) { +// aa.Add(ui.KeyActions{ +// ui.KeyShiftD: ui.NewKeyAction("Sort Desired", d.GetTable().SortColCmd(1, true), false), +// ui.KeyShiftC: ui.NewKeyAction("Sort Current", d.GetTable().SortColCmd(2, true), false), +// }) +// } -func (d *ReplicationController) showPods(app *App, _, res, sel string) { - ns, n := namespaced(sel) - nrc, err := k8s.NewReplicationController(app.Conn()).Get(ns, n) - if err != nil { - app.Flash().Err(err) - return - } +// func (d *ReplicationController) showPods(app *App, _, res, sel string) { +// ns, n := k8s.Namespaced(sel) +// nrc, err := k8s.NewReplicationController(app.Conn()).Get(ns, n) +// if err != nil { +// app.Flash().Err(err) +// return +// } - rc, ok := nrc.(*v1.ReplicationController) - if !ok { - log.Fatal().Msg("Expecting valid replication controller") - } - showPodsFromLabels(app, ns, rc.Spec.Selector) -} +// rc, ok := nrc.(*v1.ReplicationController) +// if !ok { +// log.Fatal().Msg("Expecting valid replication controller") +// } +// showPodsWithLabels(app, ns, rc.Spec.Selector) +// } diff --git a/internal/view/registrar.go b/internal/view/registrar.go index 5d7fbdc7..1002a801 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -13,12 +13,6 @@ import ( var aliases = config.NewAliases() -func resourceFn(l resource.List) ViewFunc { - return func(title, gvr string, list resource.List) ResourceViewer { - return NewResource(title, gvr, l) - } -} - func ToResource(o *unstructured.Unstructured, obj interface{}) error { return runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &obj) } @@ -63,7 +57,7 @@ func loadCustomViewers() MetaViewers { coreRes(m) miscRes(m) appsRes(m) - authRes(m) + rbacRes(m) extRes(m) netRes(m) batchRes(m) @@ -74,72 +68,76 @@ func loadCustomViewers() MetaViewers { } func coreRes(vv MetaViewers) { - vv["v1/nodes"] = MetaViewer{ - viewFn: NewNode, - listFn: resource.NewNodeList, - } vv["v1/namespaces"] = MetaViewer{ viewerFn: NewNamespace, } vv["v1/pods"] = MetaViewer{ - viewFn: NewPod, - listFn: resource.NewPodList, - } - vv["v1/serviceaccounts"] = MetaViewer{ - listFn: resource.NewServiceAccountList, - enterFn: showSAPolicy, + viewerFn: NewPod, } vv["v1/services"] = MetaViewer{ - viewFn: NewService, - listFn: resource.NewServiceList, + viewerFn: NewService, } - vv["v1/configmaps"] = MetaViewer{ - listFn: resource.NewConfigMapList, - } - vv["v1/persistentvolumes"] = MetaViewer{ - listFn: resource.NewPersistentVolumeList, - } - vv["v1/persistentvolumeclaims"] = MetaViewer{ - listFn: resource.NewPersistentVolumeClaimList, + vv["v1/nodes"] = MetaViewer{ + viewerFn: NewNode, } vv["v1/secrets"] = MetaViewer{ - viewFn: NewSecret, - listFn: resource.NewSecretList, - } - vv["v1/endpoints"] = MetaViewer{ - listFn: resource.NewEndpointsList, - } - vv["v1/events"] = MetaViewer{ - listFn: resource.NewEventList, - } - vv["v1/replicationcontrollers"] = MetaViewer{ - viewFn: NewReplicationController, - listFn: resource.NewReplicationControllerList, + viewerFn: NewSecret, } + + // vv["v1/serviceaccounts"] = MetaViewer{ + // listFn: resource.NewServiceAccountList, + // enterFn: showSAPolicy, + // } + // vv["v1/configmaps"] = MetaViewer{ + // listFn: resource.NewConfigMapList, + // } + // vv["v1/persistentvolumes"] = MetaViewer{ + // listFn: resource.NewPersistentVolumeList, + // } + // vv["v1/persistentvolumeclaims"] = MetaViewer{ + // listFn: resource.NewPersistentVolumeClaimList, + // } + // vv["v1/endpoints"] = MetaViewer{ + // listFn: resource.NewEndpointsList, + // } + // vv["v1/events"] = MetaViewer{ + // listFn: resource.NewEventList, + // } + // vv["v1/replicationcontrollers"] = MetaViewer{ + // viewFn: NewReplicationController, + // listFn: resource.NewReplicationControllerList, + // } } func miscRes(vv MetaViewers) { - vv["storage.k8s.io/v1/storageclasses"] = MetaViewer{ - listFn: resource.NewStorageClassList, - } vv["contexts"] = MetaViewer{ viewerFn: NewContext, } - vv["users"] = MetaViewer{ - viewFn: NewSubject, - } - vv["groups"] = MetaViewer{ - viewFn: NewSubject, + vv["containers"] = MetaViewer{ + viewerFn: NewContainer, } vv["portforwards"] = MetaViewer{ - viewFn: NewPortForward, - } - vv["benchmarks"] = MetaViewer{ - viewFn: NewBench, + viewerFn: NewPortForward, } vv["screendumps"] = MetaViewer{ viewerFn: NewScreenDump, } + + // vv["storage.k8s.io/v1/storageclasses"] = MetaViewer{ + // listFn: resource.NewStorageClassList, + // } + // vv["users"] = MetaViewer{ + // viewFn: NewSubject, + // } + // vv["groups"] = MetaViewer{ + // viewFn: NewSubject, + // } + vv["benchmarks"] = MetaViewer{ + viewerFn: NewBenchmark, + } + vv["aliases"] = MetaViewer{ + viewerFn: NewAlias, + } } func appsRes(vv MetaViewers) { @@ -160,53 +158,50 @@ func appsRes(vv MetaViewers) { } } -func authRes(vv MetaViewers) { +func rbacRes(vv MetaViewers) { + vv["rbac"] = MetaViewer{ + enterFn: showRBAC, + } vv["rbac.authorization.k8s.io/v1/clusterroles"] = MetaViewer{ - listFn: resource.NewClusterRoleList, + enterFn: showRBAC, + } + vv["rbac.authorization.k8s.io/v1/roles"] = MetaViewer{ enterFn: showRBAC, } vv["rbac.authorization.k8s.io/v1/clusterrolebindings"] = MetaViewer{ - listFn: resource.NewClusterRoleBindingList, - enterFn: showClusterRoleBinding, + enterFn: showRBAC, } vv["rbac.authorization.k8s.io/v1/rolebindings"] = MetaViewer{ - listFn: resource.NewRoleBindingList, - enterFn: showRoleBinding, - } - vv["rbac.authorization.k8s.io/v1/roles"] = MetaViewer{ - listFn: resource.NewRoleList, enterFn: showRBAC, } } func extRes(vv MetaViewers) { - vv["apiextensions.k8s.io/v1/customresourcedefinitions"] = MetaViewer{ - listFn: resource.NewCustomResourceDefinitionList, - enterFn: showCRD, - } - vv["apiextensions.k8s.io/v1beta1/customresourcedefinitions"] = MetaViewer{ - listFn: resource.NewCustomResourceDefinitionList, - enterFn: showCRD, - } + // vv["apiextensions.k8s.io/v1/customresourcedefinitions"] = MetaViewer{ + // listFn: resource.NewCustomResourceDefinitionList, + // enterFn: showCRD, + // } + // vv["apiextensions.k8s.io/v1beta1/customresourcedefinitions"] = MetaViewer{ + // listFn: resource.NewCustomResourceDefinitionList, + // enterFn: showCRD, + // } } func netRes(vv MetaViewers) { - vv["networking.k8s.io/v1/networkpolicies"] = MetaViewer{ - listFn: resource.NewNetworkPolicyList, - } - vv["extensions/v1beta1/ingresses"] = MetaViewer{ - listFn: resource.NewIngressList, - } + // vv["networking.k8s.io/v1/networkpolicies"] = MetaViewer{ + // listFn: resource.NewNetworkPolicyList, + // } + // vv["extensions/v1beta1/ingresses"] = MetaViewer{ + // listFn: resource.NewIngressList, + // } } func batchRes(vv MetaViewers) { vv["batch/v1beta1/cronjobs"] = MetaViewer{ - viewFn: NewCronJob, - listFn: resource.NewCronJobList, + viewerFn: NewCronJob, } vv["batch/v1/jobs"] = MetaViewer{ - viewFn: NewJob, - listFn: resource.NewJobList, + viewerFn: NewJob, } } diff --git a/internal/view/resource.go b/internal/view/resource.go index 2fc64e6d..6bb67d17 100644 --- a/internal/view/resource.go +++ b/internal/view/resource.go @@ -1,459 +1,457 @@ package view -import ( - "bytes" - "context" - "errors" - "fmt" - "strconv" - "time" +// BOZO!! +// import ( +// "bytes" +// "context" +// "errors" +// "fmt" +// "strconv" +// "time" - "github.com/atotto/clipboard" - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/resource" - "github.com/derailed/k9s/internal/ui" - "github.com/derailed/k9s/internal/ui/dialog" - "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/printers" -) +// "github.com/atotto/clipboard" +// "github.com/derailed/k9s/internal" +// "github.com/derailed/k9s/internal/config" +// "github.com/derailed/k9s/internal/k8s" +// "github.com/derailed/k9s/internal/resource" +// "github.com/derailed/k9s/internal/ui" +// "github.com/derailed/k9s/internal/ui/dialog" +// "github.com/gdamore/tcell" +// "github.com/rs/zerolog/log" +// "k8s.io/apimachinery/pkg/labels" +// "k8s.io/apimachinery/pkg/runtime" +// "k8s.io/cli-runtime/pkg/printers" +// ) -// Resource represents a generic resource viewer. -type Resource struct { - *Table +// // Resource represents a generic resource viewer. +// type Resource struct { +// *Table - namespaces map[int]string - list resource.List - path string - gvr string - envFn EnvFunc - currentNS string -} +// namespaces map[int]string +// list resource.List +// path string +// gvr string +// envFn EnvFunc +// currentNS string +// } -// NewResource returns a new viewer. -func NewResource(title, gvr string, list resource.List) ResourceViewer { - return &Resource{ - Table: NewTable(title), - list: list, - gvr: gvr, - } -} +// // NewResource returns a new viewer. +// func NewResource(title, gvr string, list resource.List) ResourceViewer { +// return &Resource{ +// Table: NewTable(title), +// list: list, +// gvr: gvr, +// } +// } -// Init watches all running pods in given namespace -func (r *Resource) Init(ctx context.Context) error { - log.Debug().Msgf(">>> RESOURCE INIT %s", r.list.GetName()) +// // Init watches all running pods in given namespace +// func (r *Resource) Init(ctx context.Context) error { +// log.Debug().Msgf(">>> RESOURCE INIT %s", r.list.GetName()) - if err := r.Table.Init(ctx); err != nil { - return err - } - r.envFn = r.defaultK9sEnv - r.Table.setFilterFn(r.filterResource) - r.setNamespace(r.App().Config.ActiveNamespace()) - r.refresh() - row, _ := r.GetSelection() - if row == 0 && r.GetRowCount() > 0 { - r.Select(1, 0) - } +// if err := r.Table.Init(ctx); err != nil { +// return err +// } +// r.envFn = r.defaultK9sEnv +// r.Table.setFilterFn(r.filterResource) +// r.setNamespace(r.App().Config.ActiveNamespace()) +// r.refresh() +// row, _ := r.GetSelection() +// if row == 0 && r.GetRowCount() > 0 { +// r.Select(1, 0) +// } - return nil -} +// return nil +// } -func (s *Resource) SetContextFn(ContextFunc) {} +// func (s *Resource) SetContextFn(ContextFunc) {} +// func (s *Resource) SetBindKeysFn(BindKeysFunc) {} -// GVR returns a resource descriptor. -func (r *Resource) GVR() string { - return r.gvr -} +// // GVR returns a resource descriptor. +// func (r *Resource) GVR() string { +// return r.gvr +// } -// SetPath sets parent selector. -func (r *Resource) SetPath(p string) { - r.path = p -} +// // SetPath sets parent selector. +// func (r *Resource) SetParentPath(p string) { +// r.path = p +// } -// GetTable returns the underlying table view. -func (r *Resource) GetTable() *Table { return r.Table } +// // GetTable returns the underlying table view. +// func (r *Resource) GetTable() *Table { return r.Table } -// SetEnvFn sets the function to pull current viewer env vars. -func (r *Resource) SetEnvFn(f EnvFunc) { - r.envFn = f -} +// // SetEnvFn sets the function to pull current viewer env vars. +// func (r *Resource) SetEnvFn(f EnvFunc) { +// r.envFn = f +// } -// Start initializes updates. -func (r *Resource) Start() { - r.Stop() +// // Start initializes updates. +// func (r *Resource) Start() { +// log.Debug().Msgf("RESOURCE START") +// r.Stop() - log.Debug().Msgf(">>>>>>> START %s", r.list.GetName()) - r.Table.Start() +// log.Debug().Msgf(">>>>>>> START %s", r.list.GetName()) +// r.Table.Start() - var ctx context.Context - ctx, r.cancelFn = context.WithCancel(context.Background()) - r.update(ctx) -} +// var ctx context.Context +// ctx, r.cancelFn = context.WithCancel(context.Background()) +// go r.update(ctx) +// } -// Name returns the component name. -func (r *Resource) Name() string { - return r.list.GetName() -} +// // Name returns the component name. +// func (r *Resource) Name() string { +// return r.list.GetName() +// } -func (r *Resource) List() resource.List { - return r.list -} +// func (r *Resource) List() resource.List { +// return r.list +// } -func (r *Resource) filterResource(sel string) { - r.list.SetLabelSelector(sel) - r.refresh() -} +// func (r *Resource) filterResource(sel string) { +// r.list.SetLabelSelector(sel) +// r.refresh() +// } -func (r *Resource) update(ctx context.Context) { - go func(ctx context.Context) { - for { - select { - case <-ctx.Done(): - log.Debug().Msgf("%s updater canceled!", r.list.GetName()) - return - case <-time.After(time.Duration(r.app.Config.K9s.GetRefreshRate()) * time.Second): - r.app.QueueUpdateDraw(func() { - r.refresh() - }) - } - } - }(ctx) -} +// func (r *Resource) update(ctx context.Context) { +// for { +// select { +// case <-ctx.Done(): +// log.Debug().Msgf("%s updater canceled!", r.list.GetName()) +// return +// case <-time.After(time.Duration(r.app.Config.K9s.GetRefreshRate()) * time.Second): +// r.app.QueueUpdateDraw(func() { +// r.refresh() +// }) +// } +// } +// } -// ---------------------------------------------------------------------------- -// Actions()... +// // ---------------------------------------------------------------------------- +// // Actions()... -func (r *Resource) cpCmd(evt *tcell.EventKey) *tcell.EventKey { - if !r.RowSelected() { - return evt - } +// func (r *Resource) cpCmd(evt *tcell.EventKey) *tcell.EventKey { +// if !r.RowSelected() { +// return evt +// } - _, n := namespaced(r.GetSelectedItem()) - log.Debug().Msgf("Copied selection to clipboard %q", n) - r.app.Flash().Info("Current selection copied to clipboard...") - if err := clipboard.WriteAll(n); err != nil { - r.app.Flash().Err(err) - } +// _, n := k8s.Namespaced(r.GetSelectedItem()) +// log.Debug().Msgf("Copied selection to clipboard %q", n) +// r.app.Flash().Info("Current selection copied to clipboard...") +// if err := clipboard.WriteAll(n); err != nil { +// r.app.Flash().Err(err) +// } - return nil -} +// return nil +// } -func (r *Resource) enterCmd(evt *tcell.EventKey) *tcell.EventKey { - log.Debug().Msgf("RES ENTER CMD...") - // If in command mode run filter otherwise enter function. - if r.filterCmd(evt) == nil || !r.RowSelected() { - return nil - } +// func (r *Resource) enterCmd(evt *tcell.EventKey) *tcell.EventKey { +// log.Debug().Msgf("RES ENTER CMD...") +// // If in command mode run filter otherwise enter function. +// if r.filterCmd(evt) == nil || !r.RowSelected() { +// return nil +// } - f := r.defaultEnter - if r.enterFn != nil { - log.Debug().Msgf("Found custom enter") - f = r.enterFn - } - f(r.app, r.list.GetNamespace(), r.list.GetName(), r.GetSelectedItem()) +// f := r.defaultEnter +// if r.enterFn != nil { +// log.Debug().Msgf("Found custom enter") +// f = r.enterFn +// } +// f(r.app, r.list.GetNamespace(), r.list.GetName(), r.GetSelectedItem()) - return nil -} +// return nil +// } -func (r *Resource) refreshCmd(*tcell.EventKey) *tcell.EventKey { - r.app.Flash().Info("Refreshing...") - r.refresh() - return nil -} +// func (r *Resource) refreshCmd(*tcell.EventKey) *tcell.EventKey { +// r.app.Flash().Info("Refreshing...") +// r.refresh() +// return nil +// } -func (r *Resource) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { - sel := r.GetSelectedItems() - if len(sel) == 0 { - return evt - } +// func (r *Resource) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { +// ss := r.GetSelectedItems() +// if len(ss) == 0 { +// return evt +// } - var msg string - if len(sel) > 1 { - msg = fmt.Sprintf("Delete %d marked %s?", len(sel), r.list.GetName()) - } else { - msg = fmt.Sprintf("Delete %s %s?", r.list.GetName(), sel[0]) - } - dialog.ShowDelete(r.app.Content.Pages, msg, func(cascade, force bool) { - r.ShowDeleted() - if len(sel) > 1 { - r.app.Flash().Infof("Delete %d marked %s", len(sel), r.list.GetName()) - } else { - r.app.Flash().Infof("Delete resource %s %s", r.list.GetName(), sel[0]) - } - for _, res := range sel { - if err := r.list.Resource().Delete(res, cascade, force); err != nil { - r.app.Flash().Errf("Delete failed with %s", err) - } else { - r.app.forwarders.Kill(res) - } - } - r.refresh() - }, func() {}) - return nil -} +// var msg string +// if len(ss) > 1 { +// msg = fmt.Sprintf("Delete %d marked %s?", len(ss), r.list.GetName()) +// } else { +// msg = fmt.Sprintf("Delete %s %s?", r.list.GetName(), ss[0]) +// } +// dialog.ShowDelete(r.app.Content.Pages, msg, func(cascade, force bool) { +// r.ShowDeleted() +// if len(ss) > 1 { +// r.app.Flash().Infof("Delete %d marked %s", len(ss), r.list.GetName()) +// } else { +// r.app.Flash().Infof("Delete resource %s %s", r.list.GetName(), ss[0]) +// } +// for _, s := range ss { +// if err := r.list.Resource().Delete(s, cascade, force); err != nil { +// r.app.Flash().Errf("Delete failed with %s", err) +// } else { +// r.app.factory.DeleteForwarder(s) +// } +// } +// r.refresh() +// }, func() {}) +// return nil +// } -func (r *Resource) defaultEnter(app *App, ns, _, sel string) { - if !r.list.Access(resource.DescribeAccess) { - return - } +// func (r *Resource) defaultEnter(app *App, ns, _, sel string) { +// if !r.list.Access(resource.DescribeAccess) { +// return +// } - yaml, err := r.list.Resource().Describe(r.gvr, sel) - if err != nil { - r.app.Flash().Errf("Describe command failed: %s", err) - return - } +// yaml, err := r.list.Resource().Describe(r.gvr, sel) +// if err != nil { +// r.app.Flash().Errf("Describe command failed: %s", err) +// return +// } - details := NewDetails("Describe") - details.SetSubject(sel) - details.SetTextColor(r.app.Styles.FgColor()) - details.SetText(colorizeYAML(r.app.Styles.Views().Yaml, yaml)) - details.ScrollToBeginning() - r.app.inject(details) -} +// details := NewDetails("Describe") +// details.SetSubject(sel) +// details.SetTextColor(r.app.Styles.FgColor()) +// details.SetText(colorizeYAML(r.app.Styles.Views().Yaml, yaml)) +// details.ScrollToBeginning() +// r.app.inject(details) +// } -func (r *Resource) describeCmd(evt *tcell.EventKey) *tcell.EventKey { - if !r.RowSelected() { - return evt - } - r.defaultEnter(r.app, r.list.GetNamespace(), r.list.GetName(), r.GetSelectedItem()) +// func (r *Resource) describeCmd(evt *tcell.EventKey) *tcell.EventKey { +// if !r.RowSelected() { +// return evt +// } +// r.defaultEnter(r.app, r.list.GetNamespace(), r.list.GetName(), r.GetSelectedItem()) - return nil -} +// return nil +// } -func (r *Resource) viewCmd(evt *tcell.EventKey) *tcell.EventKey { - if !r.RowSelected() { - return evt - } +// func (r *Resource) viewCmd(evt *tcell.EventKey) *tcell.EventKey { +// if !r.RowSelected() { +// return evt +// } - sel := r.GetSelectedItem() - ns, n := resource.Namespaced(sel) - if ns == "" { - ns = r.list.GetNamespace() - } - log.Debug().Msgf("------ NAMESPACES %q vs %q", ns, r.list.GetNamespace()) - o, err := r.app.factory.Get(ns, r.gvr, n, labels.Everything()) - if err != nil { - r.app.Flash().Errf("Unable to get resource %s", err) - return nil - } +// path := r.GetSelectedItem() +// log.Debug().Msgf("------ NAMESPACES %q vs %q", path, r.list.GetNamespace()) +// o, err := r.app.factory.Get(r.gvr, path, labels.Everything()) +// if err != nil { +// r.app.Flash().Errf("Unable to get resource %s", err) +// return nil +// } - raw, err := marshalObject(o) - if err != nil { - r.app.Flash().Errf("Unable to marshal resource %s", err) - return nil - } +// raw, err := marshalObject(o) +// if err != nil { +// r.app.Flash().Errf("Unable to marshal resource %s", err) +// return nil +// } - details := NewDetails("YAML") - details.SetSubject(sel) - details.SetTextColor(r.app.Styles.FgColor()) - details.SetText(colorizeYAML(r.app.Styles.Views().Yaml, raw)) - details.ScrollToBeginning() - r.app.inject(details) +// details := NewDetails("YAML") +// details.SetSubject(path) +// details.SetTextColor(r.app.Styles.FgColor()) +// details.SetText(colorizeYAML(r.app.Styles.Views().Yaml, raw)) +// details.ScrollToBeginning() +// r.app.inject(details) - return nil -} +// return nil +// } -func 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 - } +// func 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 -} +// return buff.String(), nil +// } -func (r *Resource) editCmd(evt *tcell.EventKey) *tcell.EventKey { - if !r.RowSelected() { - return evt - } +// func (r *Resource) editCmd(evt *tcell.EventKey) *tcell.EventKey { +// if !r.RowSelected() { +// return evt +// } - r.Stop() - defer r.Start() - { - ns, po := namespaced(r.GetSelectedItem()) - args := make([]string, 0, 10) - args = append(args, "edit") - args = append(args, r.list.GetName()) - args = append(args, "-n", ns) - args = append(args, "--context", r.app.Config.K9s.CurrentContext) - if cfg := r.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { - args = append(args, "--kubeconfig", *cfg) - } - if !runK(true, r.app, append(args, po)...) { - r.app.Flash().Err(errors.New("Edit exec failed")) - } - } +// r.Stop() +// defer r.Start() +// { +// ns, po := k8s.Namespaced(r.GetSelectedItem()) +// args := make([]string, 0, 10) +// args = append(args, "edit") +// args = append(args, r.list.GetName()) +// args = append(args, "-n", ns) +// args = append(args, "--context", r.app.Config.K9s.CurrentContext) +// if cfg := r.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { +// args = append(args, "--kubeconfig", *cfg) +// } +// if !runK(true, r.app, append(args, po)...) { +// r.app.Flash().Err(errors.New("Edit exec failed")) +// } +// } - return evt -} +// return evt +// } -func (r *Resource) setNamespace(ns string) { - log.Debug().Msgf("!!!!!! SETTING NS %q", ns) - if r.list.Namespaced() { - r.currentNS = ns - r.list.SetNamespace(ns) - } -} +// func (r *Resource) setNamespace(ns string) { +// log.Debug().Msgf("!!!!!! SETTING NS %q", ns) +// if r.list.Namespaced() { +// r.currentNS = ns +// r.list.SetNamespace(ns) +// } +// } -func (r *Resource) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { - i, _ := strconv.Atoi(string(evt.Rune())) - ns := r.namespaces[i] - if ns == "" { - ns = resource.AllNamespace - } - if r.currentNS == ns { - return nil - } +// func (r *Resource) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { +// i, _ := strconv.Atoi(string(evt.Rune())) +// ns := r.namespaces[i] +// if ns == "" { +// ns = resource.AllNamespace +// } +// if r.currentNS == ns { +// return nil +// } - r.app.switchNS(ns) - r.setNamespace(ns) - r.app.Flash().Infof("Viewing namespace `%s`...", ns) - r.refresh() - r.UpdateTitle() - r.SelectRow(1, true) - r.app.CmdBuff().Reset() - if err := r.app.Config.SetActiveNamespace(r.currentNS); err != nil { - log.Error().Err(err).Msg("Config save NS failed!") - } - if err := r.app.Config.Save(); err != nil { - log.Error().Err(err).Msg("Config save failed!") - } +// r.app.switchNS(ns) +// r.setNamespace(ns) +// r.app.Flash().Infof("Viewing namespace `%s`...", ns) +// r.refresh() +// r.UpdateTitle() +// r.SelectRow(1, true) +// r.app.CmdBuff().Reset() +// if err := r.app.Config.SetActiveNamespace(r.currentNS); err != nil { +// log.Error().Err(err).Msg("Config save NS failed!") +// } +// if err := r.app.Config.Save(); err != nil { +// log.Error().Err(err).Msg("Config save failed!") +// } - return nil -} +// return nil +// } -func (r *Resource) refresh() { - log.Debug().Msgf("----> Refreshing (%q) -- %q -- `%s", r.currentNS, r.list.GetNamespace(), r.list.GetName()) - if r.list.Namespaced() { - r.list.SetNamespace(r.currentNS) - } +// func (r *Resource) refresh() { +// log.Debug().Msgf("----> Refreshing (%q) -- %q -- `%s", r.currentNS, r.list.GetNamespace(), r.list.GetName()) +// if r.list.Namespaced() { +// r.list.SetNamespace(r.currentNS) +// } - if r.app.Conn() == nil { - log.Error().Msg("No api connection") - return - } +// if r.app.Conn() == nil { +// log.Error().Msg("No api connection") +// return +// } - ctx := context.WithValue(context.Background(), internal.KeyFactory, r.app.factory) - ctx = context.WithValue(ctx, internal.KeySelection, r.path) - if err := r.list.Reconcile(ctx, r.gvr); err != nil { - r.app.Flash().Err(err) - } +// ctx := context.WithValue(context.Background(), internal.KeyFactory, r.app.factory) +// ctx = context.WithValue(ctx, internal.KeyPath, r.path) +// if err := r.list.Reconcile(ctx, r.gvr); err != nil { +// r.app.Flash().Err(err) +// } - data := r.list.Data() - // BOZO!! - // if r.decorateFn != nil { - // data = r.decorateFn(data) - // } - r.refreshActions() - r.Update(data) -} +// data := r.list.Data() +// // BOZO!! +// // if r.decorateFn != nil { +// // data = r.decorateFn(data) +// // } +// r.refreshActions() +// r.Update(data) +// } -func (r *Resource) namespaceActions(aa ui.KeyActions) { - if r.app.Conn() == nil || !r.list.Access(resource.NamespaceAccess) { - return - } - r.namespaces = make(map[int]string, config.MaxFavoritesNS) - aa[tcell.Key(ui.NumKeys[0])] = ui.NewKeyAction(resource.AllNamespace, r.switchNamespaceCmd, true) - r.namespaces[0] = resource.AllNamespace - index := 1 - for _, n := range r.app.Config.FavNamespaces() { - if n == resource.AllNamespace { - continue - } - aa[tcell.Key(ui.NumKeys[index])] = ui.NewKeyAction(n, r.switchNamespaceCmd, true) - r.namespaces[index] = n - index++ - } -} +// func (r *Resource) namespaceActions(aa ui.KeyActions) { +// if r.app.Conn() == nil || !r.list.Access(resource.NamespaceAccess) { +// return +// } +// r.namespaces = make(map[int]string, config.MaxFavoritesNS) +// aa[tcell.Key(ui.NumKeys[0])] = ui.NewKeyAction(resource.AllNamespace, r.switchNamespaceCmd, true) +// r.namespaces[0] = resource.AllNamespace +// index := 1 +// for _, n := range r.app.Config.FavNamespaces() { +// if n == resource.AllNamespace { +// continue +// } +// aa[tcell.Key(ui.NumKeys[index])] = ui.NewKeyAction(n, r.switchNamespaceCmd, true) +// r.namespaces[index] = n +// index++ +// } +// } -func (r *Resource) refreshActions() { - aa := ui.KeyActions{ - ui.KeyC: ui.NewKeyAction("Copy", r.cpCmd, false), - tcell.KeyEnter: ui.NewKeyAction("Enter", r.enterCmd, false), - tcell.KeyCtrlR: ui.NewKeyAction("Refresh", r.refreshCmd, false), - } - r.namespaceActions(aa) +// func (r *Resource) refreshActions() { +// aa := ui.KeyActions{ +// ui.KeyC: ui.NewKeyAction("Copy", r.cpCmd, false), +// tcell.KeyEnter: ui.NewKeyAction("Enter", r.enterCmd, false), +// tcell.KeyCtrlR: ui.NewKeyAction("Refresh", r.refreshCmd, false), +// } +// r.namespaceActions(aa) - if r.list.Access(resource.EditAccess) { - aa[ui.KeyE] = ui.NewKeyAction("Edit", r.editCmd, true) - } - if r.list.Access(resource.DeleteAccess) { - aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", r.deleteCmd, true) - } - if r.list.Access(resource.ViewAccess) { - aa[ui.KeyY] = ui.NewKeyAction("YAML", r.viewCmd, true) - } - if r.list.Access(resource.DescribeAccess) { - aa[ui.KeyD] = ui.NewKeyAction("Describe", r.describeCmd, true) - } - r.customActions(aa) - r.Actions().Set(aa) -} +// if r.list.Access(resource.EditAccess) { +// aa[ui.KeyE] = ui.NewKeyAction("Edit", r.editCmd, true) +// } +// if r.list.Access(resource.DeleteAccess) { +// aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", r.deleteCmd, true) +// } +// if r.list.Access(resource.ViewAccess) { +// aa[ui.KeyY] = ui.NewKeyAction("YAML", r.viewCmd, true) +// } +// if r.list.Access(resource.DescribeAccess) { +// aa[ui.KeyD] = ui.NewKeyAction("Describe", r.describeCmd, true) +// } +// r.customActions(aa) +// r.Actions().Set(aa) +// } -func (r *Resource) customActions(aa ui.KeyActions) { - pp := config.NewPlugins() - if err := pp.Load(); err != nil { - log.Warn().Msgf("No plugin configuration found") - return - } +// func (r *Resource) customActions(aa ui.KeyActions) { +// pp := config.NewPlugins() +// if err := pp.Load(); err != nil { +// log.Warn().Msgf("No plugin configuration found") +// return +// } - for k, plugin := range pp.Plugin { - if !in(plugin.Scopes, r.list.GetName()) { - continue - } - key, err := asKey(plugin.ShortCut) - if err != nil { - log.Error().Err(err).Msg("Unable to map shortcut to a key") - continue - } - _, ok := aa[key] - if ok { - log.Error().Err(fmt.Errorf("Doh! you are trying to overide an existing command `%s", k)).Msg("Invalid shortcut") - continue - } - aa[key] = ui.NewKeyAction( - plugin.Description, - r.execCmd(plugin.Command, plugin.Background, plugin.Args...), - true) - } -} +// for k, plugin := range pp.Plugin { +// if !in(plugin.Scopes, r.list.GetName()) { +// continue +// } +// key, err := asKey(plugin.ShortCut) +// if err != nil { +// log.Error().Err(err).Msg("Unable to map shortcut to a key") +// continue +// } +// _, ok := aa[key] +// if ok { +// log.Error().Err(fmt.Errorf("Doh! you are trying to overide an existing command `%s", k)).Msg("Invalid shortcut") +// continue +// } +// aa[key] = ui.NewKeyAction( +// plugin.Description, +// r.execCmd(plugin.Command, plugin.Background, plugin.Args...), +// true) +// } +// } -func (r *Resource) execCmd(bin string, bg bool, args ...string) ui.ActionHandler { - return func(evt *tcell.EventKey) *tcell.EventKey { - if !r.RowSelected() { - return evt - } +// func (r *Resource) execCmd(bin string, bg bool, args ...string) ui.ActionHandler { +// return func(evt *tcell.EventKey) *tcell.EventKey { +// if !r.RowSelected() { +// return evt +// } - var ( - env = r.envFn() - aa = make([]string, len(args)) - err error - ) - for i, a := range args { - aa[i], err = env.envFor(a) - if err != nil { - log.Error().Err(err).Msg("Args match failed") - return nil - } - } +// var ( +// env = r.envFn() +// aa = make([]string, len(args)) +// err error +// ) +// for i, a := range args { +// aa[i], err = env.envFor(a) +// if err != nil { +// log.Error().Err(err).Msg("Args match failed") +// return nil +// } +// } - if run(true, r.app, bin, bg, aa...) { - r.app.Flash().Info("Custom CMD launched!") - } else { - r.app.Flash().Info("Custom CMD failed!") - } - return nil - } -} +// if run(true, r.app, bin, bg, aa...) { +// r.app.Flash().Info("Custom CMD launched!") +// } else { +// r.app.Flash().Info("Custom CMD failed!") +// } +// return nil +// } +// } -func (r *Resource) defaultK9sEnv() K9sEnv { - return defaultK9sEnv(r.app, r.GetSelectedItem(), r.GetRow()) -} +// func (r *Resource) defaultK9sEnv() K9sEnv { +// return defaultK9sEnv(r.app, r.GetSelectedItem(), r.GetRow()) +// } diff --git a/internal/view/restart_extender.go b/internal/view/restart_extender.go index 4ec93739..8cd331f5 100644 --- a/internal/view/restart_extender.go +++ b/internal/view/restart_extender.go @@ -15,15 +15,15 @@ type RestartExtender struct { } // NewRestartExtender returns a new extender. -func NewRestartExtender(r ResourceViewer) ResourceViewer { - re := RestartExtender{ResourceViewer: r} - re.BindKeys() +func NewRestartExtender(v ResourceViewer) ResourceViewer { + r := RestartExtender{ResourceViewer: v} + r.SetBindKeysFn(r.bindKeys) - return &re + return &r } // BindKeys creates additional menu actions. -func (r *RestartExtender) BindKeys() { +func (r *RestartExtender) bindKeys(aa ui.KeyActions) { r.Actions().Add(ui.KeyActions{ tcell.KeyCtrlT: ui.NewKeyAction("Restart", r.restartCmd, true), }) @@ -50,7 +50,6 @@ func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey { } func (r *RestartExtender) restartRollout(path string) error { - ns, n := namespaced(path) res, err := dao.AccessorFor(r.App().factory, dao.GVR(r.GVR())) if err != nil { return nil @@ -61,5 +60,5 @@ func (r *RestartExtender) restartRollout(path string) error { return errors.New("resource is not restartable") } - return s.Restart(ns, n) + return s.Restart(path) } diff --git a/internal/view/rs.go b/internal/view/rs.go index aae55e36..4f81aa28 100644 --- a/internal/view/rs.go +++ b/internal/view/rs.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/watch" @@ -30,7 +31,7 @@ type ReplicaSet struct { // NewReplicaSet returns a new viewer. func NewReplicaSet(gvr dao.GVR) ResourceViewer { r := ReplicaSet{ - ResourceViewer: NewGeneric(gvr), + ResourceViewer: NewBrowser(gvr), } r.bindKeys() r.GetTable().SetEnterFn(r.showPods) @@ -47,9 +48,8 @@ func (r *ReplicaSet) bindKeys() { }) } -func (r *ReplicaSet) showPods(app *App, _, res, sel string) { - ns, n := namespaced(sel) - o, err := app.factory.Get(ns, r.GVR(), n, labels.Everything()) +func (r *ReplicaSet) showPods(app *App, _, gvr, path string) { + o, err := app.factory.Get(r.GVR(), path, labels.Everything()) if err != nil { app.Flash().Err(err) return @@ -61,7 +61,7 @@ func (r *ReplicaSet) showPods(app *App, _, res, sel string) { app.Flash().Err(err) } - showPodsFromSelector(app, ns, rs.Spec.Selector) + showPodsFromSelector(app, path, rs.Spec.Selector) } func (r *ReplicaSet) rollbackCmd(evt *tcell.EventKey) *tcell.EventKey { @@ -70,9 +70,9 @@ func (r *ReplicaSet) rollbackCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - r.showModal(fmt.Sprintf("Rollback %s %s?", r.List().GetName(), sel), func(_ int, button string) { + r.showModal(fmt.Sprintf("Rollback %s %s?", r.GVR(), sel), func(_ int, button string) { if button == "OK" { - r.App().Flash().Infof("Rolling back %s %s", r.List().GetName(), sel) + r.App().Flash().Infof("Rolling back %s %s", r.GVR(), sel) if res, err := rollback(r.App().factory, sel); err != nil { r.App().Flash().Err(err) } else { @@ -103,8 +103,8 @@ func (r *ReplicaSet) showModal(msg string, done func(int, string)) { // ---------------------------------------------------------------------------- // Helpers... -func findRS(f *watch.Factory, ns, n string) (*v1.ReplicaSet, error) { - o, err := f.Get(ns, "apps/v1/replicasets", n, labels.Everything()) +func findRS(f *watch.Factory, path string) (*v1.ReplicaSet, error) { + o, err := f.Get("apps/v1/replicasets", path, labels.Everything()) if err != nil { return nil, err } @@ -118,8 +118,8 @@ func findRS(f *watch.Factory, ns, n string) (*v1.ReplicaSet, error) { return &rs, nil } -func findDP(f *watch.Factory, ns, n string) (*appsv1.Deployment, error) { - o, err := f.Get(ns, "apps/v1/deployments", n, labels.Everything()) +func findDP(f *watch.Factory, path string) (*appsv1.Deployment, error) { + o, err := f.Get("apps/v1/deployments", path, labels.Everything()) if err != nil { return nil, err } @@ -162,9 +162,8 @@ func getRevision(rs *v1.ReplicaSet) (int64, error) { return int64(vers), nil } -func rollback(f *watch.Factory, selectedItem string) (string, error) { - ns, n := namespaced(selectedItem) - rs, err := findRS(f, ns, n) +func rollback(f *watch.Factory, path string) (string, error) { + rs, err := findRS(f, path) if err != nil { return "", err } @@ -181,7 +180,7 @@ func rollback(f *watch.Factory, selectedItem string) (string, error) { if err != nil { return "", err } - dp, err := findDP(f, ns, name) + dp, err := findDP(f, k8s.FQN(rs.Namespace, name)) if err != nil { return "", err } diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go index 7f3d305c..69171b1c 100644 --- a/internal/view/scale_extender.go +++ b/internal/view/scale_extender.go @@ -18,13 +18,13 @@ type ScaleExtender struct { func NewScaleExtender(r ResourceViewer) ResourceViewer { s := ScaleExtender{ResourceViewer: r} - s.BindKeys() + s.bindKeys(s.Actions()) return &s } -func (s *ScaleExtender) BindKeys() { - s.Actions().Add(ui.KeyActions{ +func (s *ScaleExtender) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ ui.KeyS: ui.NewKeyAction("Scale", s.scaleCmd, true), }) } @@ -103,16 +103,14 @@ func (s *ScaleExtender) makeStyledForm() *tview.Form { } func (s *ScaleExtender) scale(path string, replicas int) error { - ns, n := namespaced(path) res, err := dao.AccessorFor(s.App().factory, dao.GVR(s.GVR())) if err != nil { return nil } - log.Debug().Msgf("SCALER %#v", res) scaler, ok := res.(dao.Scalable) if !ok { return fmt.Errorf("expecting a scalable resource for %q", s.GVR()) } - return scaler.Scale(ns, n, int32(replicas)) + return scaler.Scale(path, int32(replicas)) } diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go index 2b55d085..c4261172 100644 --- a/internal/view/screen_dump.go +++ b/internal/view/screen_dump.go @@ -24,7 +24,7 @@ type ScreenDump struct { // NewScreenDump returns a new viewer. func NewScreenDump(gvr dao.GVR) ResourceViewer { s := ScreenDump{ - ResourceViewer: NewGeneric(gvr), + ResourceViewer: NewBrowser(gvr), } // BOZO!! Rename Table s.GetTable().SetBorderFocusColor(tcell.ColorSteelBlue) @@ -38,19 +38,21 @@ func NewScreenDump(gvr dao.GVR) ResourceViewer { return &s } -// Start starts the directory watcher. -func (s *ScreenDump) Start() { - s.Stop() +// BOZO!! +// BOZO !! Need model watcher! +// // Start starts the directory watcher. +// func (s *ScreenDump) Start() { +// s.Stop() - s.GetTable().Actions().Delete(tcell.KeyCtrlS) +// s.GetTable().Actions().Delete(tcell.KeyCtrlS) - s.GetTable().Start() - var ctx context.Context - ctx, s.GetTable().cancelFn = context.WithCancel(context.Background()) - if err := s.watchDumpDir(ctx); err != nil { - s.App().Flash().Errf("Unable to watch screen dumps directory %s", err) - } -} +// s.GetTable().Start() +// var ctx context.Context +// ctx, s.GetTable().cancelFn = context.WithCancel(context.Background()) +// if err := s.watchDumpDir(ctx); err != nil { +// s.App().Flash().Errf("Unable to watch screen dumps directory %s", err) +// } +// } func (s *ScreenDump) dirContext(ctx context.Context) context.Context { dir := filepath.Join(config.K9sDumpDir, s.App().Config.K9s.CurrentCluster) diff --git a/internal/view/secret.go b/internal/view/secret.go index c3cc0b6b..675050cf 100644 --- a/internal/view/secret.go +++ b/internal/view/secret.go @@ -1,14 +1,15 @@ package view import ( - "context" - "sigs.k8s.io/yaml" - "github.com/derailed/k9s/internal/resource" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" ) // Secret presents a secret viewer. @@ -17,42 +18,42 @@ type Secret struct { } // NewSecrets returns a new viewer. -func NewSecret(title, gvr string, list resource.List) ResourceViewer { - return &Secret{ - ResourceViewer: NewResource(title, gvr, list), +func NewSecret(gvr dao.GVR) ResourceViewer { + s := Secret{ + ResourceViewer: NewBrowser(gvr), } + s.SetBindKeysFn(s.bindKeys) + + return &s } -func (s *Secret) Init(ctx context.Context) error { - if err := s.ResourceViewer.Init(ctx); err != nil { - return err - } - s.bindKeys() - - return nil -} - -func (s *Secret) bindKeys() { - s.Actions().Add(ui.KeyActions{ +func (s *Secret) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ tcell.KeyCtrlX: ui.NewKeyAction("Decode", s.decodeCmd, true), }) } func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey { - sel := s.GetTable().GetSelectedItem() - if sel == "" { + path := s.GetTable().GetSelectedItem() + if path == "" { return evt } - ns, n := namespaced(sel) - sec, err := s.App().Conn().DialOrDie().CoreV1().Secrets(ns).Get(n, metav1.GetOptions{}) + o, err := s.App().factory.Get("v1/secrets", path, labels.Everything()) if err != nil { - s.App().Flash().Errf("Unable to retrieve secret %s", err) - return evt + s.App().Flash().Err(err) + return nil } - d := make(map[string]string, len(sec.Data)) - for k, val := range sec.Data { + var secret v1.Secret + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &secret) + if err != nil { + s.App().Flash().Err(err) + return nil + } + + d := make(map[string]string, len(secret.Data)) + for k, val := range secret.Data { d[k] = string(val) } raw, err := yaml.Marshal(d) @@ -62,7 +63,7 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey { } details := NewDetails("Decoder") - details.SetSubject(sel) + details.SetSubject(path) details.SetTextColor(s.App().Styles.FgColor()) details.SetText(colorizeYAML(s.App().Styles.Views().Yaml, string(raw))) details.ScrollToBeginning() diff --git a/internal/view/sts.go b/internal/view/sts.go index 55257a62..e4b0cd12 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -20,30 +20,26 @@ func NewStatefulSet(gvr dao.GVR) ResourceViewer { s := StatefulSet{ ResourceViewer: NewRestartExtender( NewScaleExtender( - NewLogsExtender( - NewGeneric(gvr), - func() string { return "" }, - ), + NewLogsExtender(NewBrowser(gvr), nil), ), ), } - s.BindKeys() + s.SetBindKeysFn(s.bindKeys) s.GetTable().SetEnterFn(s.showPods) s.GetTable().SetColorerFn(render.StatefulSet{}.ColorerFunc()) return &s } -func (s *StatefulSet) BindKeys() { - s.Actions().Add(ui.KeyActions{ +func (s *StatefulSet) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ ui.KeyShiftD: ui.NewKeyAction("Sort Desired", s.GetTable().SortColCmd(1, true), false), ui.KeyShiftC: ui.NewKeyAction("Sort Current", s.GetTable().SortColCmd(2, true), false), }) } -func (s *StatefulSet) showPods(app *App, _, res, sel string) { - ns, n := namespaced(sel) - o, err := app.factory.Get(ns, s.GVR(), n, labels.Everything()) +func (s *StatefulSet) showPods(app *App, _, gvr, path string) { + o, err := app.factory.Get(s.GVR(), path, labels.Everything()) if err != nil { app.Flash().Err(err) return @@ -55,5 +51,5 @@ func (s *StatefulSet) showPods(app *App, _, res, sel string) { app.Flash().Err(err) } - showPodsFromSelector(app, ns, sts.Spec.Selector) + showPodsFromSelector(app, path, sts.Spec.Selector) } diff --git a/internal/view/subject.go b/internal/view/subject.go index c3c5e3fb..fb9d18e4 100644 --- a/internal/view/subject.go +++ b/internal/view/subject.go @@ -1,20 +1,10 @@ package view import ( - "context" - "fmt" - "reflect" - "time" - + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" ) type ( @@ -26,7 +16,7 @@ type ( // Subject presents a user/group viewer. Subject struct { - *Table + ResourceViewer subjectKind string cache render.RowEvents @@ -34,80 +24,48 @@ type ( ) // NewSubject returns a new subject viewer. -func NewSubject(title, _ string, _ resource.List) ResourceViewer { - return &Subject{Table: NewTable(title)} +func NewSubject(gvr dao.GVR) ResourceViewer { + s := Subject{ResourceViewer: NewBrowser(gvr)} + s.GetTable().SetColorerFn(render.Subject{}.ColorerFunc()) + // s.GetTable().SetSortCol(1, len(s.Header()), true) + s.SetBindKeysFn(s.bindKeys) + + return &s } -func (*Subject) SetContextFn(ContextFunc) {} +// BOZO!! +// // Start runs the refresh loop. +// func (s *Subject) Start() { +// s.Stop() -// GVR returns a resource descriptor. -func (s *Subject) GVR() string { - return "n/a" -} - -// GetTable returns the table view. -func (s *Subject) GetTable() *Table { return s.Table } - -// SetEnvFn sets up K9s env vars. -func (s *Subject) SetEnvFn(EnvFunc) {} - -// List returns the resource lister. -func (s *Subject) List() resource.List { return nil } - -// SetPath sets parent selector. -func (s *Subject) SetPath(_ string) {} - -// Init initializes the view. -func (s *Subject) Init(ctx context.Context) error { - app, err := extractApp(ctx) - if err != nil { - return err - } - s.subjectKind = mapCmdSubject(app.Config.K9s.ActiveCluster().View.Active) - s.Table = NewTable(s.subjectKind) - s.SetColorerFn(render.Subject{}.ColorerFunc()) - if err := s.Table.Init(ctx); err != nil { - return err - } - s.SetSortCol(1, len(s.Header()), true) - s.SelectRow(1, true) - s.bindKeys() - s.refresh() - - return nil -} - -// Start runs the refresh loop. -func (s *Subject) Start() { - s.Stop() - - var ctx context.Context - ctx, s.cancelFn = context.WithCancel(context.Background()) - go func(ctx context.Context) { - for { - select { - case <-ctx.Done(): - log.Debug().Msgf("Subject:%s Watch bailing out!", s.subjectKind) - return - case <-time.After(time.Duration(s.app.Config.K9s.GetRefreshRate()) * time.Second): - s.refresh() - } - } - }(ctx) -} +// var ctx context.Context +// ctx, s.cancelFn = context.WithCancel(context.Background()) +// go func(ctx context.Context) { +// for { +// select { +// case <-ctx.Done(): +// log.Debug().Msgf("Subject:%s Watch bailing out!", s.subjectKind) +// return +// case <-time.After(time.Duration(s.App().Config.K9s.GetRefreshRate()) * time.Second): +// s.refresh() +// } +// } +// }(ctx) +// } // Name returns the component name func (s *Subject) Name() string { - return "subject" + return "subjects" } -func (s *Subject) bindKeys() { - s.Actions().Delete(ui.KeyShiftA, ui.KeyShiftP, tcell.KeyCtrlSpace, ui.KeySpace) - s.Actions().Add(ui.KeyActions{ - tcell.KeyEnter: ui.NewKeyAction("Policies", s.policyCmd, true), - tcell.KeyEscape: ui.NewKeyAction("Back", s.resetCmd, false), - ui.KeySlash: ui.NewKeyAction("Filter", s.activateCmd, false), - ui.KeyShiftK: ui.NewKeyAction("Sort Kind", s.SortColCmd(1, true), false), +func (s *Subject) bindKeys(aa ui.KeyActions) { + aa.Delete(ui.KeyShiftA, ui.KeyShiftP, tcell.KeyCtrlSpace, ui.KeySpace) + aa.Add(ui.KeyActions{ + tcell.KeyEnter: ui.NewKeyAction("Policies", s.policyCmd, true), + // BOZO!! + // tcell.KeyEscape: ui.NewKeyAction("Back", s.resetCmd, false), + // ui.KeySlash: ui.NewKeyAction("Filter", s.activateCmd, false), + ui.KeyShiftK: ui.NewKeyAction("Sort Kind", s.GetTable().SortColCmd(1, true), false), }) } @@ -116,211 +74,213 @@ func (s *Subject) SetSubject(n string) { s.subjectKind = mapSubject(n) } -func (s *Subject) refresh() { - log.Debug().Msgf("Refreshing Subject...") - data, err := s.reconcile() - if err != nil { - log.Error().Err(err).Msgf("Refresh for %s", s.subjectKind) - s.app.Flash().Err(err) - } - s.app.QueueUpdateDraw(func() { - s.Update(data) - }) -} +// BOZO!! +// func (s *Subject) refresh() { +// log.Debug().Msgf("Refreshing Subject...") +// data, err := s.reconcile() +// if err != nil { +// log.Error().Err(err).Msgf("Refresh for %s", s.subjectKind) +// s.App().Flash().Err(err) +// } +// s.App().QueueUpdateDraw(func() { +// s.GetTable().Update(data) +// }) +// } func (s *Subject) policyCmd(evt *tcell.EventKey) *tcell.EventKey { - if !s.RowSelected() { + if !s.GetTable().RowSelected() { return evt } - _, n := namespaced(s.GetSelectedItem()) - subject, err := mapFuSubject(s.subjectKind) - if err != nil { - s.app.Flash().Err(err) - return nil - } - s.app.inject(NewPolicy(s.app, subject, n)) + // _, n := k8s.Namespaced(s.GetSelectedItem()) + // subject, err := mapFuSubject(s.subjectKind) + // if err != nil { + // s.App().Flash().Err(err) + // return nil + // } + // BOZO!! + // s.App().inject(NewPolicy(s.app, subject, n)) return nil } -func (s *Subject) resetCmd(evt *tcell.EventKey) *tcell.EventKey { - if !s.SearchBuff().Empty() { - s.SearchBuff().Reset() - return nil - } +// func (s *Subject) resetCmd(evt *tcell.EventKey) *tcell.EventKey { +// if !s.SearchBuff().Empty() { +// s.SearchBuff().Reset() +// return nil +// } - return s.backCmd(evt) -} +// return s.backCmd(evt) +// } -func (s *Subject) backCmd(evt *tcell.EventKey) *tcell.EventKey { - if s.SearchBuff().IsActive() { - s.SearchBuff().Reset() - return nil - } +// func (s *Subject) backCmd(evt *tcell.EventKey) *tcell.EventKey { +// if s.SearchBuff().IsActive() { +// s.SearchBuff().Reset() +// return nil +// } - return s.app.PrevCmd(evt) -} +// return s.App().PrevCmd(evt) +// } -func (s *Subject) reconcile() (render.TableData, error) { - var table render.TableData - if s.app.Conn() == nil { - return table, nil - } +// func (s *Subject) reconcile() (render.TableData, error) { +// var table render.TableData +// if s.App().Conn() == nil { +// return table, nil +// } - rows, err := s.fetchClusterRoleBindings() - if err != nil { - return table, err - } +// rows, err := s.fetchClusterRoleBindings() +// if err != nil { +// return table, err +// } - nrows, err := s.fetchRoleBindings() - if err != nil { - return table, err - } - for k, v := range nrows { - rows[k] = v - } +// nrows, err := s.fetchRoleBindings() +// if err != nil { +// return table, err +// } +// for k, v := range nrows { +// rows[k] = v +// } - return buildTable(s, rows), nil -} +// return buildTable(s, rows), nil +// } -func (s *Subject) Header() render.HeaderRow { - return render.Subject{}.Header(render.AllNamespaces) -} +// func (s *Subject) Header() render.HeaderRow { +// return render.Subject{}.Header(render.AllNamespaces) +// } -func (s *Subject) GetCache() render.RowEvents { - return s.cache -} +// func (s *Subject) GetCache() render.RowEvents { +// return s.cache +// } -func (s *Subject) SetCache(rows render.RowEvents) { - s.cache = rows -} +// func (s *Subject) SetCache(rows render.RowEvents) { +// s.cache = rows +// } -func buildTable(c TableInfo, rows render.Rows) render.TableData { - table := render.TableData{ - Header: c.Header(), - Namespace: "*", - } +// func buildTable(c TableInfo, rows render.Rows) render.TableData { +// table := render.TableData{ +// Header: c.Header(), +// Namespace: "*", +// } - cache := c.GetCache() - if len(cache) == 0 { - cache := make(render.RowEvents, 0, len(rows)) - for _, row := range rows { - cache = append(cache, render.RowEvent{Kind: render.EventAdd, Row: row}) - } - table.RowEvents = cache - return table - } +// cache := c.GetCache() +// if len(cache) == 0 { +// cache := make(render.RowEvents, 0, len(rows)) +// for _, row := range rows { +// cache = append(cache, render.RowEvent{Kind: render.EventAdd, Row: row}) +// } +// table.RowEvents = cache +// return table +// } - for _, row := range rows { - idx, ok := cache.FindIndex(row.ID) - if !ok { - cache = append(cache, render.RowEvent{Kind: render.EventAdd, Row: row}) - continue - } +// for _, row := range rows { +// idx, ok := cache.FindIndex(row.ID) +// if !ok { +// cache = append(cache, render.RowEvent{Kind: render.EventAdd, Row: row}) +// continue +// } - old := cache[idx].Row - deltas := make(render.DeltaRow, len(row.Fields)) - if reflect.DeepEqual(old, row) { - cache[idx].Kind = render.EventUnchanged - cache[idx].Deltas = deltas - continue - } +// old := cache[idx].Row +// deltas := make(render.DeltaRow, len(row.Fields)) +// if reflect.DeepEqual(old, row) { +// cache[idx].Kind = render.EventUnchanged +// cache[idx].Deltas = deltas +// continue +// } - cache[idx].Kind = render.EventUpdate - for i, field := range old.Fields { - if field != row.Fields[i] { - deltas[i] = field - } - } - cache[idx].Deltas = deltas - } +// cache[idx].Kind = render.EventUpdate +// for i, field := range old.Fields { +// if field != row.Fields[i] { +// deltas[i] = field +// } +// } +// cache[idx].Deltas = deltas +// } - for _, row := range rows { - if _, ok := cache.FindIndex(row.ID); !ok { - cache.Delete(row.ID) - } - } - table.RowEvents = cache +// for _, row := range rows { +// if _, ok := cache.FindIndex(row.ID); !ok { +// cache.Delete(row.ID) +// } +// } +// table.RowEvents = cache - return table -} +// return table +// } -func (s *Subject) fetchClusterRoleBindings() (render.Rows, error) { - s.app.factory.Preload(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterroles") - oo, err := s.app.factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) - if err != nil { - return nil, err - } +// func (s *Subject) fetchClusterRoleBindings() (render.Rows, error) { +// s.App().factory.Preload(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterroles") +// oo, err := s.App().factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) +// if err != nil { +// return nil, err +// } - rows := make(render.Rows, 0, len(oo)) - for _, o := range oo { - var crb rbacv1.ClusterRoleBinding - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) - if err != nil { - return nil, err - } - for _, subject := range crb.Subjects { - if subject.Kind != s.subjectKind { - continue - } - rows = append(rows, render.Row{ - ID: subject.Name, - Fields: render.Fields{subject.Name, "ClusterRoleBinding", crb.Name}, - }) - } - } +// rows := make(render.Rows, 0, len(oo)) +// for _, o := range oo { +// var crb rbacv1.ClusterRoleBinding +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) +// if err != nil { +// return nil, err +// } +// for _, subject := range crb.Subjects { +// if subject.Kind != s.subjectKind { +// continue +// } +// rows = append(rows, render.Row{ +// ID: subject.Name, +// Fields: render.Fields{subject.Name, "ClusterRoleBinding", crb.Name}, +// }) +// } +// } - return rows, nil -} +// return rows, nil +// } -func (s *Subject) fetchRoleBindings() (render.Rows, error) { - s.app.factory.Preload(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterroles") - oo, err := s.app.factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) - if err != nil { - return nil, err - } +// func (s *Subject) fetchRoleBindings() (render.Rows, error) { +// s.App().factory.Preload(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterroles") +// oo, err := s.App().factory.List(render.ClusterWide, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) +// if err != nil { +// return nil, err +// } - rows := make(render.Rows, 0, len(oo)) - for _, o := range oo { - var rb rbacv1.RoleBinding - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) - if err != nil { - return nil, err - } - for _, subject := range rb.Subjects { - if subject.Kind == s.subjectKind { - rows = append(rows, render.Row{ - ID: subject.Name, - Fields: render.Fields{subject.Name, "RoleBinding", rb.Name}, - }) - } - } - } +// rows := make(render.Rows, 0, len(oo)) +// for _, o := range oo { +// var rb rbacv1.RoleBinding +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) +// if err != nil { +// return nil, err +// } +// for _, subject := range rb.Subjects { +// if subject.Kind == s.subjectKind { +// rows = append(rows, render.Row{ +// ID: subject.Name, +// Fields: render.Fields{subject.Name, "RoleBinding", rb.Name}, +// }) +// } +// } +// } - return rows, nil -} +// return rows, nil +// } -func mapCmdSubject(subject string) string { - switch subject { - case "groups": - return group - case "sas": - return sa - default: - return user - } -} +// func mapCmdSubject(subject string) string { +// switch subject { +// case "groups": +// return group +// case "sas": +// return sa +// default: +// return user +// } +// } -func mapFuSubject(subject string) (string, error) { - switch subject { - case group: - return "g", nil - case sa: - return "s", nil - case user: - return "u", nil - default: - return "", fmt.Errorf("Unknown subject %q should be one of user, group, serviceaccount", subject) - } -} +// func mapFuSubject(subject string) (string, error) { +// switch subject { +// case group: +// return "g", nil +// case sa: +// return "s", nil +// case user: +// return "u", nil +// default: +// return "", fmt.Errorf("Unknown subject %q should be one of user, group, serviceaccount", subject) +// } +// } diff --git a/internal/view/svc.go b/internal/view/svc.go index eb7ac9f2..303d6b48 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -7,13 +7,15 @@ import ( "time" "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/k8s" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/perf" - "github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" ) // Service represents a service viewer. @@ -24,14 +26,11 @@ type Service struct { } // NewService returns a new viewer. -func NewService(title, gvr string, list resource.List) ResourceViewer { +func NewService(gvr dao.GVR) ResourceViewer { s := Service{ - ResourceViewer: NewLogsExtender( - NewResource(title, gvr, list), - func() string { return "" }, - ), + ResourceViewer: NewLogsExtender(NewBrowser(gvr), nil), } - s.BindKeys() + s.SetBindKeysFn(s.bindKeys) s.GetTable().SetEnterFn(s.showPods) return &s @@ -39,28 +38,30 @@ func NewService(title, gvr string, list resource.List) ResourceViewer { // Protocol... -func (s *Service) BindKeys() { - s.Actions().Add(ui.KeyActions{ +func (s *Service) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ tcell.KeyCtrlB: ui.NewKeyAction("Bench", s.benchCmd, true), tcell.KeyCtrlK: ui.NewKeyAction("Bench Stop", s.benchStopCmd, true), ui.KeyShiftT: ui.NewKeyAction("Sort Type", s.GetTable().SortColCmd(1, true), false), }) } -func (s *Service) showPods(app *App, ns, res, sel string) { - log.Debug().Msgf("SVC SHOW PODS %q -- %q -- %q", ns, res, sel) - ns, n := namespaced(sel) - svc, err := k8s.NewService(app.Conn()).Get(ns, n) +func (s *Service) showPods(app *App, ns, gvr, path string) { + log.Debug().Msgf("SVC SHOW PODS %q", path) + o, err := app.factory.Get(gvr, path, labels.Everything()) if err != nil { app.Flash().Err(err) return } - sv, ok := svc.(*v1.Service) - if !ok { - log.Fatal().Msg("Expecting a valid service") + var svc v1.Service + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &svc) + if err != nil { + app.Flash().Err(err) + return } - showPodsFromLabels(s.App(), sel, sv.Spec.Selector) + + showPodsWithLabels(app, path, svc.Spec.Selector) } func (s *Service) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey { @@ -180,11 +181,3 @@ func benchTimedOut(app *App) { app.StatusReset() }) } - -func showPodsFromLabels(app *App, path string, sel map[string]string) { - var labels []string - for k, v := range sel { - labels = append(labels, fmt.Sprintf("%s=%s", k, v)) - } - showPods(app, path, strings.Join(labels, ","), "") -} diff --git a/internal/view/table.go b/internal/view/table.go index b7c39a35..e38d72f1 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -14,7 +14,6 @@ type Table struct { app *App filterFn func(string) - cancelFn context.CancelFunc enterFn EnterFunc } @@ -47,21 +46,16 @@ func (t *Table) App() *App { // Start runs the component. func (t *Table) Start() { - log.Debug().Msgf("---- Table START %s", t.BaseTitle) + log.Debug().Msgf("Table START %s", t.BaseTitle) t.SearchBuff().AddListener(t.app.Cmd()) t.SearchBuff().AddListener(t) } // Stop terminates the component. func (t *Table) Stop() { + log.Debug().Msgf("TABLE %s", t.BaseTitle) t.SearchBuff().RemoveListener(t.app.Cmd()) t.SearchBuff().RemoveListener(t) - - if t.cancelFn != nil { - t.cancelFn() - t.cancelFn = nil - log.Debug().Msgf(">>>> Table STOP %s", t.BaseTitle) - } } // MasterComponent returns the master component. diff --git a/internal/view/types.go b/internal/view/types.go index 3941b37a..a5840522 100644 --- a/internal/view/types.go +++ b/internal/view/types.go @@ -53,25 +53,6 @@ type Viewer interface { Refresh() } -// ResourceViewer represents a generic resource viewer. -type ResourceViewer interface { - TableViewer - - // List returns a resource List. - List() resource.List - - // SetEnvFn sets a function to pull viewer env vars for plugins. - SetEnvFn(EnvFunc) - - // SetPath set parents selector. - SetPath(p string) - - // GVR returns a resource descriptor. - GVR() string - - SetContextFn(ContextFunc) -} - // TableViewer represents a tabular viewer. type TableViewer interface { Viewer @@ -80,6 +61,23 @@ type TableViewer interface { GetTable() *Table } +// ResourceViewer represents a generic resource viewer. +type ResourceViewer interface { + TableViewer + + // SetEnvFn sets a function to pull viewer env vars for plugins. + SetEnvFn(EnvFunc) + + // GVR returns a resource descriptor. + GVR() string + + // SetContextFn provision a custom context. + SetContextFn(ContextFunc) + + // SetBindKeys provision additional key bindings. + SetBindKeysFn(BindKeysFunc) +} + type LogViewer interface { ResourceViewer diff --git a/internal/watch/factory.go b/internal/watch/factory.go index 570398c8..cc3bb5e6 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -57,14 +57,16 @@ type Factory struct { stopChan chan struct{} tweakListOptions internalinterfaces.TweakListOptionsFunc activeNS string + forwarders Forwarders } // NewFactory returns a new informers factory. func NewFactory(client k8s.Connection) *Factory { return &Factory{ - client: client, - stopChan: make(chan struct{}), - factories: make(map[string]di.DynamicSharedInformerFactory), + client: client, + stopChan: make(chan struct{}), + factories: make(map[string]di.DynamicSharedInformerFactory), + forwarders: NewForwarders(), } } @@ -92,7 +94,7 @@ func (f *Factory) Show(ns, gvr string) { } } -func (f *Factory) List(ns, gvr string, sel labels.Selector) ([]runtime.Object, error) { +func (f *Factory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) { auth, err := f.Client().CanI(ns, gvr, []string{"list"}) if err != nil { return nil, err @@ -101,7 +103,7 @@ func (f *Factory) List(ns, gvr string, sel labels.Selector) ([]runtime.Object, e return nil, fmt.Errorf("User has insufficient access to list %s", gvr) } - log.Debug().Msgf(">>>>>>>>>>>>>> FACTORY LISTING %q -- %q", ns, gvr) + log.Debug().Msgf(">>> FACTORY LISTING %q -- %q", ns, gvr) inf := f.ForResource(ns, gvr) if inf == nil { return nil, fmt.Errorf("No resource for GVR %s", gvr) @@ -113,8 +115,9 @@ func (f *Factory) List(ns, gvr string, sel labels.Selector) ([]runtime.Object, e return inf.Lister().ByNamespace(ns).List(sel) } -func (f *Factory) Get(ns, gvr, name string, sel labels.Selector) (runtime.Object, error) { - log.Debug().Msgf("<<<<<<<<<<<<<<<<< FACTORY GET %q --- %q:%q", gvr, ns, name) +func (f *Factory) Get(gvr, path string, sel labels.Selector) (runtime.Object, error) { + ns, n := k8s.Namespaced(path) + log.Debug().Msgf(">>> FACTORY GET %q --- %q:%q -- %q", gvr, ns, n, path) auth, err := f.Client().CanI(ns, gvr, []string{"get"}) if err != nil { return nil, err @@ -124,15 +127,16 @@ func (f *Factory) Get(ns, gvr, name string, sel labels.Selector) (runtime.Object } fac := f.ensureFactory(ns) + log.Debug().Msgf("GVR: %#v", toGVR(gvr)) inf := fac.ForResource(toGVR(gvr)) if inf == nil { return nil, fmt.Errorf("No resource for GVR %s", gvr) } if ns == render.ClusterWide { - return inf.Lister().Get(name) + return inf.Lister().Get(n) } - return inf.Lister().ByNamespace(ns).Get(name) + return inf.Lister().ByNamespace(ns).Get(n) } func (f *Factory) WaitForCacheSync() map[schema.GroupVersionResource]bool { @@ -161,6 +165,31 @@ func (f *Factory) Terminate() { for k := range f.factories { delete(f.factories, k) } + f.forwarders.DeleteAll() +} + +// DeleteForwarder deletes portforward for a given container. +func (f *Factory) DeleteForwarder(path string) { + if fwd, ok := f.forwarders[path]; ok { + fwd.Stop() + delete(f.forwarders, path) + } +} + +// RegisterForwarder registers a new portforward for a given container. +func (f *Factory) RegisterForwarder(pf Forwarder) { + f.forwarders[pf.Path()] = pf +} + +// Forwards returns all portforwards. +func (f *Factory) Forwarders() Forwarders { + return f.forwarders +} + +// ForwarderFor returns a portforward for a given container or nil if none exists. +func (f *Factory) ForwarderFor(path string) (Forwarder, bool) { + fwd, ok := f.forwarders[path] + return fwd, ok } // Start initializes the informers until caller cancels the context. @@ -187,6 +216,8 @@ func (f *Factory) isClusterWide() bool { func (f *Factory) preload(ns string) { f.ForResource(ns, "v1/pods") f.ForResource(render.AllNamespaces, "apiextensions.k8s.io/v1beta1/customresourcedefinitions") + f.ForResource(render.ClusterWide, "rbac.authorization.k8s.io/v1/clusterroles") + f.ForResource(render.AllNamespaces, "rbac.authorization.k8s.io/v1/roles") } func (f *Factory) FactoryFor(ns string) di.DynamicSharedInformerFactory { @@ -198,12 +229,7 @@ func (f *Factory) Preload(ns, gvr string) { } func (f *Factory) ForResource(ns, gvr string) informers.GenericInformer { - defer func(t time.Time) { - log.Debug().Msgf("ForResource Elapsed %v", time.Since(t)) - }(time.Now()) - fact := f.ensureFactory(ns) - log.Debug().Msgf("--- FORRESOURCE %q -- %q -- %#v", ns, gvr, toGVR(gvr)) inf := fact.ForResource(toGVR(gvr)) fact.Start(f.stopChan) @@ -225,8 +251,6 @@ func (f *Factory) ensureFactory(ns string) di.DynamicSharedInformerFactory { nil, ) f.preload(ns) - // f.WaitForCacheSync() - f.Dump() return f.factories[ns] } @@ -239,8 +263,9 @@ func (f *Factory) register(gvr, ns string, stopChan <-chan struct{}) error { return nil } -func toGVR(s string) schema.GroupVersionResource { - tokens := strings.Split(s, "/") +func toGVR(gvr string) schema.GroupVersionResource { + log.Debug().Msgf("GVR -- %q", gvr) + tokens := strings.Split(gvr, "/") if len(tokens) < 3 { tokens = append([]string{""}, tokens...) } diff --git a/internal/model/forwarders.go b/internal/watch/forwarders.go similarity index 99% rename from internal/model/forwarders.go rename to internal/watch/forwarders.go index 6cccd04e..2b6fe9d3 100644 --- a/internal/model/forwarders.go +++ b/internal/watch/forwarders.go @@ -1,4 +1,4 @@ -package model +package watch import ( "strings"