// SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s package view import ( "context" "errors" "fmt" "strconv" "strings" "github.com/atotto/clipboard" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/tcell/v2" "github.com/derailed/tview" "github.com/rs/zerolog/log" "github.com/sahilm/fuzzy" ) func clipboardWrite(text string) error { return clipboard.WriteAll(text) } func sanitizeEsc(s string) string { return strings.ReplaceAll(s, "[]", "]") } func cpCmd(flash *model.Flash, v *tview.TextView) func(*tcell.EventKey) *tcell.EventKey { return func(evt *tcell.EventKey) *tcell.EventKey { if err := clipboardWrite(sanitizeEsc(v.GetText(true))); err != nil { flash.Err(err) return evt } flash.Info("Content copied to clipboard...") return nil } } func parsePFAnn(s string) (string, string, bool) { tokens := strings.Split(s, ":") if len(tokens) != 2 { return "", "", false } return tokens[0], tokens[1], true } func k8sEnv(c *client.Config) Env { ctx, err := c.CurrentContextName() if err != nil { ctx = render.NAValue } cluster, err := c.CurrentClusterName() if err != nil { cluster = render.NAValue } user, err := c.CurrentUserName() if err != nil { user = render.NAValue } groups, err := c.CurrentGroupNames() if err != nil { groups = []string{render.NAValue} } var cfg string kcfg := c.Flags().KubeConfig if kcfg != nil && *kcfg != "" { cfg = *kcfg } return Env{ "CONTEXT": ctx, "CLUSTER": cluster, "USER": user, "GROUPS": strings.Join(groups, ","), "KUBECONFIG": cfg, } } func defaultEnv(c *client.Config, path string, header render.Header, row render.Row) Env { env := k8sEnv(c) env["NAMESPACE"], env["NAME"] = client.Namespaced(path) for _, col := range header.Columns(true) { i := header.IndexOf(col, true) if i >= 0 && i < len(row.Fields) { env["COL-"+col] = row.Fields[i] } } return env } func describeResource(app *App, m ui.Tabular, gvr client.GVR, path string) { v := NewLiveView(app, "Describe", model.NewDescribe(gvr, path)) if err := app.inject(v, false); err != nil { app.Flash().Err(err) } } func toLabelsStr(labels map[string]string) string { ll := make([]string, 0, len(labels)) for k, v := range labels { ll = append(ll, fmt.Sprintf("%s=%s", k, v)) } return strings.Join(ll, ",") } func showPods(app *App, path, labelSel, fieldSel string) { v := NewPod(client.NewGVR("v1/pods")) v.SetContextFn(podCtx(app, path, fieldSel)) v.SetLabelFilter(cmd.ToLabels(labelSel)) ns, _ := client.Namespaced(path) if err := app.Config.SetActiveNamespace(ns); err != nil { log.Error().Err(err).Msg("Config NS set failed!") } if err := app.inject(v, false); err != nil { app.Flash().Err(err) } } func podCtx(app *App, path, fieldSel string) ContextFunc { return func(ctx context.Context) context.Context { ctx = context.WithValue(ctx, internal.KeyPath, path) return context.WithValue(ctx, internal.KeyFields, fieldSel) } } func extractApp(ctx context.Context) (*App, error) { app, ok := ctx.Value(internal.KeyApp).(*App) if !ok { return nil, errors.New("no application found in context") } return app, nil } // AsKey maps a string representation of a key to a tcell key. func asKey(key string) (tcell.Key, error) { for k, v := range tcell.KeyNames { if key == v { return k, nil } } return 0, fmt.Errorf("no matching key found %s", key) } // FwFQN returns a fully qualified ns/name:container id. func fwFQN(po, co string) string { return po + "|" + co } func isTCPPort(p string) bool { return !strings.Contains(p, "UDP") } // ContainerID computes container ID based on ns/po/co. func containerID(path, co string) string { ns, n := client.Namespaced(path) po := strings.Split(n, "-")[0] return ns + "/" + po + ":" + co } // UrlFor computes fq url for a given benchmark configuration. func urlFor(cfg config.BenchConfig, port string) string { host := "localhost" if cfg.HTTP.Host != "" { host = cfg.HTTP.Host } path := "/" if cfg.HTTP.Path != "" { path = cfg.HTTP.Path } return "http://" + host + ":" + port + path } func fqn(ns, n string) string { if ns == "" { return n } return ns + "/" + n } func decorateCpuMemHeaderRows(app *App, data *render.TableData) { for colIndex, header := range data.Header { var check string if header.Name == "%CPU/L" { check = "cpu" } if header.Name == "%MEM/L" { check = "memory" } if len(check) == 0 { continue } for _, re := range data.RowEvents { if re.Row.Fields[colIndex] == render.NAValue { continue } n, err := strconv.Atoi(re.Row.Fields[colIndex]) if err != nil { continue } if n > 100 { n = 100 } severity := app.Config.K9s.Thresholds.LevelFor(check, n) if severity == config.SeverityLow { continue } color := app.Config.K9s.Thresholds.SeverityColor(check, n) if len(color) > 0 { re.Row.Fields[colIndex] = "[" + color + "::b]" + re.Row.Fields[colIndex] } } } } func matchTag(i int, s string) string { return `<<<"search_` + strconv.Itoa(i) + `">>>` + s + `<<<"">>>` } func linesWithRegions(lines []string, matches fuzzy.Matches) []string { ll := make([]string, len(lines)) copy(ll, lines) offsetForLine := make(map[int]int) for i, m := range matches { for _, loc := range dao.ContinuousRanges(m.MatchedIndexes) { start, end := loc[0]+offsetForLine[m.Index], loc[1]+offsetForLine[m.Index] line := ll[m.Index] if end > len(line) { end = len(line) } regionStr := matchTag(i, line[start:end]) ll[m.Index] = line[:start] + regionStr + line[end:] offsetForLine[m.Index] += len(regionStr) - (end - start) } } return ll }