checkpoint
parent
2adafc0133
commit
bf2847bea7
|
|
@ -79,7 +79,7 @@ linters-settings:
|
|||
# exclude: /path/to/file.txt
|
||||
|
||||
funlen:
|
||||
lines: 65
|
||||
lines: 75
|
||||
statements: 40
|
||||
|
||||
govet:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ builds:
|
|||
- arm
|
||||
goarm:
|
||||
- 7
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w -X github.com/derailed/k9s/cmd.version={{.Version}} -X github.com/derailed/k9s/cmd.commit={{.Commit}} -X github.com/derailed/k9s/cmd.date={{.Date}}
|
||||
archives:
|
||||
|
|
|
|||
64
go.mod
64
go.mod
|
|
@ -2,66 +2,40 @@ module github.com/derailed/k9s
|
|||
|
||||
go 1.13
|
||||
|
||||
replace (
|
||||
github.com/docker/docker => github.com/docker/docker v1.4.2-0.20181221150755-2cb26cfe9cbf
|
||||
k8s.io/api => k8s.io/api v0.0.0-20190918155943-95b840bb6a1f
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20190918162238-f783a3654da8
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20190918163234-a9c1f33e9fb9
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.0.0-20190918163108-da9fdfce26bb
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269
|
||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.0.0-20190828162817-608eb1dad4ac
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.0.0-20190918163402-db86a8c7bb21
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20190918161219-8c8f079fddc3
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.0.0-20190918162944-7a93a0ddadd8
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.0.0-20190918162534-de037b596c1e
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.0.0-20190918162820-3b5c1246eb18
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20190918164019-21692a0861df
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.0.0-20190918162654-250a1838aa2c
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.0.0-20190918163543-cfa506e53441
|
||||
k8s.io/metrics => k8s.io/metrics v0.0.0-20190918162108-227c654b2546
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.0.0-20190918161442-d4c9c65c82af
|
||||
)
|
||||
replace github.com/derailed/popeye => /Users/fernand/go_wk/derailed/src/github.com/derailed/popeye
|
||||
|
||||
require (
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/atotto/clipboard v0.1.2
|
||||
github.com/derailed/popeye v0.0.0-00010101000000-000000000000
|
||||
github.com/derailed/tview v0.3.9
|
||||
github.com/drone/envsubst v1.0.2 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/fatih/color v1.6.0
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gdamore/tcell v1.3.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
|
||||
github.com/mattn/go-runewidth v0.0.8
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9
|
||||
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec
|
||||
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c
|
||||
github.com/openfaas/faas-provider v0.15.0
|
||||
github.com/petergtz/pegomock v2.6.0+incompatible
|
||||
github.com/rakyll/hey v0.1.2
|
||||
github.com/rivo/tview v0.0.0-20191018115645-bacbf5155bc1
|
||||
github.com/petergtz/pegomock v2.7.0+incompatible
|
||||
github.com/rakyll/hey v0.1.3
|
||||
github.com/rs/zerolog v1.18.0
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/stretchr/testify v1.5.1
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
helm.sh/helm/v3 v3.0.2
|
||||
k8s.io/api v0.0.0
|
||||
k8s.io/apimachinery v0.0.0
|
||||
k8s.io/cli-runtime v0.0.0
|
||||
k8s.io/client-go v0.0.0
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
helm.sh/helm/v3 v3.1.2
|
||||
k8s.io/api v0.18.0
|
||||
k8s.io/apimachinery v0.18.0
|
||||
k8s.io/cli-runtime v0.18.0
|
||||
k8s.io/client-go v0.18.0
|
||||
k8s.io/klog v1.0.0
|
||||
k8s.io/kubectl v0.0.0
|
||||
k8s.io/kubernetes v1.16.3
|
||||
k8s.io/metrics v0.0.0
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
k8s.io/kubectl v0.18.0
|
||||
k8s.io/metrics v0.18.0
|
||||
rsc.io/letsencrypt v0.0.3 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -27,6 +28,7 @@ const (
|
|||
cacheMXKey = "metrics"
|
||||
cacheMXAPIKey = "metricsAPI"
|
||||
checkConnTimeout = 10 * time.Second
|
||||
CallTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
var supportedMetricsAPIVersions = []string{"v1beta1"}
|
||||
|
|
@ -86,6 +88,33 @@ func makeCacheKey(ns, gvr string, vv []string) string {
|
|||
return ns + ":" + gvr + "::" + strings.Join(vv, ",")
|
||||
}
|
||||
|
||||
// ActiveCluster returns the current cluster name.
|
||||
func (a *APIClient) ActiveCluster() string {
|
||||
c, err := a.config.CurrentClusterName()
|
||||
if err != nil {
|
||||
log.Error().Msgf("Unable to located active cluster")
|
||||
return ""
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// IsActiveNamespace returns true if namespaces matches.
|
||||
func (a *APIClient) IsActiveNamespace(ns string) bool {
|
||||
if a.ActiveNamespace() == AllNamespaces {
|
||||
return true
|
||||
}
|
||||
return a.ActiveNamespace() == ns
|
||||
}
|
||||
|
||||
// ActiveNamespace returns the current namespace.
|
||||
func (a *APIClient) ActiveNamespace() string {
|
||||
ns, err := a.CurrentNamespaceName()
|
||||
if err != nil {
|
||||
return AllNamespaces
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
func (a *APIClient) clearCache() {
|
||||
for _, k := range a.cache.Keys() {
|
||||
a.cache.Remove(k)
|
||||
|
|
@ -104,9 +133,12 @@ func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error)
|
|||
}
|
||||
}
|
||||
dial, sar := a.DialOrDie().AuthorizationV1().SelfSubjectAccessReviews(), makeSAR(ns, gvr)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), CallTimeout)
|
||||
defer cancel()
|
||||
for _, v := range verbs {
|
||||
sar.Spec.ResourceAttributes.Verb = v
|
||||
resp, err := dial.Create(sar)
|
||||
resp, err := dial.Create(ctx, sar, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf(" Dial Failed!")
|
||||
a.cache.Add(key, false, cacheExpiry)
|
||||
|
|
@ -135,7 +167,9 @@ func (a *APIClient) ServerVersion() (*version.Info, error) {
|
|||
|
||||
// ValidNamespaces returns all available namespaces.
|
||||
func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
|
||||
nn, err := a.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), CallTimeout)
|
||||
defer cancel()
|
||||
nn, err := a.DialOrDie().CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -197,7 +231,9 @@ func (a *APIClient) HasMetrics() bool {
|
|||
a.cache.Add(cacheMXKey, flag, cacheExpiry)
|
||||
return flag
|
||||
}
|
||||
if _, err := dial.MetricsV1beta1().NodeMetricses().List(metav1.ListOptions{Limit: 1}); err == nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), CallTimeout)
|
||||
defer cancel()
|
||||
if _, err := dial.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{Limit: 1}); err == nil {
|
||||
flag = true
|
||||
}
|
||||
a.cache.Add(cacheMXKey, flag, cacheExpiry)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
|
@ -129,7 +130,7 @@ func (m *MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeM
|
|||
}
|
||||
|
||||
// FetchNodesMetrics return all metrics for nodes.
|
||||
func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
|
||||
func (m *MetricsServer) FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMetricsList, error) {
|
||||
const msg = "user is not authorized to list node metrics"
|
||||
|
||||
mx := new(mv1beta1.NodeMetricsList)
|
||||
|
|
@ -150,7 +151,7 @@ func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
|
|||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
mxList, err := client.MetricsV1beta1().NodeMetricses().List(metav1.ListOptions{})
|
||||
mxList, err := client.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
|
@ -160,7 +161,7 @@ func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
|
|||
}
|
||||
|
||||
// FetchPodsMetrics return all metrics for pods in a given namespace.
|
||||
func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, error) {
|
||||
func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1beta1.PodMetricsList, error) {
|
||||
mx := new(mv1beta1.PodMetricsList)
|
||||
const msg = "user is not authorized to list pods metrics"
|
||||
|
||||
|
|
@ -184,7 +185,7 @@ func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, e
|
|||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
mxList, err := client.MetricsV1beta1().PodMetricses(ns).List(metav1.ListOptions{})
|
||||
mxList, err := client.MetricsV1beta1().PodMetricses(ns).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
|
@ -194,7 +195,7 @@ func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, e
|
|||
}
|
||||
|
||||
// FetchPodMetrics return all metrics for pods in a given namespace.
|
||||
func (m *MetricsServer) FetchPodMetrics(fqn string) (*mv1beta1.PodMetrics, error) {
|
||||
func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1beta1.PodMetrics, error) {
|
||||
var mx *mv1beta1.PodMetrics
|
||||
const msg = "user is not authorized to list pod metrics"
|
||||
|
||||
|
|
@ -218,7 +219,7 @@ func (m *MetricsServer) FetchPodMetrics(fqn string) (*mv1beta1.PodMetrics, error
|
|||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
mx, err = client.MetricsV1beta1().PodMetricses(ns).Get(n, metav1.GetOptions{})
|
||||
mx, err = client.MetricsV1beta1().PodMetricses(ns).Get(ctx, n, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,15 @@ type Connection interface {
|
|||
|
||||
// CheckConnectivity checks if api server connection is happy or not.
|
||||
CheckConnectivity() bool
|
||||
|
||||
// ActiveCluster returns the current cluster name.
|
||||
ActiveCluster() string
|
||||
|
||||
// ActiveNamespace returns the current namespace.
|
||||
ActiveNamespace() string
|
||||
|
||||
// IsActiveNamespace checks if given ns is active.
|
||||
IsActiveNamespace(string) bool
|
||||
}
|
||||
|
||||
// CurrentMetrics tracks current cpu/mem.
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package color
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// ColorFmt colorize a string with ansi colors.
|
||||
|
|
@ -29,7 +27,6 @@ const (
|
|||
|
||||
// Colorize returns an ASCII colored string based on given color.
|
||||
func Colorize(s string, c Paint) string {
|
||||
log.Debug().Msgf("Painting %#v", c)
|
||||
if c == 0 {
|
||||
return s
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,8 @@ func (a *Aliases) loadDefaultAliases() {
|
|||
a.declare("help", "h", "?")
|
||||
a.declare("quit", "q", "Q")
|
||||
a.declare("aliases", "alias", "a")
|
||||
a.declare("popeye", "pop")
|
||||
a.declare("sanitize", "san", "sanitize")
|
||||
a.declare("contexts", "context", "ctx")
|
||||
a.declare("users", "user", "usr")
|
||||
a.declare("groups", "group", "grp")
|
||||
|
|
|
|||
|
|
@ -142,6 +142,51 @@ func (mock *MockConnection) HasMetrics() bool {
|
|||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) ActiveCluster() string {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("ActiveCluster", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()})
|
||||
var ret0 string
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) ActiveNamespace() string {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("ActiveNamespace", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()})
|
||||
var ret0 string
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) IsActiveNamespace(s string) bool {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("IsActiveNamespace", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
|
||||
var ret0 bool
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(bool)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) IsNamespaced(_param0 string) bool {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
|
|
|
|||
|
|
@ -1,123 +1,124 @@
|
|||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
// BOZO!! v1.18.0
|
||||
// import (
|
||||
// "context"
|
||||
// "fmt"
|
||||
// "os"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
// "github.com/derailed/k9s/internal/client"
|
||||
// "github.com/derailed/k9s/internal/render"
|
||||
// "github.com/rs/zerolog/log"
|
||||
// "helm.sh/helm/v3/pkg/action"
|
||||
// "k8s.io/apimachinery/pkg/runtime"
|
||||
// )
|
||||
|
||||
var (
|
||||
_ Accessor = (*Chart)(nil)
|
||||
_ Nuker = (*Chart)(nil)
|
||||
_ Describer = (*Chart)(nil)
|
||||
)
|
||||
// var (
|
||||
// _ Accessor = (*Chart)(nil)
|
||||
// _ Nuker = (*Chart)(nil)
|
||||
// _ Describer = (*Chart)(nil)
|
||||
// )
|
||||
|
||||
// Chart represents a helm chart.
|
||||
type Chart struct {
|
||||
NonResource
|
||||
}
|
||||
// // Chart represents a helm chart.
|
||||
// type Chart struct {
|
||||
// NonResource
|
||||
// }
|
||||
|
||||
// List returns a collection of resources.
|
||||
func (c *Chart) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
cfg, err := c.EnsureHelmConfig(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// // List returns a collection of resources.
|
||||
// func (c *Chart) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
// cfg, err := c.EnsureHelmConfig(ns)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
rr, err := action.NewList(cfg).Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// rr, err := action.NewList(cfg).Run()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
oo := make([]runtime.Object, 0, len(rr))
|
||||
for _, r := range rr {
|
||||
oo = append(oo, render.ChartRes{Release: r})
|
||||
}
|
||||
// oo := make([]runtime.Object, 0, len(rr))
|
||||
// for _, r := range rr {
|
||||
// oo = append(oo, render.ChartRes{Release: r})
|
||||
// }
|
||||
|
||||
return oo, nil
|
||||
}
|
||||
// return oo, nil
|
||||
// }
|
||||
|
||||
// Get returns a resource.
|
||||
func (c *Chart) Get(_ context.Context, path string) (runtime.Object, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := c.EnsureHelmConfig(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := action.NewGet(cfg).Run(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// // Get returns a resource.
|
||||
// func (c *Chart) Get(_ context.Context, path string) (runtime.Object, error) {
|
||||
// ns, n := client.Namespaced(path)
|
||||
// cfg, err := c.EnsureHelmConfig(ns)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// resp, err := action.NewGet(cfg).Run(n)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return render.ChartRes{Release: resp}, nil
|
||||
}
|
||||
// return render.ChartRes{Release: resp}, nil
|
||||
// }
|
||||
|
||||
// Describe returns the chart notes.
|
||||
func (c *Chart) Describe(path string) (string, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := c.EnsureHelmConfig(ns)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := action.NewGet(cfg).Run(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// // Describe returns the chart notes.
|
||||
// func (c *Chart) Describe(path string) (string, error) {
|
||||
// ns, n := client.Namespaced(path)
|
||||
// cfg, err := c.EnsureHelmConfig(ns)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// resp, err := action.NewGet(cfg).Run(n)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
return resp.Info.Notes, nil
|
||||
}
|
||||
// return resp.Info.Notes, nil
|
||||
// }
|
||||
|
||||
// ToYAML returns the chart manifest.
|
||||
func (c *Chart) ToYAML(path string) (string, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := c.EnsureHelmConfig(ns)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := action.NewGet(cfg).Run(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// // ToYAML returns the chart manifest.
|
||||
// func (c *Chart) ToYAML(path string) (string, error) {
|
||||
// ns, n := client.Namespaced(path)
|
||||
// cfg, err := c.EnsureHelmConfig(ns)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// resp, err := action.NewGet(cfg).Run(n)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
return resp.Manifest, nil
|
||||
}
|
||||
// return resp.Manifest, nil
|
||||
// }
|
||||
|
||||
// Delete uninstall a Chart.
|
||||
func (c *Chart) Delete(path string, cascade, force bool) error {
|
||||
ns, n := client.Namespaced(path)
|
||||
cfg, err := c.EnsureHelmConfig(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// // Delete uninstall a Chart.
|
||||
// func (c *Chart) Delete(path string, cascade, force bool) error {
|
||||
// ns, n := client.Namespaced(path)
|
||||
// cfg, err := c.EnsureHelmConfig(ns)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
res, err := action.NewUninstall(cfg).Run(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// res, err := action.NewUninstall(cfg).Run(n)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
if res != nil && res.Info != "" {
|
||||
return fmt.Errorf("%s", res.Info)
|
||||
}
|
||||
// if res != nil && res.Info != "" {
|
||||
// return fmt.Errorf("%s", res.Info)
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// EnsureHelmConfig return a new configuration.
|
||||
func (c *Chart) EnsureHelmConfig(ns string) (*action.Configuration, error) {
|
||||
cfg := new(action.Configuration)
|
||||
flags := c.Client().Config().Flags()
|
||||
if err := cfg.Init(flags, ns, os.Getenv("HELM_DRIVER"), helmLogger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
// // EnsureHelmConfig return a new configuration.
|
||||
// func (c *Chart) EnsureHelmConfig(ns string) (*action.Configuration, error) {
|
||||
// cfg := new(action.Configuration)
|
||||
// flags := c.Client().Config().Flags()
|
||||
// if err := cfg.Init(flags, ns, os.Getenv("HELM_DRIVER"), helmLogger); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return cfg, nil
|
||||
// }
|
||||
|
||||
func helmLogger(s string, args ...interface{}) {
|
||||
log.Debug().Msgf("%s %v", s, args)
|
||||
}
|
||||
// func helmLogger(s string, args ...interface{}) {
|
||||
// log.Debug().Msgf("%s %v", s, args)
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
|
|||
err error
|
||||
)
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if pmx, err = client.DialMetrics(c.Client()).FetchPodMetrics(fqn); err != nil {
|
||||
if pmx, err = client.DialMetrics(c.Client()).FetchPodMetrics(ctx, fqn); err != nil {
|
||||
log.Warn().Err(err).Msgf("No metrics found for pod %q", fqn)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ func (c *conn) SupportsRes(grp string, versions []string) (string, bool, error)
|
|||
func (c *conn) ServerVersion() (*version.Info, error) { return nil, nil }
|
||||
func (c *conn) CurrentNamespaceName() (string, error) { return "", nil }
|
||||
func (c *conn) CanI(ns, gvr string, verbs []string) (bool, error) { return true, nil }
|
||||
func (c *conn) ActiveCluster() string { return "" }
|
||||
func (c *conn) ActiveNamespace() string { return "" }
|
||||
func (c *conn) IsActiveNamespace(string) bool { return false }
|
||||
|
||||
type podFactory struct{}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
|
|
@ -33,7 +34,9 @@ func (c *CronJob) Run(path string) error {
|
|||
}
|
||||
|
||||
// BOZO!! Factory resource??
|
||||
cj, err := c.Client().DialOrDie().BatchV1beta1().CronJobs(ns).Get(n, metav1.GetOptions{})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
defer cancel()
|
||||
cj, err := c.Client().DialOrDie().BatchV1beta1().CronJobs(ns).Get(ctx, n, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -51,7 +54,7 @@ func (c *CronJob) Run(path string) error {
|
|||
},
|
||||
Spec: cj.Spec.JobTemplate.Spec,
|
||||
}
|
||||
_, err = c.Client().DialOrDie().BatchV1().Jobs(ns).Create(job)
|
||||
_, err = c.Client().DialOrDie().BatchV1().Jobs(ns).Create(ctx, job, metav1.CreateOptions{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/kubectl/pkg/describe"
|
||||
"k8s.io/kubectl/pkg/describe/versioned"
|
||||
)
|
||||
|
||||
// Describe describes a resource.
|
||||
|
|
@ -31,7 +30,7 @@ func Describe(c client.Connection, gvr client.GVR, path string) (string, error)
|
|||
log.Error().Err(err).Msgf("Unable to find mapper for %s %s", gvr, n)
|
||||
return "", err
|
||||
}
|
||||
d, err := versioned.Describer(c.Config().Flags(), mapping)
|
||||
d, err := describe.Describer(c.Config().Flags(), mapping)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping)
|
||||
return "", err
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func (d *Deployment) IsHappy(dp appsv1.Deployment) bool {
|
|||
}
|
||||
|
||||
// Scale a Deployment.
|
||||
func (d *Deployment) Scale(path string, replicas int32) error {
|
||||
func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) error {
|
||||
ns, n := client.Namespaced(path)
|
||||
auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", []string{client.GetVerb, client.UpdateVerb})
|
||||
if err != nil {
|
||||
|
|
@ -45,18 +45,18 @@ func (d *Deployment) Scale(path string, replicas int32) error {
|
|||
return fmt.Errorf("user is not authorized to scale a deployment")
|
||||
}
|
||||
|
||||
scale, err := d.Client().DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{})
|
||||
scale, err := d.Client().DialOrDie().AppsV1().Deployments(ns).GetScale(ctx, n, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scale.Spec.Replicas = replicas
|
||||
_, err = d.Client().DialOrDie().AppsV1().Deployments(ns).UpdateScale(n, scale)
|
||||
_, err = d.Client().DialOrDie().AppsV1().Deployments(ns).UpdateScale(ctx, n, scale, metav1.UpdateOptions{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart a Deployment rollout.
|
||||
func (d *Deployment) Restart(path string) error {
|
||||
func (d *Deployment) Restart(ctx context.Context, path string) error {
|
||||
dp, err := d.Load(d.Factory, path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -75,7 +75,13 @@ func (d *Deployment) Restart(path string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = d.Client().DialOrDie().AppsV1().Deployments(dp.Namespace).Patch(dp.Name, types.StrategicMergePatchType, update)
|
||||
_, err = d.Client().DialOrDie().AppsV1().Deployments(dp.Namespace).Patch(
|
||||
ctx,
|
||||
dp.Name,
|
||||
types.StrategicMergePatchType,
|
||||
update,
|
||||
metav1.PatchOptions{},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func (d *DaemonSet) IsHappy(ds appsv1.DaemonSet) bool {
|
|||
}
|
||||
|
||||
// Restart a DaemonSet rollout.
|
||||
func (d *DaemonSet) Restart(path string) error {
|
||||
func (d *DaemonSet) Restart(ctx context.Context, path string) error {
|
||||
ds, err := d.GetInstance(path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -56,7 +56,13 @@ func (d *DaemonSet) Restart(path string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = d.Client().DialOrDie().AppsV1().DaemonSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update)
|
||||
_, err = d.Client().DialOrDie().AppsV1().DaemonSets(ds.Namespace).Patch(
|
||||
ctx,
|
||||
ds.Name,
|
||||
types.StrategicMergePatchType,
|
||||
update,
|
||||
metav1.PatchOptions{},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error)
|
|||
err error
|
||||
)
|
||||
if client.IsClusterScoped(ns) {
|
||||
ll, err = g.dynClient().List(metav1.ListOptions{LabelSelector: labelSel})
|
||||
ll, err = g.dynClient().List(ctx, metav1.ListOptions{LabelSelector: labelSel})
|
||||
} else {
|
||||
ll, err = g.dynClient().Namespace(ns).List(metav1.ListOptions{LabelSelector: labelSel})
|
||||
ll, err = g.dynClient().Namespace(ns).List(ctx, metav1.ListOptions{LabelSelector: labelSel})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -57,15 +57,15 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error)
|
|||
|
||||
// Get returns a given resource.
|
||||
func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||
log.Debug().Msgf("GENERIC-GET %q", path)
|
||||
var opts metav1.GetOptions
|
||||
|
||||
ns, n := client.Namespaced(path)
|
||||
dial := g.dynClient()
|
||||
if client.IsClusterScoped(ns) {
|
||||
return dial.Get(n, opts)
|
||||
return dial.Get(ctx, n, opts)
|
||||
}
|
||||
|
||||
return dial.Namespace(ns).Get(n, opts)
|
||||
return dial.Namespace(ns).Get(ctx, n, opts)
|
||||
}
|
||||
|
||||
// Describe describes a resource.
|
||||
|
|
@ -111,11 +111,14 @@ func (g *Generic) Delete(path string, cascade, force bool) error {
|
|||
PropagationPolicy: &p,
|
||||
GracePeriodSeconds: grace,
|
||||
}
|
||||
// BOZO!! Move to caller!
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
defer cancel()
|
||||
if client.IsClusterScoped(ns) {
|
||||
return g.dynClient().Delete(n, &opts)
|
||||
return g.dynClient().Delete(ctx, n, opts)
|
||||
}
|
||||
|
||||
return g.dynClient().Namespace(ns).Delete(n, &opts)
|
||||
return g.dynClient().Namespace(ns).Delete(ctx, n, opts)
|
||||
}
|
||||
|
||||
func (g *Generic) dynClient() dynamic.NamespaceableResourceInterface {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func (n *Node) ToggleCordon(path string, cordon bool) error {
|
|||
}
|
||||
return fmt.Errorf("node is already uncordoned")
|
||||
}
|
||||
err, patchErr := h.PatchOrReplace(n.Factory.Client().DialOrDie())
|
||||
err, patchErr := h.PatchOrReplace(n.Factory.Client().DialOrDie(), false)
|
||||
if patchErr != nil {
|
||||
return patchErr
|
||||
}
|
||||
|
|
@ -97,8 +97,30 @@ func (n *Node) Drain(path string, opts DrainOptions, w io.Writer) error {
|
|||
}
|
||||
|
||||
// Get returns a node resource.
|
||||
func (n *Node) Get(_ context.Context, path string) (runtime.Object, error) {
|
||||
return FetchNode(n.Factory, path)
|
||||
func (n *Node) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||
var (
|
||||
nmx *mv1beta1.NodeMetricsList
|
||||
err error
|
||||
)
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if nmx, err = client.DialMetrics(n.Client()).FetchNodesMetrics(ctx); err != nil {
|
||||
log.Warn().Err(err).Msgf("No node metrics")
|
||||
}
|
||||
}
|
||||
|
||||
no, err := FetchNode(ctx, n.Factory, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&no)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &render.NodeWithMetrics{
|
||||
Raw: &unstructured.Unstructured{Object: o},
|
||||
MX: nodeMetricsFor(MetaFQN(no.ObjectMeta), nmx),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// List returns a collection of node resources.
|
||||
|
|
@ -113,12 +135,12 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
err error
|
||||
)
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if nmx, err = client.DialMetrics(n.Client()).FetchNodesMetrics(); err != nil {
|
||||
if nmx, err = client.DialMetrics(n.Client()).FetchNodesMetrics(ctx); err != nil {
|
||||
log.Warn().Err(err).Msgf("No node metrics")
|
||||
}
|
||||
}
|
||||
|
||||
nn, err := FetchNodes(n.Factory, labels)
|
||||
nn, err := FetchNodes(ctx, n.Factory, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -141,7 +163,7 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
// Helpers...
|
||||
|
||||
// FetchNode retrieves a node.
|
||||
func FetchNode(f Factory, path string) (*v1.Node, error) {
|
||||
func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) {
|
||||
auth, err := f.Client().CanI("", "v1/nodes", []string{"get"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -150,11 +172,11 @@ func FetchNode(f Factory, path string) (*v1.Node, error) {
|
|||
return nil, fmt.Errorf("user is not authorized to list nodes")
|
||||
}
|
||||
|
||||
return f.Client().DialOrDie().CoreV1().Nodes().Get(path, metav1.GetOptions{})
|
||||
return f.Client().DialOrDie().CoreV1().Nodes().Get(ctx, path, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// FetchNodes retrieves all nodes.
|
||||
func FetchNodes(f Factory, labelsSel string) (*v1.NodeList, error) {
|
||||
func FetchNodes(ctx context.Context, f Factory, labelsSel string) (*v1.NodeList, error) {
|
||||
auth, err := f.Client().CanI("", "v1/nodes", []string{client.ListVerb})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -163,7 +185,7 @@ func FetchNodes(f Factory, labelsSel string) (*v1.NodeList, error) {
|
|||
return nil, fmt.Errorf("user is not authorized to list nodes")
|
||||
}
|
||||
|
||||
return f.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{
|
||||
return f.Client().DialOrDie().CoreV1().Nodes().List(ctx, metav1.ListOptions{
|
||||
LabelSelector: labelsSel,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) {
|
|||
|
||||
var pmx *mv1beta1.PodMetrics
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if pmx, err = client.DialMetrics(p.Client()).FetchPodMetrics(path); err != nil {
|
||||
if pmx, err = client.DialMetrics(p.Client()).FetchPodMetrics(ctx, path); err != nil {
|
||||
log.Debug().Err(err).Msgf("No pod metrics")
|
||||
}
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
|
||||
var pmx *mv1beta1.PodMetricsList
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if pmx, err = client.DialMetrics(p.Client()).FetchPodsMetrics(ns); err != nil {
|
||||
if pmx, err = client.DialMetrics(p.Client()).FetchPodsMetrics(ctx, ns); err != nil {
|
||||
log.Debug().Err(err).Msgf("No pods metrics")
|
||||
}
|
||||
}
|
||||
|
|
@ -235,10 +235,9 @@ func tailLogs(ctx context.Context, logger Logger, c LogChan, opts LogOptions) er
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Context(ctx)
|
||||
|
||||
// This call will block if nothing is in the stream!!
|
||||
stream, err := req.Stream()
|
||||
stream, err := req.Stream(ctx)
|
||||
if err != nil {
|
||||
c <- opts.DecorateLog([]byte(err.Error() + "\n"))
|
||||
log.Error().Err(err).Msgf("Unable to obtain log stream failed for `%s", opts.Path)
|
||||
|
|
@ -263,11 +262,11 @@ func readLogs(stream io.ReadCloser, c LogChan, opts LogOptions) {
|
|||
if err != nil {
|
||||
if err == io.EOF {
|
||||
log.Warn().Err(err).Msgf("Stream closed for %s", opts.Info())
|
||||
c <- NewLogItemFromString("<STREAM> closed")
|
||||
c <- opts.DecorateLog([]byte("log stream closed\n"))
|
||||
return
|
||||
}
|
||||
log.Warn().Err(err).Msgf("Stream READ error %s", opts.Info())
|
||||
c <- NewLogItemFromString("<STREAM> failed")
|
||||
c <- opts.DecorateLog([]byte("log stream failed\n"))
|
||||
return
|
||||
}
|
||||
c <- opts.DecorateLog(bytes)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
package dao
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
cfg "github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/popeye/pkg"
|
||||
"github.com/derailed/popeye/pkg/config"
|
||||
"github.com/derailed/popeye/types"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
var _ Accessor = (*Popeye)(nil)
|
||||
|
||||
// Popeye tracks cluster sanitization.
|
||||
type Popeye struct {
|
||||
NonResource
|
||||
}
|
||||
|
||||
// NewPopeye returns a new set of aliases.
|
||||
func NewPopeye(f Factory) *Popeye {
|
||||
a := Popeye{}
|
||||
a.Init(f, client.NewGVR("popeye"))
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
type readWriteCloser struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
// Close close read stream.
|
||||
func (readWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns a collection of aliases.
|
||||
func (p *Popeye) List(ctx context.Context, _ string) ([]runtime.Object, error) {
|
||||
defer func(t time.Time) {
|
||||
log.Debug().Msgf("Popeye -- Elapsed %v", time.Since(t))
|
||||
}(time.Now())
|
||||
|
||||
js := "json"
|
||||
flags := config.NewFlags()
|
||||
spinach := filepath.Join(cfg.K9sHome, "spinach.yml")
|
||||
flags.Spinach = &spinach
|
||||
flags.Output = &js
|
||||
popeye, err := pkg.NewPopeye(flags, &log.Logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
popeye.SetFactory(newPopFactory(p.Factory))
|
||||
if err = popeye.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buff := readWriteCloser{Buffer: bytes.NewBufferString("")}
|
||||
popeye.SetOutputTarget(buff)
|
||||
if err = popeye.Sanitize(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var b render.Builder
|
||||
if err = json.Unmarshal(buff.Bytes(), &b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oo := make([]runtime.Object, 0, len(b.Report.Sections))
|
||||
sort.Sort(b.Report.Sections)
|
||||
for _, s := range b.Report.Sections {
|
||||
s.Tally.Count = len(s.Outcome)
|
||||
if s.Tally.Sum() > 0 {
|
||||
oo = append(oo, s)
|
||||
}
|
||||
}
|
||||
|
||||
return oo, nil
|
||||
}
|
||||
|
||||
// Get fetch a resource.
|
||||
func (a *Popeye) Get(_ context.Context, _ string) (runtime.Object, error) {
|
||||
return nil, errors.New("NYI!!")
|
||||
}
|
||||
|
||||
type popFactory struct {
|
||||
Factory
|
||||
}
|
||||
|
||||
var _ types.Factory = (*popFactory)(nil)
|
||||
|
||||
func newPopFactory(f Factory) *popFactory {
|
||||
return &popFactory{Factory: f}
|
||||
}
|
||||
|
||||
func (p *popFactory) Client() types.Connection {
|
||||
return &popConnection{Connection: p.Factory.Client()}
|
||||
}
|
||||
|
||||
type popConnection struct {
|
||||
client.Connection
|
||||
}
|
||||
|
||||
var _ types.Connection = (*popConnection)(nil)
|
||||
|
||||
func (c *popConnection) Config() types.Config {
|
||||
return c.Connection.Config()
|
||||
}
|
||||
|
||||
func (c *popConnection) CurrentNamespaceName() (string, error) {
|
||||
return c.ActiveNamespace(), nil
|
||||
}
|
||||
func (c *popConnection) CurrentClusterName() (string, error) {
|
||||
return c.Connection.ActiveCluster(), nil
|
||||
}
|
||||
func (c *popConnection) Flags() *genericclioptions.ConfigFlags {
|
||||
return c.Connection.Config().Flags()
|
||||
}
|
||||
func (c *popConnection) RESTConfig() (*restclient.Config, error) {
|
||||
return c.Connection.Config().RESTConfig()
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"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"
|
||||
|
|
@ -119,6 +120,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) {
|
|||
}
|
||||
|
||||
func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) {
|
||||
log.Debug().Msgf("LOAD-CR %q", path)
|
||||
o, err := r.Factory.Get(crGVR, path, true, labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -47,8 +47,11 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
|
|||
client.NewGVR("apps/v1/statefulsets"): &StatefulSet{},
|
||||
client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{},
|
||||
client.NewGVR("batch/v1/jobs"): &Job{},
|
||||
client.NewGVR("charts"): &Chart{},
|
||||
client.NewGVR("openfaas"): &OpenFaas{},
|
||||
// BOZO!! v1.18.0
|
||||
// client.NewGVR("charts"): &Chart{},
|
||||
client.NewGVR("openfaas"): &OpenFaas{},
|
||||
client.NewGVR("popeye"): &Popeye{},
|
||||
client.NewGVR("report"): &Sanitizer{},
|
||||
}
|
||||
|
||||
r, ok := m[gvr]
|
||||
|
|
@ -163,6 +166,20 @@ func loadK9s(m ResourceMetas) {
|
|||
Verbs: []string{},
|
||||
Categories: []string{"k9s"},
|
||||
}
|
||||
m[client.NewGVR("popeye")] = metav1.APIResource{
|
||||
Name: "popeye",
|
||||
Kind: "Popeye",
|
||||
SingularName: "popeye",
|
||||
Verbs: []string{},
|
||||
Categories: []string{"k9s"},
|
||||
}
|
||||
m[client.NewGVR("report")] = metav1.APIResource{
|
||||
Name: "report",
|
||||
Kind: "Report",
|
||||
SingularName: "report",
|
||||
Verbs: []string{},
|
||||
Categories: []string{"k9s"},
|
||||
}
|
||||
m[client.NewGVR("contexts")] = metav1.APIResource{
|
||||
Name: "contexts",
|
||||
Kind: "Contexts",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/polymorphichelpers"
|
||||
)
|
||||
|
||||
|
|
@ -94,7 +95,7 @@ func (r *ReplicaSet) Rollback(fqn string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = rb.Rollback(dp, map[string]string{}, version, false)
|
||||
_, err = rb.Rollback(dp, map[string]string{}, version, cmdutil.DryRunNone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
package dao
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
cfg "github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/popeye/pkg"
|
||||
"github.com/derailed/popeye/pkg/config"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
var _ Accessor = (*Sanitizer)(nil)
|
||||
|
||||
// Sanitizer tracks cluster sanitization.
|
||||
type Sanitizer struct {
|
||||
NonResource
|
||||
}
|
||||
|
||||
// NewSanitizer returns a new set of aliases.
|
||||
func NewSanitizer(f Factory) *Sanitizer {
|
||||
s := Sanitizer{}
|
||||
s.Init(f, client.NewGVR("report"))
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
// List returns a collection of aliases.
|
||||
func (s *Sanitizer) List(ctx context.Context, _ string) ([]runtime.Object, error) {
|
||||
report, ok := ctx.Value(internal.KeyPath).(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no sanitizer report path")
|
||||
}
|
||||
sections := []string{report}
|
||||
js := "json"
|
||||
flags := config.NewFlags()
|
||||
flags.Sections = §ions
|
||||
spinach := filepath.Join(cfg.K9sHome, "spinach.yml")
|
||||
flags.Spinach = &spinach
|
||||
flags.Output = &js
|
||||
|
||||
popeye, err := pkg.NewPopeye(flags, &log.Logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
popeye.SetFactory(newPopFactory(s.Factory))
|
||||
if err = popeye.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buff := readWriteCloser{Buffer: bytes.NewBufferString("")}
|
||||
popeye.SetOutputTarget(buff)
|
||||
if err = popeye.Sanitize(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var b render.Builder
|
||||
if err = json.Unmarshal(buff.Bytes(), &b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oo := make([]runtime.Object, len(b.Report.Sections))
|
||||
for i, s := range b.Report.Sections {
|
||||
oo[i] = s
|
||||
}
|
||||
|
||||
return oo, nil
|
||||
}
|
||||
|
||||
// Get fetch a resource.
|
||||
func (*Sanitizer) Get(_ context.Context, _ string) (runtime.Object, error) {
|
||||
return nil, errors.New("NYI!!")
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ func (s *StatefulSet) IsHappy(sts appsv1.StatefulSet) bool {
|
|||
}
|
||||
|
||||
// Scale a StatefulSet.
|
||||
func (s *StatefulSet) Scale(path string, replicas int32) error {
|
||||
func (s *StatefulSet) Scale(ctx context.Context, path string, replicas int32) error {
|
||||
ns, n := client.Namespaced(path)
|
||||
auth, err := s.Client().CanI(ns, "apps/v1/statefulsets:scale", []string{client.GetVerb, client.UpdateVerb})
|
||||
if err != nil {
|
||||
|
|
@ -45,18 +45,18 @@ func (s *StatefulSet) Scale(path string, replicas int32) error {
|
|||
return fmt.Errorf("user is not authorized to scale statefulsets")
|
||||
}
|
||||
|
||||
scale, err := s.Client().DialOrDie().AppsV1().StatefulSets(ns).GetScale(n, metav1.GetOptions{})
|
||||
scale, err := s.Client().DialOrDie().AppsV1().StatefulSets(ns).GetScale(ctx, n, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scale.Spec.Replicas = replicas
|
||||
_, err = s.Client().DialOrDie().AppsV1().StatefulSets(ns).UpdateScale(n, scale)
|
||||
_, err = s.Client().DialOrDie().AppsV1().StatefulSets(ns).UpdateScale(ctx, n, scale, metav1.UpdateOptions{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart a StatefulSet rollout.
|
||||
func (s *StatefulSet) Restart(path string) error {
|
||||
func (s *StatefulSet) Restart(ctx context.Context, path string) error {
|
||||
sts, err := s.getStatefulSet(path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -76,7 +76,13 @@ func (s *StatefulSet) Restart(path string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = s.Client().DialOrDie().AppsV1().StatefulSets(sts.Namespace).Patch(sts.Name, types.StrategicMergePatchType, update)
|
||||
_, err = s.Client().DialOrDie().AppsV1().StatefulSets(sts.Namespace).Patch(
|
||||
ctx,
|
||||
sts.Name,
|
||||
types.StrategicMergePatchType,
|
||||
update,
|
||||
metav1.PatchOptions{},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ type Table struct {
|
|||
|
||||
// Get returns a given resource.
|
||||
func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||
ns, n := client.Namespaced(path)
|
||||
|
||||
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
|
||||
_, codec := t.codec()
|
||||
|
||||
|
|
@ -30,18 +28,17 @@ func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o, err := c.Get().
|
||||
ns, n := client.Namespaced(path)
|
||||
req := c.Get().
|
||||
SetHeader("Accept", a).
|
||||
Namespace(ns).
|
||||
Name(n).
|
||||
Resource(t.gvr.R()).
|
||||
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
||||
Do().Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
VersionedParams(&metav1beta1.TableOptions{}, codec)
|
||||
if ns != client.ClusterScope {
|
||||
req = req.Namespace(ns)
|
||||
}
|
||||
|
||||
return o, nil
|
||||
return req.Do(ctx).Get()
|
||||
}
|
||||
|
||||
// List all Resources in a given namespace.
|
||||
|
|
@ -63,7 +60,7 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
Namespace(ns).
|
||||
Resource(t.gvr.R()).
|
||||
VersionedParams(&metav1.ListOptions{LabelSelector: labelSel}, codec).
|
||||
Do().Get()
|
||||
Do(ctx).Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ type Describer interface {
|
|||
// Scalable represents resources that can scale.
|
||||
type Scalable interface {
|
||||
// Scale scales a resource up or down.
|
||||
Scale(path string, replicas int32) error
|
||||
Scale(ctx context.Context, path string, replicas int32) error
|
||||
}
|
||||
|
||||
// Controller represents a pod controller.
|
||||
|
|
@ -132,7 +132,7 @@ type Switchable interface {
|
|||
// Restartable represents a restartable resource.
|
||||
type Restartable interface {
|
||||
// Restart performs a rollout restart.
|
||||
Restart(path string) error
|
||||
Restart(ctx context.Context, path string) error
|
||||
}
|
||||
|
||||
// Runnable represents a runnable resource.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
|
@ -20,8 +22,8 @@ type (
|
|||
// MetricsService calls the metrics server for metrics info.
|
||||
MetricsService interface {
|
||||
HasMetrics() bool
|
||||
FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error)
|
||||
FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, error)
|
||||
FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMetricsList, error)
|
||||
FetchPodsMetrics(ctx context.Context, ns string) (*mv1beta1.PodMetricsList, error)
|
||||
}
|
||||
|
||||
// Cluster represents a kubernetes resource.
|
||||
|
|
@ -77,13 +79,13 @@ func (c *Cluster) UserName() string {
|
|||
}
|
||||
|
||||
// Metrics gathers node level metrics and compute utilization percentages.
|
||||
func (c *Cluster) Metrics(mx *client.ClusterMetrics) error {
|
||||
nn, err := dao.FetchNodes(c.factory, "")
|
||||
func (c *Cluster) Metrics(ctx context.Context, mx *client.ClusterMetrics) error {
|
||||
nn, err := dao.FetchNodes(ctx, c.factory, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nmx, err := c.mx.FetchNodesMetrics()
|
||||
nmx, err := c.mx.FetchNodesMetrics(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
)
|
||||
|
|
@ -90,8 +92,10 @@ func (c *ClusterInfo) Refresh() {
|
|||
data.K9sVer = c.version
|
||||
data.K8sVer = c.cluster.Version()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
defer cancel()
|
||||
var mx client.ClusterMetrics
|
||||
if err := c.cluster.Metrics(&mx); err == nil {
|
||||
if err := c.cluster.Metrics(ctx, &mx); err == nil {
|
||||
data.Cpu, data.Mem, data.Ephemeral = mx.PercCPU, mx.PercMEM, mx.PercEphemeral
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -176,7 +176,6 @@ func (l *Log) load() error {
|
|||
if l.cancelFn != nil {
|
||||
l.cancelFn()
|
||||
}
|
||||
close(c)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, er
|
|||
hh = append(hh, c)
|
||||
}
|
||||
|
||||
mm, err := h.checkMetrics()
|
||||
mm, err := h.checkMetrics(ctx)
|
||||
if err != nil {
|
||||
return hh, nil
|
||||
}
|
||||
|
|
@ -62,15 +62,15 @@ func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, er
|
|||
return hh, nil
|
||||
}
|
||||
|
||||
func (h *PulseHealth) checkMetrics() (health.Checks, error) {
|
||||
func (h *PulseHealth) checkMetrics(ctx context.Context) (health.Checks, error) {
|
||||
dial := client.DialMetrics(h.factory.Client())
|
||||
|
||||
nn, err := dao.FetchNodes(h.factory, "")
|
||||
nn, err := dao.FetchNodes(ctx, h.factory, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nmx, err := dial.FetchNodesMetrics()
|
||||
nmx, err := dial.FetchNodesMetrics(ctx)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Fetching metrics")
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ import (
|
|||
// BOZO!! Break up deps and merge into single registrar
|
||||
var Registry = map[string]ResourceMeta{
|
||||
// Custom...
|
||||
"charts": {
|
||||
DAO: &dao.Chart{},
|
||||
Renderer: &render.Chart{},
|
||||
},
|
||||
// BOZO!! v1.18.0
|
||||
// "charts": {
|
||||
// DAO: &dao.Chart{},
|
||||
// Renderer: &render.Chart{},
|
||||
// },
|
||||
"pulses": {
|
||||
DAO: &dao.Pulse{},
|
||||
},
|
||||
|
|
@ -62,6 +63,14 @@ var Registry = map[string]ResourceMeta{
|
|||
DAO: &dao.Alias{},
|
||||
Renderer: &render.Alias{},
|
||||
},
|
||||
"popeye": {
|
||||
DAO: &dao.Popeye{},
|
||||
Renderer: &render.Popeye{},
|
||||
},
|
||||
"report": {
|
||||
DAO: &dao.Sanitizer{},
|
||||
TreeRenderer: &xray.Section{},
|
||||
},
|
||||
|
||||
// Core...
|
||||
"v1/endpoints": {
|
||||
|
|
|
|||
|
|
@ -117,7 +117,6 @@ func (b *Benchmark) Run(cluster string, done func()) {
|
|||
// this call will block until the benchmark is complete or timesout.
|
||||
b.worker.Run()
|
||||
b.worker.Stop()
|
||||
log.Debug().Msgf("YO!! %t %s", b.canceled, buff)
|
||||
if len(buff.Bytes()) > 0 {
|
||||
if err := b.save(cluster, buff); err != nil {
|
||||
log.Error().Err(err).Msg("Saving Benchmark")
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error {
|
|||
if !ok {
|
||||
return fmt.Errorf("expecting row 0 to be a string but got %T", row.Cells[0])
|
||||
}
|
||||
|
||||
r.ID = client.FQN(nns, n)
|
||||
r.Fields = make(Fields, 0, len(g.Header(ns)))
|
||||
r.Fields = append(r.Fields, nns)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error {
|
|||
pdb.Name,
|
||||
numbToStr(pdb.Spec.MinAvailable),
|
||||
numbToStr(pdb.Spec.MaxUnavailable),
|
||||
strconv.Itoa(int(pdb.Status.PodDisruptionsAllowed)),
|
||||
strconv.Itoa(int(pdb.Status.DisruptionsAllowed)),
|
||||
strconv.Itoa(int(pdb.Status.CurrentHealthy)),
|
||||
strconv.Itoa(int(pdb.Status.DesiredHealthy)),
|
||||
strconv.Itoa(int(pdb.Status.ExpectedPods)),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/util/node"
|
||||
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
)
|
||||
|
||||
|
|
@ -262,7 +261,7 @@ func (*Pod) Statuses(ss []v1.ContainerStatus) (cr, ct, rc int) {
|
|||
func (p *Pod) Phase(po *v1.Pod) string {
|
||||
status := string(po.Status.Phase)
|
||||
if po.Status.Reason != "" {
|
||||
if po.DeletionTimestamp != nil && po.Status.Reason == node.NodeUnreachablePodReason {
|
||||
if po.DeletionTimestamp != nil && po.Status.Reason == "NodeLost" {
|
||||
return "Unknown"
|
||||
}
|
||||
status = po.Status.Reason
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/popeye/pkg/config"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// Popeye renders a sanitizer to screen.
|
||||
type Popeye struct{}
|
||||
|
||||
// ColorerFunc colors a resource row.
|
||||
func (Popeye) ColorerFunc() ColorerFunc {
|
||||
return func(ns string, h Header, re RowEvent) tcell.Color {
|
||||
c := DefaultColorer(ns, h, re)
|
||||
|
||||
warnCol := h.IndexOf("WARNING", true)
|
||||
status, _ := strconv.Atoi(strings.TrimSpace(re.Row.Fields[warnCol]))
|
||||
if status > 0 {
|
||||
c = tcell.ColorOrange
|
||||
}
|
||||
errCol := h.IndexOf("ERROR", true)
|
||||
status, _ = strconv.Atoi(strings.TrimSpace(re.Row.Fields[errCol]))
|
||||
if status > 0 {
|
||||
c = ErrColor
|
||||
}
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
// Header returns a header row.
|
||||
func (Popeye) Header(ns string) Header {
|
||||
return Header{
|
||||
HeaderColumn{Name: "RESOURCE"},
|
||||
HeaderColumn{Name: "SCORE%", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "SCANNED", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "OK", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "INFO", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "WARNING", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "ERROR", Align: tview.AlignRight},
|
||||
}
|
||||
}
|
||||
|
||||
// Render renders a K8s resource to screen.
|
||||
func (Popeye) Render(o interface{}, ns string, r *Row) error {
|
||||
s, ok := o.(Section)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected Section, but got %T", o)
|
||||
}
|
||||
|
||||
r.ID = s.Title
|
||||
r.Fields = append(r.Fields,
|
||||
s.Title,
|
||||
strconv.Itoa(s.Tally.Score()),
|
||||
strconv.Itoa(s.Tally.OK+s.Tally.Info+s.Tally.Warning+s.Tally.Error),
|
||||
strconv.Itoa(s.Tally.OK),
|
||||
strconv.Itoa(s.Tally.Info),
|
||||
strconv.Itoa(s.Tally.Warning),
|
||||
strconv.Itoa(s.Tally.Error),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
// BOZO!! export!
|
||||
type (
|
||||
// Builder represents sanitizer
|
||||
Builder struct {
|
||||
Report Report `json:"popeye" yaml:"popeye"`
|
||||
}
|
||||
|
||||
// Report represents the output of a sanitization pass.
|
||||
Report struct {
|
||||
Score int `json:"score" yaml:"score"`
|
||||
Grade string `json:"grade" yaml:"grade"`
|
||||
Sections Sections `json:"sanitizers,omitempty" yaml:"sanitizers,omitempty"`
|
||||
}
|
||||
|
||||
// Sections represents a collection of sections.
|
||||
Sections []Section
|
||||
|
||||
// Section represents a sanitizer pass
|
||||
Section struct {
|
||||
Title string `json:"sanitizer" yaml:"sanitizer"`
|
||||
Tally *Tally `json:"tally" yaml:"tally"`
|
||||
Outcome Outcome `json:"issues,omitempty" yaml:"issues,omitempty"`
|
||||
}
|
||||
|
||||
Outcome map[string]Issues
|
||||
Issues []Issue
|
||||
|
||||
Issue struct {
|
||||
Group string `yaml:"group" json:"group"`
|
||||
Level config.Level `yaml:"level" json:"level"`
|
||||
Message string `yaml:"message" json:"message"`
|
||||
}
|
||||
|
||||
Tally struct {
|
||||
OK, Info, Warning, Error int
|
||||
Count int
|
||||
}
|
||||
)
|
||||
|
||||
func (t *Tally) Sum() int {
|
||||
return t.OK + t.Info + t.Warning + t.Error
|
||||
}
|
||||
|
||||
func (t *Tally) Score() int {
|
||||
oks := t.OK + t.Info
|
||||
return toPerc(float64(oks), float64(oks+t.Warning+t.Error))
|
||||
}
|
||||
|
||||
func toPerc(v1, v2 float64) int {
|
||||
if v2 == 0 {
|
||||
return 0
|
||||
}
|
||||
return int(math.Floor((v1 / v2) * 100))
|
||||
}
|
||||
|
||||
func (s Sections) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s Sections) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s Sections) Less(i, j int) bool {
|
||||
t1, t2 := s[i].Tally, s[j].Tally
|
||||
return t1.Score() < t2.Score()
|
||||
}
|
||||
|
||||
// GetObjectKind returns a schema object.
|
||||
func (Section) GetObjectKind() schema.ObjectKind {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyObject returns a container copy.
|
||||
func (s Section) DeepCopyObject() runtime.Object {
|
||||
return s
|
||||
}
|
||||
|
||||
// MaxSeverity gather the max severity in a collection of issues.
|
||||
func (s Section) MaxSeverity() config.Level {
|
||||
max := config.OkLevel
|
||||
for _, issues := range s.Outcome {
|
||||
m := issues.MaxSeverity()
|
||||
if m > max {
|
||||
max = m
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
// MaxSeverity gather the max severity in a collection of issues.
|
||||
func (i Issues) MaxSeverity() config.Level {
|
||||
max := config.OkLevel
|
||||
for _, is := range i {
|
||||
if is.Level > max {
|
||||
max = is.Level
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
// CountSeverity counts severity level instances
|
||||
func (i Issues) CountSeverity(l config.Level) int {
|
||||
var count int
|
||||
for _, is := range i {
|
||||
if is.Level == l {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
|
@ -164,6 +164,9 @@ func (r RowEvents) FindIndex(id string) (int, bool) {
|
|||
|
||||
// Sort rows based on column index and order.
|
||||
func (r RowEvents) Sort(ns string, sortCol int, ageCol bool, asc bool) {
|
||||
if sortCol == -1 {
|
||||
return
|
||||
}
|
||||
t := RowEventSorter{NS: ns, Events: r, Index: sortCol, Asc: asc}
|
||||
sort.Sort(t)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,12 @@ type Command struct {
|
|||
|
||||
// NewCommand returns a new command view.
|
||||
func NewCommand(styles *config.Styles, m *model.FishBuff) *Command {
|
||||
c := Command{styles: styles, TextView: tview.NewTextView(), model: m, suggestionIndex: -1}
|
||||
c := Command{
|
||||
styles: styles,
|
||||
TextView: tview.NewTextView(),
|
||||
model: m,
|
||||
suggestionIndex: -1,
|
||||
}
|
||||
c.SetWordWrap(true)
|
||||
c.ShowCursor(true)
|
||||
c.SetWrap(true)
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ func (t *Table) doUpdate(data render.TableData) {
|
|||
}
|
||||
custData := data.Customize(cols, t.wide)
|
||||
|
||||
if (t.sortCol.name == "" || custData.Header.IndexOf(t.sortCol.name, false) == -1) && len(custData.Header) > 0 {
|
||||
if (t.sortCol.name == "" || custData.Header.IndexOf(t.sortCol.name, false) == -1) && len(custData.Header) > 0 && t.sortCol.name != "NONE" {
|
||||
t.sortCol.name = custData.Header[0].Name
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -487,10 +487,6 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *App) viewResource(gvr, path string, clearStack bool) error {
|
||||
return a.command.run(gvr, path, clearStack)
|
||||
}
|
||||
|
||||
func (a *App) gotoResource(cmd, path string, clearStack bool) error {
|
||||
return a.command.run(cmd, path, clearStack)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
|
|||
if c.specialCmd(cmd) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmds := strings.Split(cmd, " ")
|
||||
gvr, v, err := c.viewMetaFor(cmds[0])
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ func podCtx(app *App, path, labelSel, fieldSel string) ContextFunc {
|
|||
|
||||
ns, _ := client.Namespaced(path)
|
||||
mx := client.NewMetricsServer(app.factory.Client())
|
||||
nmx, err := mx.FetchPodsMetrics(ns)
|
||||
nmx, err := mx.FetchPodsMetrics(ctx, ns)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msgf("No pods metrics")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package view
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -132,9 +133,12 @@ func (n *Node) yamlCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
defer cancel()
|
||||
|
||||
sel := n.GetTable().GetSelectedItem()
|
||||
gvr := n.GVR().GVR()
|
||||
o, err := n.App().factory.Client().DynDialOrDie().Resource(gvr).Get(sel, metav1.GetOptions{})
|
||||
o, err := n.App().factory.Client().DynDialOrDie().Resource(gvr).Get(ctx, sel, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
n.App().Flash().Errf("Unable to get resource %q -- %s", n.GVR(), err)
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
// Popeye represents a sanitizer view.
|
||||
type Popeye struct {
|
||||
ResourceViewer
|
||||
}
|
||||
|
||||
// NewPopeye returns a new view.
|
||||
func NewPopeye(gvr client.GVR) ResourceViewer {
|
||||
p := Popeye{
|
||||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
p.GetTable().SetColorerFn(render.Popeye{}.ColorerFunc())
|
||||
p.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
||||
p.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
||||
p.GetTable().SetSortCol("SCORE%", true)
|
||||
p.GetTable().SetDecorateFn(p.decorateRows)
|
||||
p.SetBindKeysFn(p.bindKeys)
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
// Init initializes the view.
|
||||
func (p *Popeye) Init(ctx context.Context) error {
|
||||
if err := p.ResourceViewer.Init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
p.GetTable().GetModel().SetNamespace("*")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Popeye) decorateRows(data render.TableData) render.TableData {
|
||||
var sum int
|
||||
for _, re := range data.RowEvents {
|
||||
n, err := strconv.Atoi(re.Row.Fields[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
sum += n
|
||||
}
|
||||
score := sum / len(data.RowEvents)
|
||||
p.GetTable().Path = fmt.Sprintf("Score %d -- %s", score, grade(score))
|
||||
return data
|
||||
}
|
||||
|
||||
func (p *Popeye) bindKeys(aa ui.KeyActions) {
|
||||
aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
|
||||
aa.Add(ui.KeyActions{
|
||||
tcell.KeyEnter: ui.NewKeyAction("Goto", p.describeCmd, true),
|
||||
ui.KeyShiftR: ui.NewKeyAction("Sort Resource", p.GetTable().SortColCmd("RESOURCE", true), false),
|
||||
ui.KeyShiftS: ui.NewKeyAction("Sort Score", p.GetTable().SortColCmd("SCORE%", true), false),
|
||||
ui.KeyShiftO: ui.NewKeyAction("Sort OK", p.GetTable().SortColCmd("OK", true), false),
|
||||
ui.KeyShiftI: ui.NewKeyAction("Sort Info", p.GetTable().SortColCmd("INFO", true), false),
|
||||
ui.KeyShiftW: ui.NewKeyAction("Sort Warning", p.GetTable().SortColCmd("WARNING", true), false),
|
||||
ui.KeyShiftE: ui.NewKeyAction("Sort Error", p.GetTable().SortColCmd("ERROR", true), false),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Popeye) describeCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := p.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
return evt
|
||||
}
|
||||
|
||||
v := NewSanitizer(client.NewGVR("report"))
|
||||
v.SetContextFn(sanitizerCtx(path))
|
||||
|
||||
if err := p.App().inject(v); err != nil {
|
||||
p.App().Flash().Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sanitizerCtx(path string) ContextFunc {
|
||||
return func(ctx context.Context) context.Context {
|
||||
ctx = context.WithValue(ctx, internal.KeyPath, path)
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func grade(score int) string {
|
||||
switch {
|
||||
case score >= 90:
|
||||
return "A"
|
||||
case score >= 80:
|
||||
return "B"
|
||||
case score >= 70:
|
||||
return "C"
|
||||
case score >= 60:
|
||||
return "D"
|
||||
case score >= 50:
|
||||
return "E"
|
||||
default:
|
||||
return "F"
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +72,13 @@ func miscViewers(vv MetaViewers) {
|
|||
vv[client.NewGVR("pulses")] = MetaViewer{
|
||||
viewerFn: NewPulse,
|
||||
}
|
||||
vv[client.NewGVR("popeye")] = MetaViewer{
|
||||
viewerFn: NewPopeye,
|
||||
}
|
||||
vv[client.NewGVR("report")] = MetaViewer{
|
||||
viewerFn: NewSanitizer,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func appsViewers(vv MetaViewers) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/ui/dialog"
|
||||
|
|
@ -43,8 +45,10 @@ func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
msg = fmt.Sprintf("Restart %d deployments?", len(paths))
|
||||
}
|
||||
dialog.ShowConfirm(r.App().Content.Pages, "Confirm Restart", msg, func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
defer cancel()
|
||||
for _, path := range paths {
|
||||
if err := r.restartRollout(path); err != nil {
|
||||
if err := r.restartRollout(ctx, path); err != nil {
|
||||
r.App().Flash().Err(err)
|
||||
} else {
|
||||
r.App().Flash().Infof("Rollout restart in progress for `%s...", path)
|
||||
|
|
@ -55,7 +59,7 @@ func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *RestartExtender) restartRollout(path string) error {
|
||||
func (r *RestartExtender) restartRollout(ctx context.Context, path string) error {
|
||||
res, err := dao.AccessorFor(r.App().factory, r.GVR())
|
||||
if err != nil {
|
||||
return nil
|
||||
|
|
@ -65,5 +69,5 @@ func (r *RestartExtender) restartRollout(path string) error {
|
|||
return errors.New("resource is not restartable")
|
||||
}
|
||||
|
||||
return s.Restart(path)
|
||||
return s.Restart(ctx, path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,447 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/xray"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var _ ResourceViewer = (*Sanitizer)(nil)
|
||||
|
||||
// Sanitizer represents an sanitizer tree view.
|
||||
type Sanitizer struct {
|
||||
*ui.Tree
|
||||
|
||||
app *App
|
||||
gvr client.GVR
|
||||
meta metav1.APIResource
|
||||
model *model.Tree
|
||||
cancelFn context.CancelFunc
|
||||
envFn EnvFunc
|
||||
contextFn ContextFunc
|
||||
}
|
||||
|
||||
// NewSanitizer returns a new view.
|
||||
func NewSanitizer(gvr client.GVR) ResourceViewer {
|
||||
return &Sanitizer{
|
||||
gvr: gvr,
|
||||
Tree: ui.NewTree(),
|
||||
model: model.NewTree(gvr),
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the view
|
||||
func (s *Sanitizer) Init(ctx context.Context) error {
|
||||
s.envFn = s.k9sEnv
|
||||
|
||||
if err := s.Tree.Init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
s.SetKeyListenerFn(s.keyEntered)
|
||||
|
||||
var err error
|
||||
s.meta, err = dao.MetaAccess.MetaFor(s.gvr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.app, err = extractApp(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.bindKeys()
|
||||
s.SetBackgroundColor(s.app.Styles.Xray().BgColor.Color())
|
||||
s.SetBorderColor(s.app.Styles.Xray().FgColor.Color())
|
||||
s.SetBorderFocusColor(s.app.Styles.Frame().Border.FocusColor.Color())
|
||||
s.SetGraphicsColor(s.app.Styles.Xray().GraphicColor.Color())
|
||||
s.SetTitle(strings.Title(s.gvr.R()))
|
||||
|
||||
s.model.SetRefreshRate(time.Duration(s.app.Config.K9s.GetRefreshRate()) * time.Second)
|
||||
s.model.SetNamespace(client.CleanseNamespace(s.app.Config.ActiveNamespace()))
|
||||
s.model.AddListener(s)
|
||||
|
||||
s.SetChangedFunc(func(n *tview.TreeNode) {
|
||||
spec, ok := n.GetReference().(xray.NodeSpec)
|
||||
if !ok {
|
||||
log.Error().Msgf("No ref found on node %s", n.GetText())
|
||||
return
|
||||
}
|
||||
s.SetSelectedItem(spec.AsPath())
|
||||
s.refreshActions()
|
||||
})
|
||||
s.refreshActions()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtraHints returns additional hints.
|
||||
func (s *Sanitizer) ExtraHints() map[string]string {
|
||||
if !s.app.Styles.Xray().ShowIcons {
|
||||
return nil
|
||||
}
|
||||
return xray.EmojiInfo()
|
||||
}
|
||||
|
||||
// SetInstance sets specific resource instance.
|
||||
func (s *Sanitizer) SetInstance(string) {}
|
||||
|
||||
func (s *Sanitizer) bindKeys() {
|
||||
s.Actions().Add(ui.KeyActions{
|
||||
tcell.KeyEnter: ui.NewKeyAction("Goto", s.gotoCmd, true),
|
||||
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", s.activateCmd, false),
|
||||
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", s.eraseCmd, false),
|
||||
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", s.eraseCmd, false),
|
||||
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", s.eraseCmd, false),
|
||||
tcell.KeyCtrlU: ui.NewSharedKeyAction("Clear Filter", s.clearCmd, false),
|
||||
tcell.KeyCtrlW: ui.NewSharedKeyAction("Clear Filter", s.clearCmd, false),
|
||||
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", s.resetCmd, false),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Sanitizer) keyEntered() {
|
||||
s.ClearSelection()
|
||||
s.update(s.filter(s.model.Peek()))
|
||||
}
|
||||
|
||||
func (s *Sanitizer) refreshActions() {
|
||||
}
|
||||
|
||||
// GetSelectedPath returns the current selection as string.
|
||||
func (s *Sanitizer) GetSelectedPath() string {
|
||||
spec := s.selectedSpec()
|
||||
if spec == nil {
|
||||
return ""
|
||||
}
|
||||
return spec.Path()
|
||||
}
|
||||
|
||||
func (s *Sanitizer) selectedSpec() *xray.NodeSpec {
|
||||
node := s.GetCurrentNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref, ok := node.GetReference().(xray.NodeSpec)
|
||||
if !ok {
|
||||
log.Error().Msgf("Expecting a NodeSpec!")
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ref
|
||||
}
|
||||
|
||||
// EnvFn returns an plugin env function if available.
|
||||
func (s *Sanitizer) EnvFn() EnvFunc {
|
||||
return s.envFn
|
||||
}
|
||||
|
||||
func (s *Sanitizer) k9sEnv() Env {
|
||||
env := k8sEnv(s.app.Conn().Config())
|
||||
|
||||
spec := s.selectedSpec()
|
||||
if spec == nil {
|
||||
return env
|
||||
}
|
||||
|
||||
env["FILTER"] = s.CmdBuff().String()
|
||||
if env["FILTER"] == "" {
|
||||
ns, n := client.Namespaced(spec.Path())
|
||||
env["NAMESPACE"], env["FILTER"] = ns, n
|
||||
}
|
||||
|
||||
switch spec.GVR() {
|
||||
case "containers":
|
||||
_, co := client.Namespaced(spec.Path())
|
||||
env["CONTAINER"] = co
|
||||
ns, n := client.Namespaced(*spec.ParentPath())
|
||||
env["NAMESPACE"], env["POD"], env["NAME"] = ns, n, co
|
||||
default:
|
||||
ns, n := client.Namespaced(spec.Path())
|
||||
env["NAMESPACE"], env["NAME"] = ns, n
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
// Aliases returns all available aliases.
|
||||
func (s *Sanitizer) Aliases() []string {
|
||||
return append(s.meta.ShortNames, s.meta.SingularName, s.meta.Name)
|
||||
}
|
||||
|
||||
func (s *Sanitizer) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if s.app.InCmdMode() {
|
||||
return evt
|
||||
}
|
||||
s.CmdBuff().SetActive(true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sanitizer) clearCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !s.CmdBuff().IsActive() {
|
||||
return evt
|
||||
}
|
||||
s.CmdBuff().Clear()
|
||||
s.model.ClearFilter()
|
||||
s.Start()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sanitizer) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if s.CmdBuff().IsActive() {
|
||||
s.CmdBuff().Delete()
|
||||
}
|
||||
s.UpdateTitle()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sanitizer) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !s.CmdBuff().InCmdMode() {
|
||||
s.CmdBuff().Reset()
|
||||
return s.app.PrevCmd(evt)
|
||||
}
|
||||
s.CmdBuff().Reset()
|
||||
s.model.ClearFilter()
|
||||
s.Start()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sanitizer) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if s.CmdBuff().IsActive() {
|
||||
if ui.IsLabelSelector(s.CmdBuff().String()) {
|
||||
s.Start()
|
||||
}
|
||||
s.CmdBuff().SetActive(false)
|
||||
s.GetRoot().ExpandAll()
|
||||
return nil
|
||||
}
|
||||
|
||||
spec := s.selectedSpec()
|
||||
if spec == nil {
|
||||
return nil
|
||||
}
|
||||
if len(spec.GVRs) <= 2 {
|
||||
return nil
|
||||
}
|
||||
path := strings.Replace(spec.Path(), "::", "/", 1)
|
||||
if strings.Contains(path, "[") {
|
||||
return nil
|
||||
}
|
||||
if len(strings.Split(path, "/")) == 1 && spec.GVR() != "node" {
|
||||
path = "-/" + path
|
||||
}
|
||||
if err := s.app.gotoResource(client.NewGVR(spec.GVR()).R(), path, false); err != nil {
|
||||
log.Debug().Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sanitizer) filter(root *xray.TreeNode) *xray.TreeNode {
|
||||
q := s.CmdBuff().String()
|
||||
if s.CmdBuff().Empty() || ui.IsLabelSelector(q) {
|
||||
return root
|
||||
}
|
||||
|
||||
s.UpdateTitle()
|
||||
if ui.IsFuzzySelector(q) {
|
||||
return root.Filter(q, fuzzyFilter)
|
||||
}
|
||||
|
||||
return root.Filter(q, rxFilter)
|
||||
}
|
||||
|
||||
// TreeNodeSelected callback for node selection.
|
||||
func (s *Sanitizer) TreeNodeSelected() {
|
||||
s.app.QueueUpdateDraw(func() {
|
||||
n := s.GetCurrentNode()
|
||||
if n != nil {
|
||||
n.SetColor(s.app.Styles.Xray().CursorColor.Color())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TreeLoadFailed notifies the load failed.
|
||||
func (s *Sanitizer) TreeLoadFailed(err error) {
|
||||
s.app.Flash().Err(err)
|
||||
}
|
||||
|
||||
func (s *Sanitizer) update(node *xray.TreeNode) {
|
||||
root := makeTreeNode(node, s.ExpandNodes(), s.app.Styles)
|
||||
if node == nil {
|
||||
s.app.QueueUpdateDraw(func() {
|
||||
s.SetRoot(root)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range node.Children {
|
||||
s.hydrate(root, c)
|
||||
}
|
||||
if s.GetSelectedItem() == "" {
|
||||
s.SetSelectedItem(node.Spec().Path())
|
||||
}
|
||||
|
||||
s.app.QueueUpdateDraw(func() {
|
||||
s.SetRoot(root)
|
||||
root.Walk(func(node, parent *tview.TreeNode) bool {
|
||||
spec, ok := node.GetReference().(xray.NodeSpec)
|
||||
if !ok {
|
||||
log.Error().Msgf("Expecting a NodeSpec but got %T", node.GetReference())
|
||||
return false
|
||||
}
|
||||
// BOZO!! Figure this out expand/collapse but the root
|
||||
if parent != nil {
|
||||
node.SetExpanded(s.ExpandNodes())
|
||||
} else {
|
||||
node.SetExpanded(true)
|
||||
}
|
||||
|
||||
if spec.AsPath() == s.GetSelectedItem() {
|
||||
node.SetExpanded(true).SetSelectable(true)
|
||||
s.SetCurrentNode(node)
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// TreeChanged notifies the model data changed.
|
||||
func (s *Sanitizer) TreeChanged(node *xray.TreeNode) {
|
||||
s.Count = node.Count(s.gvr.String())
|
||||
s.update(s.filter(node))
|
||||
s.UpdateTitle()
|
||||
}
|
||||
|
||||
func (s *Sanitizer) hydrate(parent *tview.TreeNode, n *xray.TreeNode) {
|
||||
node := makeTreeNode(n, s.ExpandNodes(), s.app.Styles)
|
||||
for _, c := range n.Children {
|
||||
s.hydrate(node, c)
|
||||
}
|
||||
parent.AddChild(node)
|
||||
}
|
||||
|
||||
// SetEnvFn sets the custom environment function.
|
||||
func (s *Sanitizer) SetEnvFn(EnvFunc) {}
|
||||
|
||||
// Refresh updates the view
|
||||
func (s *Sanitizer) Refresh() {
|
||||
}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (s *Sanitizer) BufferChanged(t string) {}
|
||||
|
||||
// BufferActive indicates the buff activity changed.
|
||||
func (s *Sanitizer) BufferActive(state bool, k model.BufferKind) {
|
||||
s.app.BufferActive(state, k)
|
||||
}
|
||||
|
||||
func (s *Sanitizer) defaultContext() context.Context {
|
||||
ctx := context.WithValue(context.Background(), internal.KeyFactory, s.app.factory)
|
||||
ctx = context.WithValue(ctx, internal.KeyFields, "")
|
||||
if s.CmdBuff().Empty() {
|
||||
ctx = context.WithValue(ctx, internal.KeyLabels, "")
|
||||
} else {
|
||||
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(s.CmdBuff().String()))
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Start initializes resource watch loop.
|
||||
func (s *Sanitizer) Start() {
|
||||
s.Stop()
|
||||
|
||||
s.CmdBuff().AddListener(s.app.Cmd())
|
||||
s.CmdBuff().AddListener(s)
|
||||
|
||||
ctx := s.defaultContext()
|
||||
ctx, s.cancelFn = context.WithCancel(ctx)
|
||||
if s.contextFn != nil {
|
||||
ctx = s.contextFn(ctx)
|
||||
}
|
||||
s.model.Refresh(ctx)
|
||||
s.UpdateTitle()
|
||||
}
|
||||
|
||||
// Stop terminates watch loop.
|
||||
func (s *Sanitizer) Stop() {
|
||||
if s.cancelFn == nil {
|
||||
return
|
||||
}
|
||||
s.cancelFn()
|
||||
s.cancelFn = nil
|
||||
|
||||
s.CmdBuff().RemoveListener(s.app.Cmd())
|
||||
s.CmdBuff().RemoveListener(s)
|
||||
}
|
||||
|
||||
// SetBindKeysFn sets up extra key bindings.
|
||||
func (s *Sanitizer) SetBindKeysFn(BindKeysFunc) {}
|
||||
|
||||
// SetContextFn sets custom context.
|
||||
func (s *Sanitizer) SetContextFn(f ContextFunc) {
|
||||
s.contextFn = f
|
||||
}
|
||||
|
||||
// Name returns the component name.
|
||||
func (s *Sanitizer) Name() string { return "report" }
|
||||
|
||||
// GetTable returns the underlying table.
|
||||
func (s *Sanitizer) GetTable() *Table { return nil }
|
||||
|
||||
// GVR returns a resource descriptor.
|
||||
func (s *Sanitizer) GVR() client.GVR { return s.gvr }
|
||||
|
||||
// App returns the current app handle.
|
||||
func (s *Sanitizer) App() *App {
|
||||
return s.app
|
||||
}
|
||||
|
||||
// UpdateTitle updates the view title.
|
||||
func (s *Sanitizer) UpdateTitle() {
|
||||
t := s.styleTitle()
|
||||
s.app.QueueUpdateDraw(func() {
|
||||
s.SetTitle(t)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Sanitizer) styleTitle() string {
|
||||
base := strings.Title(s.gvr.R())
|
||||
ns := s.model.GetNamespace()
|
||||
if client.IsAllNamespaces(ns) {
|
||||
ns = client.NamespaceAll
|
||||
}
|
||||
|
||||
buff := s.CmdBuff().String()
|
||||
var title string
|
||||
if ns == client.ClusterScope {
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, s.Count), s.app.Styles.Frame())
|
||||
} else {
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, s.Count), s.app.Styles.Frame())
|
||||
}
|
||||
if buff == "" {
|
||||
return title
|
||||
}
|
||||
|
||||
if ui.IsLabelSelector(buff) {
|
||||
buff = ui.TrimLabelSelector(buff)
|
||||
}
|
||||
|
||||
return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), s.app.Styles.Frame())
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/tview"
|
||||
|
|
@ -73,7 +75,9 @@ func (s *ScaleExtender) makeScaleForm(sel string) *tview.Form {
|
|||
s.App().Flash().Err(err)
|
||||
return
|
||||
}
|
||||
if err := s.scale(sel, count); err != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||
defer cancel()
|
||||
if err := s.scale(ctx, sel, count); err != nil {
|
||||
log.Error().Err(err).Msgf("DP %s scaling failed", sel)
|
||||
s.App().Flash().Err(err)
|
||||
} else {
|
||||
|
|
@ -104,7 +108,7 @@ func (s *ScaleExtender) makeStyledForm() *tview.Form {
|
|||
return f
|
||||
}
|
||||
|
||||
func (s *ScaleExtender) scale(path string, replicas int) error {
|
||||
func (s *ScaleExtender) scale(ctx context.Context, path string, replicas int) error {
|
||||
res, err := dao.AccessorFor(s.App().factory, s.GVR())
|
||||
if err != nil {
|
||||
return nil
|
||||
|
|
@ -114,5 +118,5 @@ func (s *ScaleExtender) scale(path string, replicas int) error {
|
|||
return fmt.Errorf("expecting a scalable resource for %q", s.GVR())
|
||||
}
|
||||
|
||||
return scaler.Scale(path, int32(replicas))
|
||||
return scaler.Scale(ctx, path, int32(replicas))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -470,11 +470,10 @@ func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
if spec == nil {
|
||||
return nil
|
||||
}
|
||||
log.Debug().Msgf("SELECTED REF %#v", spec)
|
||||
if len(strings.Split(spec.Path(), "/")) == 1 {
|
||||
return nil
|
||||
}
|
||||
if err := x.app.viewResource(client.NewGVR(spec.GVR()).R(), spec.Path(), false); err != nil {
|
||||
if err := x.app.gotoResource(client.NewGVR(spec.GVR()).R(), spec.Path(), false); err != nil {
|
||||
x.app.Flash().Err(err)
|
||||
}
|
||||
|
||||
|
|
@ -542,7 +541,6 @@ func (x *Xray) update(node *xray.TreeNode) {
|
|||
}
|
||||
|
||||
if spec.AsPath() == x.GetSelectedItem() {
|
||||
log.Debug().Msgf("SEL %q--%q", spec.Path(), x.GetSelectedItem())
|
||||
node.SetExpanded(true).SetSelectable(true)
|
||||
x.SetCurrentNode(node)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ func (c *Container) Render(ctx context.Context, ns string, o interface{}) error
|
|||
}
|
||||
pns, _ := client.Namespaced(parent.ID)
|
||||
c.envRefs(f, root, pns, co.Container)
|
||||
// if !root.IsLeaf() {
|
||||
parent.Add(root)
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
package xray
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/popeye/pkg/config"
|
||||
)
|
||||
|
||||
// Section represents an xray renderer.
|
||||
type Section struct{}
|
||||
|
||||
// Render renders an xray node.
|
||||
func (s *Section) Render(ctx context.Context, ns string, o interface{}) error {
|
||||
section, ok := o.(render.Section)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expected Section, but got %T", o)
|
||||
}
|
||||
root := NewTreeNode(section.Title, section.Title)
|
||||
parent, ok := ctx.Value(KeyParent).(*TreeNode)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
||||
}
|
||||
s.outcomeRefs(root, section)
|
||||
parent.Add(root)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanse(s string) string {
|
||||
s = strings.Replace(s, "[", "(", -1)
|
||||
s = strings.Replace(s, "]", ")", -1)
|
||||
s = strings.Replace(s, "/", "::", -1)
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *Section) outcomeRefs(parent *TreeNode, section render.Section) {
|
||||
for k, issues := range section.Outcome {
|
||||
p := NewTreeNode(section.Title, cleanse(k))
|
||||
parent.Add(p)
|
||||
for _, i := range issues {
|
||||
msg := colorize(cleanse(i.Message), i.Level)
|
||||
c := NewTreeNode(fmt.Sprintf("issue_%d", i.Level), msg)
|
||||
if i.Group == "__root__" {
|
||||
p.Add(c)
|
||||
continue
|
||||
}
|
||||
if pa := p.Find(childOf(section.Title), i.Group); pa != nil {
|
||||
pa.Add(c)
|
||||
continue
|
||||
}
|
||||
pa := NewTreeNode(childOf(section.Title), i.Group)
|
||||
pa.Add(c)
|
||||
p.Add(pa)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func childOf(s string) string {
|
||||
switch s {
|
||||
case "deployment", "statefulset", "daemonset":
|
||||
return "v1/pods"
|
||||
case "pod":
|
||||
return "containers"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func colorize(s string, l config.Level) string {
|
||||
c := "green"
|
||||
switch l {
|
||||
case config.ErrorLevel:
|
||||
c = "red"
|
||||
case config.WarnLevel:
|
||||
c = "orange"
|
||||
case config.InfoLevel:
|
||||
c = "blue"
|
||||
}
|
||||
return fmt.Sprintf("[%s::]%s", c, s)
|
||||
}
|
||||
|
|
@ -469,36 +469,70 @@ func (t TreeNode) toEmojiTitle() (title string) {
|
|||
}
|
||||
|
||||
func toEmoji(gvr string) string {
|
||||
if ic := toEmojiXRay(gvr); ic != "" {
|
||||
return ic
|
||||
}
|
||||
switch gvr {
|
||||
case "containers":
|
||||
return "🐳"
|
||||
case "v1/namespaces", "namespaces":
|
||||
return "🗂"
|
||||
case "v1/pods", "pods":
|
||||
return "🚛"
|
||||
case "v1/services", "services":
|
||||
return "💁♀️"
|
||||
case "v1/serviceaccounts", "serviceaccounts":
|
||||
return "💳"
|
||||
case "v1/persistentvolumes", "persistentvolumes":
|
||||
return "📚"
|
||||
case "v1/persistentvolumeclaims", "persistentvolumeclaims":
|
||||
return "🎟"
|
||||
case "v1/secrets", "secrets":
|
||||
return "🔒"
|
||||
case "v1/configmaps", "configmaps":
|
||||
return "🗺"
|
||||
case "apps/v1/deployments", "deployments":
|
||||
return "🪂"
|
||||
case "apps/v1/statefulsets", "statefulsets":
|
||||
return "🎎"
|
||||
case "apps/v1/daemonsets", "daemonsets":
|
||||
return "😈"
|
||||
case "replicasets", "replicaset":
|
||||
return "👯♂️"
|
||||
case "nodes", "node":
|
||||
return "🖥 "
|
||||
case "horizontalpodautoscalers", "horizontalpodautoscaler":
|
||||
return "♎️"
|
||||
case "clusterrolebindings", "clusterrolebinding", "clusterroles", "clusterrole":
|
||||
return "👩"
|
||||
case "rolebindings", "rolebinding", "roles", "role":
|
||||
return "👨🏻"
|
||||
case "networkpolicies", "networkpolicy":
|
||||
return "📕"
|
||||
case "poddisruptionbudgets", "poddisruptionbudget":
|
||||
return "🏷 "
|
||||
case "issue_0":
|
||||
return "👍"
|
||||
case "issue_1":
|
||||
return "🔊"
|
||||
case "issue_2":
|
||||
return "☣️ "
|
||||
case "issue_3":
|
||||
return "🧨"
|
||||
case "report":
|
||||
return "🧼"
|
||||
default:
|
||||
return "📎"
|
||||
}
|
||||
}
|
||||
|
||||
func toEmojiXRay(gvr string) string {
|
||||
switch gvr {
|
||||
case "containers", "container":
|
||||
return "🐳"
|
||||
case "v1/namespaces", "namespaces", "namespace":
|
||||
return "🗂 "
|
||||
case "v1/pods", "pods", "pod":
|
||||
return "🚛"
|
||||
case "v1/services", "services", "service":
|
||||
return "💁♀️"
|
||||
case "v1/serviceaccounts", "serviceaccounts", "serviceaccount":
|
||||
return "💳"
|
||||
case "v1/persistentvolumes", "persistentvolumes", "persistentvolume":
|
||||
return "📚"
|
||||
case "v1/persistentvolumeclaims", "persistentvolumeclaims", "persistentvolumeclaim":
|
||||
return "🎟 "
|
||||
case "v1/secrets", "secrets", "secret":
|
||||
return "🔒"
|
||||
case "v1/configmaps", "configmaps", "configmap":
|
||||
return "🗺 "
|
||||
case "apps/v1/deployments", "deployments", "deployment":
|
||||
return "🪂"
|
||||
case "apps/v1/statefulsets", "statefulsets", "statefulset":
|
||||
return "🎎"
|
||||
case "apps/v1/daemonsets", "daemonsets", "daemonset":
|
||||
return "😈"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// EmojiInfo returns emoji help.
|
||||
func EmojiInfo() map[string]string {
|
||||
GVRs := []string{
|
||||
|
|
|
|||
Loading…
Reference in New Issue