// SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s package config import ( "errors" "io/fs" "log/slog" "os" "sync" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/slogs" "gopkg.in/yaml.v2" ) // Alias tracks shortname to GVR mappings. type Alias map[string]string // ShortNames represents a collection of shortnames for aliases. type ShortNames map[string][]string // Aliases represents a collection of aliases. type Aliases struct { Alias Alias `yaml:"aliases"` mx sync.RWMutex } // NewAliases return a new alias. func NewAliases() *Aliases { return &Aliases{ Alias: make(Alias, 50), } } func (a *Aliases) AliasesFor(s string) []string { aa := make([]string, 0, 10) a.mx.RLock() defer a.mx.RUnlock() for k, v := range a.Alias { if v == s { aa = append(aa, k) } } return aa } // Keys returns all aliases keys. func (a *Aliases) Keys() []string { a.mx.RLock() defer a.mx.RUnlock() ss := make([]string, 0, len(a.Alias)) for k := range a.Alias { ss = append(ss, k) } return ss } // ShortNames return all shortnames. func (a *Aliases) ShortNames() ShortNames { a.mx.RLock() defer a.mx.RUnlock() m := make(ShortNames, len(a.Alias)) for alias, gvr := range a.Alias { if v, ok := m[gvr]; ok { m[gvr] = append(v, alias) } else { m[gvr] = []string{alias} } } return m } // Clear remove all aliases. func (a *Aliases) Clear() { a.mx.Lock() defer a.mx.Unlock() for k := range a.Alias { delete(a.Alias, k) } } // Get retrieves an alias. func (a *Aliases) Get(k string) (string, bool) { a.mx.RLock() defer a.mx.RUnlock() v, ok := a.Alias[k] return v, ok } // Define declares a new alias. func (a *Aliases) Define(gvr string, aliases ...string) { a.mx.Lock() defer a.mx.Unlock() // BOZO!! Could not get full events struct using this api group?? if gvr == "events.k8s.io/v1/events" || gvr == "extensions/v1beta1" { return } for _, alias := range aliases { if _, ok := a.Alias[alias]; !ok && alias != "" { a.Alias[alias] = gvr } } } // Load K9s aliases. func (a *Aliases) Load(path string) error { a.loadDefaultAliases() f, err := EnsureAliasesCfgFile() if err != nil { slog.Error("Unable to gen config aliases", slogs.Error, err) } // load global alias file if err := a.LoadFile(f); err != nil { return err } // load context specific aliases if any return a.LoadFile(path) } // LoadFile loads alias from a given file. func (a *Aliases) LoadFile(path string) error { if path == "" { return nil } if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { return nil } bb, err := os.ReadFile(path) if err != nil { return err } if err := data.JSONValidator.Validate(json.AliasesSchema, bb); err != nil { slog.Warn("Aliases validation failed", slogs.Error, err) } var aa Aliases if err := yaml.Unmarshal(bb, &aa); err != nil { return err } a.mx.Lock() defer a.mx.Unlock() for k, v := range aa.Alias { a.Alias[k] = v } return nil } func (a *Aliases) declare(key string, aliases ...string) { a.Alias[key] = key for _, alias := range aliases { a.Alias[alias] = key } } func (a *Aliases) loadDefaultAliases() { a.mx.Lock() defer a.mx.Unlock() a.declare("help", "h", "?") a.declare("quit", "q", "q!", "qa", "Q") a.declare("aliases", "alias", "a") // !!BOZO!! // a.declare("popeye", "pop") a.declare("helm", "charts", "chart", "hm") a.declare("dir", "d") a.declare("contexts", "context", "ctx") a.declare("users", "user", "usr") a.declare("groups", "group", "grp") a.declare("portforwards", "portforward", "pf") a.declare("benchmarks", "benchmark", "bench") a.declare("screendumps", "screendump", "sd") a.declare("pulses", "pulse", "pu", "hz") a.declare("xrays", "xray", "x") a.declare("workloads", "workload", "wk") } // Save alias to disk. func (a *Aliases) Save() error { slog.Debug("Saving Aliases...") return a.SaveAliases(AppAliasesFile) } // SaveAliases saves aliases to a given file. func (a *Aliases) SaveAliases(path string) error { if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil { return err } cfg, err := yaml.Marshal(a) if err != nil { return err } return os.WriteFile(path, cfg, data.DefaultFileMod) }