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