checkpoint
parent
2adafc0133
commit
bf2847bea7
|
|
@ -79,7 +79,7 @@ linters-settings:
|
||||||
# exclude: /path/to/file.txt
|
# exclude: /path/to/file.txt
|
||||||
|
|
||||||
funlen:
|
funlen:
|
||||||
lines: 65
|
lines: 75
|
||||||
statements: 40
|
statements: 40
|
||||||
|
|
||||||
govet:
|
govet:
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ builds:
|
||||||
- arm
|
- arm
|
||||||
goarm:
|
goarm:
|
||||||
- 7
|
- 7
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
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}}
|
- -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:
|
archives:
|
||||||
|
|
|
||||||
64
go.mod
64
go.mod
|
|
@ -2,66 +2,40 @@ module github.com/derailed/k9s
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
replace (
|
replace github.com/derailed/popeye => /Users/fernand/go_wk/derailed/src/github.com/derailed/popeye
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
|
||||||
github.com/atotto/clipboard v0.1.2
|
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/derailed/tview v0.3.9
|
||||||
github.com/drone/envsubst v1.0.2 // indirect
|
github.com/drone/envsubst v1.0.2 // indirect
|
||||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
|
github.com/fatih/color v1.9.0
|
||||||
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
|
|
||||||
github.com/fatih/color v1.6.0
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/gdamore/tcell v1.3.0
|
github.com/gdamore/tcell v1.3.0
|
||||||
github.com/ghodss/yaml v1.0.0
|
github.com/ghodss/yaml v1.0.0
|
||||||
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.8
|
github.com/mattn/go-runewidth v0.0.9
|
||||||
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec
|
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec
|
||||||
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c
|
github.com/openfaas/faas-cli v0.0.0-20200124160744-30b7cec9634c
|
||||||
github.com/openfaas/faas-provider v0.15.0
|
github.com/openfaas/faas-provider v0.15.0
|
||||||
github.com/petergtz/pegomock v2.6.0+incompatible
|
github.com/petergtz/pegomock v2.7.0+incompatible
|
||||||
github.com/rakyll/hey v0.1.2
|
github.com/rakyll/hey v0.1.3
|
||||||
github.com/rivo/tview v0.0.0-20191018115645-bacbf5155bc1
|
|
||||||
github.com/rs/zerolog v1.18.0
|
github.com/rs/zerolog v1.18.0
|
||||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||||
github.com/sahilm/fuzzy v0.1.0
|
github.com/sahilm/fuzzy v0.1.0
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/spf13/cobra v0.0.6
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.5.1
|
||||||
golang.org/x/text v0.3.2
|
golang.org/x/text v0.3.2
|
||||||
gopkg.in/yaml.v2 v2.2.4
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
helm.sh/helm/v3 v3.0.2
|
helm.sh/helm/v3 v3.1.2
|
||||||
k8s.io/api v0.0.0
|
k8s.io/api v0.18.0
|
||||||
k8s.io/apimachinery v0.0.0
|
k8s.io/apimachinery v0.18.0
|
||||||
k8s.io/cli-runtime v0.0.0
|
k8s.io/cli-runtime v0.18.0
|
||||||
k8s.io/client-go v0.0.0
|
k8s.io/client-go v0.18.0
|
||||||
k8s.io/klog v1.0.0
|
k8s.io/klog v1.0.0
|
||||||
k8s.io/kubectl v0.0.0
|
k8s.io/kubectl v0.18.0
|
||||||
k8s.io/kubernetes v1.16.3
|
k8s.io/metrics v0.18.0
|
||||||
k8s.io/metrics v0.0.0
|
rsc.io/letsencrypt v0.0.3 // indirect
|
||||||
sigs.k8s.io/yaml v1.1.0
|
sigs.k8s.io/yaml v1.2.0
|
||||||
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787
|
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -27,6 +28,7 @@ const (
|
||||||
cacheMXKey = "metrics"
|
cacheMXKey = "metrics"
|
||||||
cacheMXAPIKey = "metricsAPI"
|
cacheMXAPIKey = "metricsAPI"
|
||||||
checkConnTimeout = 10 * time.Second
|
checkConnTimeout = 10 * time.Second
|
||||||
|
CallTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var supportedMetricsAPIVersions = []string{"v1beta1"}
|
var supportedMetricsAPIVersions = []string{"v1beta1"}
|
||||||
|
|
@ -86,6 +88,33 @@ func makeCacheKey(ns, gvr string, vv []string) string {
|
||||||
return ns + ":" + gvr + "::" + strings.Join(vv, ",")
|
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() {
|
func (a *APIClient) clearCache() {
|
||||||
for _, k := range a.cache.Keys() {
|
for _, k := range a.cache.Keys() {
|
||||||
a.cache.Remove(k)
|
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)
|
dial, sar := a.DialOrDie().AuthorizationV1().SelfSubjectAccessReviews(), makeSAR(ns, gvr)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), CallTimeout)
|
||||||
|
defer cancel()
|
||||||
for _, v := range verbs {
|
for _, v := range verbs {
|
||||||
sar.Spec.ResourceAttributes.Verb = v
|
sar.Spec.ResourceAttributes.Verb = v
|
||||||
resp, err := dial.Create(sar)
|
resp, err := dial.Create(ctx, sar, metav1.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msgf(" Dial Failed!")
|
log.Warn().Err(err).Msgf(" Dial Failed!")
|
||||||
a.cache.Add(key, false, cacheExpiry)
|
a.cache.Add(key, false, cacheExpiry)
|
||||||
|
|
@ -135,7 +167,9 @@ func (a *APIClient) ServerVersion() (*version.Info, error) {
|
||||||
|
|
||||||
// ValidNamespaces returns all available namespaces.
|
// ValidNamespaces returns all available namespaces.
|
||||||
func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +231,9 @@ func (a *APIClient) HasMetrics() bool {
|
||||||
a.cache.Add(cacheMXKey, flag, cacheExpiry)
|
a.cache.Add(cacheMXKey, flag, cacheExpiry)
|
||||||
return flag
|
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
|
flag = true
|
||||||
}
|
}
|
||||||
a.cache.Add(cacheMXKey, flag, cacheExpiry)
|
a.cache.Add(cacheMXKey, flag, cacheExpiry)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -129,7 +130,7 @@ func (m *MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeM
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchNodesMetrics return all metrics for nodes.
|
// 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"
|
const msg = "user is not authorized to list node metrics"
|
||||||
|
|
||||||
mx := new(mv1beta1.NodeMetricsList)
|
mx := new(mv1beta1.NodeMetricsList)
|
||||||
|
|
@ -150,7 +151,7 @@ func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mx, err
|
return mx, err
|
||||||
}
|
}
|
||||||
mxList, err := client.MetricsV1beta1().NodeMetricses().List(metav1.ListOptions{})
|
mxList, err := client.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mx, err
|
return mx, err
|
||||||
}
|
}
|
||||||
|
|
@ -160,7 +161,7 @@ func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchPodsMetrics return all metrics for pods in a given namespace.
|
// 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)
|
mx := new(mv1beta1.PodMetricsList)
|
||||||
const msg = "user is not authorized to list pods metrics"
|
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 {
|
if err != nil {
|
||||||
return mx, err
|
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 {
|
if err != nil {
|
||||||
return mx, err
|
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.
|
// 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
|
var mx *mv1beta1.PodMetrics
|
||||||
const msg = "user is not authorized to list pod metrics"
|
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 {
|
if err != nil {
|
||||||
return mx, err
|
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 {
|
if err != nil {
|
||||||
return mx, err
|
return mx, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,15 @@ type Connection interface {
|
||||||
|
|
||||||
// CheckConnectivity checks if api server connection is happy or not.
|
// CheckConnectivity checks if api server connection is happy or not.
|
||||||
CheckConnectivity() bool
|
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.
|
// CurrentMetrics tracks current cpu/mem.
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ package color
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ColorFmt colorize a string with ansi colors.
|
// ColorFmt colorize a string with ansi colors.
|
||||||
|
|
@ -29,7 +27,6 @@ const (
|
||||||
|
|
||||||
// Colorize returns an ASCII colored string based on given color.
|
// Colorize returns an ASCII colored string based on given color.
|
||||||
func Colorize(s string, c Paint) string {
|
func Colorize(s string, c Paint) string {
|
||||||
log.Debug().Msgf("Painting %#v", c)
|
|
||||||
if c == 0 {
|
if c == 0 {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,8 @@ func (a *Aliases) loadDefaultAliases() {
|
||||||
a.declare("help", "h", "?")
|
a.declare("help", "h", "?")
|
||||||
a.declare("quit", "q", "Q")
|
a.declare("quit", "q", "Q")
|
||||||
a.declare("aliases", "alias", "a")
|
a.declare("aliases", "alias", "a")
|
||||||
|
a.declare("popeye", "pop")
|
||||||
|
a.declare("sanitize", "san", "sanitize")
|
||||||
a.declare("contexts", "context", "ctx")
|
a.declare("contexts", "context", "ctx")
|
||||||
a.declare("users", "user", "usr")
|
a.declare("users", "user", "usr")
|
||||||
a.declare("groups", "group", "grp")
|
a.declare("groups", "group", "grp")
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,51 @@ func (mock *MockConnection) HasMetrics() bool {
|
||||||
return ret0
|
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 {
|
func (mock *MockConnection) IsNamespaced(_param0 string) bool {
|
||||||
if mock == nil {
|
if mock == nil {
|
||||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||||
|
|
|
||||||
|
|
@ -1,123 +1,124 @@
|
||||||
package dao
|
package dao
|
||||||
|
|
||||||
import (
|
// BOZO!! v1.18.0
|
||||||
"context"
|
// import (
|
||||||
"fmt"
|
// "context"
|
||||||
"os"
|
// "fmt"
|
||||||
|
// "os"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
// "github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/render"
|
// "github.com/derailed/k9s/internal/render"
|
||||||
"github.com/rs/zerolog/log"
|
// "github.com/rs/zerolog/log"
|
||||||
"helm.sh/helm/v3/pkg/action"
|
// "helm.sh/helm/v3/pkg/action"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
// "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
// )
|
||||||
|
|
||||||
var (
|
// var (
|
||||||
_ Accessor = (*Chart)(nil)
|
// _ Accessor = (*Chart)(nil)
|
||||||
_ Nuker = (*Chart)(nil)
|
// _ Nuker = (*Chart)(nil)
|
||||||
_ Describer = (*Chart)(nil)
|
// _ Describer = (*Chart)(nil)
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Chart represents a helm chart.
|
// // Chart represents a helm chart.
|
||||||
type Chart struct {
|
// type Chart struct {
|
||||||
NonResource
|
// NonResource
|
||||||
}
|
// }
|
||||||
|
|
||||||
// List returns a collection of resources.
|
// // List returns a collection of resources.
|
||||||
func (c *Chart) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
// func (c *Chart) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||||
cfg, err := c.EnsureHelmConfig(ns)
|
// cfg, err := c.EnsureHelmConfig(ns)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
rr, err := action.NewList(cfg).Run()
|
// rr, err := action.NewList(cfg).Run()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
oo := make([]runtime.Object, 0, len(rr))
|
// oo := make([]runtime.Object, 0, len(rr))
|
||||||
for _, r := range rr {
|
// for _, r := range rr {
|
||||||
oo = append(oo, render.ChartRes{Release: r})
|
// oo = append(oo, render.ChartRes{Release: r})
|
||||||
}
|
// }
|
||||||
|
|
||||||
return oo, nil
|
// return oo, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get returns a resource.
|
// // Get returns a resource.
|
||||||
func (c *Chart) Get(_ context.Context, path string) (runtime.Object, error) {
|
// func (c *Chart) Get(_ context.Context, path string) (runtime.Object, error) {
|
||||||
ns, n := client.Namespaced(path)
|
// ns, n := client.Namespaced(path)
|
||||||
cfg, err := c.EnsureHelmConfig(ns)
|
// cfg, err := c.EnsureHelmConfig(ns)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
resp, err := action.NewGet(cfg).Run(n)
|
// resp, err := action.NewGet(cfg).Run(n)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return render.ChartRes{Release: resp}, nil
|
// return render.ChartRes{Release: resp}, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Describe returns the chart notes.
|
// // Describe returns the chart notes.
|
||||||
func (c *Chart) Describe(path string) (string, error) {
|
// func (c *Chart) Describe(path string) (string, error) {
|
||||||
ns, n := client.Namespaced(path)
|
// ns, n := client.Namespaced(path)
|
||||||
cfg, err := c.EnsureHelmConfig(ns)
|
// cfg, err := c.EnsureHelmConfig(ns)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
resp, err := action.NewGet(cfg).Run(n)
|
// resp, err := action.NewGet(cfg).Run(n)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return resp.Info.Notes, nil
|
// return resp.Info.Notes, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ToYAML returns the chart manifest.
|
// // ToYAML returns the chart manifest.
|
||||||
func (c *Chart) ToYAML(path string) (string, error) {
|
// func (c *Chart) ToYAML(path string) (string, error) {
|
||||||
ns, n := client.Namespaced(path)
|
// ns, n := client.Namespaced(path)
|
||||||
cfg, err := c.EnsureHelmConfig(ns)
|
// cfg, err := c.EnsureHelmConfig(ns)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
resp, err := action.NewGet(cfg).Run(n)
|
// resp, err := action.NewGet(cfg).Run(n)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return resp.Manifest, nil
|
// return resp.Manifest, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Delete uninstall a Chart.
|
// // Delete uninstall a Chart.
|
||||||
func (c *Chart) Delete(path string, cascade, force bool) error {
|
// func (c *Chart) Delete(path string, cascade, force bool) error {
|
||||||
ns, n := client.Namespaced(path)
|
// ns, n := client.Namespaced(path)
|
||||||
cfg, err := c.EnsureHelmConfig(ns)
|
// cfg, err := c.EnsureHelmConfig(ns)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
res, err := action.NewUninstall(cfg).Run(n)
|
// res, err := action.NewUninstall(cfg).Run(n)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
if res != nil && res.Info != "" {
|
// if res != nil && res.Info != "" {
|
||||||
return fmt.Errorf("%s", res.Info)
|
// return fmt.Errorf("%s", res.Info)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// EnsureHelmConfig return a new configuration.
|
// // EnsureHelmConfig return a new configuration.
|
||||||
func (c *Chart) EnsureHelmConfig(ns string) (*action.Configuration, error) {
|
// func (c *Chart) EnsureHelmConfig(ns string) (*action.Configuration, error) {
|
||||||
cfg := new(action.Configuration)
|
// cfg := new(action.Configuration)
|
||||||
flags := c.Client().Config().Flags()
|
// flags := c.Client().Config().Flags()
|
||||||
if err := cfg.Init(flags, ns, os.Getenv("HELM_DRIVER"), helmLogger); err != nil {
|
// if err := cfg.Init(flags, ns, os.Getenv("HELM_DRIVER"), helmLogger); err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
return cfg, nil
|
// return cfg, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func helmLogger(s string, args ...interface{}) {
|
// func helmLogger(s string, args ...interface{}) {
|
||||||
log.Debug().Msgf("%s %v", s, args)
|
// log.Debug().Msgf("%s %v", s, args)
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
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)
|
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) ServerVersion() (*version.Info, error) { return nil, nil }
|
||||||
func (c *conn) CurrentNamespaceName() (string, error) { return "", 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) 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{}
|
type podFactory struct{}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package dao
|
package dao
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
|
@ -33,7 +34,9 @@ func (c *CronJob) Run(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BOZO!! Factory resource??
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +54,7 @@ func (c *CronJob) Run(path string) error {
|
||||||
},
|
},
|
||||||
Spec: cj.Spec.JobTemplate.Spec,
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"k8s.io/kubectl/pkg/describe"
|
"k8s.io/kubectl/pkg/describe"
|
||||||
"k8s.io/kubectl/pkg/describe/versioned"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Describe describes a resource.
|
// 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)
|
log.Error().Err(err).Msgf("Unable to find mapper for %s %s", gvr, n)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
d, err := versioned.Describer(c.Config().Flags(), mapping)
|
d, err := describe.Describer(c.Config().Flags(), mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping)
|
log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping)
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func (d *Deployment) IsHappy(dp appsv1.Deployment) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale a Deployment.
|
// 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)
|
ns, n := client.Namespaced(path)
|
||||||
auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", []string{client.GetVerb, client.UpdateVerb})
|
auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", []string{client.GetVerb, client.UpdateVerb})
|
||||||
if err != nil {
|
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")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
scale.Spec.Replicas = replicas
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart a Deployment rollout.
|
// 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)
|
dp, err := d.Load(d.Factory, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -75,7 +75,13 @@ func (d *Deployment) Restart(path string) error {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ func (d *DaemonSet) IsHappy(ds appsv1.DaemonSet) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart a DaemonSet rollout.
|
// 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)
|
ds, err := d.GetInstance(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -56,7 +56,13 @@ func (d *DaemonSet) Restart(path string) error {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error)
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if client.IsClusterScoped(ns) {
|
if client.IsClusterScoped(ns) {
|
||||||
ll, err = g.dynClient().List(metav1.ListOptions{LabelSelector: labelSel})
|
ll, err = g.dynClient().List(ctx, metav1.ListOptions{LabelSelector: labelSel})
|
||||||
} else {
|
} 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -57,15 +57,15 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error)
|
||||||
|
|
||||||
// Get returns a given resource.
|
// Get returns a given resource.
|
||||||
func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error) {
|
func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||||
|
log.Debug().Msgf("GENERIC-GET %q", path)
|
||||||
var opts metav1.GetOptions
|
var opts metav1.GetOptions
|
||||||
|
|
||||||
ns, n := client.Namespaced(path)
|
ns, n := client.Namespaced(path)
|
||||||
dial := g.dynClient()
|
dial := g.dynClient()
|
||||||
if client.IsClusterScoped(ns) {
|
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.
|
// Describe describes a resource.
|
||||||
|
|
@ -111,11 +111,14 @@ func (g *Generic) Delete(path string, cascade, force bool) error {
|
||||||
PropagationPolicy: &p,
|
PropagationPolicy: &p,
|
||||||
GracePeriodSeconds: grace,
|
GracePeriodSeconds: grace,
|
||||||
}
|
}
|
||||||
|
// BOZO!! Move to caller!
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||||
|
defer cancel()
|
||||||
if client.IsClusterScoped(ns) {
|
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 {
|
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")
|
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 {
|
if patchErr != nil {
|
||||||
return patchErr
|
return patchErr
|
||||||
}
|
}
|
||||||
|
|
@ -97,8 +97,30 @@ func (n *Node) Drain(path string, opts DrainOptions, w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a node resource.
|
// Get returns a node resource.
|
||||||
func (n *Node) Get(_ context.Context, path string) (runtime.Object, error) {
|
func (n *Node) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||||
return FetchNode(n.Factory, path)
|
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.
|
// 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
|
err error
|
||||||
)
|
)
|
||||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
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")
|
log.Warn().Err(err).Msgf("No node metrics")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nn, err := FetchNodes(n.Factory, labels)
|
nn, err := FetchNodes(ctx, n.Factory, labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +163,7 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
// FetchNode retrieves a node.
|
// 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"})
|
auth, err := f.Client().CanI("", "v1/nodes", []string{"get"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 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.
|
// 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})
|
auth, err := f.Client().CanI("", "v1/nodes", []string{client.ListVerb})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 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,
|
LabelSelector: labelsSel,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||||
|
|
||||||
var pmx *mv1beta1.PodMetrics
|
var pmx *mv1beta1.PodMetrics
|
||||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
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")
|
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
|
var pmx *mv1beta1.PodMetricsList
|
||||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
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")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Context(ctx)
|
|
||||||
|
|
||||||
// This call will block if nothing is in the stream!!
|
// This call will block if nothing is in the stream!!
|
||||||
stream, err := req.Stream()
|
stream, err := req.Stream(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c <- opts.DecorateLog([]byte(err.Error() + "\n"))
|
c <- opts.DecorateLog([]byte(err.Error() + "\n"))
|
||||||
log.Error().Err(err).Msgf("Unable to obtain log stream failed for `%s", opts.Path)
|
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 != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Warn().Err(err).Msgf("Stream closed for %s", opts.Info())
|
log.Warn().Err(err).Msgf("Stream closed for %s", opts.Info())
|
||||||
c <- NewLogItemFromString("<STREAM> closed")
|
c <- opts.DecorateLog([]byte("log stream closed\n"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Warn().Err(err).Msgf("Stream READ error %s", opts.Info())
|
log.Warn().Err(err).Msgf("Stream READ error %s", opts.Info())
|
||||||
c <- NewLogItemFromString("<STREAM> failed")
|
c <- opts.DecorateLog([]byte("log stream failed\n"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c <- opts.DecorateLog(bytes)
|
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"
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"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) {
|
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())
|
o, err := r.Factory.Get(crGVR, path, true, labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,11 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
|
||||||
client.NewGVR("apps/v1/statefulsets"): &StatefulSet{},
|
client.NewGVR("apps/v1/statefulsets"): &StatefulSet{},
|
||||||
client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{},
|
client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{},
|
||||||
client.NewGVR("batch/v1/jobs"): &Job{},
|
client.NewGVR("batch/v1/jobs"): &Job{},
|
||||||
client.NewGVR("charts"): &Chart{},
|
// BOZO!! v1.18.0
|
||||||
client.NewGVR("openfaas"): &OpenFaas{},
|
// client.NewGVR("charts"): &Chart{},
|
||||||
|
client.NewGVR("openfaas"): &OpenFaas{},
|
||||||
|
client.NewGVR("popeye"): &Popeye{},
|
||||||
|
client.NewGVR("report"): &Sanitizer{},
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := m[gvr]
|
r, ok := m[gvr]
|
||||||
|
|
@ -163,6 +166,20 @@ func loadK9s(m ResourceMetas) {
|
||||||
Verbs: []string{},
|
Verbs: []string{},
|
||||||
Categories: []string{"k9s"},
|
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{
|
m[client.NewGVR("contexts")] = metav1.APIResource{
|
||||||
Name: "contexts",
|
Name: "contexts",
|
||||||
Kind: "Contexts",
|
Kind: "Contexts",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||||
"k8s.io/kubectl/pkg/polymorphichelpers"
|
"k8s.io/kubectl/pkg/polymorphichelpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -94,7 +95,7 @@ func (r *ReplicaSet) Rollback(fqn string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = rb.Rollback(dp, map[string]string{}, version, false)
|
_, err = rb.Rollback(dp, map[string]string{}, version, cmdutil.DryRunNone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// 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)
|
ns, n := client.Namespaced(path)
|
||||||
auth, err := s.Client().CanI(ns, "apps/v1/statefulsets:scale", []string{client.GetVerb, client.UpdateVerb})
|
auth, err := s.Client().CanI(ns, "apps/v1/statefulsets:scale", []string{client.GetVerb, client.UpdateVerb})
|
||||||
if err != nil {
|
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")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
scale.Spec.Replicas = replicas
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart a StatefulSet rollout.
|
// 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)
|
sts, err := s.getStatefulSet(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -76,7 +76,13 @@ func (s *StatefulSet) Restart(path string) error {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ type Table struct {
|
||||||
|
|
||||||
// Get returns a given resource.
|
// Get returns a given resource.
|
||||||
func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
|
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)
|
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
|
||||||
_, codec := t.codec()
|
_, codec := t.codec()
|
||||||
|
|
||||||
|
|
@ -30,18 +28,17 @@ func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
o, err := c.Get().
|
ns, n := client.Namespaced(path)
|
||||||
|
req := c.Get().
|
||||||
SetHeader("Accept", a).
|
SetHeader("Accept", a).
|
||||||
Namespace(ns).
|
|
||||||
Name(n).
|
Name(n).
|
||||||
Resource(t.gvr.R()).
|
Resource(t.gvr.R()).
|
||||||
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
VersionedParams(&metav1beta1.TableOptions{}, codec)
|
||||||
Do().Get()
|
if ns != client.ClusterScope {
|
||||||
if err != nil {
|
req = req.Namespace(ns)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return o, nil
|
return req.Do(ctx).Get()
|
||||||
}
|
}
|
||||||
|
|
||||||
// List all Resources in a given namespace.
|
// 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).
|
Namespace(ns).
|
||||||
Resource(t.gvr.R()).
|
Resource(t.gvr.R()).
|
||||||
VersionedParams(&metav1.ListOptions{LabelSelector: labelSel}, codec).
|
VersionedParams(&metav1.ListOptions{LabelSelector: labelSel}, codec).
|
||||||
Do().Get()
|
Do(ctx).Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ type Describer interface {
|
||||||
// Scalable represents resources that can scale.
|
// Scalable represents resources that can scale.
|
||||||
type Scalable interface {
|
type Scalable interface {
|
||||||
// Scale scales a resource up or down.
|
// 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.
|
// Controller represents a pod controller.
|
||||||
|
|
@ -132,7 +132,7 @@ type Switchable interface {
|
||||||
// Restartable represents a restartable resource.
|
// Restartable represents a restartable resource.
|
||||||
type Restartable interface {
|
type Restartable interface {
|
||||||
// Restart performs a rollout restart.
|
// Restart performs a rollout restart.
|
||||||
Restart(path string) error
|
Restart(ctx context.Context, path string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runnable represents a runnable resource.
|
// Runnable represents a runnable resource.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
@ -20,8 +22,8 @@ type (
|
||||||
// MetricsService calls the metrics server for metrics info.
|
// MetricsService calls the metrics server for metrics info.
|
||||||
MetricsService interface {
|
MetricsService interface {
|
||||||
HasMetrics() bool
|
HasMetrics() bool
|
||||||
FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error)
|
FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMetricsList, error)
|
||||||
FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, error)
|
FetchPodsMetrics(ctx context.Context, ns string) (*mv1beta1.PodMetricsList, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cluster represents a kubernetes resource.
|
// Cluster represents a kubernetes resource.
|
||||||
|
|
@ -77,13 +79,13 @@ func (c *Cluster) UserName() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metrics gathers node level metrics and compute utilization percentages.
|
// Metrics gathers node level metrics and compute utilization percentages.
|
||||||
func (c *Cluster) Metrics(mx *client.ClusterMetrics) error {
|
func (c *Cluster) Metrics(ctx context.Context, mx *client.ClusterMetrics) error {
|
||||||
nn, err := dao.FetchNodes(c.factory, "")
|
nn, err := dao.FetchNodes(ctx, c.factory, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nmx, err := c.mx.FetchNodesMetrics()
|
nmx, err := c.mx.FetchNodesMetrics(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
)
|
)
|
||||||
|
|
@ -90,8 +92,10 @@ func (c *ClusterInfo) Refresh() {
|
||||||
data.K9sVer = c.version
|
data.K9sVer = c.version
|
||||||
data.K8sVer = c.cluster.Version()
|
data.K8sVer = c.cluster.Version()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||||
|
defer cancel()
|
||||||
var mx client.ClusterMetrics
|
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
|
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 {
|
if l.cancelFn != nil {
|
||||||
l.cancelFn()
|
l.cancelFn()
|
||||||
}
|
}
|
||||||
close(c)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, er
|
||||||
hh = append(hh, c)
|
hh = append(hh, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
mm, err := h.checkMetrics()
|
mm, err := h.checkMetrics(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return hh, nil
|
return hh, nil
|
||||||
}
|
}
|
||||||
|
|
@ -62,15 +62,15 @@ func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, er
|
||||||
return hh, nil
|
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())
|
dial := client.DialMetrics(h.factory.Client())
|
||||||
|
|
||||||
nn, err := dao.FetchNodes(h.factory, "")
|
nn, err := dao.FetchNodes(ctx, h.factory, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
nmx, err := dial.FetchNodesMetrics()
|
nmx, err := dial.FetchNodesMetrics(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("Fetching metrics")
|
log.Error().Err(err).Msgf("Fetching metrics")
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,11 @@ import (
|
||||||
// BOZO!! Break up deps and merge into single registrar
|
// BOZO!! Break up deps and merge into single registrar
|
||||||
var Registry = map[string]ResourceMeta{
|
var Registry = map[string]ResourceMeta{
|
||||||
// Custom...
|
// Custom...
|
||||||
"charts": {
|
// BOZO!! v1.18.0
|
||||||
DAO: &dao.Chart{},
|
// "charts": {
|
||||||
Renderer: &render.Chart{},
|
// DAO: &dao.Chart{},
|
||||||
},
|
// Renderer: &render.Chart{},
|
||||||
|
// },
|
||||||
"pulses": {
|
"pulses": {
|
||||||
DAO: &dao.Pulse{},
|
DAO: &dao.Pulse{},
|
||||||
},
|
},
|
||||||
|
|
@ -62,6 +63,14 @@ var Registry = map[string]ResourceMeta{
|
||||||
DAO: &dao.Alias{},
|
DAO: &dao.Alias{},
|
||||||
Renderer: &render.Alias{},
|
Renderer: &render.Alias{},
|
||||||
},
|
},
|
||||||
|
"popeye": {
|
||||||
|
DAO: &dao.Popeye{},
|
||||||
|
Renderer: &render.Popeye{},
|
||||||
|
},
|
||||||
|
"report": {
|
||||||
|
DAO: &dao.Sanitizer{},
|
||||||
|
TreeRenderer: &xray.Section{},
|
||||||
|
},
|
||||||
|
|
||||||
// Core...
|
// Core...
|
||||||
"v1/endpoints": {
|
"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.
|
// this call will block until the benchmark is complete or timesout.
|
||||||
b.worker.Run()
|
b.worker.Run()
|
||||||
b.worker.Stop()
|
b.worker.Stop()
|
||||||
log.Debug().Msgf("YO!! %t %s", b.canceled, buff)
|
|
||||||
if len(buff.Bytes()) > 0 {
|
if len(buff.Bytes()) > 0 {
|
||||||
if err := b.save(cluster, buff); err != nil {
|
if err := b.save(cluster, buff); err != nil {
|
||||||
log.Error().Err(err).Msg("Saving Benchmark")
|
log.Error().Err(err).Msg("Saving Benchmark")
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,6 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("expecting row 0 to be a string but got %T", row.Cells[0])
|
return fmt.Errorf("expecting row 0 to be a string but got %T", row.Cells[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
r.ID = client.FQN(nns, n)
|
r.ID = client.FQN(nns, n)
|
||||||
r.Fields = make(Fields, 0, len(g.Header(ns)))
|
r.Fields = make(Fields, 0, len(g.Header(ns)))
|
||||||
r.Fields = append(r.Fields, nns)
|
r.Fields = append(r.Fields, nns)
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error {
|
||||||
pdb.Name,
|
pdb.Name,
|
||||||
numbToStr(pdb.Spec.MinAvailable),
|
numbToStr(pdb.Spec.MinAvailable),
|
||||||
numbToStr(pdb.Spec.MaxUnavailable),
|
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.CurrentHealthy)),
|
||||||
strconv.Itoa(int(pdb.Status.DesiredHealthy)),
|
strconv.Itoa(int(pdb.Status.DesiredHealthy)),
|
||||||
strconv.Itoa(int(pdb.Status.ExpectedPods)),
|
strconv.Itoa(int(pdb.Status.ExpectedPods)),
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/kubernetes/pkg/util/node"
|
|
||||||
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
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 {
|
func (p *Pod) Phase(po *v1.Pod) string {
|
||||||
status := string(po.Status.Phase)
|
status := string(po.Status.Phase)
|
||||||
if po.Status.Reason != "" {
|
if po.Status.Reason != "" {
|
||||||
if po.DeletionTimestamp != nil && po.Status.Reason == node.NodeUnreachablePodReason {
|
if po.DeletionTimestamp != nil && po.Status.Reason == "NodeLost" {
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
status = po.Status.Reason
|
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.
|
// Sort rows based on column index and order.
|
||||||
func (r RowEvents) Sort(ns string, sortCol int, ageCol bool, asc bool) {
|
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}
|
t := RowEventSorter{NS: ns, Events: r, Index: sortCol, Asc: asc}
|
||||||
sort.Sort(t)
|
sort.Sort(t)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,12 @@ type Command struct {
|
||||||
|
|
||||||
// NewCommand returns a new command view.
|
// NewCommand returns a new command view.
|
||||||
func NewCommand(styles *config.Styles, m *model.FishBuff) *Command {
|
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.SetWordWrap(true)
|
||||||
c.ShowCursor(true)
|
c.ShowCursor(true)
|
||||||
c.SetWrap(true)
|
c.SetWrap(true)
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ func (t *Table) doUpdate(data render.TableData) {
|
||||||
}
|
}
|
||||||
custData := data.Customize(cols, t.wide)
|
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
|
t.sortCol.name = custData.Header[0].Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -487,10 +487,6 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return nil
|
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 {
|
func (a *App) gotoResource(cmd, path string, clearStack bool) error {
|
||||||
return a.command.run(cmd, path, clearStack)
|
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) {
|
if c.specialCmd(cmd) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmds := strings.Split(cmd, " ")
|
cmds := strings.Split(cmd, " ")
|
||||||
gvr, v, err := c.viewMetaFor(cmds[0])
|
gvr, v, err := c.viewMetaFor(cmds[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ func podCtx(app *App, path, labelSel, fieldSel string) ContextFunc {
|
||||||
|
|
||||||
ns, _ := client.Namespaced(path)
|
ns, _ := client.Namespaced(path)
|
||||||
mx := client.NewMetricsServer(app.factory.Client())
|
mx := client.NewMetricsServer(app.factory.Client())
|
||||||
nmx, err := mx.FetchPodsMetrics(ns)
|
nmx, err := mx.FetchPodsMetrics(ctx, ns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug().Err(err).Msgf("No pods metrics")
|
log.Debug().Err(err).Msgf("No pods metrics")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -132,9 +133,12 @@ func (n *Node) yamlCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
sel := n.GetTable().GetSelectedItem()
|
sel := n.GetTable().GetSelectedItem()
|
||||||
gvr := n.GVR().GVR()
|
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 {
|
if err != nil {
|
||||||
n.App().Flash().Errf("Unable to get resource %q -- %s", n.GVR(), err)
|
n.App().Flash().Errf("Unable to get resource %q -- %s", n.GVR(), err)
|
||||||
return nil
|
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{
|
vv[client.NewGVR("pulses")] = MetaViewer{
|
||||||
viewerFn: NewPulse,
|
viewerFn: NewPulse,
|
||||||
}
|
}
|
||||||
|
vv[client.NewGVR("popeye")] = MetaViewer{
|
||||||
|
viewerFn: NewPopeye,
|
||||||
|
}
|
||||||
|
vv[client.NewGVR("report")] = MetaViewer{
|
||||||
|
viewerFn: NewSanitizer,
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func appsViewers(vv MetaViewers) {
|
func appsViewers(vv MetaViewers) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package view
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/derailed/k9s/internal/ui/dialog"
|
"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))
|
msg = fmt.Sprintf("Restart %d deployments?", len(paths))
|
||||||
}
|
}
|
||||||
dialog.ShowConfirm(r.App().Content.Pages, "Confirm Restart", msg, func() {
|
dialog.ShowConfirm(r.App().Content.Pages, "Confirm Restart", msg, func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
|
||||||
|
defer cancel()
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
if err := r.restartRollout(path); err != nil {
|
if err := r.restartRollout(ctx, path); err != nil {
|
||||||
r.App().Flash().Err(err)
|
r.App().Flash().Err(err)
|
||||||
} else {
|
} else {
|
||||||
r.App().Flash().Infof("Rollout restart in progress for `%s...", path)
|
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
|
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())
|
res, err := dao.AccessorFor(r.App().factory, r.GVR())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -65,5 +69,5 @@ func (r *RestartExtender) restartRollout(path string) error {
|
||||||
return errors.New("resource is not restartable")
|
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
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
|
|
@ -73,7 +75,9 @@ func (s *ScaleExtender) makeScaleForm(sel string) *tview.Form {
|
||||||
s.App().Flash().Err(err)
|
s.App().Flash().Err(err)
|
||||||
return
|
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)
|
log.Error().Err(err).Msgf("DP %s scaling failed", sel)
|
||||||
s.App().Flash().Err(err)
|
s.App().Flash().Err(err)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -104,7 +108,7 @@ func (s *ScaleExtender) makeStyledForm() *tview.Form {
|
||||||
return f
|
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())
|
res, err := dao.AccessorFor(s.App().factory, s.GVR())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 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 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 {
|
if spec == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Debug().Msgf("SELECTED REF %#v", spec)
|
|
||||||
if len(strings.Split(spec.Path(), "/")) == 1 {
|
if len(strings.Split(spec.Path(), "/")) == 1 {
|
||||||
return nil
|
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)
|
x.app.Flash().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -542,7 +541,6 @@ func (x *Xray) update(node *xray.TreeNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.AsPath() == x.GetSelectedItem() {
|
if spec.AsPath() == x.GetSelectedItem() {
|
||||||
log.Debug().Msgf("SEL %q--%q", spec.Path(), x.GetSelectedItem())
|
|
||||||
node.SetExpanded(true).SetSelectable(true)
|
node.SetExpanded(true).SetSelectable(true)
|
||||||
x.SetCurrentNode(node)
|
x.SetCurrentNode(node)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,7 @@ func (c *Container) Render(ctx context.Context, ns string, o interface{}) error
|
||||||
}
|
}
|
||||||
pns, _ := client.Namespaced(parent.ID)
|
pns, _ := client.Namespaced(parent.ID)
|
||||||
c.envRefs(f, root, pns, co.Container)
|
c.envRefs(f, root, pns, co.Container)
|
||||||
// if !root.IsLeaf() {
|
|
||||||
parent.Add(root)
|
parent.Add(root)
|
||||||
// }
|
|
||||||
|
|
||||||
return nil
|
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 {
|
func toEmoji(gvr string) string {
|
||||||
|
if ic := toEmojiXRay(gvr); ic != "" {
|
||||||
|
return ic
|
||||||
|
}
|
||||||
switch gvr {
|
switch gvr {
|
||||||
case "containers":
|
case "replicasets", "replicaset":
|
||||||
return "🐳"
|
return "👯♂️"
|
||||||
case "v1/namespaces", "namespaces":
|
case "nodes", "node":
|
||||||
return "🗂"
|
return "🖥 "
|
||||||
case "v1/pods", "pods":
|
case "horizontalpodautoscalers", "horizontalpodautoscaler":
|
||||||
return "🚛"
|
return "♎️"
|
||||||
case "v1/services", "services":
|
case "clusterrolebindings", "clusterrolebinding", "clusterroles", "clusterrole":
|
||||||
return "💁♀️"
|
return "👩"
|
||||||
case "v1/serviceaccounts", "serviceaccounts":
|
case "rolebindings", "rolebinding", "roles", "role":
|
||||||
return "💳"
|
return "👨🏻"
|
||||||
case "v1/persistentvolumes", "persistentvolumes":
|
case "networkpolicies", "networkpolicy":
|
||||||
return "📚"
|
return "📕"
|
||||||
case "v1/persistentvolumeclaims", "persistentvolumeclaims":
|
case "poddisruptionbudgets", "poddisruptionbudget":
|
||||||
return "🎟"
|
return "🏷 "
|
||||||
case "v1/secrets", "secrets":
|
case "issue_0":
|
||||||
return "🔒"
|
return "👍"
|
||||||
case "v1/configmaps", "configmaps":
|
case "issue_1":
|
||||||
return "🗺"
|
return "🔊"
|
||||||
case "apps/v1/deployments", "deployments":
|
case "issue_2":
|
||||||
return "🪂"
|
return "☣️ "
|
||||||
case "apps/v1/statefulsets", "statefulsets":
|
case "issue_3":
|
||||||
return "🎎"
|
return "🧨"
|
||||||
case "apps/v1/daemonsets", "daemonsets":
|
case "report":
|
||||||
return "😈"
|
return "🧼"
|
||||||
default:
|
default:
|
||||||
return "📎"
|
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.
|
// EmojiInfo returns emoji help.
|
||||||
func EmojiInfo() map[string]string {
|
func EmojiInfo() map[string]string {
|
||||||
GVRs := []string{
|
GVRs := []string{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue