parent
77ffacc2e4
commit
5c1ff0ed7b
2
Makefile
2
Makefile
|
|
@ -3,7 +3,7 @@ PACKAGE := github.com/derailed/$(NAME)
|
||||||
GIT := $(shell git rev-parse --short HEAD)
|
GIT := $(shell git rev-parse --short HEAD)
|
||||||
SOURCE_DATE_EPOCH ?= $(shell date +%s)
|
SOURCE_DATE_EPOCH ?= $(shell date +%s)
|
||||||
DATE := $(shell date -u -d @${SOURCE_DATE_EPOCH} +%FT%T%Z)
|
DATE := $(shell date -u -d @${SOURCE_DATE_EPOCH} +%FT%T%Z)
|
||||||
VERSION ?= v0.23.1
|
VERSION ?= v0.23.2
|
||||||
IMG_NAME := derailed/k9s
|
IMG_NAME := derailed/k9s
|
||||||
IMAGE := ${IMG_NAME}:${VERSION}
|
IMAGE := ${IMG_NAME}:${VERSION}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||||
|
|
||||||
|
# Release v0.23.2
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better are as ever very much noted and appreciated!
|
||||||
|
|
||||||
|
If you feel K9s is helping your Kubernetes journey, please consider joining our [sponsorhip program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||||
|
|
||||||
|
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Write Mode
|
||||||
|
|
||||||
|
K9s is writable by default, meaning you can interact with your cluster and make changes using one shot commands ie edit, delete, scale, etc... There `readOnly` config option that can be specified in the configuration or via a cli arg to override this behavior. In this drop, we're introducing a symmetrical command line arg aka `--write` that overrides a K9s session and make it writable tho the readOnly config option is set to true.
|
||||||
|
|
||||||
|
## Inverse Log Filtering
|
||||||
|
|
||||||
|
In the last drop, we've introduces reverse filters to filter out resources from table views. Now you will be able to apply inverse filtering on your log views as well via `/!fred`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resolved Issues/Features
|
||||||
|
|
||||||
|
* [Issue #906](https://github.com/derailed/k9s/issues/906) Print resources in pod view. With Feelings. Thanks Claudio!
|
||||||
|
* [Issue #889](https://github.com/derailed/k9s/issues/889) Disable readOnly config
|
||||||
|
* [Issue #564](https://github.com/derailed/k9s/issues/564) Invert filter mode on logs
|
||||||
|
|
||||||
|
## Resolved PRs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
48
cmd/root.go
48
cmd/root.go
|
|
@ -28,7 +28,6 @@ var (
|
||||||
version, commit, date = "dev", "dev", client.NA
|
version, commit, date = "dev", "dev", client.NA
|
||||||
k9sFlags *config.Flags
|
k9sFlags *config.Flags
|
||||||
k8sFlags *genericclioptions.ConfigFlags
|
k8sFlags *genericclioptions.ConfigFlags
|
||||||
demoMode = new(bool)
|
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: appName,
|
Use: appName,
|
||||||
|
|
@ -40,7 +39,6 @@ var (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(versionCmd(), infoCmd())
|
rootCmd.AddCommand(versionCmd(), infoCmd())
|
||||||
initTransientFlags()
|
|
||||||
initK9sFlags()
|
initK9sFlags()
|
||||||
initK8sFlags()
|
initK8sFlags()
|
||||||
|
|
||||||
|
|
@ -105,28 +103,15 @@ func loadConfiguration() *config.Config {
|
||||||
log.Warn().Msg("Unable to locate K9s config. Generating new configuration...")
|
log.Warn().Msg("Unable to locate K9s config. Generating new configuration...")
|
||||||
}
|
}
|
||||||
|
|
||||||
if demoMode != nil {
|
|
||||||
k9sCfg.SetDemoMode(*demoMode)
|
|
||||||
}
|
|
||||||
if *k9sFlags.RefreshRate != config.DefaultRefreshRate {
|
if *k9sFlags.RefreshRate != config.DefaultRefreshRate {
|
||||||
k9sCfg.K9s.OverrideRefreshRate(*k9sFlags.RefreshRate)
|
k9sCfg.K9s.OverrideRefreshRate(*k9sFlags.RefreshRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
if k9sFlags.Headless != nil {
|
k9sCfg.K9s.OverrideHeadless(*k9sFlags.Headless)
|
||||||
k9sCfg.K9s.OverrideHeadless(*k9sFlags.Headless)
|
k9sCfg.K9s.OverrideCrumbsless(*k9sFlags.Crumbsless)
|
||||||
}
|
k9sCfg.K9s.OverrideReadOnly(*k9sFlags.ReadOnly)
|
||||||
|
k9sCfg.K9s.OverrideWrite(*k9sFlags.Write)
|
||||||
if k9sFlags.Crumbsless != nil {
|
k9sCfg.K9s.OverrideCommand(*k9sFlags.Command)
|
||||||
k9sCfg.K9s.OverrideCrumbsless(*k9sFlags.Crumbsless)
|
|
||||||
}
|
|
||||||
|
|
||||||
if k9sFlags.ReadOnly != nil {
|
|
||||||
k9sCfg.K9s.OverrideReadOnly(*k9sFlags.ReadOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
if k9sFlags.Command != nil {
|
|
||||||
k9sCfg.K9s.OverrideCommand(*k9sFlags.Command)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isBoolSet(k9sFlags.AllNamespaces) && k9sCfg.SetActiveNamespace(client.AllNamespaces) != nil {
|
if isBoolSet(k9sFlags.AllNamespaces) && k9sCfg.SetActiveNamespace(client.AllNamespaces) != nil {
|
||||||
log.Error().Msg("Setting active namespace")
|
log.Error().Msg("Setting active namespace")
|
||||||
|
|
@ -175,15 +160,6 @@ func parseLevel(level string) zerolog.Level {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initTransientFlags() {
|
|
||||||
rootCmd.Flags().BoolVar(
|
|
||||||
demoMode,
|
|
||||||
"demo",
|
|
||||||
false,
|
|
||||||
"Enable demo mode to show keyboard commands",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initK9sFlags() {
|
func initK9sFlags() {
|
||||||
k9sFlags = config.NewFlags()
|
k9sFlags = config.NewFlags()
|
||||||
rootCmd.Flags().IntVarP(
|
rootCmd.Flags().IntVarP(
|
||||||
|
|
@ -204,6 +180,12 @@ func initK9sFlags() {
|
||||||
false,
|
false,
|
||||||
"Turn K9s header off",
|
"Turn K9s header off",
|
||||||
)
|
)
|
||||||
|
rootCmd.Flags().BoolVar(
|
||||||
|
k9sFlags.Crumbsless,
|
||||||
|
"crumbsless",
|
||||||
|
false,
|
||||||
|
"Turn K9s crumbs off",
|
||||||
|
)
|
||||||
rootCmd.Flags().BoolVarP(
|
rootCmd.Flags().BoolVarP(
|
||||||
k9sFlags.AllNamespaces,
|
k9sFlags.AllNamespaces,
|
||||||
"all-namespaces", "A",
|
"all-namespaces", "A",
|
||||||
|
|
@ -220,13 +202,13 @@ func initK9sFlags() {
|
||||||
k9sFlags.ReadOnly,
|
k9sFlags.ReadOnly,
|
||||||
"readonly",
|
"readonly",
|
||||||
false,
|
false,
|
||||||
"Toggles readOnly mode by overriding configuration setting",
|
"Sets readOnly mode by overriding readOnly configuration setting",
|
||||||
)
|
)
|
||||||
rootCmd.Flags().BoolVar(
|
rootCmd.Flags().BoolVar(
|
||||||
k9sFlags.Crumbsless,
|
k9sFlags.Write,
|
||||||
"crumbsless",
|
"write",
|
||||||
false,
|
false,
|
||||||
"Turn K9s crumbs off",
|
"Sets write mode by overriding the readOnly configuration setting",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ type (
|
||||||
K9s *K9s `yaml:"k9s"`
|
K9s *K9s `yaml:"k9s"`
|
||||||
client client.Connection
|
client client.Connection
|
||||||
settings KubeSettings
|
settings KubeSettings
|
||||||
demoMode bool
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -70,16 +69,6 @@ func NewConfig(ks KubeSettings) *Config {
|
||||||
return &Config{K9s: NewK9s(), settings: ks}
|
return &Config{K9s: NewK9s(), settings: ks}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DemoMode returns true if demo mode is active, false otherwise.
|
|
||||||
func (c *Config) DemoMode() bool {
|
|
||||||
return c.demoMode
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDemoMode sets the demo mode.
|
|
||||||
func (c *Config) SetDemoMode(b bool) {
|
|
||||||
c.demoMode = b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refine the configuration based on cli args.
|
// Refine the configuration based on cli args.
|
||||||
func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error {
|
func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error {
|
||||||
cfg, err := flags.ToRawKubeConfigLoader().RawConfig()
|
cfg, err := flags.ToRawKubeConfigLoader().RawConfig()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ type Flags struct {
|
||||||
Command *string
|
Command *string
|
||||||
AllNamespaces *bool
|
AllNamespaces *bool
|
||||||
ReadOnly *bool
|
ReadOnly *bool
|
||||||
|
Write *bool
|
||||||
Crumbsless *bool
|
Crumbsless *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,6 +32,7 @@ func NewFlags() *Flags {
|
||||||
Command: strPtr(DefaultCommand),
|
Command: strPtr(DefaultCommand),
|
||||||
AllNamespaces: boolPtr(false),
|
AllNamespaces: boolPtr(false),
|
||||||
ReadOnly: boolPtr(false),
|
ReadOnly: boolPtr(false),
|
||||||
|
Write: boolPtr(false),
|
||||||
Crumbsless: boolPtr(false),
|
Crumbsless: boolPtr(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "github.com/derailed/k9s/internal/client"
|
import (
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultRefreshRate = 2
|
defaultRefreshRate = 2
|
||||||
|
|
@ -56,7 +58,17 @@ func (k *K9s) OverrideCrumbsless(b bool) {
|
||||||
|
|
||||||
// OverrideReadOnly set the readonly mode manually.
|
// OverrideReadOnly set the readonly mode manually.
|
||||||
func (k *K9s) OverrideReadOnly(b bool) {
|
func (k *K9s) OverrideReadOnly(b bool) {
|
||||||
k.manualReadOnly = &b
|
if b {
|
||||||
|
k.manualReadOnly = &b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverrideWrite set the write mode manually.
|
||||||
|
func (k *K9s) OverrideWrite(b bool) {
|
||||||
|
if b {
|
||||||
|
flag := !b
|
||||||
|
k.manualReadOnly = &flag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OverrideCommand set the command manually.
|
// OverrideCommand set the command manually.
|
||||||
|
|
@ -100,6 +112,7 @@ func (k *K9s) IsReadOnly() bool {
|
||||||
if k.manualReadOnly != nil {
|
if k.manualReadOnly != nil {
|
||||||
readOnly = *k.manualReadOnly
|
readOnly = *k.manualReadOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
return readOnly
|
return readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,57 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestIsReadOnly(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
config string
|
||||||
|
read, write bool
|
||||||
|
readOnly bool
|
||||||
|
}{
|
||||||
|
"writable": {
|
||||||
|
config: "k9s.yml",
|
||||||
|
},
|
||||||
|
"writable_read_override": {
|
||||||
|
config: "k9s.yml",
|
||||||
|
read: true,
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
"writable_write_override": {
|
||||||
|
config: "k9s.yml",
|
||||||
|
write: true,
|
||||||
|
},
|
||||||
|
"readonly": {
|
||||||
|
config: "k9s_readonly.yml",
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
"readonly_read_override": {
|
||||||
|
config: "k9s_readonly.yml",
|
||||||
|
read: true,
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
"readonly_write_override": {
|
||||||
|
config: "k9s_readonly.yml",
|
||||||
|
write: true,
|
||||||
|
},
|
||||||
|
"readonly_both_override": {
|
||||||
|
config: "k9s_readonly.yml",
|
||||||
|
read: true,
|
||||||
|
write: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mk := NewMockKubeSettings()
|
||||||
|
cfg := config.NewConfig(mk)
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Nil(t, cfg.Load("testdata/"+u.config))
|
||||||
|
cfg.K9s.OverrideReadOnly(u.read)
|
||||||
|
cfg.K9s.OverrideWrite(u.write)
|
||||||
|
assert.Equal(t, u.readOnly, cfg.K9s.IsReadOnly())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestK9sValidate(t *testing.T) {
|
func TestK9sValidate(t *testing.T) {
|
||||||
mc := NewMockConnection()
|
mc := NewMockConnection()
|
||||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
k9s:
|
k9s:
|
||||||
refreshRate: 2
|
refreshRate: 2
|
||||||
|
readOnly: false
|
||||||
logger:
|
logger:
|
||||||
tail: 200
|
tail: 200
|
||||||
buffer: 2000
|
buffer: 2000
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
k9s:
|
||||||
|
refreshRate: 2
|
||||||
|
readOnly: true
|
||||||
|
logger:
|
||||||
|
tail: 200
|
||||||
|
buffer: 2000
|
||||||
|
currentContext: minikube
|
||||||
|
currentCluster: minikube
|
||||||
|
clusters:
|
||||||
|
minikube:
|
||||||
|
namespace:
|
||||||
|
active: kube-system
|
||||||
|
favorites:
|
||||||
|
- default
|
||||||
|
- kube-public
|
||||||
|
- istio-system
|
||||||
|
- all
|
||||||
|
- kube-system
|
||||||
|
view:
|
||||||
|
active: ctx
|
||||||
|
fred:
|
||||||
|
namespace:
|
||||||
|
active: default
|
||||||
|
favorites:
|
||||||
|
- default
|
||||||
|
- kube-public
|
||||||
|
- istio-system
|
||||||
|
- all
|
||||||
|
- kube-system
|
||||||
|
view:
|
||||||
|
active: po
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
|
|
@ -13,6 +14,19 @@ import (
|
||||||
"k8s.io/cli-runtime/pkg/printers"
|
"k8s.io/cli-runtime/pkg/printers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
inverseRx = regexp.MustCompile(`\A\!`)
|
||||||
|
fuzzyRx = regexp.MustCompile(`\A\-f`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsInverseSelector checks if inverse char has been provided.
|
||||||
|
func IsInverseSelector(s string) bool {
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return inverseRx.MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
// IsFuzzySelector checks if filter is fuzzy or not.
|
// IsFuzzySelector checks if filter is fuzzy or not.
|
||||||
func IsFuzzySelector(s string) bool {
|
func IsFuzzySelector(s string) bool {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
|
|
||||||
|
|
@ -180,8 +180,6 @@ func (l LogItems) Filter(q string, showTime bool) ([]int, [][]int, error) {
|
||||||
return matches, indices, nil
|
return matches, indices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var fuzzyRx = regexp.MustCompile(`\A\-f`)
|
|
||||||
|
|
||||||
func (l LogItems) fuzzyFilter(q string, showTime bool) ([]int, [][]int) {
|
func (l LogItems) fuzzyFilter(q string, showTime bool) ([]int, [][]int) {
|
||||||
q = strings.TrimSpace(q)
|
q = strings.TrimSpace(q)
|
||||||
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
||||||
|
|
@ -195,22 +193,32 @@ func (l LogItems) fuzzyFilter(q string, showTime bool) ([]int, [][]int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LogItems) filterLogs(q string, showTime bool) ([]int, [][]int, error) {
|
func (l LogItems) filterLogs(q string, showTime bool) ([]int, [][]int, error) {
|
||||||
|
var invert bool
|
||||||
|
if IsInverseSelector(q) {
|
||||||
|
invert = true
|
||||||
|
q = q[1:]
|
||||||
|
}
|
||||||
rx, err := regexp.Compile(`(?i)` + q)
|
rx, err := regexp.Compile(`(?i)` + q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
||||||
for i, line := range l.Lines(showTime) {
|
for i, line := range l.Lines(showTime) {
|
||||||
if locs := rx.FindIndex(line); locs != nil {
|
locs := rx.FindIndex(line)
|
||||||
matches = append(matches, i)
|
if locs != nil && invert {
|
||||||
ii := make([]int, 0, 10)
|
continue
|
||||||
for i := 0; i < len(locs); i += 2 {
|
|
||||||
for j := locs[i]; j < locs[i+1]; j++ {
|
|
||||||
ii = append(ii, j)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
indices = append(indices, ii)
|
|
||||||
}
|
}
|
||||||
|
if locs == nil && !invert {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matches = append(matches, i)
|
||||||
|
ii := make([]int, 0, 10)
|
||||||
|
for i := 0; i < len(locs); i += 2 {
|
||||||
|
for j := locs[i]; j < locs[i+1]; j++ {
|
||||||
|
ii = append(ii, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indices = append(indices, ii)
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches, indices, nil
|
return matches, indices, nil
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ type Log struct {
|
||||||
filter string
|
filter string
|
||||||
lastSent int
|
lastSent int
|
||||||
flushTimeout time.Duration
|
flushTimeout time.Duration
|
||||||
filtering bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLog returns a new model.
|
// NewLog returns a new model.
|
||||||
|
|
@ -176,28 +175,15 @@ func (l *Log) Filter(q string) {
|
||||||
defer l.mx.Unlock()
|
defer l.mx.Unlock()
|
||||||
|
|
||||||
if len(q) == 0 {
|
if len(q) == 0 {
|
||||||
l.filter, l.filtering = "", false
|
l.filter = ""
|
||||||
l.fireLogCleared()
|
l.fireLogCleared()
|
||||||
l.fireLogBuffChanged(l.lines)
|
l.fireLogBuffChanged(l.lines)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.filter = q
|
l.filter = q
|
||||||
// BOZO!! No needed since cmdbuff is now throttled!!
|
l.fireLogCleared()
|
||||||
if l.filtering {
|
l.fireLogBuffChanged(l.lines)
|
||||||
return
|
|
||||||
}
|
|
||||||
l.filtering = true
|
|
||||||
go func(l *Log) {
|
|
||||||
<-time.After(500 * time.Millisecond)
|
|
||||||
l.fireLogCleared()
|
|
||||||
l.fireLogBuffChanged(l.lines)
|
|
||||||
l.mx.Lock()
|
|
||||||
{
|
|
||||||
l.filtering = false
|
|
||||||
}
|
|
||||||
l.mx.Unlock()
|
|
||||||
}(l)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Log) load() error {
|
func (l *Log) load() error {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,10 @@ func TestLogFilter(t *testing.T) {
|
||||||
q: `pod-line-[1-3]{1}`,
|
q: `pod-line-[1-3]{1}`,
|
||||||
e: 4,
|
e: 4,
|
||||||
},
|
},
|
||||||
|
"invert": {
|
||||||
|
q: `!pod-line-1`,
|
||||||
|
e: 8,
|
||||||
|
},
|
||||||
"fuzzy": {
|
"fuzzy": {
|
||||||
q: `-f po-l1`,
|
q: `-f po-l1`,
|
||||||
e: 2,
|
e: 2,
|
||||||
|
|
@ -75,13 +79,13 @@ func TestLogFilter(t *testing.T) {
|
||||||
|
|
||||||
m.Notify()
|
m.Notify()
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 2, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, u.e, len(v.data))
|
assert.Equal(t, u.e, len(v.data))
|
||||||
|
|
||||||
m.ClearFilter()
|
m.ClearFilter()
|
||||||
assert.Equal(t, 2, v.dataCalled)
|
assert.Equal(t, 2, v.dataCalled)
|
||||||
assert.Equal(t, 2, v.clearCalled)
|
assert.Equal(t, 3, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, size, len(v.data))
|
assert.Equal(t, size, len(v.data))
|
||||||
})
|
})
|
||||||
|
|
@ -195,7 +199,7 @@ func TestLogTimedout(t *testing.T) {
|
||||||
}
|
}
|
||||||
m.Notify()
|
m.Notify()
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 2, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
const e = "\x1b[38;5;209ml\x1b[0m\x1b[38;5;209mi\x1b[0m\x1b[38;5;209mn\x1b[0m\x1b[38;5;209me\x1b[0m\x1b[38;5;209m1\x1b[0m"
|
const e = "\x1b[38;5;209ml\x1b[0m\x1b[38;5;209mi\x1b[0m\x1b[38;5;209mn\x1b[0m\x1b[38;5;209me\x1b[0m\x1b[38;5;209m1\x1b[0m"
|
||||||
assert.Equal(t, e, string(v.data[0]))
|
assert.Equal(t, e, string(v.data[0]))
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package model
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
|
|
@ -27,10 +26,6 @@ func NewPulseHealth(f dao.Factory) *PulseHealth {
|
||||||
|
|
||||||
// List returns a canned collection of resources health.
|
// List returns a canned collection of resources health.
|
||||||
func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||||
defer func(t time.Time) {
|
|
||||||
log.Debug().Msgf("PulseHealthCheck %v", time.Since(t))
|
|
||||||
}(time.Now())
|
|
||||||
|
|
||||||
gvrs := []string{
|
gvrs := []string{
|
||||||
"v1/pods",
|
"v1/pods",
|
||||||
"v1/events",
|
"v1/events",
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,11 @@ func (h Header) HasAge() bool {
|
||||||
return h.IndexOf(ageCol, true) != -1
|
return h.IndexOf(ageCol, true) != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsMetricsCol checks if given column index represents metrics.
|
||||||
|
func (h Header) IsMetricsCol(col int) bool {
|
||||||
|
return h[col].MX
|
||||||
|
}
|
||||||
|
|
||||||
// IsAgeCol checks if given column index is the age column.
|
// IsAgeCol checks if given column index is the age column.
|
||||||
func (h Header) IsAgeCol(col int) bool {
|
func (h Header) IsAgeCol(col int) bool {
|
||||||
if !h.HasAge() || col >= len(h) {
|
if !h.HasAge() || col >= len(h) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -259,30 +260,26 @@ func mapToIfc(m interface{}) (s string) {
|
||||||
|
|
||||||
func toMcPerc(v1, v2 *resource.Quantity) string {
|
func toMcPerc(v1, v2 *resource.Quantity) string {
|
||||||
m := v1.MilliValue()
|
m := v1.MilliValue()
|
||||||
return toMc(m) + " (" +
|
return fmt.Sprintf("%s (%d%%)", toMc(m), client.ToPercentage(m, v2.MilliValue()))
|
||||||
strconv.Itoa(client.ToPercentage(m, v2.MilliValue())) + "%)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toMiPerc(v1, v2 *resource.Quantity) string {
|
func toMiPerc(v1, v2 *resource.Quantity) string {
|
||||||
m := v1.Value()
|
m := v1.Value()
|
||||||
return toMi(m) + " (" +
|
return fmt.Sprintf("%s (%d%%)", toMi(m), client.ToPercentage(m, v2.Value()))
|
||||||
strconv.Itoa(client.ToPercentage(m, v2.Value())) + "%)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toMc(v int64) string {
|
func toMc(v int64) string {
|
||||||
if v == 0 {
|
if v == 0 {
|
||||||
return ZeroValue
|
return ZeroValue
|
||||||
}
|
}
|
||||||
p := message.NewPrinter(language.English)
|
return AsThousands(v)
|
||||||
return p.Sprintf("%d", v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toMi(v int64) string {
|
func toMi(v int64) string {
|
||||||
if v == 0 {
|
if v == 0 {
|
||||||
return ZeroValue
|
return ZeroValue
|
||||||
}
|
}
|
||||||
p := message.NewPrinter(language.English)
|
return AsThousands(client.ToMB(v))
|
||||||
return p.Sprintf("%d", client.ToMB(v))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func boolPtrToStr(b *bool) string {
|
func boolPtrToStr(b *bool) string {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fvbommel/sortorder"
|
"github.com/fvbommel/sortorder"
|
||||||
|
|
@ -146,8 +147,14 @@ func (rr Rows) Find(id string) (int, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort rows based on column index and order.
|
// Sort rows based on column index and order.
|
||||||
func (rr Rows) Sort(col int, asc bool) {
|
func (rr Rows) Sort(col int, asc, isNum, isDur bool) {
|
||||||
t := RowSorter{Rows: rr, Index: col, Asc: asc}
|
t := RowSorter{
|
||||||
|
Rows: rr,
|
||||||
|
Index: col,
|
||||||
|
IsNumber: isNum,
|
||||||
|
IsDuration: isDur,
|
||||||
|
Asc: asc,
|
||||||
|
}
|
||||||
sort.Sort(t)
|
sort.Sort(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,9 +162,10 @@ func (rr Rows) Sort(col int, asc bool) {
|
||||||
|
|
||||||
// RowSorter sorts rows.
|
// RowSorter sorts rows.
|
||||||
type RowSorter struct {
|
type RowSorter struct {
|
||||||
Rows Rows
|
Rows Rows
|
||||||
Index int
|
Index int
|
||||||
Asc bool
|
IsNumber, IsDuration bool
|
||||||
|
Asc bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s RowSorter) Len() int {
|
func (s RowSorter) Len() int {
|
||||||
|
|
@ -169,7 +177,7 @@ func (s RowSorter) Swap(i, j int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s RowSorter) Less(i, j int) bool {
|
func (s RowSorter) Less(i, j int) bool {
|
||||||
return Less(s.Asc, s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index])
|
return Less(s.Asc, s.IsNumber, s.IsDuration, s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
@ -185,8 +193,13 @@ func toAgeDuration(dur string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Less return true if c1 < c2.
|
// Less return true if c1 < c2.
|
||||||
func Less(asc bool, c1, c2 string) bool {
|
func Less(asc, isNumber, isDuration bool, c1, c2 string) bool {
|
||||||
c1, c2 = toAgeDuration(c1), toAgeDuration(c2)
|
if isNumber {
|
||||||
|
c1, c2 = strings.Replace(c1, ",", "", -1), strings.Replace(c2, ",", "", -1)
|
||||||
|
}
|
||||||
|
if isDuration {
|
||||||
|
c1, c2 = toAgeDuration(c1), toAgeDuration(c2)
|
||||||
|
}
|
||||||
b := sortorder.NaturalLess(c1, c2)
|
b := sortorder.NaturalLess(c1, c2)
|
||||||
if asc {
|
if asc {
|
||||||
return b
|
return b
|
||||||
|
|
|
||||||
|
|
@ -196,12 +196,19 @@ 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, numCol, asc bool) {
|
||||||
if sortCol == -1 {
|
if sortCol == -1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t := RowEventSorter{NS: ns, Events: r, Index: sortCol, Asc: asc}
|
t := RowEventSorter{
|
||||||
|
NS: ns,
|
||||||
|
Events: r,
|
||||||
|
Index: sortCol,
|
||||||
|
Asc: asc,
|
||||||
|
IsNumber: numCol,
|
||||||
|
IsDuration: ageCol,
|
||||||
|
}
|
||||||
sort.Sort(t)
|
sort.Sort(t)
|
||||||
|
|
||||||
iids, fields := map[string][]string{}, make(StringSet, 0, len(r))
|
iids, fields := map[string][]string{}, make(StringSet, 0, len(r))
|
||||||
|
|
@ -227,10 +234,12 @@ func (r RowEvents) Sort(ns string, sortCol int, ageCol bool, asc bool) {
|
||||||
|
|
||||||
// RowEventSorter sorts row events by a given colon.
|
// RowEventSorter sorts row events by a given colon.
|
||||||
type RowEventSorter struct {
|
type RowEventSorter struct {
|
||||||
Events RowEvents
|
Events RowEvents
|
||||||
Index int
|
Index int
|
||||||
NS string
|
NS string
|
||||||
Asc bool
|
IsNumber bool
|
||||||
|
IsDuration bool
|
||||||
|
Asc bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r RowEventSorter) Len() int {
|
func (r RowEventSorter) Len() int {
|
||||||
|
|
@ -243,7 +252,7 @@ func (r RowEventSorter) Swap(i, j int) {
|
||||||
|
|
||||||
func (r RowEventSorter) Less(i, j int) bool {
|
func (r RowEventSorter) Less(i, j int) bool {
|
||||||
f1, f2 := r.Events[i].Row.Fields, r.Events[j].Row.Fields
|
f1, f2 := r.Events[i].Row.Fields, r.Events[j].Row.Fields
|
||||||
return Less(r.Asc, f1[r.Index], f2[r.Index])
|
return Less(r.Asc, r.IsNumber, r.IsDuration, f1[r.Index], f2[r.Index])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -409,10 +409,10 @@ func TestRowEventsDelete(t *testing.T) {
|
||||||
|
|
||||||
func TestRowEventsSort(t *testing.T) {
|
func TestRowEventsSort(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
re render.RowEvents
|
re render.RowEvents
|
||||||
col int
|
col int
|
||||||
age, asc bool
|
age, num, asc bool
|
||||||
e render.RowEvents
|
e render.RowEvents
|
||||||
}{
|
}{
|
||||||
"age_time": {
|
"age_time": {
|
||||||
re: render.RowEvents{
|
re: render.RowEvents{
|
||||||
|
|
@ -483,7 +483,7 @@ func TestRowEventsSort(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
u.re.Sort("", u.col, u.age, u.asc)
|
u.re.Sort("", u.col, u.age, u.num, u.asc)
|
||||||
assert.Equal(t, u.e, u.re)
|
assert.Equal(t, u.e, u.re)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -231,10 +231,10 @@ func TestRowsUpsert(t *testing.T) {
|
||||||
|
|
||||||
func TestRowsSortText(t *testing.T) {
|
func TestRowsSortText(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
rows render.Rows
|
rows render.Rows
|
||||||
col int
|
col int
|
||||||
asc bool
|
asc, num bool
|
||||||
e render.Rows
|
e render.Rows
|
||||||
}{
|
}{
|
||||||
"plainAsc": {
|
"plainAsc": {
|
||||||
rows: render.Rows{
|
rows: render.Rows{
|
||||||
|
|
@ -266,6 +266,7 @@ func TestRowsSortText(t *testing.T) {
|
||||||
{Fields: []string{"1", "blee"}},
|
{Fields: []string{"1", "blee"}},
|
||||||
},
|
},
|
||||||
col: 0,
|
col: 0,
|
||||||
|
num: true,
|
||||||
asc: true,
|
asc: true,
|
||||||
e: render.Rows{
|
e: render.Rows{
|
||||||
{Fields: []string{"1", "blee"}},
|
{Fields: []string{"1", "blee"}},
|
||||||
|
|
@ -278,6 +279,7 @@ func TestRowsSortText(t *testing.T) {
|
||||||
{Fields: []string{"1", "blee"}},
|
{Fields: []string{"1", "blee"}},
|
||||||
},
|
},
|
||||||
col: 0,
|
col: 0,
|
||||||
|
num: true,
|
||||||
asc: false,
|
asc: false,
|
||||||
e: render.Rows{
|
e: render.Rows{
|
||||||
{Fields: []string{"10", "duh"}},
|
{Fields: []string{"10", "duh"}},
|
||||||
|
|
@ -301,7 +303,7 @@ func TestRowsSortText(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
u.rows.Sort(u.col, u.asc)
|
u.rows.Sort(u.col, u.asc, u.num, false)
|
||||||
assert.Equal(t, u.e, u.rows)
|
assert.Equal(t, u.e, u.rows)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -342,7 +344,7 @@ func TestRowsSortDuration(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
u.rows.Sort(u.col, u.asc)
|
u.rows.Sort(u.col, u.asc, false, true)
|
||||||
assert.Equal(t, u.e, u.rows)
|
assert.Equal(t, u.e, u.rows)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -369,13 +371,13 @@ func TestRowsSortMetrics(t *testing.T) {
|
||||||
},
|
},
|
||||||
"metricDesc": {
|
"metricDesc": {
|
||||||
rows: render.Rows{
|
rows: render.Rows{
|
||||||
{Fields: []string{"10m", "100Mi"}},
|
{Fields: []string{"10000m", "1000Mi"}},
|
||||||
{Fields: []string{"1m", "50Mi"}},
|
{Fields: []string{"1m", "50Mi"}},
|
||||||
},
|
},
|
||||||
col: 1,
|
col: 1,
|
||||||
asc: false,
|
asc: false,
|
||||||
e: render.Rows{
|
e: render.Rows{
|
||||||
{Fields: []string{"10m", "100Mi"}},
|
{Fields: []string{"10000m", "1000Mi"}},
|
||||||
{Fields: []string{"1m", "50Mi"}},
|
{Fields: []string{"1m", "50Mi"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -384,7 +386,7 @@ func TestRowsSortMetrics(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
u.rows.Sort(u.col, u.asc)
|
u.rows.Sort(u.col, u.asc, true, false)
|
||||||
assert.Equal(t, u.e, u.rows)
|
assert.Equal(t, u.e, u.rows)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -234,10 +234,12 @@ func (t *Table) doUpdate(data render.TableData) {
|
||||||
c.SetTextColor(fg)
|
c.SetTextColor(fg)
|
||||||
col++
|
col++
|
||||||
}
|
}
|
||||||
|
colIndex := custData.Header.IndexOf(t.sortCol.name, false)
|
||||||
custData.RowEvents.Sort(
|
custData.RowEvents.Sort(
|
||||||
custData.Namespace,
|
custData.Namespace,
|
||||||
custData.Header.IndexOf(t.sortCol.name, false),
|
colIndex,
|
||||||
t.sortCol.name == "AGE",
|
t.sortCol.name == "AGE",
|
||||||
|
data.Header.IsMetricsCol(colIndex),
|
||||||
t.sortCol.asc,
|
t.sortCol.asc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue