Add customizable dump directory property (#1321)
* Add customizable dump directory property Add property for configuration file and arguments * Resolve Comments Co-authored-by: Artem Vlasov <artemv@tradeix.com>mine
parent
08ee61a298
commit
0249f7cf2c
|
|
@ -355,6 +355,8 @@ K9s uses aliases to navigate most K8s resources.
|
|||
- default
|
||||
view:
|
||||
active: dp
|
||||
# The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info)
|
||||
screenDumpDir: /tmp
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
25
cmd/info.go
25
cmd/info.go
|
|
@ -6,7 +6,10 @@ import (
|
|||
"github.com/derailed/k9s/internal/color"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v2"
|
||||
"os"
|
||||
)
|
||||
|
||||
func infoCmd() *cobra.Command {
|
||||
|
|
@ -26,7 +29,7 @@ func printInfo() {
|
|||
printLogo(color.Cyan)
|
||||
printTuple(fmat, "Configuration", config.K9sConfigFile, color.Cyan)
|
||||
printTuple(fmat, "Logs", config.DefaultLogFile, color.Cyan)
|
||||
printTuple(fmat, "Screen Dumps", config.K9sDumpDir, color.Cyan)
|
||||
printTuple(fmat, "Screen Dumps", getScreenDumpDirForInfo(), color.Cyan)
|
||||
}
|
||||
|
||||
func printLogo(c color.Paint) {
|
||||
|
|
@ -35,3 +38,23 @@ func printLogo(c color.Paint) {
|
|||
}
|
||||
fmt.Fprintln(out)
|
||||
}
|
||||
|
||||
// getScreenDumpDirForInfo get default screen dump config dir or from config.K9sConfigFile configuration.
|
||||
func getScreenDumpDirForInfo() string {
|
||||
if config.K9sConfigFile == "" {
|
||||
return config.K9sDefaultScreenDumpDir
|
||||
}
|
||||
|
||||
f, err := os.ReadFile(config.K9sConfigFile)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Reads k9s config file %v", err)
|
||||
return config.K9sDefaultScreenDumpDir
|
||||
}
|
||||
|
||||
var cfg config.Config
|
||||
if err := yaml.Unmarshal(f, &cfg); err != nil {
|
||||
log.Error().Err(err).Msgf("Unmarshal k9s config %v", err)
|
||||
return config.K9sDefaultScreenDumpDir
|
||||
}
|
||||
return cfg.K9s.GetScreenDumpDir()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_getScreenDumpDirForInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
k9sConfigFile string
|
||||
expectedScreenDumpDir string
|
||||
}{
|
||||
{
|
||||
name: "withK9sConfigFile",
|
||||
k9sConfigFile: "testdata/k9s.yml",
|
||||
expectedScreenDumpDir: "/tmp",
|
||||
},
|
||||
{
|
||||
name: "withEmptyK9sConfigFile",
|
||||
k9sConfigFile: "",
|
||||
expectedScreenDumpDir: config.K9sDefaultScreenDumpDir,
|
||||
},
|
||||
{
|
||||
name: "withInvalidK9sConfigFilePath",
|
||||
k9sConfigFile: "invalid",
|
||||
expectedScreenDumpDir: config.K9sDefaultScreenDumpDir,
|
||||
},
|
||||
{
|
||||
name: "withScreenDumpDirEmptyInK9sConfigFile",
|
||||
k9sConfigFile: "testdata/k9s1.yml",
|
||||
expectedScreenDumpDir: config.K9sDefaultScreenDumpDir,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
initK9sConfigFile := config.K9sConfigFile
|
||||
|
||||
config.K9sConfigFile = tt.k9sConfigFile
|
||||
|
||||
assert.Equal(t, tt.expectedScreenDumpDir, getScreenDumpDirForInfo())
|
||||
|
||||
config.K9sConfigFile = initK9sConfigFile
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +108,7 @@ func loadConfiguration() *config.Config {
|
|||
k9sCfg.K9s.OverrideReadOnly(*k9sFlags.ReadOnly)
|
||||
k9sCfg.K9s.OverrideWrite(*k9sFlags.Write)
|
||||
k9sCfg.K9s.OverrideCommand(*k9sFlags.Command)
|
||||
k9sCfg.K9s.OverrideScreenDumpDir(*k9sFlags.ScreenDumpDir)
|
||||
|
||||
if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil {
|
||||
log.Error().Err(err).Msgf("refine failed")
|
||||
|
|
@ -210,6 +211,13 @@ func initK9sFlags() {
|
|||
false,
|
||||
"Sets write mode by overriding the readOnly configuration setting",
|
||||
)
|
||||
rootCmd.Flags().StringVar(
|
||||
k9sFlags.ScreenDumpDir,
|
||||
"screen-dump-dir",
|
||||
"",
|
||||
"Sets a path to a dir for a screen dumps",
|
||||
)
|
||||
rootCmd.Flags()
|
||||
}
|
||||
|
||||
func initK8sFlags() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
k9s:
|
||||
refreshRate: 2
|
||||
readOnly: false
|
||||
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
|
||||
screenDumpDir: /tmp
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
k9s:
|
||||
refreshRate: 10
|
||||
namespace:
|
||||
active: fred
|
||||
favorites:
|
||||
- blee
|
||||
- duh
|
||||
- crap
|
||||
|
|
@ -19,8 +19,8 @@ const K9sConfig = "K9SCONFIG"
|
|||
var (
|
||||
// K9sConfigFile represents K9s config file location.
|
||||
K9sConfigFile = filepath.Join(K9sHome(), "config.yml")
|
||||
// K9sDumpDir represents a directory where K9s screen dumps will be persisted.
|
||||
K9sDumpDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-screens-%s", MustK9sUser()))
|
||||
// K9sDefaultScreenDumpDir represents a default directory where K9s screen dumps will be persisted.
|
||||
K9sDefaultScreenDumpDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-screens-%s", MustK9sUser()))
|
||||
)
|
||||
|
||||
type (
|
||||
|
|
@ -116,6 +116,8 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c
|
|||
c.K9s.CurrentCluster = *flags.ClusterName
|
||||
}
|
||||
|
||||
EnsurePath(c.K9s.GetScreenDumpDir(), DefaultDirMod)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -347,6 +347,7 @@ var expectedConfig = `k9s:
|
|||
memory:
|
||||
critical: 90
|
||||
warn: 70
|
||||
screenDumpDir: /tmp
|
||||
`
|
||||
|
||||
var resetConfig = `k9s:
|
||||
|
|
@ -393,4 +394,5 @@ var resetConfig = `k9s:
|
|||
memory:
|
||||
critical: 90
|
||||
warn: 70
|
||||
screenDumpDir: /tmp
|
||||
`
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ type Flags struct {
|
|||
ReadOnly *bool
|
||||
Write *bool
|
||||
Crumbsless *bool
|
||||
ScreenDumpDir *string
|
||||
}
|
||||
|
||||
// NewFlags returns new configuration flags.
|
||||
|
|
@ -47,6 +48,7 @@ func NewFlags() *Flags {
|
|||
ReadOnly: boolPtr(false),
|
||||
Write: boolPtr(false),
|
||||
Crumbsless: boolPtr(false),
|
||||
ScreenDumpDir: strPtr(K9sDefaultScreenDumpDir),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,14 @@ type K9s struct {
|
|||
CurrentCluster string `yaml:"currentCluster"`
|
||||
Clusters map[string]*Cluster `yaml:"clusters,omitempty"`
|
||||
Thresholds Threshold `yaml:"thresholds"`
|
||||
ScreenDumpDir string `yaml:"screenDumpDir"`
|
||||
manualRefreshRate int
|
||||
manualHeadless *bool
|
||||
manualLogoless *bool
|
||||
manualCrumbsless *bool
|
||||
manualReadOnly *bool
|
||||
manualCommand *string
|
||||
manualScreenDumpDir *string
|
||||
}
|
||||
|
||||
// NewK9s create a new K9s configuration.
|
||||
|
|
@ -40,6 +42,7 @@ func NewK9s() *K9s {
|
|||
Logger: NewLogger(),
|
||||
Clusters: make(map[string]*Cluster),
|
||||
Thresholds: NewThreshold(),
|
||||
ScreenDumpDir: K9sDefaultScreenDumpDir,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,6 +94,11 @@ func (k *K9s) OverrideCommand(cmd string) {
|
|||
k.manualCommand = &cmd
|
||||
}
|
||||
|
||||
// OverrideScreenDumpDir set the screen dump dir manually.
|
||||
func (k *K9s) OverrideScreenDumpDir(dir string) {
|
||||
k.manualScreenDumpDir = &dir
|
||||
}
|
||||
|
||||
// IsHeadless returns headless setting.
|
||||
func (k *K9s) IsHeadless() bool {
|
||||
h := k.Headless
|
||||
|
|
@ -155,6 +163,20 @@ func (k *K9s) ActiveCluster() *Cluster {
|
|||
return k.Clusters[k.CurrentCluster]
|
||||
}
|
||||
|
||||
func (k *K9s) GetScreenDumpDir() string {
|
||||
screenDumpDir := k.ScreenDumpDir
|
||||
|
||||
if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" {
|
||||
screenDumpDir = *k.manualScreenDumpDir
|
||||
}
|
||||
|
||||
if screenDumpDir == "" {
|
||||
return K9sDefaultScreenDumpDir
|
||||
}
|
||||
|
||||
return screenDumpDir
|
||||
}
|
||||
|
||||
func (k *K9s) validateDefaults() {
|
||||
if k.RefreshRate <= 0 {
|
||||
k.RefreshRate = defaultRefreshRate
|
||||
|
|
@ -162,6 +184,9 @@ func (k *K9s) validateDefaults() {
|
|||
if k.MaxConnRetry <= 0 {
|
||||
k.MaxConnRetry = defaultMaxConnRetry
|
||||
}
|
||||
if k.ScreenDumpDir == "" {
|
||||
k.ScreenDumpDir = K9sDefaultScreenDumpDir
|
||||
}
|
||||
}
|
||||
|
||||
func (k *K9s) validateClusters(c client.Connection, ks KubeSettings) {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ func TestK9sValidate(t *testing.T) {
|
|||
assert.Equal(t, "ctx1", c.CurrentContext)
|
||||
assert.Equal(t, "c1", c.CurrentCluster)
|
||||
assert.Equal(t, 1, len(c.Clusters))
|
||||
assert.Equal(t, config.K9sDefaultScreenDumpDir, c.GetScreenDumpDir())
|
||||
_, ok := c.Clusters[c.CurrentCluster]
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
|
@ -130,3 +131,38 @@ func TestK9sActiveCluster(t *testing.T) {
|
|||
assert.Equal(t, "kube-system", cl.Namespace.Active)
|
||||
assert.Equal(t, 5, len(cl.Namespace.Favorites))
|
||||
}
|
||||
|
||||
func TestGetScreenDumpDir(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
|
||||
assert.Equal(t, "/tmp", cfg.K9s.GetScreenDumpDir())
|
||||
}
|
||||
|
||||
func TestGetScreenDumpDirOverride(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.K9s.OverrideScreenDumpDir("/override")
|
||||
|
||||
assert.Equal(t, "/override", cfg.K9s.GetScreenDumpDir())
|
||||
}
|
||||
|
||||
func TestGetScreenDumpDirOverrideEmpty(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s.yml"))
|
||||
cfg.K9s.OverrideScreenDumpDir("")
|
||||
|
||||
assert.Equal(t, "/tmp", cfg.K9s.GetScreenDumpDir())
|
||||
}
|
||||
|
||||
func TestGetScreenDumpDirEmpty(t *testing.T) {
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
assert.Nil(t, cfg.Load("testdata/k9s1.yml"))
|
||||
cfg.K9s.OverrideScreenDumpDir("")
|
||||
|
||||
assert.Equal(t, config.K9sDefaultScreenDumpDir, cfg.K9s.GetScreenDumpDir())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,3 +29,4 @@ k9s:
|
|||
- kube-system
|
||||
view:
|
||||
active: po
|
||||
screenDumpDir: /tmp
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ func (d *Details) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (d *Details) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if path, err := saveYAML(d.app.Config.K9s.CurrentCluster, d.title, d.text.GetText(true)); err != nil {
|
||||
if path, err := saveYAML(d.app.Config.K9s.GetScreenDumpDir(), d.app.Config.K9s.CurrentCluster, d.title, d.text.GetText(true)); err != nil {
|
||||
d.app.Flash().Err(err)
|
||||
} else {
|
||||
d.app.Flash().Infof("Log %s saved successfully!", path)
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ func (v *LiveView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (v *LiveView) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if path, err := saveYAML(v.app.Config.K9s.CurrentCluster, v.title, v.text.GetText(true)); err != nil {
|
||||
if path, err := saveYAML(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.CurrentCluster, v.title, v.text.GetText(true)); err != nil {
|
||||
v.app.Flash().Err(err)
|
||||
} else {
|
||||
v.app.Flash().Infof("Log %s saved successfully!", path)
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
// SaveCmd dumps the logs to file.
|
||||
func (l *Log) SaveCmd(*tcell.EventKey) *tcell.EventKey {
|
||||
if path, err := saveData(l.app.Config.K9s.CurrentContext, l.model.GetPath(), l.logs.GetText(true)); err != nil {
|
||||
if path, err := saveData(l.app.Config.K9s.GetScreenDumpDir(), l.app.Config.K9s.CurrentContext, l.model.GetPath(), l.logs.GetText(true)); err != nil {
|
||||
l.app.Flash().Err(err)
|
||||
} else {
|
||||
l.app.Flash().Infof("Log %s saved successfully!", path)
|
||||
|
|
@ -429,8 +429,8 @@ func ensureDir(dir string) error {
|
|||
return os.MkdirAll(dir, 0744)
|
||||
}
|
||||
|
||||
func saveData(cluster, name, data string) (string, error) {
|
||||
dir := filepath.Join(config.K9sDumpDir, dao.SanitizeFilename(cluster))
|
||||
func saveData(screenDumpDir, cluster, name, data string) (string, error) {
|
||||
dir := filepath.Join(screenDumpDir, dao.SanitizeFilename(cluster))
|
||||
if err := ensureDir(dir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,8 +106,7 @@ func TestLogViewSave(t *testing.T) {
|
|||
ii.Lines(0, false, ll)
|
||||
v.Flush(ll)
|
||||
|
||||
config.K9sDumpDir = "/tmp"
|
||||
dir := filepath.Join(config.K9sDumpDir, app.Config.K9s.CurrentCluster)
|
||||
dir := filepath.Join(app.Config.K9s.GetScreenDumpDir(), app.Config.K9s.CurrentCluster)
|
||||
c1, _ := os.ReadDir(dir)
|
||||
v.SaveCmd(nil)
|
||||
c2, _ := os.ReadDir(dir)
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ func (l *Logger) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (l *Logger) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if path, err := saveYAML(l.app.Config.K9s.CurrentCluster, l.title, l.GetText(true)); err != nil {
|
||||
if path, err := saveYAML(l.app.Config.K9s.GetScreenDumpDir(), l.app.Config.K9s.CurrentCluster, l.title, l.GetText(true)); err != nil {
|
||||
l.app.Flash().Err(err)
|
||||
} else {
|
||||
l.app.Flash().Infof("Log %s saved successfully!", path)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func NewScreenDump(gvr client.GVR) ResourceViewer {
|
|||
}
|
||||
|
||||
func (s *ScreenDump) dirContext(ctx context.Context) context.Context {
|
||||
dir := filepath.Join(config.K9sDumpDir, s.App().Config.K9s.CurrentCluster)
|
||||
dir := filepath.Join(s.App().Config.K9s.GetScreenDumpDir(), s.App().Config.K9s.CurrentCluster)
|
||||
log.Debug().Msgf("SD-DIR %q", dir)
|
||||
config.EnsureFullPath(dir, config.DefaultDirMod)
|
||||
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ func (t *Table) BufferActive(state bool, k model.BufferKind) {
|
|||
}
|
||||
|
||||
func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if path, err := saveTable(t.app.Config.K9s.CurrentCluster, t.GVR().R(), t.Path, t.GetFilteredData()); err != nil {
|
||||
if path, err := saveTable(t.app.Config.K9s.GetScreenDumpDir(), t.app.Config.K9s.CurrentCluster, t.GVR().R(), t.Path, t.GetFilteredData()); err != nil {
|
||||
t.app.Flash().Err(err)
|
||||
} else {
|
||||
t.app.Flash().Infof("File %s saved successfully!", path)
|
||||
|
|
|
|||
|
|
@ -9,17 +9,16 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func computeFilename(cluster, ns, title, path string) (string, error) {
|
||||
func computeFilename(screenDumpDir, cluster, ns, title, path string) (string, error) {
|
||||
now := time.Now().UnixNano()
|
||||
|
||||
dir := filepath.Join(config.K9sDumpDir, dao.SanitizeFilename(cluster))
|
||||
dir := filepath.Join(screenDumpDir, dao.SanitizeFilename(cluster))
|
||||
if err := ensureDir(dir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -39,13 +38,13 @@ func computeFilename(cluster, ns, title, path string) (string, error) {
|
|||
return strings.ToLower(filepath.Join(dir, fName)), nil
|
||||
}
|
||||
|
||||
func saveTable(cluster, title, path string, data render.TableData) (string, error) {
|
||||
func saveTable(screenDumpDir, cluster, title, path string, data render.TableData) (string, error) {
|
||||
ns := data.Namespace
|
||||
if client.IsClusterWide(ns) {
|
||||
ns = client.NamespaceAll
|
||||
}
|
||||
|
||||
fPath, err := computeFilename(cluster, ns, title, path)
|
||||
fPath, err := computeFilename(screenDumpDir, cluster, ns, title, path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func TestTableSave(t *testing.T) {
|
|||
v.Init(makeContext())
|
||||
v.SetTitle("k9s-test")
|
||||
|
||||
dir := filepath.Join(config.K9sDumpDir, v.app.Config.K9s.CurrentCluster)
|
||||
dir := filepath.Join(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.CurrentCluster)
|
||||
c1, _ := os.ReadDir(dir)
|
||||
v.saveCmd(nil)
|
||||
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ func enableRegion(str string) string {
|
|||
return strings.ReplaceAll(strings.ReplaceAll(str, "<<<", "["), ">>>", "]")
|
||||
}
|
||||
|
||||
func saveYAML(cluster, name, data string) (string, error) {
|
||||
dir := filepath.Join(config.K9sDumpDir, dao.SanitizeFilename(cluster))
|
||||
func saveYAML(screenDumpDir, cluster, name, data string) (string, error) {
|
||||
dir := filepath.Join(screenDumpDir, dao.SanitizeFilename(cluster))
|
||||
if err := ensureDir(dir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue