checkpoint
parent
4c2c4793dc
commit
e293e1af90
|
|
@ -100,7 +100,7 @@ linters-settings:
|
||||||
- atomicalign
|
- atomicalign
|
||||||
enable-all: false
|
enable-all: false
|
||||||
disable:
|
disable:
|
||||||
- shadow
|
# - shadow
|
||||||
disable-all: false
|
disable-all: false
|
||||||
golint:
|
golint:
|
||||||
# minimal confidence for issues, default is 0.8
|
# minimal confidence for issues, default is 0.8
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func (a Aliases) loadDefaults() {
|
||||||
a.Alias["cr"] = "rbac.authorization.k8s.io/v1/clusterroles"
|
a.Alias["cr"] = "rbac.authorization.k8s.io/v1/clusterroles"
|
||||||
a.Alias["crb"] = "rbac.authorization.k8s.io/v1/clusterrolebindings"
|
a.Alias["crb"] = "rbac.authorization.k8s.io/v1/clusterrolebindings"
|
||||||
a.Alias["ro"] = "rbac.authorization.k8s.io/v1/roles"
|
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["np"] = "networking.k8s.io/v1/networkpolicies"
|
||||||
{
|
{
|
||||||
a.Alias["ctx"] = contexts
|
a.Alias["ctx"] = contexts
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// MaxFavoritesNS number # favorite namespaces to keep in the configuration.
|
// MaxFavoritesNS number # favorite namespaces to keep in the configuration.
|
||||||
MaxFavoritesNS = 10
|
MaxFavoritesNS = 9
|
||||||
defaultNS = "default"
|
defaultNS = "default"
|
||||||
allNS = "all"
|
allNS = "all"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ func newStyle() Style {
|
||||||
Body: newBody(),
|
Body: newBody(),
|
||||||
Frame: newFrame(),
|
Frame: newFrame(),
|
||||||
Info: newInfo(),
|
Info: newInfo(),
|
||||||
Table: newTable(),
|
Table: newGetTable(),
|
||||||
Views: newViews(),
|
Views: newViews(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +211,7 @@ func newInfo() Info {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTable returns a new table style.
|
// NewTable returns a new table style.
|
||||||
func newTable() Table {
|
func newGetTable() Table {
|
||||||
return Table{
|
return Table{
|
||||||
FgColor: "aqua",
|
FgColor: "aqua",
|
||||||
BgColor: "black",
|
BgColor: "black",
|
||||||
|
|
@ -293,7 +293,7 @@ func (s *Styles) Title() Title {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Table returns table styles.
|
// Table returns table styles.
|
||||||
func (s *Styles) Table() Table {
|
func (s *Styles) GetTable() Table {
|
||||||
return s.K9s.Table
|
return s.K9s.Table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ func TestSkinNone(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, "cadetblue", s.Body().FgColor)
|
assert.Equal(t, "cadetblue", s.Body().FgColor)
|
||||||
assert.Equal(t, "black", s.Body().BgColor)
|
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.ColorCadetBlue, s.FgColor())
|
||||||
assert.Equal(t, tcell.ColorBlack, s.BgColor())
|
assert.Equal(t, tcell.ColorBlack, s.BgColor())
|
||||||
assert.Equal(t, tcell.ColorBlack, tview.Styles.PrimitiveBackgroundColor)
|
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, "white", s.Body().FgColor)
|
||||||
assert.Equal(t, "black", s.Body().BgColor)
|
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.ColorWhite, s.FgColor())
|
||||||
assert.Equal(t, tcell.ColorBlack, s.BgColor())
|
assert.Equal(t, tcell.ColorBlack, s.BgColor())
|
||||||
assert.Equal(t, tcell.ColorBlack, tview.Styles.PrimitiveBackgroundColor)
|
assert.Equal(t, tcell.ColorBlack, tview.Styles.PrimitiveBackgroundColor)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dao
|
||||||
|
|
||||||
|
// Alias represents an alias resource.
|
||||||
|
type Alias struct {
|
||||||
|
Generic
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Accessor = &Alias{}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
Resource
|
Generic
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Accessor = &Context{}
|
var _ Accessor = &Context{}
|
||||||
|
|
@ -47,16 +47,16 @@ func (c *Context) List(string, metav1.ListOptions) ([]runtime.Object, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a Context.
|
// 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()
|
ctx, err := c.config().CurrentContextName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if ctx == n {
|
if ctx == path {
|
||||||
return fmt.Errorf("trying to delete your current context %s", n)
|
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.
|
// MustCurrentContextName return the active context name.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -33,5 +33,6 @@ func Describe(c k8s.Connection, gvr GVR, ns, n string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug().Msgf("DESCRIBE FOR %q -- %q:%q", gvr, ns, n)
|
||||||
return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true})
|
return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
@ -16,7 +17,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Deployment struct {
|
type Deployment struct {
|
||||||
Resource
|
Generic
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Accessor = &Deployment{}
|
var _ Accessor = &Deployment{}
|
||||||
|
|
@ -25,7 +26,8 @@ var _ Restartable = &Deployment{}
|
||||||
var _ Scalable = &Deployment{}
|
var _ Scalable = &Deployment{}
|
||||||
|
|
||||||
// Scale a 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{})
|
scale, err := d.Client().DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -37,8 +39,8 @@ func (d *Deployment) Scale(ns, n string, replicas int32) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart a Deployment rollout.
|
// Restart a Deployment rollout.
|
||||||
func (d *Deployment) Restart(ns, n string) error {
|
func (d *Deployment) Restart(path string) error {
|
||||||
o, err := d.Get(ns, string(d.gvr), n, labels.Everything())
|
o, err := d.Get(string(d.gvr), path, labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -54,14 +56,14 @@ func (d *Deployment) Restart(ns, n string) error {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logs tail logs for all pods represented by this Deployment.
|
// Logs tail logs for all pods represented by this Deployment.
|
||||||
func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||||
log.Debug().Msgf("Tailing Deployment %q -- %q", opts.Namespace, opts.Name)
|
log.Debug().Msgf("Tailing Deployment %q -- %q", opts.Path)
|
||||||
o, err := d.Get(opts.Namespace, string(d.gvr), opts.Name, labels.Everything())
|
o, err := d.Get(string(d.gvr), opts.Path, labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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)
|
return podLogs(ctx, c, dp.Spec.Selector.MatchLabels, opts)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/derailed/k9s/internal/watch"
|
"github.com/derailed/k9s/internal/watch"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
|
@ -20,7 +21,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DaemonSet struct {
|
type DaemonSet struct {
|
||||||
Resource
|
Generic
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Accessor = &DaemonSet{}
|
var _ Accessor = &DaemonSet{}
|
||||||
|
|
@ -28,8 +29,8 @@ var _ Loggable = &DaemonSet{}
|
||||||
var _ Restartable = &DaemonSet{}
|
var _ Restartable = &DaemonSet{}
|
||||||
|
|
||||||
// Restart a DaemonSet rollout.
|
// Restart a DaemonSet rollout.
|
||||||
func (d *DaemonSet) Restart(ns, n string) error {
|
func (d *DaemonSet) Restart(path string) error {
|
||||||
o, err := d.Get(ns, string(d.gvr), n, labels.Everything())
|
o, err := d.Get(string(d.gvr), path, labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -45,14 +46,14 @@ func (d *DaemonSet) Restart(ns, n string) error {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logs tail logs for all pods represented by this DaemonSet.
|
// Logs tail logs for all pods represented by this DaemonSet.
|
||||||
func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||||
log.Debug().Msgf("Tailing DaemonSet %q -- %q", opts.Namespace, opts.Name)
|
log.Debug().Msgf("Tailing DaemonSet %q", opts.Path)
|
||||||
o, err := d.Get(opts.Namespace, "apps/v1/daemonsets", opts.Name, labels.Everything())
|
o, err := d.Get("apps/v1/daemonsets", opts.Path, labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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)
|
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
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -94,17 +96,17 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L
|
||||||
}
|
}
|
||||||
|
|
||||||
po := Pod{}
|
po := Pod{}
|
||||||
|
po.Init(f, "v1/pods")
|
||||||
for _, o := range oo {
|
for _, o := range oo {
|
||||||
var pod v1.Pod
|
var pod v1.Pod
|
||||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if pod.Status.Phase == v1.PodRunning {
|
log.Debug().Msgf("TAILING logs on pod %q", pod.Name)
|
||||||
opts.Namespace, opts.Name = pod.Namespace, pod.Name
|
opts.Path = k8s.FQN(pod.Namespace, pod.Name)
|
||||||
if err := po.TailLogs(ctx, c, opts); err != nil {
|
if err := po.TailLogs(ctx, c, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -1,57 +1,42 @@
|
||||||
package dao
|
package dao
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/color"
|
"github.com/derailed/k9s/internal/color"
|
||||||
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
// LogOptions represent logger options.
|
||||||
// Fqn uniquely describes a container
|
type LogOptions struct {
|
||||||
Fqn struct {
|
Path string
|
||||||
Namespace, Name, Container string
|
Container string
|
||||||
}
|
Lines int64
|
||||||
|
Color color.Paint
|
||||||
// LogOptions represent logger options.
|
Previous bool
|
||||||
LogOptions struct {
|
SingleContainer bool
|
||||||
Fqn
|
MultiPods bool
|
||||||
|
}
|
||||||
Lines int64
|
|
||||||
Color color.Paint
|
|
||||||
Previous bool
|
|
||||||
SingleContainer bool
|
|
||||||
MultiPods bool
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// HasContainer checks if a container is present.
|
// HasContainer checks if a container is present.
|
||||||
func (o LogOptions) HasContainer() bool {
|
func (o LogOptions) HasContainer() bool {
|
||||||
return o.Container != ""
|
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.
|
// FixedSizeName returns a normalize fixed size pod name if possible.
|
||||||
func (o LogOptions) FixedSizeName() string {
|
func (o LogOptions) FixedSizeName() string {
|
||||||
tokens := strings.Split(o.Name, "-")
|
_, n := k8s.Namespaced(o.Path)
|
||||||
|
tokens := strings.Split(n, "-")
|
||||||
if len(tokens) < 3 {
|
if len(tokens) < 3 {
|
||||||
return o.Name
|
return n
|
||||||
}
|
}
|
||||||
var s []string
|
var s []string
|
||||||
for i := 0; i < len(tokens)-1; i++ {
|
for i := 0; i < len(tokens)-1; i++ {
|
||||||
s = append(s, tokens[i])
|
s = append(s, tokens[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
return Truncate(strings.Join(s, "-"), 15) + "-" + tokens[len(tokens)-1]
|
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.
|
// DecorateLog add a log header to display po/co information along with the log message.
|
||||||
func (o LogOptions) DecorateLog(msg string) string {
|
func (o LogOptions) DecorateLog(msg string) string {
|
||||||
|
_, n := k8s.Namespaced(o.Path)
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.MultiPods {
|
if o.MultiPods {
|
||||||
return colorize(o.Color, o.Name+":"+o.Container+" ") + msg
|
return colorize(o.Color, n+":"+o.Container+" ") + msg
|
||||||
}
|
}
|
||||||
|
|
||||||
if !o.SingleContainer {
|
if !o.SingleContainer {
|
||||||
|
|
@ -88,17 +74,18 @@ func Truncate(str string, width int) string {
|
||||||
return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis))
|
return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespaced return a namesapace and a name.
|
// BOZO!!
|
||||||
func Namespaced(n string) (string, string) {
|
// // Namespaced return a namesapace and a name.
|
||||||
ns, po := path.Split(n)
|
// 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.
|
// // FQN returns a fully qualified resource name.
|
||||||
func FQN(ns, n string) string {
|
// func FQN(ns, n string) string {
|
||||||
if ns == "" {
|
// if ns == "" {
|
||||||
return n
|
// return n
|
||||||
}
|
// }
|
||||||
return ns + "/" + n
|
// return ns + "/" + n
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package dao
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/color"
|
"github.com/derailed/k9s/internal/color"
|
||||||
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/derailed/k9s/internal/watch"
|
"github.com/derailed/k9s/internal/watch"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
@ -22,24 +23,23 @@ import (
|
||||||
|
|
||||||
const defaultTimeout = 1 * time.Second
|
const defaultTimeout = 1 * time.Second
|
||||||
|
|
||||||
type Logger interface {
|
// Pod represents a pod resource.
|
||||||
Logs(ns, n string, opts *v1.PodLogOptions) *restclient.Request
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pod struct {
|
type Pod struct {
|
||||||
Resource
|
Generic
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Accessor = &Pod{}
|
var _ Accessor = &Pod{}
|
||||||
|
var _Loggable = &Pod{}
|
||||||
|
|
||||||
// Logs fetch container logs for a given pod and container.
|
// 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)
|
return p.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Containers returns all container names on pod
|
// Containers returns all container names on pod
|
||||||
func (p *Pod) Containers(ns, n string, includeInit bool) ([]string, error) {
|
func (p *Pod) Containers(path string, includeInit bool) ([]string, error) {
|
||||||
o, err := p.Get(ns, "v1/pod", n, labels.Everything())
|
o, err := p.Get("v1/pod", path, labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -78,8 +78,7 @@ func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("Expecting an informer")
|
return errors.New("Expecting an informer")
|
||||||
}
|
}
|
||||||
ns, n := Namespaced(opts.FQN())
|
o, err := fac.Get("v1/pods", opts.Path, labels.Everything())
|
||||||
o, err := fac.Get(ns, "v1/pods", n, labels.Everything())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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{
|
o := v1.PodLogOptions{
|
||||||
Container: opts.Container,
|
Container: opts.Container,
|
||||||
Follow: true,
|
Follow: true,
|
||||||
TailLines: &opts.Lines,
|
TailLines: &opts.Lines,
|
||||||
Previous: opts.Previous,
|
Previous: opts.Previous,
|
||||||
}
|
}
|
||||||
req := logger.Logs(opts.Namespace, opts.Name, &o)
|
req := logger.Logs(opts.Path, &o)
|
||||||
ctxt, cancelFunc := context.WithCancel(ctx)
|
ctxt, cancelFunc := context.WithCancel(ctx)
|
||||||
req.Context(ctxt)
|
req.Context(ctxt)
|
||||||
|
|
||||||
|
|
@ -132,8 +131,8 @@ func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptio
|
||||||
stream, err := req.Stream()
|
stream, err := req.Stream()
|
||||||
atomic.StoreInt32(&blocked, 0)
|
atomic.StoreInt32(&blocked, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("Log stream failed 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())
|
return fmt.Errorf("Unable to obtain log stream for %s", opts.Path)
|
||||||
}
|
}
|
||||||
go readLogs(ctx, stream, c, opts)
|
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) {
|
func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) {
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Debug().Msgf(">>> Closing stream `%s", opts.Path())
|
log.Debug().Msgf(">>> Closing stream `%s", opts.Path)
|
||||||
if err := stream.Close(); err != nil {
|
if err := stream.Close(); err != nil {
|
||||||
log.Error().Err(err).Msg("Cloing stream")
|
log.Error().Err(err).Msg("Cloing stream")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package dao
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/model"
|
"github.com/derailed/k9s/internal/model"
|
||||||
|
|
@ -12,14 +13,14 @@ import (
|
||||||
|
|
||||||
// Reconcile previous vs current state and emits delta events.
|
// Reconcile previous vs current state and emits delta events.
|
||||||
func Reconcile(ctx context.Context, table render.TableData, gvr GVR) (render.TableData, error) {
|
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 {
|
if !ok {
|
||||||
return table, fmt.Errorf("no path specified for %s", gvr)
|
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)
|
log.Debug().Msgf(" Reconcile %q in ns %q with path %q", gvr, table.Namespace, path)
|
||||||
factory, ok := ctx.Value(internal.KeyFactory).(Factory)
|
factory, ok := ctx.Value(internal.KeyFactory).(Factory)
|
||||||
if !ok {
|
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)
|
table.Header = m.Renderer.Header(table.Namespace)
|
||||||
oo, err := m.Model.List(ctx)
|
oo, err := m.Model.List(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return table, err
|
||||||
}
|
}
|
||||||
log.Debug().Msgf("Model returned [%d] items", len(oo))
|
log.Debug().Msgf("Model returned [%d] items", len(oo))
|
||||||
rows := make(render.Rows, 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 {
|
if err := m.Model.Hydrate(oo, rows, m.Renderer); err != nil {
|
||||||
return table, err
|
return table, err
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +67,7 @@ func update(table *render.TableData, rows render.Rows) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if index, ok := table.RowEvents.FindIndex(row.ID); ok {
|
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() {
|
if delta.IsBlank() {
|
||||||
table.RowEvents[index].Kind, table.RowEvents[index].Deltas = render.EventUnchanged, blankDelta
|
table.RowEvents[index].Kind, table.RowEvents[index].Deltas = render.EventUnchanged, blankDelta
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -15,21 +15,35 @@ import (
|
||||||
// MetaViewers represents a collection of meta viewers.
|
// MetaViewers represents a collection of meta viewers.
|
||||||
type ResourceMetas map[GVR]metav1.APIResource
|
type ResourceMetas map[GVR]metav1.APIResource
|
||||||
|
|
||||||
|
// Accessors represents a collection of dao accessors.
|
||||||
|
type Accessors map[GVR]Accessor
|
||||||
|
|
||||||
var resMetas ResourceMetas
|
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) {
|
func AccessorFor(f Factory, gvr GVR) (Accessor, error) {
|
||||||
m := map[GVR]Accessor{
|
m := Accessors{
|
||||||
|
"alias": &Alias{},
|
||||||
"contexts": &Context{},
|
"contexts": &Context{},
|
||||||
|
"containers": &Container{},
|
||||||
"screendumps": &ScreenDump{},
|
"screendumps": &ScreenDump{},
|
||||||
|
"benchmarks": &Benchmark{},
|
||||||
|
"portforwards": &PortForward{},
|
||||||
|
"v1/services": &Service{},
|
||||||
|
"v1/pods": &Pod{},
|
||||||
"apps/v1/deployments": &Deployment{},
|
"apps/v1/deployments": &Deployment{},
|
||||||
"apps/v1/daemonsets": &DaemonSet{},
|
"apps/v1/daemonsets": &DaemonSet{},
|
||||||
"extensions/v1beta1/daemonsets": &DaemonSet{},
|
"extensions/v1beta1/daemonsets": &DaemonSet{},
|
||||||
"apps/v1/statefulsets": &StatefulSet{},
|
"apps/v1/statefulsets": &StatefulSet{},
|
||||||
|
"batch/v1beta1/cronjobs": &CronJob{},
|
||||||
|
"batch/v1/jobs": &Job{},
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := m[gvr]
|
r, ok := m[gvr]
|
||||||
if !ok {
|
if !ok {
|
||||||
r = &Resource{}
|
r = &Generic{}
|
||||||
log.Warn().Msgf("No DAO registry entry for %q. Going generic!", gvr)
|
log.Warn().Msgf("No DAO registry entry for %q. Going generic!", gvr)
|
||||||
}
|
}
|
||||||
r.Init(f, gvr)
|
r.Init(f, gvr)
|
||||||
|
|
@ -56,6 +70,17 @@ func MetaFor(gvr GVR) (metav1.APIResource, error) {
|
||||||
return m, nil
|
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.
|
// Load hydrates server preferred+CRDs resource metadata.
|
||||||
func Load(f *watch.Factory) error {
|
func Load(f *watch.Factory) error {
|
||||||
resMetas = make(ResourceMetas, 100)
|
resMetas = make(ResourceMetas, 100)
|
||||||
|
|
@ -70,23 +95,82 @@ func Load(f *watch.Factory) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadNonResource(m ResourceMetas) 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{
|
m["contexts"] = metav1.APIResource{
|
||||||
Name: "contexts",
|
Name: "contexts",
|
||||||
SingularName: "context",
|
SingularName: "context",
|
||||||
Namespaced: false,
|
Namespaced: false,
|
||||||
Kind: "Context",
|
Kind: "Contexts",
|
||||||
ShortNames: []string{"ctx"},
|
ShortNames: []string{"ctx"},
|
||||||
Verbs: []string{},
|
Verbs: []string{},
|
||||||
Categories: []string{"K9s"},
|
Categories: []string{"k9s"},
|
||||||
}
|
}
|
||||||
m["screendumps"] = metav1.APIResource{
|
m["screendumps"] = metav1.APIResource{
|
||||||
Name: "screendumps",
|
Name: "screendumps",
|
||||||
SingularName: "screendump",
|
SingularName: "screendump",
|
||||||
Namespaced: false,
|
Namespaced: false,
|
||||||
Kind: "ScreenDump",
|
Kind: "ScreenDumps",
|
||||||
ShortNames: []string{"sd"},
|
ShortNames: []string{"sd"},
|
||||||
Verbs: []string{"delete"},
|
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
|
return nil
|
||||||
|
|
@ -113,7 +197,7 @@ func loadPreferred(f *watch.Factory, m ResourceMetas) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCRDs(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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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())
|
|
||||||
}
|
|
||||||
|
|
@ -2,20 +2,19 @@ package dao
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ScreenDump struct {
|
type ScreenDump struct {
|
||||||
Resource
|
Generic
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Accessor = &ScreenDump{}
|
var _ Accessor = &ScreenDump{}
|
||||||
var _ Nuker = &ScreenDump{}
|
var _ Nuker = &ScreenDump{}
|
||||||
|
|
||||||
// Delete a ScreenDump.
|
// Delete a ScreenDump.
|
||||||
func (d *ScreenDump) Delete(dir, sel string, cascade, force bool) error {
|
func (d *ScreenDump) Delete(path string, cascade, force bool) error {
|
||||||
log.Debug().Msgf("ScreenDump DELETE %q:%q", dir, sel)
|
log.Debug().Msgf("ScreenDump DELETE %q", path)
|
||||||
return os.Remove(filepath.Join("/"+dir, sel))
|
return os.Remove(path)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
@ -16,7 +17,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatefulSet struct {
|
type StatefulSet struct {
|
||||||
Resource
|
Generic
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Accessor = &StatefulSet{}
|
var _ Accessor = &StatefulSet{}
|
||||||
|
|
@ -25,7 +26,8 @@ var _ Restartable = &StatefulSet{}
|
||||||
var _ Scalable = &StatefulSet{}
|
var _ Scalable = &StatefulSet{}
|
||||||
|
|
||||||
// Scale a 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{})
|
scale, err := s.Client().DialOrDie().AppsV1().StatefulSets(ns).GetScale(n, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -37,8 +39,8 @@ func (s *StatefulSet) Scale(ns, n string, replicas int32) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart a StatefulSet rollout.
|
// Restart a StatefulSet rollout.
|
||||||
func (s *StatefulSet) Restart(ns, n string) error {
|
func (s *StatefulSet) Restart(path string) error {
|
||||||
o, err := s.Get(ns, string(s.gvr), n, labels.Everything())
|
o, err := s.Get(string(s.gvr), path, labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -54,14 +56,14 @@ func (s *StatefulSet) Restart(ns, n string) error {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logs tail logs for all pods represented by this StatefulSet.
|
// Logs tail logs for all pods represented by this StatefulSet.
|
||||||
func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||||
log.Debug().Msgf("Tailing StatefulSet %q -- %q", opts.Namespace, opts.Name)
|
log.Debug().Msgf("Tailing StatefulSet %q", opts.Path)
|
||||||
o, err := s.Get(opts.Namespace, string(s.gvr), opts.Name, labels.Everything())
|
o, err := s.Get(string(s.gvr), opts.Path, labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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)
|
return podLogs(ctx, c, dp.Spec.Selector.MatchLabels, opts)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"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/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Factory interface {
|
type Factory interface {
|
||||||
|
|
@ -15,7 +18,7 @@ type Factory interface {
|
||||||
Client() k8s.Connection
|
Client() k8s.Connection
|
||||||
|
|
||||||
// Get fetch a given resource.
|
// 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 fetch a collection of resources.
|
||||||
List(ns, gvr string, sel labels.Selector) ([]runtime.Object, error)
|
List(ns, gvr string, sel labels.Selector) ([]runtime.Object, error)
|
||||||
|
|
@ -25,6 +28,12 @@ type Factory interface {
|
||||||
|
|
||||||
// WaitForCacheSync synchronize the cache.
|
// WaitForCacheSync synchronize the cache.
|
||||||
WaitForCacheSync() map[schema.GroupVersionResource]bool
|
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.
|
// Accessor represents an accessible k8s resource.
|
||||||
|
|
@ -42,13 +51,13 @@ type Loggable interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Scalable interface {
|
type Scalable interface {
|
||||||
Scale(ns, n string, replicas int32) error
|
Scale(path string, replicas int32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nuker represents a resource deleter.
|
// Nuker represents a resource deleter.
|
||||||
type Nuker interface {
|
type Nuker interface {
|
||||||
// Delete removes a resource from the api server.
|
// 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.
|
// Switchable represents a switchable resource.
|
||||||
|
|
@ -60,5 +69,16 @@ type Switchable interface {
|
||||||
// Restartable represents a restartable resource.
|
// Restartable represents a restartable resource.
|
||||||
type Restartable interface {
|
type Restartable interface {
|
||||||
// Restart performs a rollout restart.
|
// 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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,6 @@ type (
|
||||||
ServerVersion() (*version.Info, error)
|
ServerVersion() (*version.Info, error)
|
||||||
FetchNodes() (*v1.NodeList, error)
|
FetchNodes() (*v1.NodeList, error)
|
||||||
CurrentNamespaceName() (string, error)
|
CurrentNamespaceName() (string, error)
|
||||||
CheckNSAccess(ns string) error
|
|
||||||
CheckListNSAccess() error
|
|
||||||
CanI(ns, gvr string, verbs []string) (bool, error)
|
CanI(ns, gvr string, verbs []string) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,25 +83,6 @@ func InitConnectionOrDie(config *Config) *APIClient {
|
||||||
return &conn
|
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 {
|
func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview {
|
||||||
res := GVR(gvr).AsGVR()
|
res := GVR(gvr).AsGVR()
|
||||||
return &authorizationv1.SelfSubjectAccessReview{
|
return &authorizationv1.SelfSubjectAccessReview{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,108 +1,109 @@
|
||||||
package k8s
|
package k8s
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
"fmt"
|
// import (
|
||||||
|
// "fmt"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
// "github.com/rs/zerolog/log"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
// "k8s.io/client-go/tools/clientcmd"
|
||||||
"k8s.io/client-go/tools/clientcmd/api"
|
// "k8s.io/client-go/tools/clientcmd/api"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// NamedContext represents a named cluster context.
|
// // NamedContext represents a named cluster context.
|
||||||
type NamedContext struct {
|
// type NamedContext struct {
|
||||||
Name string
|
// Name string
|
||||||
Context *api.Context
|
// Context *api.Context
|
||||||
config *Config
|
// config *Config
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewNamedContext returns a new named context.
|
// // NewNamedContext returns a new named context.
|
||||||
func NewNamedContext(c *Config, n string, ctx *api.Context) *NamedContext {
|
// func NewNamedContext(c *Config, n string, ctx *api.Context) *NamedContext {
|
||||||
return &NamedContext{Name: n, Context: ctx, config: c}
|
// return &NamedContext{Name: n, Context: ctx, config: c}
|
||||||
}
|
// }
|
||||||
|
|
||||||
// MustCurrentContextName return the active context name.
|
// // MustCurrentContextName return the active context name.
|
||||||
func (c *NamedContext) MustCurrentContextName() string {
|
// func (c *NamedContext) MustCurrentContextName() string {
|
||||||
cl, err := c.config.CurrentContextName()
|
// cl, err := c.config.CurrentContextName()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Fetching current context")
|
// log.Fatal().Err(err).Msg("Fetching current context")
|
||||||
}
|
// }
|
||||||
return cl
|
// return cl
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// // ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Context represents a Kubernetes Context.
|
// // Context represents a Kubernetes Context.
|
||||||
type Context struct {
|
// type Context struct {
|
||||||
*base
|
// *base
|
||||||
Connection
|
// Connection
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewContext returns a new Context.
|
// // NewContext returns a new Context.
|
||||||
func NewContext(c Connection) *Context {
|
// func NewContext(c Connection) *Context {
|
||||||
return &Context{&base{}, c}
|
// return &Context{&base{}, c}
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get a Context.
|
// // Get a Context.
|
||||||
func (c *Context) Get(_, n string) (interface{}, error) {
|
// func (c *Context) Get(_, n string) (interface{}, error) {
|
||||||
ctx, err := c.Config().GetContext(n)
|
// ctx, err := c.Config().GetContext(n)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
return &NamedContext{Name: n, Context: ctx}, nil
|
// return &NamedContext{Name: n, Context: ctx}, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// List all Contexts on the current cluster.
|
// // List all Contexts on the current cluster.
|
||||||
func (c *Context) List(string, metav1.ListOptions) (Collection, error) {
|
// func (c *Context) List(string, metav1.ListOptions) (Collection, error) {
|
||||||
ctxs, err := c.Config().Contexts()
|
// ctxs, err := c.Config().Contexts()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
cc := make([]interface{}, 0, len(ctxs))
|
// cc := make([]interface{}, 0, len(ctxs))
|
||||||
for k, v := range ctxs {
|
// for k, v := range ctxs {
|
||||||
cc = append(cc, NewNamedContext(c.Config(), k, v))
|
// cc = append(cc, NewNamedContext(c.Config(), k, v))
|
||||||
}
|
// }
|
||||||
|
|
||||||
return cc, nil
|
// return cc, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Delete a Context.
|
// // Delete a Context.
|
||||||
func (c *Context) Delete(_, n string, cascade, force bool) error {
|
// func (c *Context) Delete(_, n string, cascade, force bool) error {
|
||||||
ctx, err := c.Config().CurrentContextName()
|
// ctx, err := c.Config().CurrentContextName()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if ctx == n {
|
// if ctx == n {
|
||||||
return fmt.Errorf("trying to delete your current context %s", n)
|
// return fmt.Errorf("trying to delete your current context %s", n)
|
||||||
}
|
// }
|
||||||
return c.Config().DelContext(n)
|
// return c.Config().DelContext(n)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// MustCurrentContextName return the active context name.
|
// // MustCurrentContextName return the active context name.
|
||||||
func (c *Context) MustCurrentContextName() string {
|
// func (c *Context) MustCurrentContextName() string {
|
||||||
cl, err := c.Config().CurrentContextName()
|
// cl, err := c.Config().CurrentContextName()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Fetching current context")
|
// log.Fatal().Err(err).Msg("Fetching current context")
|
||||||
}
|
// }
|
||||||
return cl
|
// return cl
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Switch to another context.
|
// // Switch to another context.
|
||||||
func (c *Context) Switch(ctx string) error {
|
// func (c *Context) Switch(ctx string) error {
|
||||||
c.SwitchContextOrDie(ctx)
|
// c.SwitchContextOrDie(ctx)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// KubeUpdate modifies kubeconfig default context.
|
// // KubeUpdate modifies kubeconfig default context.
|
||||||
func (c *Context) KubeUpdate(n string) error {
|
// func (c *Context) KubeUpdate(n string) error {
|
||||||
config, err := c.Config().RawConfig()
|
// config, err := c.Config().RawConfig()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if err := c.Switch(n); err != nil {
|
// if err := c.Switch(n); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
return clientcmd.ModifyConfig(
|
// return clientcmd.ModifyConfig(
|
||||||
clientcmd.NewDefaultPathOptions(), config, true,
|
// clientcmd.NewDefaultPathOptions(), config, true,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
// }
|
|
||||||
|
|
@ -20,8 +20,17 @@ func toPerc(v1, v2 float64) float64 {
|
||||||
return math.Round((v1 / v2) * 100)
|
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)
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -91,7 +91,7 @@ func (p *PortForward) FQN() string {
|
||||||
func (p *PortForward) Start(path, co string, ports []string) (*portforward.PortForwarder, error) {
|
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()
|
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{})
|
pod, err := p.DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,42 @@
|
||||||
package k8s
|
package k8s
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
// import (
|
||||||
)
|
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
// )
|
||||||
|
|
||||||
// PersistentVolume represents a Kubernetes PersistentVolume.
|
// // PersistentVolume represents a Kubernetes PersistentVolume.
|
||||||
type PersistentVolume struct {
|
// type PersistentVolume struct {
|
||||||
*base
|
// *base
|
||||||
Connection
|
// Connection
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewPersistentVolume returns a new PersistentVolume.
|
// // NewPersistentVolume returns a new PersistentVolume.
|
||||||
func NewPersistentVolume(c Connection) *PersistentVolume {
|
// func NewPersistentVolume(c Connection) *PersistentVolume {
|
||||||
return &PersistentVolume{&base{}, c}
|
// return &PersistentVolume{&base{}, c}
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get a PersistentVolume.
|
// // Get a PersistentVolume.
|
||||||
func (p *PersistentVolume) Get(_, n string) (interface{}, error) {
|
// func (p *PersistentVolume) Get(_, n string) (interface{}, error) {
|
||||||
return p.DialOrDie().CoreV1().PersistentVolumes().Get(n, metav1.GetOptions{})
|
// return p.DialOrDie().CoreV1().PersistentVolumes().Get(n, metav1.GetOptions{})
|
||||||
}
|
// }
|
||||||
|
|
||||||
// List all PersistentVolumes in a given namespace.
|
// // List all PersistentVolumes in a given namespace.
|
||||||
func (p *PersistentVolume) List(ns string, opts metav1.ListOptions) (Collection, error) {
|
// func (p *PersistentVolume) List(ns string, opts metav1.ListOptions) (Collection, error) {
|
||||||
rr, err := p.DialOrDie().CoreV1().PersistentVolumes().List(opts)
|
// rr, err := p.DialOrDie().CoreV1().PersistentVolumes().List(opts)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
cc := make(Collection, len(rr.Items))
|
// cc := make(Collection, len(rr.Items))
|
||||||
for i, r := range rr.Items {
|
// for i, r := range rr.Items {
|
||||||
cc[i] = r
|
// cc[i] = r
|
||||||
}
|
// }
|
||||||
|
|
||||||
return cc, nil
|
// return cc, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Delete a PersistentVolume.
|
// // Delete a PersistentVolume.
|
||||||
func (p *PersistentVolume) Delete(_, n string, cascade, force bool) error {
|
// func (p *PersistentVolume) Delete(_, n string, cascade, force bool) error {
|
||||||
return p.DialOrDie().CoreV1().PersistentVolumes().Delete(n, nil)
|
// return p.DialOrDie().CoreV1().PersistentVolumes().Delete(n, nil)
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,41 @@
|
||||||
package k8s
|
package k8s
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
// import (
|
||||||
)
|
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
// )
|
||||||
|
|
||||||
// PersistentVolumeClaim represents a Kubernetes PersistentVolumeClaim.
|
// // PersistentVolumeClaim represents a Kubernetes PersistentVolumeClaim.
|
||||||
type PersistentVolumeClaim struct {
|
// type PersistentVolumeClaim struct {
|
||||||
*base
|
// *base
|
||||||
Connection
|
// Connection
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewPersistentVolumeClaim returns a new PersistentVolumeClaim.
|
// // NewPersistentVolumeClaim returns a new PersistentVolumeClaim.
|
||||||
func NewPersistentVolumeClaim(c Connection) *PersistentVolumeClaim {
|
// func NewPersistentVolumeClaim(c Connection) *PersistentVolumeClaim {
|
||||||
return &PersistentVolumeClaim{&base{}, c}
|
// return &PersistentVolumeClaim{&base{}, c}
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get a PersistentVolumeClaim.
|
// // Get a PersistentVolumeClaim.
|
||||||
func (p *PersistentVolumeClaim) Get(ns, n string) (interface{}, error) {
|
// func (p *PersistentVolumeClaim) Get(ns, n string) (interface{}, error) {
|
||||||
return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Get(n, metav1.GetOptions{})
|
// return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Get(n, metav1.GetOptions{})
|
||||||
}
|
// }
|
||||||
|
|
||||||
// List all PersistentVolumeClaims in a given namespace.
|
// // List all PersistentVolumeClaims in a given namespace.
|
||||||
func (p *PersistentVolumeClaim) List(ns string, opts metav1.ListOptions) (Collection, error) {
|
// func (p *PersistentVolumeClaim) List(ns string, opts metav1.ListOptions) (Collection, error) {
|
||||||
rr, err := p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).List(opts)
|
// rr, err := p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).List(opts)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
cc := make(Collection, len(rr.Items))
|
// cc := make(Collection, len(rr.Items))
|
||||||
for i, r := range rr.Items {
|
// for i, r := range rr.Items {
|
||||||
cc[i] = r
|
// cc[i] = r
|
||||||
}
|
// }
|
||||||
|
|
||||||
return cc, nil
|
// return cc, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Delete a PersistentVolumeClaim.
|
// // Delete a PersistentVolumeClaim.
|
||||||
func (p *PersistentVolumeClaim) Delete(ns, n string, cascade, force bool) error {
|
// func (p *PersistentVolumeClaim) Delete(ns, n string, cascade, force bool) error {
|
||||||
return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Delete(n, nil)
|
// return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Delete(n, nil)
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,103 +1,105 @@
|
||||||
package k8s
|
package k8s
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
"fmt"
|
// import (
|
||||||
|
// "fmt"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
// "github.com/derailed/k9s/internal/dao"
|
||||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
// metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
// "k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/client-go/dynamic"
|
// "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/client-go/rest"
|
// "k8s.io/client-go/dynamic"
|
||||||
)
|
// "k8s.io/client-go/rest"
|
||||||
|
// )
|
||||||
|
|
||||||
// Resource represents a Kubernetes Resource
|
// // Resource represents a Kubernetes Resource
|
||||||
type Resource struct {
|
// type Resource struct {
|
||||||
*base
|
// *base
|
||||||
Connection
|
// Connection
|
||||||
|
|
||||||
gvr GVR
|
// gvr dao.GVR
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewResource returns a new Resource.
|
// // NewResource returns a new Resource.
|
||||||
func NewResource(c Connection, gvr GVR) *Resource {
|
// func NewResource(c Connection, gvr GVR) *Resource {
|
||||||
return &Resource{base: &base{}, Connection: c, gvr: gvr}
|
// return &Resource{base: &base{}, Connection: c, gvr: gvr}
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetInfo returns info about apigroup.
|
// // GetInfo returns info about apigroup.
|
||||||
func (r *Resource) GetInfo() GVR {
|
// func (r *Resource) GetInfo() GVR {
|
||||||
return r.gvr
|
// return r.gvr
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Resource) nsRes() dynamic.NamespaceableResourceInterface {
|
// func (r *Resource) nsRes() dynamic.NamespaceableResourceInterface {
|
||||||
return r.DynDialOrDie().Resource(r.gvr.AsGVR())
|
// return r.DynDialOrDie().Resource(r.gvr.AsGVR())
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get a Resource.
|
// // Get a Resource.
|
||||||
func (r *Resource) Get(ns, n string) (interface{}, error) {
|
// func (r *Resource) Get(ns, n string) (interface{}, error) {
|
||||||
return r.nsRes().Namespace(ns).Get(n, metav1.GetOptions{})
|
// return r.nsRes().Namespace(ns).Get(n, metav1.GetOptions{})
|
||||||
}
|
// }
|
||||||
|
|
||||||
// List all Resources in a given namespace.
|
// // List all Resources in a given namespace.
|
||||||
func (r *Resource) List(ns string, opts metav1.ListOptions) (Collection, error) {
|
// func (r *Resource) List(ns string, opts metav1.ListOptions) (Collection, error) {
|
||||||
obj, err := r.listAll(ns, r.gvr.ToR())
|
// obj, err := r.listAll(ns, r.gvr.ToR())
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
return Collection{obj.(*metav1beta1.Table)}, nil
|
// return Collection{obj.(*metav1beta1.Table)}, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Delete a Resource.
|
// // Delete a Resource.
|
||||||
func (r *Resource) Delete(ns, n string, cascade, force bool) error {
|
// func (r *Resource) Delete(ns, n string, cascade, force bool) error {
|
||||||
return r.nsRes().Namespace(ns).Delete(n, nil)
|
// 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) {
|
// func (r *Resource) listAll(ns, n string) (runtime.Object, error) {
|
||||||
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
|
// a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
|
||||||
_, codec := r.codec()
|
// _, codec := r.codec()
|
||||||
|
|
||||||
c, err := r.getClient()
|
// c, err := r.getClient()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return c.Get().
|
// return c.Get().
|
||||||
SetHeader("Accept", a).
|
// SetHeader("Accept", a).
|
||||||
Namespace(ns).
|
// Namespace(ns).
|
||||||
Resource(n).
|
// Resource(n).
|
||||||
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
// VersionedParams(&metav1beta1.TableOptions{}, codec).
|
||||||
Do().Get()
|
// Do().Get()
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Resource) getClient() (*rest.RESTClient, error) {
|
// func (r *Resource) getClient() (*rest.RESTClient, error) {
|
||||||
crConfig := r.RestConfigOrDie()
|
// crConfig := r.RestConfigOrDie()
|
||||||
gv := r.gvr.AsGV()
|
// gv := r.gvr.AsGV()
|
||||||
crConfig.GroupVersion = &gv
|
// crConfig.GroupVersion = &gv
|
||||||
crConfig.APIPath = "/apis"
|
// crConfig.APIPath = "/apis"
|
||||||
if len(r.gvr.ToG()) == 0 {
|
// if len(r.gvr.ToG()) == 0 {
|
||||||
crConfig.APIPath = "/api"
|
// crConfig.APIPath = "/api"
|
||||||
}
|
// }
|
||||||
codec, _ := r.codec()
|
// codec, _ := r.codec()
|
||||||
crConfig.NegotiatedSerializer = codec.WithoutConversion()
|
// crConfig.NegotiatedSerializer = codec.WithoutConversion()
|
||||||
|
|
||||||
crRestClient, err := rest.RESTClientFor(crConfig)
|
// crRestClient, err := rest.RESTClientFor(crConfig)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
return crRestClient, nil
|
// return crRestClient, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Resource) codec() (serializer.CodecFactory, runtime.ParameterCodec) {
|
// func (r *Resource) codec() (serializer.CodecFactory, runtime.ParameterCodec) {
|
||||||
scheme := runtime.NewScheme()
|
// scheme := runtime.NewScheme()
|
||||||
gv := r.gvr.AsGV()
|
// gv := r.gvr.AsGV()
|
||||||
metav1.AddToGroupVersion(scheme, gv)
|
// metav1.AddToGroupVersion(scheme, gv)
|
||||||
scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{})
|
// scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{})
|
||||||
scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &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)
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -5,10 +5,17 @@ type ContextKey string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Factory represents a factory context key.
|
// Factory represents a factory context key.
|
||||||
KeyFactory ContextKey = "factory"
|
KeyFactory ContextKey = "factory"
|
||||||
KeySelection = "selection"
|
KeyLabels = "labels"
|
||||||
KeyLabels = "labels"
|
KeyFields = "fields"
|
||||||
KeyFields = "fields"
|
KeyTable = "table"
|
||||||
KeyTable = "table"
|
KeyDir = "dir"
|
||||||
KeyDir = "dir"
|
KeyPath = "path"
|
||||||
|
KeySubject = "subject"
|
||||||
|
KeyGVR = "gvr"
|
||||||
|
KeyForwards = "forwards"
|
||||||
|
KeyContainers = "containers"
|
||||||
|
KeyBenchCfg = "benchcfg"
|
||||||
|
KeyAliases = "aliases"
|
||||||
|
KeyUID = "uid"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
|
|
@ -28,10 +29,13 @@ type Container struct {
|
||||||
// List returns a collection of containers
|
// List returns a collection of containers
|
||||||
func (c *Container) List(ctx context.Context) ([]runtime.Object, error) {
|
func (c *Container) List(ctx context.Context) ([]runtime.Object, error) {
|
||||||
c.pod = nil
|
c.pod = nil
|
||||||
sel := ctx.Value(internal.KeySelection).(string)
|
path, ok := ctx.Value(internal.KeyPath).(string)
|
||||||
ns, n := render.Namespaced(sel)
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no context path for %q", c.gvr)
|
||||||
|
}
|
||||||
|
ns, _ := render.Namespaced(path)
|
||||||
c.namespace = ns
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func (g *Generic) List(ctx context.Context) ([]runtime.Object, error) {
|
||||||
// BOZO!! Need to know if gvr is namespaced or not
|
// BOZO!! Need to know if gvr is namespaced or not
|
||||||
o, err := c.Get().
|
o, err := c.Get().
|
||||||
SetHeader("Accept", fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)).
|
SetHeader("Accept", fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)).
|
||||||
// Namespace(g.namespace).
|
Namespace(g.namespace).
|
||||||
Resource(gvr.ToR()).
|
Resource(gvr.ToR()).
|
||||||
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
||||||
Do().Get()
|
Do().Get()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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))
|
mx := k8s.NewMetricsServer(n.factory.Client().(k8s.Connection))
|
||||||
mmx, err := mx.FetchNodesMetrics()
|
mmx, err := mx.FetchNodesMetrics()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Warn().Err(err).Msg("No node metrics")
|
||||||
}
|
}
|
||||||
|
|
||||||
var index int
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
|
|
@ -26,27 +25,21 @@ func (p *Pod) List(ctx context.Context) ([]runtime.Object, error) {
|
||||||
return oo, err
|
return oo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldSel, ok := ctx.Value(internal.KeyFields).(string)
|
sel, ok := ctx.Value(internal.KeyFields).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return oo, nil
|
return oo, nil
|
||||||
}
|
}
|
||||||
|
fsel, err := labels.ConvertSelectorToLabelsMap(sel)
|
||||||
sel, err := labels.ConvertSelectorToLabelsMap(fieldSel)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
nodeName := fsel["spec.nodeName"]
|
||||||
nodeName, ok := sel["spec.nodeName"]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("NYI field selector %q", nodeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
var res []runtime.Object
|
var res []runtime.Object
|
||||||
for _, o := range oo {
|
for _, o := range oo {
|
||||||
u := o.(*unstructured.Unstructured)
|
u := o.(*unstructured.Unstructured)
|
||||||
spec := u.Object["spec"].(map[string]interface{})
|
spec := u.Object["spec"].(map[string]interface{})
|
||||||
log.Debug().Msgf("Spec node %q -- %q", nodeName, spec["nodeName"])
|
if nodeName == "" || spec["nodeName"] == nodeName {
|
||||||
if spec["nodeName"] == nodeName {
|
|
||||||
res = append(res, o)
|
res = append(res, o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
// BOZO!! Break up deps and merge into single registrar
|
// BOZO!! Break up deps and merge into single registrar
|
||||||
var Registry = map[string]ResourceMeta{
|
var Registry = map[string]ResourceMeta{
|
||||||
|
// Custom...
|
||||||
"containers": ResourceMeta{
|
"containers": ResourceMeta{
|
||||||
Model: &Container{},
|
Model: &Container{},
|
||||||
Renderer: &render.Container{},
|
Renderer: &render.Container{},
|
||||||
|
|
@ -18,7 +19,24 @@ var Registry = map[string]ResourceMeta{
|
||||||
Model: &ScreenDump{},
|
Model: &ScreenDump{},
|
||||||
Renderer: &render.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{
|
"v1/pods": ResourceMeta{
|
||||||
Model: &Pod{},
|
Model: &Pod{},
|
||||||
Renderer: &render.Pod{},
|
Renderer: &render.Pod{},
|
||||||
|
|
@ -30,7 +48,20 @@ var Registry = map[string]ResourceMeta{
|
||||||
"v1/namespaces": ResourceMeta{
|
"v1/namespaces": ResourceMeta{
|
||||||
Renderer: &render.Namespace{},
|
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{
|
"apps/v1/deployments": ResourceMeta{
|
||||||
Renderer: &render.Deployment{},
|
Renderer: &render.Deployment{},
|
||||||
},
|
},
|
||||||
|
|
@ -43,31 +74,32 @@ var Registry = map[string]ResourceMeta{
|
||||||
"apps/v1/daemonsets": ResourceMeta{
|
"apps/v1/daemonsets": ResourceMeta{
|
||||||
Renderer: &render.DaemonSet{},
|
Renderer: &render.DaemonSet{},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Extensions...
|
||||||
"extensions/v1beta1/daemonsets": ResourceMeta{
|
"extensions/v1beta1/daemonsets": ResourceMeta{
|
||||||
Renderer: &render.DaemonSet{},
|
Renderer: &render.DaemonSet{},
|
||||||
},
|
},
|
||||||
|
"extensions/v1beta1/ingresses": ResourceMeta{
|
||||||
|
Renderer: &render.Ingress{},
|
||||||
|
},
|
||||||
|
|
||||||
// "v1/services": ResourceMeta{
|
// Batch...
|
||||||
// Renderer: &render.Service{},
|
"batch/v1beta1/cronjobs": ResourceMeta{
|
||||||
// },
|
Renderer: &render.CronJob{},
|
||||||
// "v1/configmaps": ResourceMeta{
|
},
|
||||||
// Renderer: &render.ConfigMap{},
|
"batch/v1/jobs": ResourceMeta{
|
||||||
// },
|
Model: &Job{},
|
||||||
// "v1/secrets": ResourceMeta{
|
Renderer: &render.Job{},
|
||||||
// Renderer: &render.ConfigMap{},
|
},
|
||||||
// },
|
|
||||||
// "batch/v1beta1/cronjobs": ResourceMeta{
|
|
||||||
// Renderer: &render.CronJob{},
|
|
||||||
// },
|
|
||||||
// "batch/v1/jobs": ResourceMeta{
|
|
||||||
// Renderer: &render.Job{},
|
|
||||||
// },
|
|
||||||
|
|
||||||
|
// CRDs...
|
||||||
"apiextensions.k8s.io/v1beta1/customresourcedefinitions": ResourceMeta{
|
"apiextensions.k8s.io/v1beta1/customresourcedefinitions": ResourceMeta{
|
||||||
Renderer: &render.CustomResourceDefinition{},
|
Renderer: &render.CustomResourceDefinition{},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// RBAC...
|
||||||
"rbac.authorization.k8s.io/v1/clusterroles": ResourceMeta{
|
"rbac.authorization.k8s.io/v1/clusterroles": ResourceMeta{
|
||||||
|
Model: &Rbac{},
|
||||||
Renderer: &render.ClusterRole{},
|
Renderer: &render.ClusterRole{},
|
||||||
},
|
},
|
||||||
"rbac.authorization.k8s.io/v1/clusterrolebindings": ResourceMeta{
|
"rbac.authorization.k8s.io/v1/clusterrolebindings": ResourceMeta{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"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 {
|
if sel, err := labels.ConvertSelectorToLabelsMap(strLabel); ok && err == nil {
|
||||||
lsel = sel.AsSelector()
|
lsel = sel.AsSelector()
|
||||||
}
|
}
|
||||||
|
log.Debug().Msgf("^^^^^Listing with selector %q:%q--%#v", r.namespace, r.gvr, lsel)
|
||||||
oo, err := r.factory.List(r.namespace, r.gvr, lsel)
|
oo, err := r.factory.List(r.gvr, r.namespace, lsel)
|
||||||
r.factory.WaitForCacheSync()
|
r.factory.WaitForCacheSync()
|
||||||
|
|
||||||
return oo, err
|
return oo, err
|
||||||
|
|
@ -41,9 +40,8 @@ func (r *Resource) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) err
|
||||||
|
|
||||||
var index int
|
var index int
|
||||||
for _, o := range oo {
|
for _, o := range oo {
|
||||||
res := o.(*unstructured.Unstructured)
|
|
||||||
var row render.Row
|
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
|
return err
|
||||||
}
|
}
|
||||||
rr[index] = row
|
rr[index] = row
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,22 @@ package model
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"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 {
|
type ScreenDump struct {
|
||||||
Resource
|
Resource
|
||||||
|
|
||||||
pod *v1.Pod
|
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) {
|
func (c *ScreenDump) List(ctx context.Context) ([]runtime.Object, error) {
|
||||||
dir, ok := ctx.Value(internal.KeyDir).(string)
|
dir, ok := ctx.Value(internal.KeyDir).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -35,7 +32,7 @@ func (c *ScreenDump) List(ctx context.Context) ([]runtime.Object, error) {
|
||||||
|
|
||||||
oo := make([]runtime.Object, len(ff))
|
oo := make([]runtime.Object, len(ff))
|
||||||
for i, f := range ff {
|
for i, f := range ff {
|
||||||
oo[i] = FileRes{file: f, dir: dir}
|
oo[i] = render.FileRes{File: f, Dir: dir}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oo, nil
|
return oo, nil
|
||||||
|
|
@ -44,38 +41,9 @@ func (c *ScreenDump) List(ctx context.Context) ([]runtime.Object, error) {
|
||||||
// Hydrate returns a pod as container rows.
|
// Hydrate returns a pod as container rows.
|
||||||
func (c *ScreenDump) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
|
func (c *ScreenDump) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
|
||||||
for i, o := range oo {
|
for i, o := range oo {
|
||||||
res, ok := o.(FileRes)
|
if err := re.Render(o, render.NonResource, &rr[i]); err != nil {
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("expecting a file resource but got %T", o)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := re.Render(res, render.NonResource, &rr[i]); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -106,12 +106,6 @@ func (s *Stack) Pop() (Component, bool) {
|
||||||
c := s.components[s.size()]
|
c := s.components[s.size()]
|
||||||
s.components = s.components[:s.size()]
|
s.components = s.components[:s.size()]
|
||||||
s.notify(StackPop, c)
|
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
|
return c, true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
|
"github.com/derailed/k9s/internal/watch"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
@ -69,45 +70,24 @@ type Lister interface {
|
||||||
Hydrate([]runtime.Object, render.Rows, Renderer) error
|
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 {
|
type Factory interface {
|
||||||
// Client retrieves an api client.
|
// Client retrieves an api client.
|
||||||
Client() k8s.Connection
|
Client() k8s.Connection
|
||||||
|
|
||||||
// Get fetch a given resource.
|
// 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 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 fetch an informer for a given resource.
|
||||||
ForResource(ns, gvr string) informers.GenericInformer
|
ForResource(ns, gvr string) informers.GenericInformer
|
||||||
|
|
||||||
// WaitForCacheSync synchronize the cache.
|
// WaitForCacheSync synchronize the cache.
|
||||||
WaitForCacheSync() map[schema.GroupVersionResource]bool
|
WaitForCacheSync() map[schema.GroupVersionResource]bool
|
||||||
|
|
||||||
|
// Forwards returns all portforwards.
|
||||||
|
Forwarders() watch.Forwarders
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceMeta represents model info about a resource.
|
// ResourceMeta represents model info about a resource.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Alias renders a aliases to screen.
|
// Alias renders a aliases to screen.
|
||||||
|
|
@ -24,25 +26,24 @@ func (Alias) Header(ns string) HeaderRow {
|
||||||
Header{Name: "RESOURCE"},
|
Header{Name: "RESOURCE"},
|
||||||
Header{Name: "COMMAND"},
|
Header{Name: "COMMAND"},
|
||||||
Header{Name: "APIGROUP"},
|
Header{Name: "APIGROUP"},
|
||||||
|
// Header{Name: "AGE", Decorator: ageDecorator},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// Render renders a K8s resource to screen.
|
||||||
func (Alias) Render(o interface{}, gvr string, r *Row) error {
|
func (Alias) Render(o interface{}, gvr string, r *Row) error {
|
||||||
aliases, ok := o.([]string)
|
a, ok := o.(AliasRes)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expected Alias, but got %T", o)
|
return fmt.Errorf("expected aliasres, but got %T", o)
|
||||||
}
|
}
|
||||||
|
|
||||||
g := k8s.GVR(gvr)
|
g := k8s.GVR(a.GVR)
|
||||||
r.ID = string(gvr)
|
r.ID = string(g)
|
||||||
r.Fields = Fields{
|
r.Fields = Fields{
|
||||||
g.ToR(),
|
g.ToR(),
|
||||||
strings.Join(aliases, ","),
|
strings.Join(a.Aliases, ","),
|
||||||
g.ToG(),
|
g.ToG(),
|
||||||
// Pad(g.ToR(), 30),
|
// time.Now().String(),
|
||||||
// Pad(strings.Join(aliases, ","), 70),
|
|
||||||
// Pad(g.ToG(), 30),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -50,15 +51,18 @@ func (Alias) Render(o interface{}, gvr string, r *Row) error {
|
||||||
|
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
// Pad a string up to the given length or truncates if greater than length.
|
// AliasRes represents an alias resource.
|
||||||
func Pad(s string, width int) string {
|
type AliasRes struct {
|
||||||
if len(s) == width {
|
GVR string
|
||||||
return s
|
Aliases []string
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s) > width {
|
// GetObjectKind returns a schema object.
|
||||||
return Truncate(s, width)
|
func (AliasRes) GetObjectKind() schema.ObjectKind {
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
return s + strings.Repeat(" ", width-len(s))
|
|
||||||
|
// DeepCopyObject returns a container copy.
|
||||||
|
func (a AliasRes) DeepCopyObject() runtime.Object {
|
||||||
|
return a
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,13 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
"golang.org/x/text/message"
|
"golang.org/x/text/message"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -23,17 +24,11 @@ var (
|
||||||
toastRx = regexp.MustCompile(`Error distribution`)
|
toastRx = regexp.MustCompile(`Error distribution`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// BenchInfo represents benchmark run info.
|
// Benchmark renders a benchmarks to screen.
|
||||||
type BenchInfo struct {
|
type Benchmark struct{}
|
||||||
File os.FileInfo
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bench renders a benchmarks to screen.
|
|
||||||
type Bench struct{}
|
|
||||||
|
|
||||||
// ColorerFunc colors a resource row.
|
// ColorerFunc colors a resource row.
|
||||||
func (Bench) ColorerFunc() ColorerFunc {
|
func (Benchmark) ColorerFunc() ColorerFunc {
|
||||||
return func(ns string, re RowEvent) tcell.Color {
|
return func(ns string, re RowEvent) tcell.Color {
|
||||||
c := tcell.ColorPaleGreen
|
c := tcell.ColorPaleGreen
|
||||||
statusCol := 2
|
statusCol := 2
|
||||||
|
|
@ -45,25 +40,25 @@ func (Bench) ColorerFunc() ColorerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header returns a header row.
|
// Header returns a header row.
|
||||||
func (Bench) Header(ns string) HeaderRow {
|
func (Benchmark) Header(ns string) HeaderRow {
|
||||||
return HeaderRow{
|
return HeaderRow{
|
||||||
Header{Name: "NAMESPACE", Align: tview.AlignLeft},
|
Header{Name: "NAMESPACE"},
|
||||||
Header{Name: "NAME", Align: tview.AlignLeft},
|
Header{Name: "NAME"},
|
||||||
Header{Name: "STATUS", Align: tview.AlignLeft},
|
Header{Name: "STATUS"},
|
||||||
Header{Name: "TIME", Align: tview.AlignLeft},
|
Header{Name: "TIME"},
|
||||||
Header{Name: "REQ/S", Align: tview.AlignRight},
|
Header{Name: "REQ/S", Align: tview.AlignRight},
|
||||||
Header{Name: "2XX", Align: tview.AlignRight},
|
Header{Name: "2XX", Align: tview.AlignRight},
|
||||||
Header{Name: "4XX/5XX", Align: tview.AlignRight},
|
Header{Name: "4XX/5XX", Align: tview.AlignRight},
|
||||||
Header{Name: "REPORT", Align: tview.AlignLeft},
|
Header{Name: "REPORT"},
|
||||||
Header{Name: "AGE", Align: tview.AlignLeft},
|
Header{Name: "AGE", Decorator: ageDecorator},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// 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)
|
bench, ok := o.(BenchInfo)
|
||||||
if !ok {
|
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)
|
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)
|
return fmt.Errorf("Unable to load bench file %s", bench.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.ID = bench.Path
|
||||||
r.Fields = make(Fields, len(b.Header(ns)))
|
r.Fields = make(Fields, len(b.Header(ns)))
|
||||||
if err := b.initRow(r.Fields, bench.File); err != nil {
|
if err := b.initRow(r.Fields, bench.File); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.augmentRow(r.Fields, data)
|
b.augmentRow(r.Fields, data)
|
||||||
r.ID = bench.Path
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
func (Bench) readFile(file string) (string, error) {
|
func (Benchmark) readFile(file string) (string, error) {
|
||||||
data, err := ioutil.ReadFile(file)
|
data, err := ioutil.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -91,7 +87,7 @@ func (Bench) readFile(file string) (string, error) {
|
||||||
return string(data), nil
|
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(), "_")
|
tokens := strings.Split(f.Name(), "_")
|
||||||
if len(tokens) < 2 {
|
if len(tokens) < 2 {
|
||||||
return fmt.Errorf("Invalid file name %s", f.Name())
|
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[0] = tokens[0]
|
||||||
row[1] = tokens[1]
|
row[1] = tokens[1]
|
||||||
row[7] = f.Name()
|
row[7] = f.Name()
|
||||||
row[8] = time.Since(f.ModTime()).String()
|
row[8] = timeToAge(f.ModTime())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Bench) augmentRow(fields Fields, data string) {
|
func (b Benchmark) augmentRow(fields Fields, data string) {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +133,7 @@ func (b Bench) augmentRow(fields Fields, data string) {
|
||||||
fields[col] = b.countReq(me)
|
fields[col] = b.countReq(me)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Bench) countReq(rr [][]string) string {
|
func (Benchmark) countReq(rr [][]string) string {
|
||||||
if len(rr) == 0 {
|
if len(rr) == 0 {
|
||||||
return "0"
|
return "0"
|
||||||
}
|
}
|
||||||
|
|
@ -156,3 +152,19 @@ func asNum(n int) string {
|
||||||
p := message.NewPrinter(language.English)
|
p := message.NewPrinter(language.English)
|
||||||
return p.Sprintf("%d", n)
|
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
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package render
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
@ -28,7 +29,7 @@ func (ClusterRole) Header(string) HeaderRow {
|
||||||
func (ClusterRole) Render(o interface{}, ns string, r *Row) error {
|
func (ClusterRole) Render(o interface{}, ns string, r *Row) error {
|
||||||
raw, ok := o.(*unstructured.Unstructured)
|
raw, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expected ClusterRole, but got %T", o)
|
return fmt.Errorf("expecting clusterrole, but got %T", o)
|
||||||
}
|
}
|
||||||
var cr rbacv1.ClusterRole
|
var cr rbacv1.ClusterRole
|
||||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &cr)
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &cr)
|
||||||
|
|
@ -36,12 +37,11 @@ func (ClusterRole) Render(o interface{}, ns string, r *Row) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := make(Fields, 0, len(r.Fields))
|
r.ID = k8s.FQN("-", cr.ObjectMeta.Name)
|
||||||
fields = append(fields,
|
r.Fields = Fields{
|
||||||
cr.Name,
|
cr.Name,
|
||||||
toAge(cr.ObjectMeta.CreationTimestamp),
|
toAge(cr.ObjectMeta.CreationTimestamp),
|
||||||
)
|
}
|
||||||
r.ID, r.Fields = MetaFQN(cr.ObjectMeta), fields
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package render
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
@ -20,7 +21,7 @@ func (ClusterRoleBinding) ColorerFunc() ColorerFunc {
|
||||||
func (ClusterRoleBinding) Header(string) HeaderRow {
|
func (ClusterRoleBinding) Header(string) HeaderRow {
|
||||||
return HeaderRow{
|
return HeaderRow{
|
||||||
Header{Name: "NAME"},
|
Header{Name: "NAME"},
|
||||||
Header{Name: "ROLE"},
|
Header{Name: "CLUSTERROLE"},
|
||||||
Header{Name: "KIND"},
|
Header{Name: "KIND"},
|
||||||
Header{Name: "SUBJECTS"},
|
Header{Name: "SUBJECTS"},
|
||||||
Header{Name: "AGE", Decorator: ageDecorator},
|
Header{Name: "AGE", Decorator: ageDecorator},
|
||||||
|
|
@ -41,15 +42,14 @@ func (ClusterRoleBinding) Render(o interface{}, ns string, r *Row) error {
|
||||||
|
|
||||||
kind, ss := renderSubjects(crb.Subjects)
|
kind, ss := renderSubjects(crb.Subjects)
|
||||||
|
|
||||||
fields := make(Fields, 0, len(r.Fields))
|
r.ID = k8s.FQN("-", crb.ObjectMeta.Name)
|
||||||
fields = append(fields,
|
r.Fields = Fields{
|
||||||
crb.Name,
|
crb.Name,
|
||||||
crb.RoleRef.Name,
|
crb.RoleRef.Name,
|
||||||
kind,
|
kind,
|
||||||
ss,
|
ss,
|
||||||
toAge(crb.ObjectMeta.CreationTimestamp),
|
toAge(crb.ObjectMeta.CreationTimestamp),
|
||||||
)
|
}
|
||||||
r.ID, r.Fields = MetaFQN(crb.ObjectMeta), fields
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,11 @@ func (CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error {
|
||||||
log.Error().Err(err).Msgf("Fields timestamp %v", err)
|
log.Error().Err(err).Msgf("Fields timestamp %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := make(Fields, 0, len(r.Fields))
|
r.ID = FQN(ClusterWide, meta["name"].(string))
|
||||||
fields = append(fields,
|
r.Fields = Fields{
|
||||||
meta["name"].(string),
|
meta["name"].(string),
|
||||||
toAge(metav1.Time{t}),
|
toAge(metav1.Time{t}),
|
||||||
)
|
}
|
||||||
|
|
||||||
r.ID, r.Fields = FQN("", meta["name"].(string)), fields
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func (CronJob) Header(ns string) HeaderRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// 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)
|
raw, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expected CronJob, but got %T", o)
|
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))
|
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) {
|
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.Name,
|
||||||
cj.Spec.Schedule,
|
cj.Spec.Schedule,
|
||||||
boolPtrToStr(cj.Spec.Suspend),
|
boolPtrToStr(cj.Spec.Suspend),
|
||||||
|
|
@ -64,7 +65,5 @@ func (CronJob) Render(o interface{}, ns string, r *Row) error {
|
||||||
toAge(cj.ObjectMeta.CreationTimestamp),
|
toAge(cj.ObjectMeta.CreationTimestamp),
|
||||||
)
|
)
|
||||||
|
|
||||||
r.ID, r.Fields = MetaFQN(cj.ObjectMeta), fields
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import "github.com/rs/zerolog/log"
|
|
||||||
|
|
||||||
// DeltaRow represents a collection of row detlas between old and new row.
|
// DeltaRow represents a collection of row detlas between old and new row.
|
||||||
type DeltaRow []string
|
type DeltaRow []string
|
||||||
|
|
||||||
// NewDeltaRow computes the delta between 2 rows.
|
// 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))
|
deltas := make(DeltaRow, len(o.Fields))
|
||||||
// Exclude age col
|
// Exclude age col
|
||||||
oldFields := o.Fields[:len(o.Fields)-1]
|
oldFields := o.Fields[:len(o.Fields)-1]
|
||||||
|
if !excludeLast {
|
||||||
|
oldFields = o.Fields[:len(o.Fields)]
|
||||||
|
}
|
||||||
for i, old := range oldFields {
|
for i, old := range oldFields {
|
||||||
if old != "" && old != n.Fields[i] {
|
if old != "" && old != n.Fields[i] {
|
||||||
log.Debug().Msgf("OLD VS NEW %q:%q", old, n.Fields[i])
|
|
||||||
deltas[i] = old
|
deltas[i] = old
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
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/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
@ -17,10 +15,6 @@ import (
|
||||||
// Deployment renders a K8s Deployment to screen.
|
// Deployment renders a K8s Deployment to screen.
|
||||||
type Deployment struct{}
|
type Deployment struct{}
|
||||||
|
|
||||||
func isAllNamespace(ns string) bool {
|
|
||||||
return ns == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColorerFunc colors a resource row.
|
// ColorerFunc colors a resource row.
|
||||||
func (Deployment) ColorerFunc() ColorerFunc {
|
func (Deployment) ColorerFunc() ColorerFunc {
|
||||||
return func(ns string, r RowEvent) tcell.Color {
|
return func(ns string, r RowEvent) tcell.Color {
|
||||||
|
|
@ -54,7 +48,6 @@ func (Deployment) Header(ns string) HeaderRow {
|
||||||
Header{Name: "READY"},
|
Header{Name: "READY"},
|
||||||
Header{Name: "UP-TO-DATE", Align: tview.AlignRight},
|
Header{Name: "UP-TO-DATE", Align: tview.AlignRight},
|
||||||
Header{Name: "AVAILABLE", Align: tview.AlignRight},
|
Header{Name: "AVAILABLE", Align: tview.AlignRight},
|
||||||
Header{Name: "SELECTOR"},
|
|
||||||
Header{Name: "AGE", Decorator: ageDecorator},
|
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.AvailableReplicas))+"/"+strconv.Itoa(int(*dp.Spec.Replicas)),
|
||||||
strconv.Itoa(int(dp.Status.UpdatedReplicas)),
|
strconv.Itoa(int(dp.Status.UpdatedReplicas)),
|
||||||
strconv.Itoa(int(dp.Status.AvailableReplicas)),
|
strconv.Itoa(int(dp.Status.AvailableReplicas)),
|
||||||
asSelector(dp.Spec.Selector),
|
|
||||||
toAge(dp.ObjectMeta.CreationTimestamp),
|
toAge(dp.ObjectMeta.CreationTimestamp),
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
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()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ func (DaemonSet) Header(ns string) HeaderRow {
|
||||||
Header{Name: "READY", Align: tview.AlignRight},
|
Header{Name: "READY", Align: tview.AlignRight},
|
||||||
Header{Name: "UP-TO-DATE", Align: tview.AlignRight},
|
Header{Name: "UP-TO-DATE", Align: tview.AlignRight},
|
||||||
Header{Name: "AVAILABLE", Align: tview.AlignRight},
|
Header{Name: "AVAILABLE", Align: tview.AlignRight},
|
||||||
Header{Name: "NODE_SELECTOR"},
|
|
||||||
Header{Name: "AGE", Decorator: ageDecorator},
|
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.NumberReady)),
|
||||||
strconv.Itoa(int(ds.Status.UpdatedNumberScheduled)),
|
strconv.Itoa(int(ds.Status.UpdatedNumberScheduled)),
|
||||||
strconv.Itoa(int(ds.Status.NumberAvailable)),
|
strconv.Itoa(int(ds.Status.NumberAvailable)),
|
||||||
mapToStr(ds.Spec.Template.Spec.NodeSelector),
|
|
||||||
toAge(ds.ObjectMeta.CreationTimestamp),
|
toAge(ds.ObjectMeta.CreationTimestamp),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -224,16 +224,17 @@ type ColorerFunc func(ns string, evt RowEvent) tcell.Color
|
||||||
|
|
||||||
// DefaultColorer set the default table row colors.
|
// DefaultColorer set the default table row colors.
|
||||||
func DefaultColorer(ns string, evt RowEvent) tcell.Color {
|
func DefaultColorer(ns string, evt RowEvent) tcell.Color {
|
||||||
|
var col = StdColor
|
||||||
switch evt.Kind {
|
switch evt.Kind {
|
||||||
case EventAdd:
|
case EventAdd:
|
||||||
return AddColor
|
col = AddColor
|
||||||
case EventUpdate:
|
case EventUpdate:
|
||||||
return ModColor
|
col = ModColor
|
||||||
case EventDelete:
|
case EventDelete:
|
||||||
return KillColor
|
col = KillColor
|
||||||
default:
|
|
||||||
return StdColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return col
|
||||||
}
|
}
|
||||||
|
|
||||||
type StringSet []string
|
type StringSet []string
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,11 @@ func (Generic) ColorerFunc() ColorerFunc {
|
||||||
|
|
||||||
// Header returns a header row.
|
// Header returns a header row.
|
||||||
func (g *Generic) Header(ns string) HeaderRow {
|
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 == "" {
|
if ns == "" {
|
||||||
h = append(h, Header{Name: "NAMESPACE"})
|
h = append(h, Header{Name: "NAMESPACE"})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/duration"
|
"k8s.io/apimachinery/pkg/util/duration"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
|
@ -26,6 +27,20 @@ const (
|
||||||
NAValue = "n/a"
|
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 {
|
type metric struct {
|
||||||
cpu, mem string
|
cpu, mem string
|
||||||
}
|
}
|
||||||
|
|
@ -217,3 +232,16 @@ func in(ll []string, s string) bool {
|
||||||
}
|
}
|
||||||
return false
|
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))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func (Ingress) Header(ns string) HeaderRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// 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)
|
raw, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expected Ingress, but got %T", o)
|
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
|
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) {
|
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,
|
ing.Name,
|
||||||
toHosts(ing.Spec.Rules),
|
toHosts(ing.Spec.Rules),
|
||||||
toAddress(ing.Status.LoadBalancer),
|
toAddress(ing.Status.LoadBalancer),
|
||||||
|
|
@ -58,8 +59,6 @@ func (Ingress) Render(o interface{}, ns string, r *Row) error {
|
||||||
toAge(ing.ObjectMeta.CreationTimestamp),
|
toAge(ing.ObjectMeta.CreationTimestamp),
|
||||||
)
|
)
|
||||||
|
|
||||||
r.ID, r.Fields = MetaFQN(ing.ObjectMeta), fields
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,15 +88,13 @@ func toTLSPorts(tls []v1beta1.IngressTLS) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func toHosts(rr []v1beta1.IngressRule) string {
|
func toHosts(rr []v1beta1.IngressRule) string {
|
||||||
var s string
|
hh := make([]string, 0, len(rr))
|
||||||
var i int
|
|
||||||
for _, r := range rr {
|
for _, r := range rr {
|
||||||
s += r.Host
|
if r.Host == "" {
|
||||||
if i < len(rr)-1 {
|
r.Host = "*"
|
||||||
s += ","
|
|
||||||
}
|
}
|
||||||
i++
|
hh = append(hh, r.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return strings.Join(hh, ",")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
batchv1 "k8s.io/api/batch/v1"
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"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.
|
// 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)
|
raw, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expected Job, but got %T", o)
|
return fmt.Errorf("Expected Job, but got %T", o)
|
||||||
}
|
}
|
||||||
var j batchv1.Job
|
var job batchv1.Job
|
||||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &j)
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cc, ii := toContainers(j.Spec.Template.Spec)
|
r.ID = MetaFQN(job.ObjectMeta)
|
||||||
|
r.Fields = make(Fields, 0, len(j.Header(ns)))
|
||||||
fields := make(Fields, 0, len(r.Fields))
|
|
||||||
if isAllNamespace(ns) {
|
if isAllNamespace(ns) {
|
||||||
fields = append(fields, j.Namespace)
|
r.Fields = append(r.Fields, job.Namespace)
|
||||||
}
|
}
|
||||||
fields = append(fields,
|
cc, ii := toContainers(job.Spec.Template.Spec)
|
||||||
j.Name,
|
r.Fields = append(r.Fields,
|
||||||
toCompletion(j.Spec, j.Status),
|
job.Name,
|
||||||
toDuration(j.Status),
|
toCompletion(job.Spec, job.Status),
|
||||||
|
toDuration(job.Status),
|
||||||
cc,
|
cc,
|
||||||
ii,
|
ii,
|
||||||
toAge(j.ObjectMeta.CreationTimestamp),
|
toAge(job.ObjectMeta.CreationTimestamp),
|
||||||
)
|
)
|
||||||
|
|
||||||
r.ID, r.Fields = MetaFQN(j.ObjectMeta), fields
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
const maxShow = 2
|
const maxShow = 2
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,13 @@ type Namespace struct{}
|
||||||
func (Namespace) ColorerFunc() ColorerFunc {
|
func (Namespace) ColorerFunc() ColorerFunc {
|
||||||
return func(ns string, r RowEvent) tcell.Color {
|
return func(ns string, r RowEvent) tcell.Color {
|
||||||
c := DefaultColorer(ns, r)
|
c := DefaultColorer(ns, r)
|
||||||
|
if r.Kind == EventAdd {
|
||||||
if r.Kind == EventAdd || r.Kind == EventUpdate {
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.Kind == EventUpdate {
|
||||||
|
c = StdColor
|
||||||
|
}
|
||||||
switch strings.TrimSpace(r.Row.Fields[1]) {
|
switch strings.TrimSpace(r.Row.Fields[1]) {
|
||||||
case "Inactive", Terminating:
|
case "Inactive", Terminating:
|
||||||
c = ErrColor
|
c = ErrColor
|
||||||
|
|
@ -54,13 +57,12 @@ func (Namespace) Render(o interface{}, _ string, r *Row) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := make(Fields, 0, len(r.Fields))
|
r.ID = MetaFQN(ns.ObjectMeta)
|
||||||
fields = append(fields,
|
r.Fields = Fields{
|
||||||
ns.Name,
|
ns.Name,
|
||||||
string(ns.Status.Phase),
|
string(ns.Status.Phase),
|
||||||
toAge(ns.ObjectMeta.CreationTimestamp),
|
toAge(ns.ObjectMeta.CreationTimestamp),
|
||||||
)
|
}
|
||||||
r.ID, r.Fields = MetaFQN(ns.ObjectMeta), fields
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Forwarder represents a port forwarder.
|
// Forwarder represents a port forwarder.
|
||||||
|
|
@ -25,18 +27,18 @@ type Forwarder interface {
|
||||||
Age() string
|
Age() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward renders a portforwards to screen.
|
// PortForward renders a portforwards to screen.
|
||||||
type Forward struct{}
|
type PortForward struct{}
|
||||||
|
|
||||||
// ColorerFunc colors a resource row.
|
// ColorerFunc colors a resource row.
|
||||||
func (Forward) ColorerFunc() ColorerFunc {
|
func (PortForward) ColorerFunc() ColorerFunc {
|
||||||
return func(ns string, re RowEvent) tcell.Color {
|
return func(ns string, re RowEvent) tcell.Color {
|
||||||
return tcell.ColorSkyblue
|
return tcell.ColorSkyblue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header returns a header row.
|
// Header returns a header row.
|
||||||
func (Forward) Header(ns string) HeaderRow {
|
func (PortForward) Header(ns string) HeaderRow {
|
||||||
return HeaderRow{
|
return HeaderRow{
|
||||||
Header{Name: "NAMESPACE"},
|
Header{Name: "NAMESPACE"},
|
||||||
Header{Name: "NAME"},
|
Header{Name: "NAME"},
|
||||||
|
|
@ -50,10 +52,10 @@ func (Forward) Header(ns string) HeaderRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// Render renders a K8s resource to screen.
|
||||||
func (f Forward) Render(o interface{}, gvr string, r *Row) error {
|
func (f PortForward) Render(o interface{}, gvr string, r *Row) error {
|
||||||
pf, ok := o.(PortForwarder)
|
pf, ok := o.(ForwardRes)
|
||||||
if !ok {
|
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], ":")
|
ports := strings.Split(pf.Ports()[0], ":")
|
||||||
|
|
@ -65,9 +67,9 @@ func (f Forward) Render(o interface{}, gvr string, r *Row) error {
|
||||||
na,
|
na,
|
||||||
pf.Container(),
|
pf.Container(),
|
||||||
strings.Join(pf.Ports(), ","),
|
strings.Join(pf.Ports(), ","),
|
||||||
UrlFor(pf.Host(), pf.HttpPath(), ports[0]),
|
UrlFor(pf.Config.Host, pf.Config.Path, ports[0]),
|
||||||
asNum(pf.C()),
|
asNum(pf.Config.C),
|
||||||
asNum(pf.N()),
|
asNum(pf.Config.N),
|
||||||
pf.Age(),
|
pf.Age(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,26 +78,27 @@ func (f Forward) Render(o interface{}, gvr string, r *Row) error {
|
||||||
|
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
type PortForwarder interface {
|
// type PortForwarder interface {
|
||||||
Forwarder
|
// Forwarder
|
||||||
BenchConfigurator
|
// BenchConfigurator
|
||||||
}
|
// }
|
||||||
|
|
||||||
type BenchConfigurators map[string]BenchConfigurator
|
// type BenchConfigurators map[string]BenchConfigurator
|
||||||
|
|
||||||
type BenchConfigurator interface {
|
// BOZO!!
|
||||||
// C returns the number of concurent connections.
|
// type BenchConfigurator interface {
|
||||||
C() int
|
// // C returns the number of concurent connections.
|
||||||
|
// C() int
|
||||||
|
|
||||||
// N returns the number of requests.
|
// // N returns the number of requests.
|
||||||
N() int
|
// N() int
|
||||||
|
|
||||||
// Host returns the forward host address.
|
// // Host returns the forward host address.
|
||||||
Host() string
|
// Host() string
|
||||||
|
|
||||||
// Path returns the http path.
|
// // Path returns the http path.
|
||||||
HttpPath() string
|
// HttpPath() string
|
||||||
}
|
// }
|
||||||
|
|
||||||
// UrlFor computes fq url for a given benchmark configuration.
|
// UrlFor computes fq url for a given benchmark configuration.
|
||||||
func UrlFor(host, path, port string) string {
|
func UrlFor(host, path, port string) string {
|
||||||
|
|
@ -108,3 +111,25 @@ func UrlFor(host, path, port string) string {
|
||||||
|
|
||||||
return "http://" + host + ":" + port + path
|
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
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,32 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"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.
|
// Rbac renders a rbac to screen.
|
||||||
|
|
@ -26,8 +51,95 @@ func (Rbac) Header(ns string) HeaderRow {
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// Render renders a K8s resource to screen.
|
||||||
func (Rbac) Render(o interface{}, gvr string, r *Row) error {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers...
|
// 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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ func (Role) Header(ns string) HeaderRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// 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)
|
raw, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expected Role, but got %T", o)
|
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
|
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) {
|
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,
|
ro.Name,
|
||||||
toAge(ro.ObjectMeta.CreationTimestamp),
|
toAge(ro.ObjectMeta.CreationTimestamp),
|
||||||
)
|
)
|
||||||
r.ID, r.Fields = MetaFQN(ro.ObjectMeta), fields
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ func (RoleBinding) Header(ns string) HeaderRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// 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)
|
raw, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expected RoleBinding, but got %T", o)
|
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)
|
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) {
|
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.Name,
|
||||||
rb.RoleRef.Name,
|
rb.RoleRef.Name,
|
||||||
kind,
|
kind,
|
||||||
ss,
|
ss,
|
||||||
toAge(rb.ObjectMeta.CreationTimestamp),
|
toAge(rb.ObjectMeta.CreationTimestamp),
|
||||||
)
|
)
|
||||||
r.ID, r.Fields = MetaFQN(rb.ObjectMeta), fields
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"vbom.ml/util/sortorder"
|
"vbom.ml/util/sortorder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ageCol = "AGE"
|
||||||
|
|
||||||
// Fields represents a collection of row fields.
|
// Fields represents a collection of row fields.
|
||||||
type Fields []string
|
type Fields []string
|
||||||
|
|
||||||
|
|
@ -29,7 +31,21 @@ type Header struct {
|
||||||
// HeaderRow represents a table header.
|
// HeaderRow represents a table header.
|
||||||
type HeaderRow []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 {
|
func (h HeaderRow) AgeCol(col int) bool {
|
||||||
|
if !h.HasAge() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return col == len(h)-1
|
return col == len(h)-1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ScreenDump renders a screendumps to screen.
|
// ScreenDump renders a screendumps to screen.
|
||||||
|
|
@ -35,27 +37,39 @@ func (ScreenDump) Header(ns string) HeaderRow {
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// Render renders a K8s resource to screen.
|
||||||
func (b ScreenDump) Render(o interface{}, ns string, r *Row) error {
|
func (b ScreenDump) Render(o interface{}, ns string, r *Row) error {
|
||||||
f, ok := o.(ScreenDumper)
|
f, ok := o.(FileRes)
|
||||||
if !ok {
|
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{
|
r.Fields = Fields{
|
||||||
f.GetFile().Name(),
|
f.File.Name(),
|
||||||
timeToAge(f.GetFile().ModTime()),
|
timeToAge(f.File.ModTime()),
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
func timeToAge(timestamp time.Time) string {
|
func timeToAge(timestamp time.Time) string {
|
||||||
return time.Since(timestamp).String()
|
return time.Since(timestamp).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScreenDumper interface {
|
// FileRes represents a file resource.
|
||||||
GetFile() os.FileInfo
|
type FileRes struct {
|
||||||
GetDir() string
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,12 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/derailed/k9s/internal/watch"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
genericprinters "k8s.io/cli-runtime/pkg/printers"
|
genericprinters "k8s.io/cli-runtime/pkg/printers"
|
||||||
"k8s.io/kubectl/pkg/describe"
|
"k8s.io/kubectl/pkg/describe"
|
||||||
|
|
@ -163,43 +157,44 @@ func (*Base) marshalObject(o runtime.Object) (string, error) {
|
||||||
return buff.String(), nil
|
return buff.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error {
|
// BOZO!!
|
||||||
f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
|
// func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error {
|
||||||
if !ok {
|
// f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
|
||||||
return fmt.Errorf("no factory in context for pod logs")
|
// if !ok {
|
||||||
}
|
// return fmt.Errorf("no factory in context for pod logs")
|
||||||
|
// }
|
||||||
|
|
||||||
ls, err := metav1.ParseToLabelSelector(toSelector(sel))
|
// ls, err := metav1.ParseToLabelSelector(toSelector(sel))
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
lsel, err := metav1.LabelSelectorAsSelector(ls)
|
// lsel, err := metav1.LabelSelectorAsSelector(ls)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
inf := f.ForResource(opts.Namespace, "v1/pods")
|
// inf := f.ForResource(opts.Namespace, "v1/pods")
|
||||||
pods, err := inf.Lister().List(lsel)
|
// pods, err := inf.Lister().List(lsel)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
if len(pods) > 1 {
|
// if len(pods) > 1 {
|
||||||
opts.MultiPods = true
|
// opts.MultiPods = true
|
||||||
}
|
// }
|
||||||
pr := NewPod(b.Connection)
|
// pr := NewPod(b.Connection)
|
||||||
for _, p := range pods {
|
// for _, p := range pods {
|
||||||
var po v1.Pod
|
// var po v1.Pod
|
||||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(p.(*unstructured.Unstructured).Object, &po)
|
// err := runtime.DefaultUnstructuredConverter.FromUnstructured(p.(*unstructured.Unstructured).Object, &po)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
// BOZO!!
|
// // BOZO!!
|
||||||
panic(err)
|
// panic(err)
|
||||||
}
|
// }
|
||||||
if po.Status.Phase == v1.PodRunning {
|
// if po.Status.Phase == v1.PodRunning {
|
||||||
opts.Namespace, opts.Name = po.Namespace, po.Name
|
// opts.Namespace, opts.Name = po.Namespace, po.Name
|
||||||
if err := pr.PodLogs(ctx, c, opts); err != nil {
|
// if err := pr.PodLogs(ctx, c, opts); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
// NewConfigMapList returns a new resource list.
|
// BOZO!!
|
||||||
func NewConfigMapList(c Connection, ns string) List {
|
// // NewConfigMapList returns a new resource list.
|
||||||
return NewCustomList(c, true, "", "v1/configmaps")
|
// func NewConfigMapList(c Connection, ns string) List {
|
||||||
}
|
// return NewCustomList(c, true, "", "v1/configmaps")
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,264 +1,265 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
"context"
|
// import (
|
||||||
"errors"
|
// "context"
|
||||||
"fmt"
|
// "errors"
|
||||||
"strconv"
|
// "fmt"
|
||||||
"strings"
|
// "strconv"
|
||||||
|
// "strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
// "github.com/derailed/k9s/internal/k8s"
|
||||||
v1 "k8s.io/api/core/v1"
|
// v1 "k8s.io/api/core/v1"
|
||||||
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
// mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type (
|
// type (
|
||||||
// Container represents a container on a pod.
|
// // Container represents a container on a pod.
|
||||||
Container struct {
|
// Container struct {
|
||||||
*Base
|
// *Base
|
||||||
|
|
||||||
pod *v1.Pod
|
// pod *v1.Pod
|
||||||
instance v1.Container
|
// instance v1.Container
|
||||||
metrics *mv1beta1.PodMetrics
|
// metrics *mv1beta1.PodMetrics
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
|
|
||||||
// NewContainerList returns a collection of container.
|
// // NewContainerList returns a collection of container.
|
||||||
func NewContainerList(c Connection, pod *v1.Pod) List {
|
// func NewContainerList(c Connection, pod *v1.Pod) List {
|
||||||
return NewList(
|
// return NewList(
|
||||||
NotNamespaced,
|
// NotNamespaced,
|
||||||
"containers",
|
// "containers",
|
||||||
NewContainer(c, pod),
|
// NewContainer(c, pod),
|
||||||
0,
|
// 0,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewContainer returns a new set of containers.
|
// // NewContainer returns a new set of containers.
|
||||||
func NewContainer(c Connection, pod *v1.Pod) *Container {
|
// func NewContainer(c Connection, pod *v1.Pod) *Container {
|
||||||
co := Container{
|
// co := Container{
|
||||||
Base: &Base{Connection: c, Resource: k8s.NewPod(c)},
|
// Base: &Base{Connection: c, Resource: k8s.NewPod(c)},
|
||||||
pod: pod,
|
// pod: pod,
|
||||||
}
|
// }
|
||||||
co.Factory = &co
|
// co.Factory = &co
|
||||||
|
|
||||||
return &co
|
// return &co
|
||||||
}
|
// }
|
||||||
|
|
||||||
// New builds a new Container instance from a k8s resource.
|
// // New builds a new Container instance from a k8s resource.
|
||||||
func (r *Container) New(i interface{}) (Columnar, error) {
|
// func (r *Container) New(i interface{}) (Columnar, error) {
|
||||||
co := NewContainer(r.Connection, r.pod)
|
// co := NewContainer(r.Connection, r.pod)
|
||||||
coi, ok := i.(v1.Container)
|
// coi, ok := i.(v1.Container)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return nil, errors.New("Expecting a container resource")
|
// return nil, errors.New("Expecting a container resource")
|
||||||
}
|
// }
|
||||||
co.instance = coi
|
// co.instance = coi
|
||||||
co.path = r.namespacedName(r.pod.ObjectMeta) + ":" + co.instance.Name
|
// 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.
|
// // SetPodMetrics set the current k8s resource metrics on associated pod.
|
||||||
func (r *Container) SetPodMetrics(m *mv1beta1.PodMetrics) {
|
// func (r *Container) SetPodMetrics(m *mv1beta1.PodMetrics) {
|
||||||
r.metrics = m
|
// r.metrics = m
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Marshal resource to yaml.
|
// // Marshal resource to yaml.
|
||||||
func (r *Container) Marshal(path string) (string, error) {
|
// func (r *Container) Marshal(path string) (string, error) {
|
||||||
return "", nil
|
// return "", nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Logs tails a given container logs
|
// // Logs tails a given container logs
|
||||||
func (r *Container) Logs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
// func (r *Container) Logs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||||
res, ok := r.Resource.(k8s.Loggable)
|
// res, ok := r.Resource.(k8s.Loggable)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return fmt.Errorf("Resource %T is not Loggable", r.Resource)
|
// 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.
|
// // List resources for a given namespace.
|
||||||
func (r *Container) List(ctx context.Context, ns string) (Columnars, error) {
|
// func (r *Container) List(ctx context.Context, ns string) (Columnars, error) {
|
||||||
icos := r.pod.Spec.InitContainers
|
// icos := r.pod.Spec.InitContainers
|
||||||
cos := r.pod.Spec.Containers
|
// cos := r.pod.Spec.Containers
|
||||||
|
|
||||||
cc := make(Columnars, 0, len(icos)+len(cos))
|
// cc := make(Columnars, 0, len(icos)+len(cos))
|
||||||
for _, co := range icos {
|
// for _, co := range icos {
|
||||||
res, err := r.New(co)
|
// res, err := r.New(co)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
cc = append(cc, res)
|
// cc = append(cc, res)
|
||||||
}
|
// }
|
||||||
for _, co := range cos {
|
// for _, co := range cos {
|
||||||
res, err := r.New(co)
|
// res, err := r.New(co)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
cc = append(cc, res)
|
// cc = append(cc, res)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return cc, nil
|
// return cc, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Header return resource header.
|
// // Header return resource header.
|
||||||
func (*Container) Header(ns string) Row {
|
// func (*Container) Header(ns string) Row {
|
||||||
return append(Row{},
|
// return append(Row{},
|
||||||
"NAME",
|
// "NAME",
|
||||||
"IMAGE",
|
// "IMAGE",
|
||||||
"READY",
|
// "READY",
|
||||||
"STATE",
|
// "STATE",
|
||||||
"RS",
|
// "RS",
|
||||||
"PROBES(L:R)",
|
// "PROBES(L:R)",
|
||||||
"CPU",
|
// "CPU",
|
||||||
"MEM",
|
// "MEM",
|
||||||
"%CPU",
|
// "%CPU",
|
||||||
"%MEM",
|
// "%MEM",
|
||||||
"PORTS",
|
// "PORTS",
|
||||||
"AGE",
|
// "AGE",
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NumCols designates if column is numerical.
|
// // NumCols designates if column is numerical.
|
||||||
func (*Container) NumCols(n string) map[string]bool {
|
// func (*Container) NumCols(n string) map[string]bool {
|
||||||
return map[string]bool{
|
// return map[string]bool{
|
||||||
"CPU": true,
|
// "CPU": true,
|
||||||
"MEM": true,
|
// "MEM": true,
|
||||||
"%CPU": true,
|
// "%CPU": true,
|
||||||
"%MEM": true,
|
// "%MEM": true,
|
||||||
"RS": true,
|
// "RS": true,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Fields retrieves displayable fields.
|
// // Fields retrieves displayable fields.
|
||||||
func (r *Container) Fields(ns string) Row {
|
// func (r *Container) Fields(ns string) Row {
|
||||||
ff := make(Row, 0, len(r.Header(ns)))
|
// ff := make(Row, 0, len(r.Header(ns)))
|
||||||
i := r.instance
|
// i := r.instance
|
||||||
|
|
||||||
c, p := gatherMetrics(i, r.metrics)
|
// c, p := gatherMetrics(i, r.metrics)
|
||||||
|
|
||||||
ready, state, restarts := "false", MissingValue, "0"
|
// ready, state, restarts := "false", MissingValue, "0"
|
||||||
cs := getContainerStatus(i.Name, r.pod.Status)
|
// cs := getContainerStatus(i.Name, r.pod.Status)
|
||||||
if cs != nil {
|
// if cs != nil {
|
||||||
ready, state, restarts = boolToStr(cs.Ready), toState(cs.State), strconv.Itoa(int(cs.RestartCount))
|
// ready, state, restarts = boolToStr(cs.Ready), toState(cs.State), strconv.Itoa(int(cs.RestartCount))
|
||||||
}
|
// }
|
||||||
|
|
||||||
return append(ff,
|
// return append(ff,
|
||||||
i.Name,
|
// i.Name,
|
||||||
i.Image,
|
// i.Image,
|
||||||
ready,
|
// ready,
|
||||||
state,
|
// state,
|
||||||
restarts,
|
// restarts,
|
||||||
probe(i.LivenessProbe)+":"+probe(i.ReadinessProbe),
|
// probe(i.LivenessProbe)+":"+probe(i.ReadinessProbe),
|
||||||
c.cpu,
|
// c.cpu,
|
||||||
c.mem,
|
// c.mem,
|
||||||
p.cpu,
|
// p.cpu,
|
||||||
p.mem,
|
// p.mem,
|
||||||
toStrPorts(i.Ports),
|
// toStrPorts(i.Ports),
|
||||||
toAge(r.pod.CreationTimestamp),
|
// toAge(r.pod.CreationTimestamp),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// // ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// // Helpers...
|
||||||
|
|
||||||
func gatherMetrics(co v1.Container, mx *mv1beta1.PodMetrics) (c, p metric) {
|
// func gatherMetrics(co v1.Container, mx *mv1beta1.PodMetrics) (c, p metric) {
|
||||||
c, p = noMetric(), noMetric()
|
// c, p = noMetric(), noMetric()
|
||||||
if mx == nil {
|
// if mx == nil {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
var (
|
// var (
|
||||||
cpu int64
|
// cpu int64
|
||||||
mem float64
|
// mem float64
|
||||||
)
|
// )
|
||||||
for _, c := range mx.Containers {
|
// for _, c := range mx.Containers {
|
||||||
if c.Name == co.Name {
|
// if c.Name == co.Name {
|
||||||
cpu = c.Usage.Cpu().MilliValue()
|
// cpu = c.Usage.Cpu().MilliValue()
|
||||||
mem = k8s.ToMB(c.Usage.Memory().Value())
|
// mem = k8s.ToMB(c.Usage.Memory().Value())
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
c = metric{
|
// c = metric{
|
||||||
cpu: ToMillicore(cpu),
|
// cpu: ToMillicore(cpu),
|
||||||
mem: ToMi(mem),
|
// mem: ToMi(mem),
|
||||||
}
|
// }
|
||||||
|
|
||||||
rcpu, rmem := containerResources(co)
|
// rcpu, rmem := containerResources(co)
|
||||||
if rcpu != nil {
|
// if rcpu != nil {
|
||||||
p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue())))
|
// p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue())))
|
||||||
}
|
// }
|
||||||
if rmem != nil {
|
// if rmem != nil {
|
||||||
p.mem = AsPerc(toPerc(mem, k8s.ToMB(rmem.Value())))
|
// p.mem = AsPerc(toPerc(mem, k8s.ToMB(rmem.Value())))
|
||||||
}
|
// }
|
||||||
|
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus {
|
// func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus {
|
||||||
for _, c := range status.ContainerStatuses {
|
// for _, c := range status.ContainerStatuses {
|
||||||
if c.Name == co {
|
// if c.Name == co {
|
||||||
return &c
|
// return &c
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
for _, c := range status.InitContainerStatuses {
|
// for _, c := range status.InitContainerStatuses {
|
||||||
if c.Name == co {
|
// if c.Name == co {
|
||||||
return &c
|
// return &c
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func toStrPorts(pp []v1.ContainerPort) string {
|
// func toStrPorts(pp []v1.ContainerPort) string {
|
||||||
ports := make([]string, len(pp))
|
// ports := make([]string, len(pp))
|
||||||
for i, p := range pp {
|
// for i, p := range pp {
|
||||||
if len(p.Name) > 0 {
|
// if len(p.Name) > 0 {
|
||||||
ports[i] = p.Name + ":"
|
// ports[i] = p.Name + ":"
|
||||||
}
|
// }
|
||||||
ports[i] += strconv.Itoa(int(p.ContainerPort))
|
// ports[i] += strconv.Itoa(int(p.ContainerPort))
|
||||||
if p.Protocol != "TCP" {
|
// if p.Protocol != "TCP" {
|
||||||
ports[i] += "╱" + string(p.Protocol)
|
// ports[i] += "╱" + string(p.Protocol)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return strings.Join(ports, ",")
|
// return strings.Join(ports, ",")
|
||||||
}
|
// }
|
||||||
|
|
||||||
func toState(s v1.ContainerState) string {
|
// func toState(s v1.ContainerState) string {
|
||||||
switch {
|
// switch {
|
||||||
case s.Waiting != nil:
|
// case s.Waiting != nil:
|
||||||
if s.Waiting.Reason != "" {
|
// if s.Waiting.Reason != "" {
|
||||||
return s.Waiting.Reason
|
// return s.Waiting.Reason
|
||||||
}
|
// }
|
||||||
return "Waiting"
|
// return "Waiting"
|
||||||
|
|
||||||
case s.Terminated != nil:
|
// case s.Terminated != nil:
|
||||||
if s.Terminated.Reason != "" {
|
// if s.Terminated.Reason != "" {
|
||||||
return s.Terminated.Reason
|
// return s.Terminated.Reason
|
||||||
}
|
// }
|
||||||
return Terminating
|
// return Terminating
|
||||||
case s.Running != nil:
|
// case s.Running != nil:
|
||||||
return Running
|
// return Running
|
||||||
default:
|
// default:
|
||||||
return MissingValue
|
// return MissingValue
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func toRes(r v1.ResourceList) (string, string) {
|
// func toRes(r v1.ResourceList) (string, string) {
|
||||||
cpu, mem := r[v1.ResourceCPU], r[v1.ResourceMemory]
|
// 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 {
|
// func probe(p *v1.Probe) string {
|
||||||
if p == nil {
|
// if p == nil {
|
||||||
return "off"
|
// return "off"
|
||||||
}
|
// }
|
||||||
return "on"
|
// return "on"
|
||||||
}
|
// }
|
||||||
|
|
||||||
func asMi(v int64) float64 {
|
// func asMi(v int64) float64 {
|
||||||
return float64(v) / 1024 * 1024
|
// return float64(v) / 1024 * 1024
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,114 +1,115 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
"testing"
|
// import (
|
||||||
|
// "testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
// "github.com/stretchr/testify/assert"
|
||||||
v1 "k8s.io/api/core/v1"
|
// v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
// "k8s.io/apimachinery/pkg/api/resource"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func TestProbe(t *testing.T) {
|
// func TestProbe(t *testing.T) {
|
||||||
uu := map[string]struct {
|
// uu := map[string]struct {
|
||||||
probe *v1.Probe
|
// probe *v1.Probe
|
||||||
e string
|
// e string
|
||||||
}{
|
// }{
|
||||||
"defined": {&v1.Probe{}, "on"},
|
// "defined": {&v1.Probe{}, "on"},
|
||||||
"undefined": {nil, "off"},
|
// "undefined": {nil, "off"},
|
||||||
}
|
// }
|
||||||
|
|
||||||
for k := range uu {
|
// for k := range uu {
|
||||||
u := uu[k]
|
// u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
// t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.e, probe(u.probe))
|
// assert.Equal(t, u.e, probe(u.probe))
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestAsMi(t *testing.T) {
|
// func TestAsMi(t *testing.T) {
|
||||||
uu := map[string]struct {
|
// uu := map[string]struct {
|
||||||
mem int64
|
// mem int64
|
||||||
e float64
|
// e float64
|
||||||
}{
|
// }{
|
||||||
"zero": {0, 0},
|
// "zero": {0, 0},
|
||||||
"1Mb": {1024 * 1024, 1.048576e+06},
|
// "1Mb": {1024 * 1024, 1.048576e+06},
|
||||||
"10Mb": {10 * 1024 * 1024, 1.048576e+07},
|
// "10Mb": {10 * 1024 * 1024, 1.048576e+07},
|
||||||
}
|
// }
|
||||||
|
|
||||||
for k := range uu {
|
// for k := range uu {
|
||||||
u := uu[k]
|
// u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
// t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.e, asMi(u.mem))
|
// assert.Equal(t, u.e, asMi(u.mem))
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestToRes(t *testing.T) {
|
// func TestToRes(t *testing.T) {
|
||||||
uu := map[string]struct {
|
// uu := map[string]struct {
|
||||||
res v1.ResourceList
|
// res v1.ResourceList
|
||||||
ecpu, emem string
|
// ecpu, emem string
|
||||||
}{
|
// }{
|
||||||
"cool": {v1.ResourceList{
|
// "cool": {v1.ResourceList{
|
||||||
v1.ResourceCPU: toQty("10m"),
|
// v1.ResourceCPU: toQty("10m"),
|
||||||
v1.ResourceMemory: toQty("20Mi"),
|
// v1.ResourceMemory: toQty("20Mi"),
|
||||||
},
|
// },
|
||||||
"10", "20"},
|
// "10", "20"},
|
||||||
"noRes": {v1.ResourceList{},
|
// "noRes": {v1.ResourceList{},
|
||||||
"0", "0"},
|
// "0", "0"},
|
||||||
}
|
// }
|
||||||
|
|
||||||
for k := range uu {
|
// for k := range uu {
|
||||||
u := uu[k]
|
// u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
// t.Run(k, func(t *testing.T) {
|
||||||
cpu, mem := toRes(u.res)
|
// cpu, mem := toRes(u.res)
|
||||||
assert.Equal(t, u.ecpu, cpu)
|
// assert.Equal(t, u.ecpu, cpu)
|
||||||
assert.Equal(t, u.emem, mem)
|
// assert.Equal(t, u.emem, mem)
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestToState(t *testing.T) {
|
// func TestToState(t *testing.T) {
|
||||||
uu := map[string]struct {
|
// uu := map[string]struct {
|
||||||
state v1.ContainerState
|
// state v1.ContainerState
|
||||||
e string
|
// e string
|
||||||
}{
|
// }{
|
||||||
"empty": {v1.ContainerState{},
|
// "empty": {v1.ContainerState{},
|
||||||
MissingValue},
|
// MissingValue},
|
||||||
"running": {
|
// "running": {
|
||||||
v1.ContainerState{Running: &v1.ContainerStateRunning{}},
|
// v1.ContainerState{Running: &v1.ContainerStateRunning{}},
|
||||||
"Running",
|
// "Running",
|
||||||
},
|
// },
|
||||||
"waiting": {
|
// "waiting": {
|
||||||
v1.ContainerState{Waiting: &v1.ContainerStateWaiting{}},
|
// v1.ContainerState{Waiting: &v1.ContainerStateWaiting{}},
|
||||||
"Waiting",
|
// "Waiting",
|
||||||
},
|
// },
|
||||||
"waitingReason": {
|
// "waitingReason": {
|
||||||
v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: "blee"}},
|
// v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: "blee"}},
|
||||||
"blee",
|
// "blee",
|
||||||
},
|
// },
|
||||||
"terminating": {
|
// "terminating": {
|
||||||
v1.ContainerState{Terminated: &v1.ContainerStateTerminated{}},
|
// v1.ContainerState{Terminated: &v1.ContainerStateTerminated{}},
|
||||||
"Terminating",
|
// "Terminating",
|
||||||
},
|
// },
|
||||||
"terminatedReason": {
|
// "terminatedReason": {
|
||||||
v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "blee"}},
|
// v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "blee"}},
|
||||||
"blee",
|
// "blee",
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
|
|
||||||
for k := range uu {
|
// for k := range uu {
|
||||||
u := uu[k]
|
// u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
// t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.e, toState(u.state))
|
// assert.Equal(t, u.e, toState(u.state))
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// // ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// // Helpers...
|
||||||
|
|
||||||
func toQty(s string) resource.Quantity {
|
// func toQty(s string) resource.Quantity {
|
||||||
q, _ := resource.ParseQuantity(s)
|
// q, _ := resource.ParseQuantity(s)
|
||||||
|
|
||||||
return q
|
// return q
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,88 +1,89 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
"fmt"
|
// import (
|
||||||
|
// "fmt"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
// "github.com/derailed/k9s/internal/k8s"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type (
|
// type (
|
||||||
// Switchable represents a switchable resource.
|
// // Switchable represents a switchable resource.
|
||||||
Switchable interface {
|
// Switchable interface {
|
||||||
Switch(ctx string) error
|
// Switch(ctx string) error
|
||||||
MustCurrentContextName() string
|
// MustCurrentContextName() string
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SwitchableCruder represents a resource that can be switched.
|
// // SwitchableCruder represents a resource that can be switched.
|
||||||
SwitchableCruder interface {
|
// SwitchableCruder interface {
|
||||||
Cruder
|
// Cruder
|
||||||
Switchable
|
// Switchable
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Context tracks a kubernetes resource.
|
// // Context tracks a kubernetes resource.
|
||||||
Context struct {
|
// Context struct {
|
||||||
*Base
|
// *Base
|
||||||
instance *k8s.NamedContext
|
// instance *k8s.NamedContext
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
|
|
||||||
// NewContextList returns a new resource list.
|
// // NewContextList returns a new resource list.
|
||||||
func NewContextList(c Connection, ns string) List {
|
// func NewContextList(c Connection, ns string) List {
|
||||||
return NewList(NotNamespaced, "ctx", NewContext(c), SwitchAccess)
|
// return NewList(NotNamespaced, "ctx", NewContext(c), SwitchAccess)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewContext instantiates a new Context.
|
// // NewContext instantiates a new Context.
|
||||||
func NewContext(c Connection) *Context {
|
// func NewContext(c Connection) *Context {
|
||||||
ctx := &Context{Base: NewBase(c, k8s.NewContext(c))}
|
// ctx := &Context{Base: NewBase(c, k8s.NewContext(c))}
|
||||||
ctx.Factory = ctx
|
// ctx.Factory = ctx
|
||||||
|
|
||||||
return ctx
|
// return ctx
|
||||||
}
|
// }
|
||||||
|
|
||||||
// New builds a new Context instance from a k8s resource.
|
// // New builds a new Context instance from a k8s resource.
|
||||||
func (r *Context) New(i interface{}) (Columnar, error) {
|
// func (r *Context) New(i interface{}) (Columnar, error) {
|
||||||
c := NewContext(r.Connection)
|
// c := NewContext(r.Connection)
|
||||||
switch instance := i.(type) {
|
// switch instance := i.(type) {
|
||||||
case *k8s.NamedContext:
|
// case *k8s.NamedContext:
|
||||||
c.instance = instance
|
// c.instance = instance
|
||||||
case k8s.NamedContext:
|
// case k8s.NamedContext:
|
||||||
c.instance = &instance
|
// c.instance = &instance
|
||||||
default:
|
// default:
|
||||||
return nil, fmt.Errorf("unknown context type %T", instance)
|
// return nil, fmt.Errorf("unknown context type %T", instance)
|
||||||
}
|
// }
|
||||||
c.path = c.instance.Name
|
// c.path = c.instance.Name
|
||||||
|
|
||||||
return c, nil
|
// return c, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Switch out current context.
|
// // Switch out current context.
|
||||||
func (r *Context) Switch(c string) error {
|
// func (r *Context) Switch(c string) error {
|
||||||
return r.Resource.(Switchable).Switch(c)
|
// return r.Resource.(Switchable).Switch(c)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Marshal the resource to yaml.
|
// // Marshal the resource to yaml.
|
||||||
func (r *Context) Marshal(path string) (string, error) {
|
// func (r *Context) Marshal(path string) (string, error) {
|
||||||
return "", nil
|
// return "", nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Header return resource header.
|
// // Header return resource header.
|
||||||
func (*Context) Header(string) Row {
|
// func (*Context) Header(string) Row {
|
||||||
return append(Row{}, "NAME", "CLUSTER", "AUTHINFO", "NAMESPACE")
|
// return append(Row{}, "NAME", "CLUSTER", "AUTHINFO", "NAMESPACE")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Fields retrieves displayable fields.
|
// // Fields retrieves displayable fields.
|
||||||
func (r *Context) Fields(ns string) Row {
|
// func (r *Context) Fields(ns string) Row {
|
||||||
ff := make(Row, 0, len(r.Header(ns)))
|
// ff := make(Row, 0, len(r.Header(ns)))
|
||||||
|
|
||||||
i := r.instance
|
// i := r.instance
|
||||||
if i.MustCurrentContextName() == i.Name {
|
// if i.MustCurrentContextName() == i.Name {
|
||||||
i.Name += "*"
|
// i.Name += "*"
|
||||||
}
|
// }
|
||||||
|
|
||||||
return append(ff,
|
// return append(ff,
|
||||||
i.Name,
|
// i.Name,
|
||||||
i.Context.Cluster,
|
// i.Context.Cluster,
|
||||||
i.Context.AuthInfo,
|
// i.Context.AuthInfo,
|
||||||
i.Context.Namespace,
|
// i.Context.Namespace,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,136 +1,137 @@
|
||||||
package resource_test
|
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!!
|
// BOZO!!
|
||||||
// func TestCTXList(t *testing.T) {
|
// import (
|
||||||
// mc := NewMockConnection()
|
// "testing"
|
||||||
// mr := NewMockSwitchableCruder()
|
|
||||||
// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sNamedCTX()}, nil)
|
|
||||||
|
|
||||||
// ctx := NewContextWithArgs(mc, mr)
|
// "github.com/derailed/k9s/internal/k8s"
|
||||||
// cc, err := ctx.List("blee", metav1.ListOptions{})
|
// "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)
|
// func NewContextListWithArgs(ns string, ctx *resource.Context) resource.List {
|
||||||
// c, err := ctx.New(k8sNamedCTX())
|
// return resource.NewList(resource.NotNamespaced, "ctx", ctx, resource.SwitchAccess)
|
||||||
// assert.Nil(t, err)
|
|
||||||
// assert.Equal(t, resource.Columnars{c}, cc)
|
|
||||||
// mr.VerifyWasCalledOnce().List("blee", metav1.ListOptions{})
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func TestCTXDelete(t *testing.T) {
|
// func NewContextWithArgs(c k8s.Connection, s resource.SwitchableCruder) *resource.Context {
|
||||||
mc := NewMockConnection()
|
// ctx := &resource.Context{Base: resource.NewBase(c, s)}
|
||||||
mr := NewMockSwitchableCruder()
|
// ctx.Factory = ctx
|
||||||
m.When(mr.Delete("", "fred", true, true)).ThenReturn(nil)
|
// 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))
|
// ctx := NewContextWithArgs(mc, mr)
|
||||||
mr.VerifyWasCalledOnce().Delete("", "fred", true, true)
|
// err := ctx.Switch("fred")
|
||||||
}
|
|
||||||
|
|
||||||
func TestCTXListHasName(t *testing.T) {
|
// assert.Nil(t, err)
|
||||||
mc := NewMockConnection()
|
// mr.VerifyWasCalledOnce().Switch("fred")
|
||||||
mr := NewMockSwitchableCruder()
|
// }
|
||||||
|
|
||||||
ctx := NewContextWithArgs(mc, mr)
|
// // BOZO!!
|
||||||
l := NewContextListWithArgs("blee", ctx)
|
// // 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) {
|
// // assert.Nil(t, err)
|
||||||
mc := NewMockConnection()
|
// // c, err := ctx.New(k8sNamedCTX())
|
||||||
mr := NewMockSwitchableCruder()
|
// // assert.Nil(t, err)
|
||||||
|
// // assert.Equal(t, resource.Columnars{c}, cc)
|
||||||
|
// // mr.VerifyWasCalledOnce().List("blee", metav1.ListOptions{})
|
||||||
|
// // }
|
||||||
|
|
||||||
ctx := NewContextWithArgs(mc, mr)
|
// func TestCTXDelete(t *testing.T) {
|
||||||
l := NewContextListWithArgs("blee", ctx)
|
// 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) {
|
// assert.Nil(t, ctx.Delete("fred", true, true))
|
||||||
mc := NewMockConnection()
|
// mr.VerifyWasCalledOnce().Delete("", "fred", true, true)
|
||||||
mr := NewMockSwitchableCruder()
|
// }
|
||||||
|
|
||||||
ctx := NewContextWithArgs(mc, mr)
|
// func TestCTXListHasName(t *testing.T) {
|
||||||
l := NewContextListWithArgs("blee", ctx)
|
// mc := NewMockConnection()
|
||||||
|
// mr := NewMockSwitchableCruder()
|
||||||
|
|
||||||
assert.NotNil(t, l.Resource())
|
// ctx := NewContextWithArgs(mc, mr)
|
||||||
}
|
// l := NewContextListWithArgs("blee", ctx)
|
||||||
|
|
||||||
func TestCTXHeader(t *testing.T) {
|
// assert.Equal(t, "ctx", l.GetName())
|
||||||
mc := NewMockConnection()
|
// }
|
||||||
mr := NewMockSwitchableCruder()
|
|
||||||
|
|
||||||
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) {
|
// assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||||
mc := NewMockConnection()
|
// }
|
||||||
m.When(mc.Config()).ThenReturn(k8sConfig())
|
|
||||||
mr := NewMockSwitchableCruder()
|
|
||||||
m.When(mr.MustCurrentContextName()).ThenReturn("test")
|
|
||||||
|
|
||||||
ctx := NewContextWithArgs(mc, mr)
|
// func TestCTXListHasResource(t *testing.T) {
|
||||||
c, err := ctx.New(k8sNamedCTX())
|
// mc := NewMockConnection()
|
||||||
assert.Nil(t, err)
|
// mr := NewMockSwitchableCruder()
|
||||||
|
|
||||||
assert.Equal(t, 4, len(c.Fields("")))
|
// ctx := NewContextWithArgs(mc, mr)
|
||||||
assert.Equal(t, "test*", c.Fields("")[0])
|
// l := NewContextListWithArgs("blee", ctx)
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers...
|
// assert.NotNil(t, l.Resource())
|
||||||
|
// }
|
||||||
|
|
||||||
func k8sConfig() *k8s.Config {
|
// func TestCTXHeader(t *testing.T) {
|
||||||
ctx := "test"
|
// mc := NewMockConnection()
|
||||||
f := genericclioptions.ConfigFlags{
|
// mr := NewMockSwitchableCruder()
|
||||||
Context: &ctx,
|
|
||||||
}
|
|
||||||
return k8s.NewConfig(&f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func k8sNamedCTX() *k8s.NamedContext {
|
// ctx := NewContextWithArgs(mc, mr)
|
||||||
return k8s.NewNamedContext(
|
|
||||||
k8sConfig(),
|
// assert.Equal(t, 4, len(ctx.Header("")))
|
||||||
"test",
|
// }
|
||||||
&api.Context{
|
|
||||||
LocationOfOrigin: "fred",
|
// func TestCTXFields(t *testing.T) {
|
||||||
Cluster: "blee",
|
// mc := NewMockConnection()
|
||||||
AuthInfo: "secret",
|
// 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",
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
@ -1,124 +1,125 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
"errors"
|
// import (
|
||||||
"fmt"
|
// "errors"
|
||||||
"strconv"
|
// "fmt"
|
||||||
|
// "strconv"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
// "github.com/derailed/k9s/internal/k8s"
|
||||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
// batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type (
|
// type (
|
||||||
// CronJob tracks a kubernetes resource.
|
// // CronJob tracks a kubernetes resource.
|
||||||
CronJob struct {
|
// CronJob struct {
|
||||||
*Base
|
// *Base
|
||||||
instance *batchv1beta1.CronJob
|
// instance *batchv1beta1.CronJob
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Runner can run jobs.
|
// // Runner can run jobs.
|
||||||
Runner interface {
|
// Runner interface {
|
||||||
Run(path string) error
|
// Run(path string) error
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Runnable can run jobs.
|
// // Runnable can run jobs.
|
||||||
Runnable interface {
|
// Runnable interface {
|
||||||
Run(ns, n string) error
|
// Run(ns, n string) error
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
|
|
||||||
// NewCronJobList returns a new resource list.
|
// // NewCronJobList returns a new resource list.
|
||||||
func NewCronJobList(c Connection, ns string) List {
|
// func NewCronJobList(c Connection, ns string) List {
|
||||||
return NewList(
|
// return NewList(
|
||||||
ns,
|
// ns,
|
||||||
"cronjob",
|
// "cronjob",
|
||||||
NewCronJob(c),
|
// NewCronJob(c),
|
||||||
AllVerbsAccess|DescribeAccess,
|
// AllVerbsAccess|DescribeAccess,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewCronJob instantiates a new CronJob.
|
// // NewCronJob instantiates a new CronJob.
|
||||||
func NewCronJob(c Connection) *CronJob {
|
// func NewCronJob(c Connection) *CronJob {
|
||||||
cj := &CronJob{&Base{Connection: c, Resource: k8s.NewCronJob(c)}, nil}
|
// cj := &CronJob{&Base{Connection: c, Resource: k8s.NewCronJob(c)}, nil}
|
||||||
cj.Factory = cj
|
// cj.Factory = cj
|
||||||
|
|
||||||
return cj
|
// return cj
|
||||||
}
|
// }
|
||||||
|
|
||||||
// New builds a new CronJob instance from a k8s resource.
|
// // New builds a new CronJob instance from a k8s resource.
|
||||||
func (r *CronJob) New(i interface{}) (Columnar, error) {
|
// func (r *CronJob) New(i interface{}) (Columnar, error) {
|
||||||
c := NewCronJob(r.Connection)
|
// c := NewCronJob(r.Connection)
|
||||||
switch instance := i.(type) {
|
// switch instance := i.(type) {
|
||||||
case *batchv1beta1.CronJob:
|
// case *batchv1beta1.CronJob:
|
||||||
c.instance = instance
|
// c.instance = instance
|
||||||
case batchv1beta1.CronJob:
|
// case batchv1beta1.CronJob:
|
||||||
c.instance = &instance
|
// c.instance = &instance
|
||||||
default:
|
// default:
|
||||||
return nil, fmt.Errorf("Expecting CronJob but got %T", instance)
|
// return nil, fmt.Errorf("Expecting CronJob but got %T", instance)
|
||||||
}
|
// }
|
||||||
c.path = c.namespacedName(c.instance.ObjectMeta)
|
// c.path = c.namespacedName(c.instance.ObjectMeta)
|
||||||
|
|
||||||
return c, nil
|
// return c, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Marshal resource to yaml.
|
// // Marshal resource to yaml.
|
||||||
func (r *CronJob) Marshal(path string) (string, error) {
|
// func (r *CronJob) Marshal(path string) (string, error) {
|
||||||
ns, n := Namespaced(path)
|
// ns, n := Namespaced(path)
|
||||||
i, err := r.Resource.Get(ns, n)
|
// i, err := r.Resource.Get(ns, n)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
|
||||||
cj, ok := i.(*batchv1beta1.CronJob)
|
// cj, ok := i.(*batchv1beta1.CronJob)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return "", errors.New("expecting cronjob resource")
|
// return "", errors.New("expecting cronjob resource")
|
||||||
}
|
// }
|
||||||
cj.TypeMeta.APIVersion = "extensions/batchv1beta1"
|
// cj.TypeMeta.APIVersion = "extensions/batchv1beta1"
|
||||||
cj.TypeMeta.Kind = "CronJob"
|
// cj.TypeMeta.Kind = "CronJob"
|
||||||
|
|
||||||
return r.marshalObject(cj)
|
// return r.marshalObject(cj)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Run a given cronjob.
|
// // Run a given cronjob.
|
||||||
func (r *CronJob) Run(pa string) error {
|
// func (r *CronJob) Run(pa string) error {
|
||||||
ns, n := Namespaced(pa)
|
// ns, n := Namespaced(pa)
|
||||||
if c, ok := r.Resource.(Runnable); ok {
|
// if c, ok := r.Resource.(Runnable); ok {
|
||||||
return c.Run(ns, n)
|
// 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.
|
// // Header return resource header.
|
||||||
func (*CronJob) Header(ns string) Row {
|
// func (*CronJob) Header(ns string) Row {
|
||||||
hh := Row{}
|
// hh := Row{}
|
||||||
if ns == AllNamespaces {
|
// if ns == AllNamespaces {
|
||||||
hh = append(hh, "NAMESPACE")
|
// 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.
|
// // Fields retrieves displayable fields.
|
||||||
func (r *CronJob) Fields(ns string) Row {
|
// func (r *CronJob) Fields(ns string) Row {
|
||||||
ff := make([]string, 0, len(r.Header(ns)))
|
// ff := make([]string, 0, len(r.Header(ns)))
|
||||||
|
|
||||||
i := r.instance
|
// i := r.instance
|
||||||
if ns == AllNamespaces {
|
// if ns == AllNamespaces {
|
||||||
ff = append(ff, i.Namespace)
|
// ff = append(ff, i.Namespace)
|
||||||
}
|
// }
|
||||||
|
|
||||||
lastScheduled := MissingValue
|
// lastScheduled := MissingValue
|
||||||
if i.Status.LastScheduleTime != nil {
|
// if i.Status.LastScheduleTime != nil {
|
||||||
lastScheduled = toAgeHuman(toAge(*i.Status.LastScheduleTime))
|
// lastScheduled = toAgeHuman(toAge(*i.Status.LastScheduleTime))
|
||||||
}
|
// }
|
||||||
|
|
||||||
return append(ff,
|
// return append(ff,
|
||||||
i.Name,
|
// i.Name,
|
||||||
i.Spec.Schedule,
|
// i.Spec.Schedule,
|
||||||
boolPtrToStr(i.Spec.Suspend),
|
// boolPtrToStr(i.Spec.Suspend),
|
||||||
strconv.Itoa(len(i.Status.Active)),
|
// strconv.Itoa(len(i.Status.Active)),
|
||||||
lastScheduled,
|
// lastScheduled,
|
||||||
toAge(i.ObjectMeta.CreationTimestamp),
|
// toAge(i.ObjectMeta.CreationTimestamp),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,131 +1,132 @@
|
||||||
package resource_test
|
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!!
|
// BOZO!!
|
||||||
|
// import (
|
||||||
|
// "testing"
|
||||||
|
|
||||||
// func TestCronJobListData(t *testing.T) {
|
// "github.com/derailed/k9s/internal/k8s"
|
||||||
// mc := NewMockConnection()
|
// "github.com/derailed/k9s/internal/resource"
|
||||||
// mr := NewMockCruder()
|
// m "github.com/petergtz/pegomock"
|
||||||
// m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sCronJob()}, nil)
|
// "github.com/stretchr/testify/assert"
|
||||||
|
// batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||||
|
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
// )
|
||||||
|
|
||||||
// l := NewCronJobListWithArgs("-", NewCronJobWithArgs(mc, mr))
|
// func NewCronJobListWithArgs(ns string, r *resource.CronJob) resource.List {
|
||||||
// // Make sure we can get deltas!
|
// return resource.NewList(ns, "cj", r, resource.AllVerbsAccess|resource.DescribeAccess)
|
||||||
// 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 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 {
|
// func TestCronJobListAccess(t *testing.T) {
|
||||||
var b bool
|
// mc := NewMockConnection()
|
||||||
return &batchv1beta1.CronJob{
|
// mr := NewMockCruder()
|
||||||
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 {
|
// ns := "blee"
|
||||||
mc := NewMockConnection()
|
// r := NewCronJobWithArgs(mc, mr)
|
||||||
c, _ := resource.NewCronJob(mc).New(k8sCronJob())
|
// l := NewCronJobListWithArgs(resource.AllNamespaces, r)
|
||||||
return c
|
// l.SetNamespace(ns)
|
||||||
}
|
|
||||||
|
|
||||||
func cronjobYaml() string {
|
// assert.Equal(t, ns, l.GetNamespace())
|
||||||
return `apiVersion: extensions/batchv1beta1
|
// assert.Equal(t, "cj", l.GetName())
|
||||||
kind: CronJob
|
// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||||
metadata:
|
// assert.True(t, l.Access(a))
|
||||||
creationTimestamp: "2018-12-14T17:36:43Z"
|
// }
|
||||||
name: fred
|
// }
|
||||||
namespace: blee
|
|
||||||
spec:
|
// func TestCronJobFields(t *testing.T) {
|
||||||
jobTemplate:
|
// r := newCronJob().Fields("blee")
|
||||||
metadata:
|
// assert.Equal(t, "fred", r[0])
|
||||||
creationTimestamp: null
|
// }
|
||||||
spec:
|
|
||||||
template:
|
// func TestCronJobMarshal(t *testing.T) {
|
||||||
metadata:
|
// mc := NewMockConnection()
|
||||||
creationTimestamp: null
|
// mr := NewMockCruder()
|
||||||
spec:
|
// m.When(mr.Get("blee", "fred")).ThenReturn(k8sCronJob(), nil)
|
||||||
containers: null
|
|
||||||
schedule: '*/1 * * * *'
|
// cm := NewCronJobWithArgs(mc, mr)
|
||||||
suspend: false
|
// ma, err := cm.Marshal("blee/fred")
|
||||||
status:
|
// mr.VerifyWasCalledOnce().Get("blee", "fred")
|
||||||
lastScheduleTime: "2018-12-14T17:36:43Z"
|
// 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"
|
||||||
|
// `
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,177 +1,178 @@
|
||||||
package resource
|
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!!
|
// BOZO!!
|
||||||
// List all resources
|
// import (
|
||||||
// func (r *Custom) List(ns string, opts v1.ListOptions) (Columnars, error) {
|
// "encoding/json"
|
||||||
// ii, err := r.Resource.List(ns, opts)
|
// "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 {
|
// if err != nil {
|
||||||
// return nil, err
|
// 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 cr, nil
|
||||||
// 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
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Header return resource header.
|
// // Marshal resource to yaml.
|
||||||
func (r *Custom) Header(ns string) Row {
|
// func (r *Custom) Marshal(path string) (string, error) {
|
||||||
hh := make(Row, 0, len(r.headers)+1)
|
// 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 {
|
// raw, err := yaml.Marshal(i)
|
||||||
hh = append(hh, "NAMESPACE")
|
// if err != nil {
|
||||||
}
|
// return "", err
|
||||||
for _, h := range r.headers {
|
// }
|
||||||
hh = append(hh, strings.ToUpper(h))
|
|
||||||
}
|
|
||||||
|
|
||||||
return hh
|
// return string(raw), nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Fields retrieves displayable fields.
|
// // BOZO!!
|
||||||
func (r *Custom) Fields(ns string) Row {
|
// // List all resources
|
||||||
ff := make(Row, 0, len(r.Header(ns)))
|
// // 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{}
|
// // if len(ii) == 0 {
|
||||||
err := json.Unmarshal(r.instance.Object.Raw, &obj)
|
// // return Columnars{}, errors.New("no resources found")
|
||||||
if err != nil {
|
// // }
|
||||||
log.Error().Err(err)
|
|
||||||
return Row{}
|
|
||||||
}
|
|
||||||
|
|
||||||
meta, ok := obj["metadata"].(map[string]interface{})
|
// // table, ok := ii[0].(*metav1beta1.Table)
|
||||||
if !ok {
|
// // if !ok {
|
||||||
log.Fatal().Msg("expecting interface map meta")
|
// // return nil, errors.New("expecting a table resource")
|
||||||
}
|
// // }
|
||||||
rns, ok := meta["namespace"].(string)
|
// // r.headers = make(Row, len(table.ColumnDefinitions))
|
||||||
if ns == AllNamespaces {
|
// // for i, h := range table.ColumnDefinitions {
|
||||||
if ok {
|
// // r.headers[i] = h.Name
|
||||||
ff = append(ff, rns)
|
// // }
|
||||||
}
|
// // 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 {
|
// // return cc, nil
|
||||||
ff = append(ff, fmt.Sprintf("%v", c))
|
// // }
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,353 +1,354 @@
|
||||||
package resource_test
|
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!!
|
// 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()
|
// mc := NewMockConnection()
|
||||||
// mr := NewMockCruder()
|
// 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)
|
// cm := NewCustomWithArgs(mc, mr)
|
||||||
// ma, err := cm.Marshal("blee/fred")
|
// ma, err := cm.Marshal("blee/fred")
|
||||||
// mr.VerifyWasCalledOnce().Get("blee", "fred")
|
// mr.VerifyWasCalledOnce().Get("blee", "fred")
|
||||||
|
|
||||||
// assert.Nil(t, err)
|
// assert.Nil(t, err)
|
||||||
// assert.Equal(t, customYaml(), ma)
|
// assert.Equal(t, unstructuredYAML(), ma)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func TestCustomMarshalWithUnstructured(t *testing.T) {
|
// // BOZO!!
|
||||||
mc := NewMockConnection()
|
// // func TestCustomListData(t *testing.T) {
|
||||||
mr := NewMockCruder()
|
// // mc := NewMockConnection()
|
||||||
m.When(mr.Get("blee", "fred")).ThenReturn(k8sUnstructured(), nil)
|
// // mr := NewMockCruder()
|
||||||
|
// // m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{k8sCustomGetTable()}, nil)
|
||||||
|
|
||||||
cm := NewCustomWithArgs(mc, mr)
|
// // l := NewCustomListWithArgs("blee", "fred", NewCustomWithArgs(mc, mr))
|
||||||
ma, err := cm.Marshal("blee/fred")
|
// // // Make sure we can get deltas!
|
||||||
mr.VerifyWasCalledOnce().Get("blee", "fred")
|
// // for i := 0; i < 2; i++ {
|
||||||
|
// // err := l.Reconcile(nil, "", "")
|
||||||
|
// // assert.Nil(t, err)
|
||||||
|
// // }
|
||||||
|
|
||||||
assert.Nil(t, err)
|
// // mr.VerifyWasCalled(m.Times(2)).List("blee", metav1.ListOptions{})
|
||||||
assert.Equal(t, unstructuredYAML(), ma)
|
// // 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!!
|
// // Helpers...
|
||||||
// func TestCustomListData(t *testing.T) {
|
|
||||||
|
// 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()
|
// mc := NewMockConnection()
|
||||||
// mr := NewMockCruder()
|
// c, _ := resource.NewCustom(mc, "g/v1/fred").New(k8sCustomRow())
|
||||||
// m.When(mr.List("blee", metav1.ListOptions{})).ThenReturn(k8s.Collection{k8sCustomTable()}, nil)
|
// return c
|
||||||
|
|
||||||
// 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])
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Helpers...
|
// func customYaml() string {
|
||||||
|
// return `typemeta:
|
||||||
func k8sCustomTable() *metav1beta1.Table {
|
// kind: ""
|
||||||
return &metav1beta1.Table{
|
// apiversion: ""
|
||||||
ColumnDefinitions: []metav1beta1.TableColumnDefinition{
|
// listmeta:
|
||||||
{Name: "A"},
|
// selflink: ""
|
||||||
{Name: "B"},
|
// resourceversion: ""
|
||||||
{Name: "C"},
|
// continue: ""
|
||||||
},
|
// remainingitemcount: null
|
||||||
Rows: []metav1beta1.TableRow{
|
// columndefinitions:
|
||||||
{
|
// - name: A
|
||||||
Object: runtime.RawExtension{
|
// type: ""
|
||||||
Raw: []byte(`{
|
// format: ""
|
||||||
"kind": "fred",
|
// description: ""
|
||||||
"apiVersion": "v1",
|
// priority: 0
|
||||||
"metadata": {
|
// - name: B
|
||||||
"namespace": "blee",
|
// type: ""
|
||||||
"name": "fred"
|
// format: ""
|
||||||
}}`),
|
// description: ""
|
||||||
},
|
// priority: 0
|
||||||
Cells: []interface{}{
|
// - name: C
|
||||||
"a",
|
// type: ""
|
||||||
"b",
|
// format: ""
|
||||||
"c",
|
// description: ""
|
||||||
},
|
// priority: 0
|
||||||
},
|
// rows:
|
||||||
},
|
// - cells:
|
||||||
}
|
// - a
|
||||||
}
|
// - b
|
||||||
|
// - c
|
||||||
func k8sUnstructured() *unstructured.Unstructured {
|
// conditions: []
|
||||||
return &unstructured.Unstructured{
|
// object:
|
||||||
Object: map[string]interface{}{
|
// raw:
|
||||||
"kind": "fred",
|
// - 123
|
||||||
"apiVersion": "v1",
|
// - 10
|
||||||
"metadata": map[string]interface{}{
|
// - 32
|
||||||
"namespace": "blee",
|
// - 32
|
||||||
"name": "fred",
|
// - 32
|
||||||
},
|
// - 32
|
||||||
},
|
// - 32
|
||||||
}
|
// - 32
|
||||||
}
|
// - 32
|
||||||
|
// - 32
|
||||||
func unstructuredYAML() string {
|
// - 34
|
||||||
return `apiVersion: v1
|
// - 107
|
||||||
kind: fred
|
// - 105
|
||||||
metadata:
|
// - 110
|
||||||
name: fred
|
// - 100
|
||||||
namespace: blee
|
// - 34
|
||||||
`
|
// - 58
|
||||||
}
|
// - 32
|
||||||
|
// - 34
|
||||||
func k8sCustomRow() *metav1beta1.TableRow {
|
// - 102
|
||||||
return &metav1beta1.TableRow{
|
// - 114
|
||||||
Object: runtime.RawExtension{
|
// - 101
|
||||||
Raw: []byte(`{
|
// - 100
|
||||||
"kind": "fred",
|
// - 34
|
||||||
"apiVersion": "v1",
|
// - 44
|
||||||
"metadata": {
|
// - 10
|
||||||
"namespace": "blee",
|
// - 32
|
||||||
"name": "fred"
|
// - 32
|
||||||
}}`),
|
// - 32
|
||||||
},
|
// - 32
|
||||||
Cells: []interface{}{
|
// - 32
|
||||||
"a",
|
// - 32
|
||||||
"b",
|
// - 32
|
||||||
"c",
|
// - 32
|
||||||
},
|
// - 34
|
||||||
}
|
// - 97
|
||||||
}
|
// - 112
|
||||||
|
// - 105
|
||||||
func newCustom() resource.Columnar {
|
// - 86
|
||||||
mc := NewMockConnection()
|
// - 101
|
||||||
c, _ := resource.NewCustom(mc, "g/v1/fred").New(k8sCustomRow())
|
// - 114
|
||||||
return c
|
// - 115
|
||||||
}
|
// - 105
|
||||||
|
// - 111
|
||||||
func customYaml() string {
|
// - 110
|
||||||
return `typemeta:
|
// - 34
|
||||||
kind: ""
|
// - 58
|
||||||
apiversion: ""
|
// - 32
|
||||||
listmeta:
|
// - 34
|
||||||
selflink: ""
|
// - 118
|
||||||
resourceversion: ""
|
// - 49
|
||||||
continue: ""
|
// - 34
|
||||||
remainingitemcount: null
|
// - 44
|
||||||
columndefinitions:
|
// - 10
|
||||||
- name: A
|
// - 32
|
||||||
type: ""
|
// - 32
|
||||||
format: ""
|
// - 32
|
||||||
description: ""
|
// - 32
|
||||||
priority: 0
|
// - 32
|
||||||
- name: B
|
// - 32
|
||||||
type: ""
|
// - 32
|
||||||
format: ""
|
// - 32
|
||||||
description: ""
|
// - 34
|
||||||
priority: 0
|
// - 109
|
||||||
- name: C
|
// - 101
|
||||||
type: ""
|
// - 116
|
||||||
format: ""
|
// - 97
|
||||||
description: ""
|
// - 100
|
||||||
priority: 0
|
// - 97
|
||||||
rows:
|
// - 116
|
||||||
- cells:
|
// - 97
|
||||||
- a
|
// - 34
|
||||||
- b
|
// - 58
|
||||||
- c
|
// - 32
|
||||||
conditions: []
|
// - 123
|
||||||
object:
|
// - 10
|
||||||
raw:
|
// - 32
|
||||||
- 123
|
// - 32
|
||||||
- 10
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 34
|
||||||
- 34
|
// - 110
|
||||||
- 107
|
// - 97
|
||||||
- 105
|
// - 109
|
||||||
- 110
|
// - 101
|
||||||
- 100
|
// - 115
|
||||||
- 34
|
// - 112
|
||||||
- 58
|
// - 97
|
||||||
- 32
|
// - 99
|
||||||
- 34
|
// - 101
|
||||||
- 102
|
// - 34
|
||||||
- 114
|
// - 58
|
||||||
- 101
|
// - 32
|
||||||
- 100
|
// - 34
|
||||||
- 34
|
// - 98
|
||||||
- 44
|
// - 108
|
||||||
- 10
|
// - 101
|
||||||
- 32
|
// - 101
|
||||||
- 32
|
// - 34
|
||||||
- 32
|
// - 44
|
||||||
- 32
|
// - 10
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 34
|
// - 32
|
||||||
- 97
|
// - 32
|
||||||
- 112
|
// - 32
|
||||||
- 105
|
// - 32
|
||||||
- 86
|
// - 32
|
||||||
- 101
|
// - 32
|
||||||
- 114
|
// - 34
|
||||||
- 115
|
// - 110
|
||||||
- 105
|
// - 97
|
||||||
- 111
|
// - 109
|
||||||
- 110
|
// - 101
|
||||||
- 34
|
// - 34
|
||||||
- 58
|
// - 58
|
||||||
- 32
|
// - 32
|
||||||
- 34
|
// - 34
|
||||||
- 118
|
// - 102
|
||||||
- 49
|
// - 114
|
||||||
- 34
|
// - 101
|
||||||
- 44
|
// - 100
|
||||||
- 10
|
// - 34
|
||||||
- 32
|
// - 10
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 32
|
// - 32
|
||||||
- 34
|
// - 32
|
||||||
- 109
|
// - 125
|
||||||
- 101
|
// - 125
|
||||||
- 116
|
// object: null
|
||||||
- 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
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,146 +1,147 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
"context"
|
// import (
|
||||||
"errors"
|
// "context"
|
||||||
"fmt"
|
// "errors"
|
||||||
"strconv"
|
// "fmt"
|
||||||
|
// "strconv"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
// "github.com/derailed/k9s/internal/k8s"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
// appsv1 "k8s.io/api/apps/v1"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Compile time checks to ensure type satisfies interface
|
// // Compile time checks to ensure type satisfies interface
|
||||||
var _ Restartable = (*Deployment)(nil)
|
// var _ Restartable = (*Deployment)(nil)
|
||||||
var _ Scalable = (*Deployment)(nil)
|
// var _ Scalable = (*Deployment)(nil)
|
||||||
|
|
||||||
// Deployment tracks a kubernetes resource.
|
// // Deployment tracks a kubernetes resource.
|
||||||
type Deployment struct {
|
// type Deployment struct {
|
||||||
*Base
|
// *Base
|
||||||
instance *appsv1.Deployment
|
// instance *appsv1.Deployment
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewDeploymentList returns a new resource list.
|
// // NewDeploymentList returns a new resource list.
|
||||||
func NewDeploymentList(c Connection, ns string) List {
|
// func NewDeploymentList(c Connection, ns string) List {
|
||||||
return NewList(
|
// return NewList(
|
||||||
ns,
|
// ns,
|
||||||
"deploy",
|
// "deploy",
|
||||||
NewDeployment(c),
|
// NewDeployment(c),
|
||||||
AllVerbsAccess|DescribeAccess,
|
// AllVerbsAccess|DescribeAccess,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewDeployment instantiates a new Deployment.
|
// // NewDeployment instantiates a new Deployment.
|
||||||
func NewDeployment(c Connection) *Deployment {
|
// func NewDeployment(c Connection) *Deployment {
|
||||||
d := &Deployment{&Base{Connection: c, Resource: k8s.NewDeployment(c)}, nil}
|
// d := &Deployment{&Base{Connection: c, Resource: k8s.NewDeployment(c)}, nil}
|
||||||
d.Factory = d
|
// d.Factory = d
|
||||||
|
|
||||||
return d
|
// return d
|
||||||
}
|
// }
|
||||||
|
|
||||||
// New builds a new Deployment instance from a k8s resource.
|
// // New builds a new Deployment instance from a k8s resource.
|
||||||
func (r *Deployment) New(i interface{}) (Columnar, error) {
|
// func (r *Deployment) New(i interface{}) (Columnar, error) {
|
||||||
c := NewDeployment(r.Connection)
|
// c := NewDeployment(r.Connection)
|
||||||
switch instance := i.(type) {
|
// switch instance := i.(type) {
|
||||||
case *appsv1.Deployment:
|
// case *appsv1.Deployment:
|
||||||
c.instance = instance
|
// c.instance = instance
|
||||||
case appsv1.Deployment:
|
// case appsv1.Deployment:
|
||||||
c.instance = &instance
|
// c.instance = &instance
|
||||||
default:
|
// default:
|
||||||
return nil, fmt.Errorf("Expecting Deployment but got %T", instance)
|
// return nil, fmt.Errorf("Expecting Deployment but got %T", instance)
|
||||||
}
|
// }
|
||||||
c.path = c.namespacedName(c.instance.ObjectMeta)
|
// c.path = c.namespacedName(c.instance.ObjectMeta)
|
||||||
|
|
||||||
return c, nil
|
// return c, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Marshal resource to yaml.
|
// // Marshal resource to yaml.
|
||||||
func (r *Deployment) Marshal(path string) (string, error) {
|
// func (r *Deployment) Marshal(path string) (string, error) {
|
||||||
ns, n := Namespaced(path)
|
// ns, n := Namespaced(path)
|
||||||
i, err := r.Resource.Get(ns, n)
|
// i, err := r.Resource.Get(ns, n)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
|
||||||
dp, ok := i.(*appsv1.Deployment)
|
// dp, ok := i.(*appsv1.Deployment)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return "", errors.New("expecting dp resource")
|
// return "", errors.New("expecting dp resource")
|
||||||
}
|
// }
|
||||||
dp.TypeMeta.APIVersion = "apps/v1"
|
// dp.TypeMeta.APIVersion = "apps/v1"
|
||||||
dp.TypeMeta.Kind = "Deployment"
|
// dp.TypeMeta.Kind = "Deployment"
|
||||||
|
|
||||||
return r.marshalObject(dp)
|
// return r.marshalObject(dp)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Logs tail logs for all pods represented by this deployment.
|
// // Logs tail logs for all pods represented by this deployment.
|
||||||
func (r *Deployment) Logs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
// func (r *Deployment) Logs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||||
instance, err := r.Resource.Get(opts.Namespace, opts.Name)
|
// instance, err := r.Resource.Get(opts.Namespace, opts.Name)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
dp, ok := instance.(*appsv1.Deployment)
|
// dp, ok := instance.(*appsv1.Deployment)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return errors.New("Expecting valid deployment")
|
// return errors.New("Expecting valid deployment")
|
||||||
}
|
// }
|
||||||
if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
|
// if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
|
||||||
return fmt.Errorf("No valid selector found on deployment %s", opts.Name)
|
// 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.
|
// // Header return resource header.
|
||||||
func (*Deployment) Header(ns string) Row {
|
// func (*Deployment) Header(ns string) Row {
|
||||||
var hh Row
|
// var hh Row
|
||||||
if ns == AllNamespaces {
|
// if ns == AllNamespaces {
|
||||||
hh = append(hh, "NAMESPACE")
|
// hh = append(hh, "NAMESPACE")
|
||||||
}
|
// }
|
||||||
|
|
||||||
return append(hh,
|
// return append(hh,
|
||||||
"NAME",
|
// "NAME",
|
||||||
"DESIRED",
|
// "DESIRED",
|
||||||
"CURRENT",
|
// "CURRENT",
|
||||||
"UP-TO-DATE",
|
// "UP-TO-DATE",
|
||||||
"AVAILABLE",
|
// "AVAILABLE",
|
||||||
"AGE",
|
// "AGE",
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NumCols designates if column is numerical.
|
// // NumCols designates if column is numerical.
|
||||||
func (*Deployment) NumCols(n string) map[string]bool {
|
// func (*Deployment) NumCols(n string) map[string]bool {
|
||||||
return map[string]bool{
|
// return map[string]bool{
|
||||||
"DESIRED": true,
|
// "DESIRED": true,
|
||||||
"CURRENT": true,
|
// "CURRENT": true,
|
||||||
"UP-TO-DATE": true,
|
// "UP-TO-DATE": true,
|
||||||
"AVAILABLE": true,
|
// "AVAILABLE": true,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Fields retrieves displayable fields.
|
// // Fields retrieves displayable fields.
|
||||||
func (r *Deployment) Fields(ns string) Row {
|
// func (r *Deployment) Fields(ns string) Row {
|
||||||
ff := make([]string, 0, len(r.Header(ns)))
|
// ff := make([]string, 0, len(r.Header(ns)))
|
||||||
|
|
||||||
i := r.instance
|
// i := r.instance
|
||||||
if ns == AllNamespaces {
|
// if ns == AllNamespaces {
|
||||||
ff = append(ff, i.Namespace)
|
// ff = append(ff, i.Namespace)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return append(ff,
|
// return append(ff,
|
||||||
i.Name,
|
// i.Name,
|
||||||
strconv.Itoa(int(*i.Spec.Replicas)),
|
// strconv.Itoa(int(*i.Spec.Replicas)),
|
||||||
strconv.Itoa(int(i.Status.Replicas)),
|
// strconv.Itoa(int(i.Status.Replicas)),
|
||||||
strconv.Itoa(int(i.Status.UpdatedReplicas)),
|
// strconv.Itoa(int(i.Status.UpdatedReplicas)),
|
||||||
strconv.Itoa(int(i.Status.AvailableReplicas)),
|
// strconv.Itoa(int(i.Status.AvailableReplicas)),
|
||||||
toAge(i.ObjectMeta.CreationTimestamp),
|
// toAge(i.ObjectMeta.CreationTimestamp),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Scale the specified resource.
|
// // Scale the specified resource.
|
||||||
func (r *Deployment) Scale(ns, n string, replicas int32) error {
|
// func (r *Deployment) Scale(ns, n string, replicas int32) error {
|
||||||
return r.Resource.(Scalable).Scale(ns, n, replicas)
|
// return r.Resource.(Scalable).Scale(ns, n, replicas)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Restart the rollout of the specified resource.
|
// // Restart the rollout of the specified resource.
|
||||||
func (r *Deployment) Restart(ns, n string) error {
|
// func (r *Deployment) Restart(ns, n string) error {
|
||||||
return r.Resource.(Restartable).Restart(ns, n)
|
// return r.Resource.(Restartable).Restart(ns, n)
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,123 @@
|
||||||
package resource_test
|
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!!
|
// BOZO!!
|
||||||
// func TestDeploymentListData(t *testing.T) {
|
// import (
|
||||||
// mc := NewMockConnection()
|
// "testing"
|
||||||
// mr := NewMockCruder()
|
|
||||||
// m.When(mr.List(resource.NotNamespaced, metav1.ListOptions{})).ThenReturn(k8s.Collection{*k8sDeployment()}, nil)
|
|
||||||
|
|
||||||
// l := NewDeploymentListWithArgs("-", NewDeploymentWithArgs(mc, mr))
|
// "github.com/derailed/k9s/internal/k8s"
|
||||||
// // Make sure we can get deltas!
|
// "github.com/derailed/k9s/internal/resource"
|
||||||
// for i := 0; i < 2; i++ {
|
// m "github.com/petergtz/pegomock"
|
||||||
// err := l.Reconcile(nil, "", "")
|
// "github.com/stretchr/testify/assert"
|
||||||
// assert.Nil(t, err)
|
// appsv1 "k8s.io/api/apps/v1"
|
||||||
// }
|
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
// )
|
||||||
|
|
||||||
// mr.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced, metav1.ListOptions{})
|
// func NewDeploymentListWithArgs(ns string, r *resource.Deployment) resource.List {
|
||||||
// td := l.Data()
|
// return resource.NewList(ns, "deploy", r, resource.AllVerbsAccess|resource.DescribeAccess)
|
||||||
// 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 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 {
|
// func TestDeploymentListAccess(t *testing.T) {
|
||||||
var i int32 = 1
|
// mc := NewMockConnection()
|
||||||
return &appsv1.Deployment{
|
// mr := NewMockCruder()
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Namespace: "blee",
|
|
||||||
Name: "fred",
|
|
||||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
|
||||||
},
|
|
||||||
Spec: appsv1.DeploymentSpec{
|
|
||||||
Replicas: &i,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDeployment() resource.Columnar {
|
// ns := "blee"
|
||||||
mc := NewMockConnection()
|
// l := NewDeploymentListWithArgs(resource.AllNamespaces, NewDeploymentWithArgs(mc, mr))
|
||||||
c, _ := resource.NewDeployment(mc).New(k8sDeployment())
|
// l.SetNamespace(ns)
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func dpYaml() string {
|
// assert.Equal(t, "blee", l.GetNamespace())
|
||||||
return `apiVersion: apps/v1
|
// assert.Equal(t, "deploy", l.GetName())
|
||||||
kind: Deployment
|
// for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||||
metadata:
|
// assert.True(t, l.Access(a))
|
||||||
creationTimestamp: "2018-12-14T17:36:43Z"
|
// }
|
||||||
name: fred
|
// }
|
||||||
namespace: blee
|
|
||||||
spec:
|
// func TestDeploymentFields(t *testing.T) {
|
||||||
replicas: 1
|
// r := newDeployment().Fields("blee")
|
||||||
selector: null
|
// assert.Equal(t, "fred", r[0])
|
||||||
strategy: {}
|
// }
|
||||||
template:
|
|
||||||
metadata:
|
// func TestDeploymentMarshal(t *testing.T) {
|
||||||
creationTimestamp: null
|
// mc := NewMockConnection()
|
||||||
spec:
|
// mr := NewMockCruder()
|
||||||
containers: null
|
// m.When(mr.Get("blee", "fred")).ThenReturn(k8sDeployment(), nil)
|
||||||
status: {}
|
|
||||||
`
|
// 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: {}
|
||||||
|
// `
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,195 +1,196 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
"context"
|
// import (
|
||||||
"errors"
|
// "context"
|
||||||
"fmt"
|
// "errors"
|
||||||
"strconv"
|
// "fmt"
|
||||||
"strings"
|
// "strconv"
|
||||||
"time"
|
// "strings"
|
||||||
|
// "time"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
// "github.com/derailed/k9s/internal/k8s"
|
||||||
batchv1 "k8s.io/api/batch/v1"
|
// batchv1 "k8s.io/api/batch/v1"
|
||||||
v1 "k8s.io/api/core/v1"
|
// v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/duration"
|
// "k8s.io/apimachinery/pkg/util/duration"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Job tracks a kubernetes resource.
|
// // Job tracks a kubernetes resource.
|
||||||
type Job struct {
|
// type Job struct {
|
||||||
*Base
|
// *Base
|
||||||
|
|
||||||
instance *batchv1.Job
|
// instance *batchv1.Job
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewJobList returns a new resource list.
|
// // NewJobList returns a new resource list.
|
||||||
func NewJobList(c Connection, ns string) List {
|
// func NewJobList(c Connection, ns string) List {
|
||||||
return NewList(
|
// return NewList(
|
||||||
ns,
|
// ns,
|
||||||
"job",
|
// "job",
|
||||||
NewJob(c),
|
// NewJob(c),
|
||||||
AllVerbsAccess|DescribeAccess,
|
// AllVerbsAccess|DescribeAccess,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewJob instantiates a new Job.
|
// // NewJob instantiates a new Job.
|
||||||
func NewJob(c Connection) *Job {
|
// func NewJob(c Connection) *Job {
|
||||||
j := &Job{
|
// j := &Job{
|
||||||
Base: &Base{Connection: c, Resource: k8s.NewJob(c)},
|
// Base: &Base{Connection: c, Resource: k8s.NewJob(c)},
|
||||||
}
|
// }
|
||||||
j.Factory = j
|
// j.Factory = j
|
||||||
|
|
||||||
return j
|
// return j
|
||||||
}
|
// }
|
||||||
|
|
||||||
// New builds a new Job instance from a k8s resource.
|
// // New builds a new Job instance from a k8s resource.
|
||||||
func (r *Job) New(i interface{}) (Columnar, error) {
|
// func (r *Job) New(i interface{}) (Columnar, error) {
|
||||||
c := NewJob(r.Connection)
|
// c := NewJob(r.Connection)
|
||||||
switch instance := i.(type) {
|
// switch instance := i.(type) {
|
||||||
case *batchv1.Job:
|
// case *batchv1.Job:
|
||||||
c.instance = instance
|
// c.instance = instance
|
||||||
case batchv1.Job:
|
// case batchv1.Job:
|
||||||
c.instance = &instance
|
// c.instance = &instance
|
||||||
default:
|
// default:
|
||||||
return nil, fmt.Errorf("Expecting Job but got %T", instance)
|
// return nil, fmt.Errorf("Expecting Job but got %T", instance)
|
||||||
}
|
// }
|
||||||
c.path = c.namespacedName(c.instance.ObjectMeta)
|
// c.path = c.namespacedName(c.instance.ObjectMeta)
|
||||||
|
|
||||||
return c, nil
|
// return c, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Marshal resource to yaml.
|
// // Marshal resource to yaml.
|
||||||
func (r *Job) Marshal(path string) (string, error) {
|
// func (r *Job) Marshal(path string) (string, error) {
|
||||||
ns, n := Namespaced(path)
|
// ns, n := Namespaced(path)
|
||||||
i, err := r.Resource.Get(ns, n)
|
// i, err := r.Resource.Get(ns, n)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
|
||||||
jo, ok := i.(*batchv1.Job)
|
// jo, ok := i.(*batchv1.Job)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return "", errors.New("expecting job resource")
|
// return "", errors.New("expecting job resource")
|
||||||
}
|
// }
|
||||||
jo.TypeMeta.APIVersion = "extensions/v1beta1"
|
// jo.TypeMeta.APIVersion = "extensions/v1beta1"
|
||||||
jo.TypeMeta.Kind = "Job"
|
// jo.TypeMeta.Kind = "Job"
|
||||||
|
|
||||||
return r.marshalObject(jo)
|
// return r.marshalObject(jo)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Containers fetch all the containers on this job, may include init containers.
|
// // Containers fetch all the containers on this job, may include init containers.
|
||||||
func (r *Job) Containers(path string, includeInit bool) ([]string, error) {
|
// func (r *Job) Containers(path string, includeInit bool) ([]string, error) {
|
||||||
ns, n := Namespaced(path)
|
// 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.
|
// // Logs retrieves logs for a given container.
|
||||||
func (r *Job) Logs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
// func (r *Job) Logs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||||
instance, err := r.Resource.Get(opts.Namespace, opts.Name)
|
// instance, err := r.Resource.Get(opts.Namespace, opts.Name)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
jo, ok := instance.(*batchv1.Job)
|
// jo, ok := instance.(*batchv1.Job)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return errors.New("expecting job resource")
|
// return errors.New("expecting job resource")
|
||||||
}
|
// }
|
||||||
if jo.Spec.Selector == nil || len(jo.Spec.Selector.MatchLabels) == 0 {
|
// if jo.Spec.Selector == nil || len(jo.Spec.Selector.MatchLabels) == 0 {
|
||||||
return fmt.Errorf("No valid selector found on job %s", opts.FQN())
|
// 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.
|
// // Header return resource header.
|
||||||
func (*Job) Header(ns string) Row {
|
// func (*Job) Header(ns string) Row {
|
||||||
hh := Row{}
|
// hh := Row{}
|
||||||
if ns == AllNamespaces {
|
// if ns == AllNamespaces {
|
||||||
hh = append(hh, "NAMESPACE")
|
// 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.
|
// // Fields retrieves displayable fields.
|
||||||
func (r *Job) Fields(ns string) Row {
|
// func (r *Job) Fields(ns string) Row {
|
||||||
ff := make([]string, 0, len(r.Header(ns)))
|
// ff := make([]string, 0, len(r.Header(ns)))
|
||||||
|
|
||||||
i := r.instance
|
// i := r.instance
|
||||||
if ns == AllNamespaces {
|
// if ns == AllNamespaces {
|
||||||
ff = append(ff, i.Namespace)
|
// ff = append(ff, i.Namespace)
|
||||||
}
|
// }
|
||||||
|
|
||||||
cc, ii := r.toContainers(i.Spec.Template.Spec)
|
// cc, ii := r.toContainers(i.Spec.Template.Spec)
|
||||||
|
|
||||||
return append(ff,
|
// return append(ff,
|
||||||
i.Name,
|
// i.Name,
|
||||||
r.toCompletion(i.Spec, i.Status),
|
// r.toCompletion(i.Spec, i.Status),
|
||||||
r.toDuration(i.Status),
|
// r.toDuration(i.Status),
|
||||||
cc,
|
// cc,
|
||||||
ii,
|
// ii,
|
||||||
toAge(i.ObjectMeta.CreationTimestamp),
|
// toAge(i.ObjectMeta.CreationTimestamp),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// // ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// // Helpers...
|
||||||
|
|
||||||
const maxShow = 2
|
// const maxShow = 2
|
||||||
|
|
||||||
func (*Job) toContainers(p v1.PodSpec) (string, string) {
|
// func (*Job) toContainers(p v1.PodSpec) (string, string) {
|
||||||
cc, ii := parseContainers(p.InitContainers)
|
// cc, ii := parseContainers(p.InitContainers)
|
||||||
cn, ci := parseContainers(p.Containers)
|
// 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...
|
// // Limit to 2 of each...
|
||||||
if len(cc) > maxShow {
|
// if len(cc) > maxShow {
|
||||||
cc = append(cc[:2], "(+"+strconv.Itoa(len(cc)-maxShow)+")...")
|
// cc = append(cc[:2], "(+"+strconv.Itoa(len(cc)-maxShow)+")...")
|
||||||
}
|
// }
|
||||||
if len(ii) > maxShow {
|
// if len(ii) > maxShow {
|
||||||
ii = append(ii[:2], "(+"+strconv.Itoa(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) {
|
// func parseContainers(cos []v1.Container) (nn, ii []string) {
|
||||||
for _, co := range cos {
|
// for _, co := range cos {
|
||||||
nn = append(nn, co.Name)
|
// nn = append(nn, co.Name)
|
||||||
ii = append(ii, co.Image)
|
// ii = append(ii, co.Image)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nn, ii
|
// return nn, ii
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (*Job) toCompletion(spec batchv1.JobSpec, status batchv1.JobStatus) (s string) {
|
// func (*Job) toCompletion(spec batchv1.JobSpec, status batchv1.JobStatus) (s string) {
|
||||||
if spec.Completions != nil {
|
// if spec.Completions != nil {
|
||||||
return strconv.Itoa(int(status.Succeeded)) + "/" + strconv.Itoa(int(*spec.Completions))
|
// return strconv.Itoa(int(status.Succeeded)) + "/" + strconv.Itoa(int(*spec.Completions))
|
||||||
}
|
// }
|
||||||
|
|
||||||
if spec.Parallelism == nil {
|
// if spec.Parallelism == nil {
|
||||||
return strconv.Itoa(int(status.Succeeded)) + "/1"
|
// return strconv.Itoa(int(status.Succeeded)) + "/1"
|
||||||
}
|
// }
|
||||||
|
|
||||||
p := *spec.Parallelism
|
// p := *spec.Parallelism
|
||||||
if p > 1 {
|
// if p > 1 {
|
||||||
return strconv.Itoa(int(status.Succeeded)) + "/1 of " + strconv.Itoa(int(p))
|
// 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 {
|
// func (*Job) toDuration(status batchv1.JobStatus) string {
|
||||||
if status.StartTime == nil {
|
// if status.StartTime == nil {
|
||||||
return MissingValue
|
// return MissingValue
|
||||||
}
|
// }
|
||||||
|
|
||||||
var d time.Duration
|
// var d time.Duration
|
||||||
switch {
|
// switch {
|
||||||
case status.CompletionTime == nil:
|
// case status.CompletionTime == nil:
|
||||||
d = time.Since(status.StartTime.Time)
|
// d = time.Since(status.StartTime.Time)
|
||||||
default:
|
// default:
|
||||||
d = status.CompletionTime.Sub(status.StartTime.Time)
|
// d = status.CompletionTime.Sub(status.StartTime.Time)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return duration.HumanDuration(d)
|
// return duration.HumanDuration(d)
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,153 +1,154 @@
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
// BOZO!!
|
||||||
"testing"
|
// import (
|
||||||
"time"
|
// "testing"
|
||||||
|
// "time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
// "github.com/stretchr/testify/assert"
|
||||||
batchv1 "k8s.io/api/batch/v1"
|
// batchv1 "k8s.io/api/batch/v1"
|
||||||
v1 "k8s.io/api/core/v1"
|
// v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func TestJobToCompletion(t *testing.T) {
|
// func TestJobToCompletion(t *testing.T) {
|
||||||
t0 := testTime()
|
// t0 := testTime()
|
||||||
t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)}
|
// t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)}
|
||||||
var c, p int32 = 10, 20
|
// var c, p int32 = 10, 20
|
||||||
|
|
||||||
uu := []struct {
|
// uu := []struct {
|
||||||
j batchv1.JobSpec
|
// j batchv1.JobSpec
|
||||||
s batchv1.JobStatus
|
// s batchv1.JobStatus
|
||||||
e string
|
// e string
|
||||||
}{
|
// }{
|
||||||
{
|
// {
|
||||||
batchv1.JobSpec{
|
// batchv1.JobSpec{
|
||||||
Completions: &c,
|
// Completions: &c,
|
||||||
Parallelism: &p,
|
// Parallelism: &p,
|
||||||
},
|
// },
|
||||||
batchv1.JobStatus{
|
// batchv1.JobStatus{
|
||||||
Succeeded: 1,
|
// Succeeded: 1,
|
||||||
Active: 1,
|
// Active: 1,
|
||||||
Failed: 0,
|
// Failed: 0,
|
||||||
StartTime: &t1,
|
// StartTime: &t1,
|
||||||
CompletionTime: &t2,
|
// CompletionTime: &t2,
|
||||||
},
|
// },
|
||||||
"1/10",
|
// "1/10",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
batchv1.JobSpec{
|
// batchv1.JobSpec{
|
||||||
Parallelism: &p,
|
// Parallelism: &p,
|
||||||
},
|
// },
|
||||||
batchv1.JobStatus{
|
// batchv1.JobStatus{
|
||||||
Succeeded: 1,
|
// Succeeded: 1,
|
||||||
Active: 1,
|
// Active: 1,
|
||||||
Failed: 0,
|
// Failed: 0,
|
||||||
StartTime: &t1,
|
// StartTime: &t1,
|
||||||
CompletionTime: &t2,
|
// CompletionTime: &t2,
|
||||||
},
|
// },
|
||||||
"1/1 of 20",
|
// "1/1 of 20",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
batchv1.JobSpec{
|
// batchv1.JobSpec{
|
||||||
Completions: &c,
|
// Completions: &c,
|
||||||
},
|
// },
|
||||||
batchv1.JobStatus{
|
// batchv1.JobStatus{
|
||||||
Succeeded: 1,
|
// Succeeded: 1,
|
||||||
Active: 1,
|
// Active: 1,
|
||||||
Failed: 0,
|
// Failed: 0,
|
||||||
StartTime: &t1,
|
// StartTime: &t1,
|
||||||
CompletionTime: &t2,
|
// CompletionTime: &t2,
|
||||||
},
|
// },
|
||||||
"1/10",
|
// "1/10",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
batchv1.JobSpec{},
|
// batchv1.JobSpec{},
|
||||||
batchv1.JobStatus{
|
// batchv1.JobStatus{
|
||||||
Succeeded: 1,
|
// Succeeded: 1,
|
||||||
Active: 1,
|
// Active: 1,
|
||||||
Failed: 0,
|
// Failed: 0,
|
||||||
StartTime: &t1,
|
// StartTime: &t1,
|
||||||
CompletionTime: &t2,
|
// CompletionTime: &t2,
|
||||||
},
|
// },
|
||||||
"1/1",
|
// "1/1",
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
|
|
||||||
var j *Job
|
// var j *Job
|
||||||
for _, u := range uu {
|
// for _, u := range uu {
|
||||||
assert.Equal(t, u.e, j.toCompletion(u.j, u.s))
|
// assert.Equal(t, u.e, j.toCompletion(u.j, u.s))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestJobToDuration(t *testing.T) {
|
// func TestJobToDuration(t *testing.T) {
|
||||||
t0 := testTime().UTC()
|
// t0 := testTime().UTC()
|
||||||
t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)}
|
// t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)}
|
||||||
|
|
||||||
uu := []struct {
|
// uu := []struct {
|
||||||
s batchv1.JobStatus
|
// s batchv1.JobStatus
|
||||||
e string
|
// e string
|
||||||
}{
|
// }{
|
||||||
{
|
// {
|
||||||
batchv1.JobStatus{
|
// batchv1.JobStatus{
|
||||||
StartTime: &t1,
|
// StartTime: &t1,
|
||||||
CompletionTime: &t2,
|
// CompletionTime: &t2,
|
||||||
},
|
// },
|
||||||
"10s",
|
// "10s",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
batchv1.JobStatus{
|
// batchv1.JobStatus{
|
||||||
StartTime: &metav1.Time{Time: time.Now().Add(-10 * time.Second)},
|
// StartTime: &metav1.Time{Time: time.Now().Add(-10 * time.Second)},
|
||||||
},
|
// },
|
||||||
"10s",
|
// "10s",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
batchv1.JobStatus{
|
// batchv1.JobStatus{
|
||||||
CompletionTime: &t2,
|
// CompletionTime: &t2,
|
||||||
},
|
// },
|
||||||
MissingValue,
|
// MissingValue,
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
|
|
||||||
var j *Job
|
// var j *Job
|
||||||
for _, u := range uu {
|
// for _, u := range uu {
|
||||||
assert.Equal(t, u.e, j.toDuration(u.s))
|
// assert.Equal(t, u.e, j.toDuration(u.s))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestJobToContainers(t *testing.T) {
|
// func TestJobToContainers(t *testing.T) {
|
||||||
uu := []struct {
|
// uu := []struct {
|
||||||
s v1.PodSpec
|
// s v1.PodSpec
|
||||||
c, i string
|
// c, i string
|
||||||
}{
|
// }{
|
||||||
{
|
// {
|
||||||
v1.PodSpec{
|
// v1.PodSpec{
|
||||||
InitContainers: []v1.Container{
|
// InitContainers: []v1.Container{
|
||||||
{Name: "i1", Image: "fred"},
|
// {Name: "i1", Image: "fred"},
|
||||||
},
|
// },
|
||||||
Containers: []v1.Container{
|
// Containers: []v1.Container{
|
||||||
{Name: "c1", Image: "blee"},
|
// {Name: "c1", Image: "blee"},
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
"i1,c1", "fred,blee",
|
// "i1,c1", "fred,blee",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
v1.PodSpec{
|
// v1.PodSpec{
|
||||||
InitContainers: []v1.Container{
|
// InitContainers: []v1.Container{
|
||||||
{Name: "i1", Image: "fred"},
|
// {Name: "i1", Image: "fred"},
|
||||||
},
|
// },
|
||||||
Containers: []v1.Container{
|
// Containers: []v1.Container{
|
||||||
{Name: "c1", Image: "blee"},
|
// {Name: "c1", Image: "blee"},
|
||||||
{Name: "c2", Image: "duh"},
|
// {Name: "c2", Image: "duh"},
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
"i1,c1,(+1)...", "fred,blee,(+1)...",
|
// "i1,c1,(+1)...", "fred,blee,(+1)...",
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
|
|
||||||
var j *Job
|
// var j *Job
|
||||||
for _, u := range uu {
|
// for _, u := range uu {
|
||||||
c, i := j.toContainers(u.s)
|
// c, i := j.toContainers(u.s)
|
||||||
assert.Equal(t, u.c, c)
|
// assert.Equal(t, u.c, c)
|
||||||
assert.Equal(t, u.i, i)
|
// assert.Equal(t, u.i, i)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue