diff --git a/.golangci.yml b/.golangci.yml index 052163af..31169efe 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -78,6 +78,62 @@ linters-settings: - errors reason: "Go 1.20+ has support for combining multiple errors, see https://go.dev/doc/go1.20#errors" + sloglint: + # Enforce not mixing key-value pairs and attributes. + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-mixed-arguments + # Default: true + no-mixed-args: true + # Enforce using key-value pairs only (overrides no-mixed-args, incompatible with attr-only). + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#key-value-pairs-only + # Default: false + kv-only: true + # Enforce using attributes only (overrides no-mixed-args, incompatible with kv-only). + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#attributes-only + # Default: false + attr-only: false + # Enforce not using global loggers. + # Values: + # - "": disabled + # - "all": report all global loggers + # - "default": report only the default slog logger + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-global + # Default: "" + no-global: "" + # Enforce using methods that accept a context. + # Values: + # - "": disabled + # - "all": report all contextless calls + # - "scope": report only if a context exists in the scope of the outermost function + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#context-only + # Default: "" + context: "" + # Enforce using static values for log messages. + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#static-messages + # Default: false + static-msg: false + # Enforce using constants instead of raw keys. + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-raw-keys + # Default: false + no-raw-keys: true + # Enforce a single key naming convention. + # Values: snake, kebab, camel, pascal + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#key-naming-convention + # Default: "" + key-naming-case: camel + # Enforce not using specific keys. + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#forbidden-keys + # Default: [] + forbidden-keys: + - time + - level + - msg + - source + # Enforce putting arguments on separate lines. + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#arguments-on-separate-lines + # Default: false + args-on-sep-lines: false + + issues: # default is true. Enables skipping of directories: @@ -124,3 +180,5 @@ linters: - misspell - prealloc - typecheck + - sloglint + diff --git a/Makefile b/Makefile index 75659c95..d9b6f5ea 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.40.5 +VERSION ?= v0.40.6 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/README.md b/README.md index e1e484f3..f653fc24 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ Your donations will go a long way in keeping our servers lights on and beers in Please refer to our [K9s documentation](https://k9scli.io) site for installation, usage, customization and tips. +--- + ## Slack Channel Wanna discuss K9s features with your fellow `K9sers` or simply show your support for this tool? @@ -73,6 +75,21 @@ Wanna discuss K9s features with your fellow `K9sers` or simply show your support * Channel: [K9sersSlack](https://k9sers.slack.com/) * Invite: [K9slackers Invite](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) +--- + +## 🄳 A Word From Our Rhodium Sponsors... + +Below are organizations that have opted to show their support and sponsor K9s. + +
+panfactum +
+
+ +> NOTE! K9s neither vouches for nor endorses these companies or products. + +--- + ## Installation K9s is available on Linux, macOS and Windows platforms. @@ -627,7 +644,7 @@ The annotation value must specify a container to forward to as well as a local p --- -## Resource Custom Columns +## Custom Views [SneakCast v0.17.0 on The Beach! - Yup! sound is sucking but what a setting!](https://youtu.be/7S33CNLAofk) @@ -654,6 +671,7 @@ You can have one or more of the following attributes: * `T` -> time column indicator * `N` -> number column indicator * `W` -> turns on wide column aka only shows while in wide mode. Defaults to the standard resource definition when present. +* `S` -> Ensures a column is visible and not wide. Overrides `wide` std resource definition if present. * `H` -> Hides the column * `L` -> Left align (default) * `R` -> Right align @@ -674,6 +692,14 @@ views: - NODE - STATUS - READY + - MEM/RL|S # => 🌚 Overrides std resource default wide attribute via `S` for `Show` + - '%MEM/R|' # => NOTE! column names with non alpha names need to be quoted as columns must be strings! + + v1/pods@fred: # => 🌚 New v0.40.6! Customize columns for a given resource and namespace! + columns: + - AGE + - NAMESPACE|WR + v1/services: columns: - AGE diff --git a/assets/sponsors/panfactum.png b/assets/sponsors/panfactum.png new file mode 100644 index 00000000..43fc667d Binary files /dev/null and b/assets/sponsors/panfactum.png differ diff --git a/change_logs/release_v0.40.6.md b/change_logs/release_v0.40.6.md new file mode 100644 index 00000000..6f6f2d9c --- /dev/null +++ b/change_logs/release_v0.40.6.md @@ -0,0 +1,99 @@ + + +# Release v0.40.6 + +## 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! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship 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) + +## Maintenance Release! + +### Breaking change + +Moved `portForwardAddress` out of clusterXXX/contextYYY/config.yaml and into the main K9s config file. +This is a global preference based on your setup vs a cluster/context specific attribute. +K9s will nag you in the logs if a specific context config still contains this attribute but should not prevent the configuration load. + +### Column Blow Reloaded! + +We've added another property to the custom view. You can now also specify namespace specific column definition for a given resource. +For instance, view pods in any namespace using one configuration and view pods in `fred` namespace using an alternate configuration. + +```yaml +# views.yaml +views: + # Using this for all pods... + v1/pods: + columns: + - AGE + - NAMESPACE|WR # => 🌚 Specifies the NAMESPACE column to be right aligned and only visible while in wide mode + - ZORG:.metadata.labels.fred\.io\.kubernetes\.blee # => 🌚 extract fred.io.kubernetes.blee label into it's own column + - BLEE:.metadata.annotations.blee|R # => 🌚 extract annotation blee into it's own column and right align it + - NAME + - IP + - NODE + - STATUS + - READY + - MEM/RL|S # => 🌚 Overrides std resource default wide attribute via `S` for `Show` + - '%MEM/R|' # => NOTE! column names with non alpha names need to be quoted as columns must be strings! + + # Use this instead for pods in namespace `fred` + v1/pods@fred: # => 🌚 New v0.40.6! Customize columns for a given resource and namespace! + columns: + - AGE + - NAMESPACE|WR +``` + +Additionally, we've added a new column attribute aka `Show` -> `S`. This allows you to now override the default resource column `wide` attribute when set. + + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A) +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#3179](https://github.com/derailed/k9s/issues/3179) Resource name with full api or group displayed (somewhere and sometimes) +* [#3178](https://github.com/derailed/k9s/issues/3178) Cronjobs with the same name in different namespaces appear together +* [#3176](https://github.com/derailed/k9s/issues/3176) Trigger all marked cronjobs +* [#3162](https://github.com/derailed/k9s/issues/3162) Context configs: context directory created under wrong cluster after context switch +* [#3161](https://github.com/derailed/k9s/issues/3161) Force wide-only columns to appear outside of wide view +* [#3147](https://github.com/derailed/k9s/issues/3147) Prompt style is overriden by body +* [#3139](https://github.com/derailed/k9s/issues/3139) CPU/R:L and MEM/R:L columns invalid in views.yaml +* [#3138](https://github.com/derailed/k9s/issues/3138) Subresources are not shown correctly in the RBAC view + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#3182](https://github.com/derailed/k9s/pull/3182) fix: Use the latest version when downloading the Ubuntu deb file +* [#3168](https://github.com/derailed/k9s/pull/3168) fix(history): handle cases where special commands add their command their command to the history +* [#3159](https://github.com/derailed/k9s/pull/3159) Added hard contrast gruvbox skins +* [#3149](https://github.com/derailed/k9s/pull/3149) fix: Pass grv on gotoResource as a String to fix non-default apiGroup list +* [#3149](https://github.com/derailed/k9s/pull/3149) Add externalsecrets plugin +* [#3140](https://github.com/derailed/k9s/pull/3140) fix: Avoid false positive matches in enableRegion (#3093) + + + Ā© 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/cmd/info.go b/cmd/info.go index 8fbb01e5..8110bbb1 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -5,12 +5,13 @@ package cmd import ( "fmt" + "log/slog" "os" "github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" - "github.com/rs/zerolog/log" "github.com/spf13/cobra" "gopkg.in/yaml.v2" ) @@ -60,13 +61,13 @@ func getScreenDumpDirForInfo() string { f, err := os.ReadFile(config.AppConfigFile) if err != nil { - log.Error().Err(err).Msgf("Reads k9s config file %v", err) + slog.Error("Unable to reads k9s config file", slogs.Error, err) return config.AppDumpsDir } var cfg config.Config if err := yaml.Unmarshal(f, &cfg); err != nil { - log.Error().Err(err).Msgf("Unmarshal k9s config %v", err) + slog.Error("Unable to unmarshal k9s config file", slogs.Error, err) return config.AppDumpsDir } if cfg.K9s == nil { diff --git a/cmd/root.go b/cmd/root.go index 610c7c7e..9cc30161 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,22 +6,25 @@ package cmd import ( "errors" "fmt" + "log/slog" "os" "runtime/debug" "strings" - - "github.com/derailed/k9s/internal/config/data" - "k8s.io/client-go/tools/clientcmd/api" + "time" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/view" + + "github.com/lmittmann/tint" + // "github.com/MatusOllah/slogcolor" "github.com/mattn/go-colorable" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/tools/clientcmd/api" ) const ( @@ -78,35 +81,37 @@ func run(cmd *cobra.Command, args []string) error { if err := config.InitLocs(); err != nil { return err } - file, err := os.OpenFile( + logFile, err := os.OpenFile( *k9sFlags.LogFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, data.DefaultFileMod, ) if err != nil { - return fmt.Errorf("Log file %q init failed: %w", *k9sFlags.LogFile, err) + return fmt.Errorf("log file %q init failed: %w", *k9sFlags.LogFile, err) } defer func() { - if file != nil { - _ = file.Close() + if logFile != nil { + _ = logFile.Close() } }() defer func() { if err := recover(); err != nil { - log.Error().Msgf("Boom! %v", err) - log.Error().Msg(string(debug.Stack())) + slog.Error("Boom!! k9s init failed", slogs.Error, err) + slog.Error("", slogs.Stack, string(debug.Stack())) printLogo(color.Red) fmt.Printf("%s", color.Colorize("Boom!! ", color.Red)) fmt.Printf("%v.\n", err) } }() - log.Logger = log.Output(zerolog.ConsoleWriter{Out: file}) - zerolog.SetGlobalLevel(parseLevel(*k9sFlags.LogLevel)) + slog.SetDefault(slog.New(tint.NewHandler(logFile, &tint.Options{ + Level: parseLevel(*k9sFlags.LogLevel), + TimeFormat: time.Kitchen, + }))) cfg, err := loadConfiguration() if err != nil { - log.Error().Err(err).Msgf("Fail to load global/context configuration") + slog.Warn("Fail to load global/context configuration", slogs.Error, err) } app := view.NewApp(cfg) if err := app.Init(version, *k9sFlags.RefreshRate); err != nil { @@ -123,7 +128,7 @@ func run(cmd *cobra.Command, args []string) error { } func loadConfiguration() (*config.Config, error) { - log.Info().Msg("🐶 K9s starting up...") + slog.Info("🐶 K9s starting up...") k8sCfg := client.NewConfig(k8sFlags) k9sCfg := config.NewConfig(k8sCfg) @@ -134,50 +139,43 @@ func loadConfiguration() (*config.Config, error) { } k9sCfg.K9s.Override(k9sFlags) if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { - log.Error().Err(err).Msgf("config refine failed") + slog.Error("Fail to refine k9s config", slogs.Error, err) errs = errors.Join(errs, err) } - conn, err := client.InitConnection(k8sCfg) - + conn, err := client.InitConnection(k8sCfg, slog.Default()) if err != nil { errs = errors.Join(errs, err) } - // Try to access server version if that fail. Connectivity issue? if !conn.CheckConnectivity() { errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName())) } - if !conn.ConnectionOK() { + slog.Warn("šŸ’£ Kubernetes connectivity toast!") errs = errors.Join(errs, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName())) + } else { + slog.Info("āœ… Kubernetes connectivity OK") } - k9sCfg.SetConnection(conn) - - log.Info().Msg("āœ… Kubernetes connectivity") if err := k9sCfg.Save(false); err != nil { - log.Error().Err(err).Msg("Config save") + slog.Error("K9s config save failed", slogs.Error, err) errs = errors.Join(errs, err) } return k9sCfg, errs } -func parseLevel(level string) zerolog.Level { +func parseLevel(level string) slog.Level { switch level { - case "trace": - return zerolog.TraceLevel case "debug": - return zerolog.DebugLevel + return slog.LevelDebug case "warn": - return zerolog.WarnLevel + return slog.LevelWarn case "error": - return zerolog.ErrorLevel - case "fatal": - return zerolog.FatalLevel + return slog.LevelError default: - return zerolog.InfoLevel + return slog.LevelInfo } } @@ -193,7 +191,7 @@ func initK9sFlags() { k9sFlags.LogLevel, "logLevel", "l", config.DefaultLogLevel, - "Specify a log level (error, warn, info, debug, trace)", + "Specify a log level (error, warn, info, debug)", ) rootCmd.Flags().StringVarP( k9sFlags.LogFile, @@ -265,7 +263,7 @@ func initK8sFlags() { rootCmd.Flags().StringVar( k8sFlags.Timeout, "request-timeout", - "", + "5s", "The length of time to wait before giving up on a single server request", ) @@ -376,7 +374,7 @@ func initK8sFlagCompletion() { _ = rootCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, s string) ([]string, cobra.ShellCompDirective) { conn := client.NewConfig(k8sFlags) - if c, err := client.InitConnection(conn); err == nil { + if c, err := client.InitConnection(conn, slog.Default()); err == nil { if nss, err := c.ValidNamespaceNames(); err == nil { return filterFlagCompletions(nss, s) } @@ -391,7 +389,7 @@ func k8sFlagCompletion[T any](picker k8sPickerFn[T]) completeFn { conn := client.NewConfig(k8sFlags) cfg, err := conn.RawConfig() if err != nil { - log.Error().Err(err).Msgf("k8s config getter failed") + slog.Error("K8s raw config getter failed", slogs.Error, err) } return filterFlagCompletions(picker(&cfg), toComplete) diff --git a/go.mod b/go.mod index d9cce2c6..e2481840 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/derailed/k9s -go 1.23.2 +go 1.24.0 require ( github.com/adrg/xdg v0.5.3 github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837 github.com/anchore/grype v0.86.1 - github.com/anchore/syft v1.19.0 + github.com/anchore/syft v1.20.0 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.3.0 github.com/derailed/popeye v0.11.3 @@ -16,12 +16,12 @@ require ( github.com/fsnotify/fsnotify v1.8.0 github.com/fvbommel/sortorder v1.1.0 github.com/go-errors/errors v1.5.1 + github.com/lmittmann/tint v1.0.7 github.com/mattn/go-colorable v0.1.14 github.com/mattn/go-runewidth v0.0.16 github.com/olekukonko/tablewriter v0.0.5 github.com/petergtz/pegomock v2.9.0+incompatible github.com/rakyll/hey v0.1.4 - github.com/rs/zerolog v1.33.0 github.com/sahilm/fuzzy v0.1.1 github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 @@ -69,7 +69,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.11.7 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect - github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/ProtonMail/go-crypto v1.1.5 // indirect github.com/acobaugh/osrelease v0.1.0 // indirect github.com/agext/levenshtein v1.2.1 // indirect github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51 // indirect @@ -79,7 +79,7 @@ require ( github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect - github.com/anchore/packageurl-go v0.1.1-0.20250117185454-edf36a908b10 // indirect + github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 // indirect github.com/anchore/stereoscope v0.0.13 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect @@ -91,9 +91,10 @@ require ( github.com/becheran/wildmatch-go v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect - github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect @@ -118,7 +119,7 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/cli v27.5.0+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v27.5.0+incompatible // indirect + github.com/docker/docker v28.0.0+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -136,7 +137,7 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/fatih/camelcase v1.0.0 // indirect - github.com/felixge/fgprof v0.9.3 // indirect + github.com/felixge/fgprof v0.9.5 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect @@ -146,7 +147,7 @@ require ( github.com/glebarez/sqlite v1.11.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-git/go-git/v5 v5.13.1 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -254,7 +255,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/profile v1.7.0 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect @@ -265,6 +266,7 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rs/zerolog v1.33.0 // indirect github.com/rubenv/sql-migrate v1.7.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/saferwall/pe v1.5.6 // indirect @@ -314,14 +316,14 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.33.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.33.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/term v0.28.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect @@ -340,10 +342,10 @@ require ( k8s.io/component-base v0.32.2 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - modernc.org/libc v1.55.3 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.34.5 // indirect + modernc.org/libc v1.61.13 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.8.2 // indirect + modernc.org/sqlite v1.35.0 // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/kustomize/api v0.18.0 // indirect diff --git a/go.sum b/go.sum index 5b880d10..d86ec2a7 100644 --- a/go.sum +++ b/go.sum @@ -249,8 +249,8 @@ github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= +github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE= @@ -283,12 +283,12 @@ github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/grype v0.86.1 h1:HWpzCOCwjKkwkIEEC5lcKI4yl6GhTF3+Z12tXWYtMoI= github.com/anchore/grype v0.86.1/go.mod h1:k3VnXfi+e/OGx1mTUL733gy3fyB4W/AdHP8fSyQML9w= -github.com/anchore/packageurl-go v0.1.1-0.20250117185454-edf36a908b10 h1:zBedM9ZGYbs/61QC4ZOKxtChx5njXKHgHqDeHuUxrTw= -github.com/anchore/packageurl-go v0.1.1-0.20250117185454-edf36a908b10/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= +github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY= +github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= github.com/anchore/stereoscope v0.0.13 h1:9Ivkh7k+vOeG3JHrt44jOg/8UdZrCvMsSjLQ7trHBig= github.com/anchore/stereoscope v0.0.13/go.mod h1:QfhhFc2pezp5aX/dVJ5qnBFpBUv5+KUTphwaQLxMUig= -github.com/anchore/syft v1.19.0 h1:cUVVdbOHtYCz+581Aq2hhaEbR1MRowbwXyo+Xw+oW20= -github.com/anchore/syft v1.19.0/go.mod h1:QyTWjG0LzowbJVNQj5ZX8UVx17eTkU73Xl7jAf6upE8= +github.com/anchore/syft v1.20.0 h1:4nVM/eiqrb2GJCkW+d1xv8M5mxply8vVblpWOvVCgN8= +github.com/anchore/syft v1.20.0/go.mod h1:h8U0q+Fk7f1d9ay4oa+gDb//AJYFuQftrBLOuS6llz4= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= @@ -326,12 +326,14 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef h1:TSFnfbbu2oAOuWbeDDTtwXWE6z+PmpgbSsMBeV7l0ww= +github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= -github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ= -github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= +github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= @@ -366,9 +368,15 @@ github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoC github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -439,8 +447,8 @@ github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvD github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U= -github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM= +github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -460,8 +468,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= -github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= -github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY= github.com/elliotchance/phpserialize v1.4.0/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= @@ -502,8 +510,9 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= -github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= +github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= +github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= @@ -530,8 +539,8 @@ github.com/gkampitakis/ciinfo v0.3.1 h1:lzjbemlGI4Q+XimPg64ss89x8Mf3xihJqy/0Mgag github.com/gkampitakis/ciinfo v0.3.1/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= -github.com/gkampitakis/go-snaps v0.5.8 h1:BB4ihcyXgJEVO/Pj/P+4bs7pFzsLcEjsfU2+mFdJh1c= -github.com/gkampitakis/go-snaps v0.5.8/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY= +github.com/gkampitakis/go-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ= +github.com/gkampitakis/go-snaps v0.5.11/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= @@ -546,8 +555,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= -github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -584,6 +593,9 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-yaml v1.15.13 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg= github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -681,6 +693,7 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -783,6 +796,7 @@ github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -848,12 +862,15 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= +github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -993,6 +1010,7 @@ github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaL github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 h1:FvA4bwjKpPqik5WsQ8+4z4DKWgA1tO1RTTtNKr5oYNA= github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554/go.mod h1:n73K/hcuJ50MiVznXyN4rde6fZY7naGKWBXOLFTyc94= github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU= @@ -1014,8 +1032,8 @@ github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1H github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -1085,8 +1103,8 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= -github.com/sanity-io/litter v1.5.6 h1:hCFycYzhRnW4niFbbmR7QKdmds69PbVa/sNmEN5euSU= -github.com/sanity-io/litter v1.5.6/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg= +github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= @@ -1282,8 +1300,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1324,8 +1342,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1384,8 +1402,8 @@ golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1509,6 +1527,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1525,14 +1544,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1918,28 +1937,28 @@ k8s.io/metrics v0.32.2 h1:7t/rZzTHFrGa9f94XcgLlm3ToAuJtdlHANcJEHlYl9g= k8s.io/metrics v0.32.2/go.mod h1:VL3nJpzcgB6L5nSljkkzoE0nilZhVgcjCfNRgoylaIQ= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= -modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= -modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= +modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= +modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo= +modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= -modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= -modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= +modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= +modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= +modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.35.0 h1:yQps4fegMnZFdphtzlfQTCNBWtS0CZv48pRpW3RFHRw= +modernc.org/sqlite v1.35.0/go.mod h1:9cr2sicr7jIaWTBKQmAxQLfBv9LL0su4ZTEV+utt3ic= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= diff --git a/internal/client/client.go b/internal/client/client.go index 60ee1892..75184ab2 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -7,13 +7,14 @@ import ( "context" "errors" "fmt" + "log/slog" "os" "path/filepath" "strings" "sync" "time" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" authorizationv1 "k8s.io/api/authorization/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/cache" @@ -34,7 +35,9 @@ const ( cacheNSKey = "validNamespaces" ) -var supportedMetricsAPIVersions = []string{"v1beta1"} +var ( + supportedMetricsAPIVersions = []string{"v1beta1"} +) // NamespaceNames tracks a collection of namespace names. type NamespaceNames map[string]struct{} @@ -50,6 +53,7 @@ type APIClient struct { mx sync.RWMutex cache *cache.LRUExpireCache connOK bool + log *slog.Logger } // NewTestAPIClient for testing ONLY!! @@ -62,15 +66,16 @@ func NewTestAPIClient() *APIClient { // InitConnection initialize connection from command line args. // Checks for connectivity with the api server. -func InitConnection(config *Config) (*APIClient, error) { +func InitConnection(config *Config, log *slog.Logger) (*APIClient, error) { a := APIClient{ config: config, cache: cache.NewLRUExpireCache(cacheSize), connOK: true, + log: log.With(slogs.Subsys, "client"), } err := a.supportsMetricsResources() if err != nil { - log.Error().Err(err).Msgf("Fail to locate metrics-server") + slog.Warn("Fail to locate metrics-server", slogs.Error, err) } if err == nil || errors.Is(err, noMetricServerErr) || errors.Is(err, metricsUnsupportedErr) { return &a, nil @@ -113,7 +118,7 @@ func makeCacheKey(ns, gvr, n string, vv []string) string { func (a *APIClient) ActiveContext() string { c, err := a.config.CurrentContextName() if err != nil { - log.Error().Msgf("Unable to located active cluster") + slog.Error("unable to located active cluster", slogs.Error, err) return "" } return c @@ -158,6 +163,8 @@ func (a *APIClient) CanI(ns, gvr, name string, verbs []string) (auth bool, err e } } + clog := a.log.With(slogs.Subsys, "can") + dial, err := a.Dial() if err != nil { return false, err @@ -169,14 +176,20 @@ func (a *APIClient) CanI(ns, gvr, name string, verbs []string) (auth bool, err e for _, v := range verbs { sar.Spec.ResourceAttributes.Verb = v resp, err := client.Create(ctx, sar, metav1.CreateOptions{}) - log.Trace().Msgf("[CAN] %s(%q/%q) <%v>", gvr, ns, name, verbs) + clog.Debug("[CAN] access", + slogs.GVR, gvr, + slogs.Namespace, ns, + slogs.ResName, name, + slogs.Verb, verbs, + ) if resp != nil { - log.Trace().Msgf(" Spec: %#v", resp.Spec) - log.Trace().Msgf(" Auth: %t [%q]", resp.Status.Allowed, resp.Status.Reason) + clog.Debug("[CAN] reps", + slogs.AuthStatus, resp.Status.Allowed, + slogs.AuthReason, resp.Status.Reason, + ) } - log.Trace().Msgf(" <<%v>>", err) if err != nil { - log.Warn().Err(err).Msgf(" Dial Failed!") + clog.Warn("Auth request failed", slogs.Error, err) a.cache.Add(key, false, cacheExpiry) return auth, err } @@ -220,7 +233,10 @@ func (a *APIClient) ServerVersion() (*version.Info, error) { func (a *APIClient) IsValidNamespace(ns string) bool { ok, err := a.isValidNamespace(ns) if err != nil { - log.Warn().Err(err).Msgf("namespace validation failed for: %q", ns) + slog.Warn("Namespace validation failed", + slogs.Namespace, ns, + slogs.Error, err, + ) } return ok @@ -287,16 +303,15 @@ func (a *APIClient) CheckConnectivity() bool { }() cfg, err := a.config.RESTConfig() - if err != nil { - log.Error().Err(err).Msgf("restConfig load failed") + slog.Error("RestConfig load failed", slogs.Error, err) a.connOK = false return a.connOK } cfg.Timeout = a.config.CallTimeout() client, err := kubernetes.NewForConfig(cfg) if err != nil { - log.Error().Err(err).Msgf("Unable to connect to api server") + slog.Error("Unable to connect to api server", slogs.Error, err) a.setConnOK(false) return a.getConnOK() } @@ -307,7 +322,7 @@ func (a *APIClient) CheckConnectivity() bool { a.reset() } } else { - log.Error().Err(err).Msgf("can't connect to cluster") + slog.Error("Unable to fetch server version", slogs.Error, err) a.setConnOK(false) } @@ -541,13 +556,13 @@ func (a *APIClient) invalidateCache() error { // SwitchContext handles kubeconfig context switches. func (a *APIClient) SwitchContext(name string) error { - log.Debug().Msgf("Switching context %q", name) + slog.Debug("Switching context", slogs.Context, name) if err := a.config.SwitchContext(name); err != nil { return err } if !a.CheckConnectivity() { - log.Debug().Msg("No connectivity, skipping cache invalidation") + slog.Debug("No connectivity, skipping cache invalidation") } else if err := a.invalidateCache(); err != nil { return err } @@ -597,7 +612,7 @@ func (a *APIClient) supportsMetricsResources() error { dial, err := a.Dial() if err != nil { - log.Warn().Err(err).Msgf("Unable to dial discovery API") + slog.Warn("Unable to dial API client for metrics", slogs.Error, err) return err } apiGroups, err := dial.Discovery().ServerGroups() diff --git a/internal/client/config.go b/internal/client/config.go index e9f01000..e34d0e3c 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -19,7 +19,7 @@ import ( ) const ( - defaultCallTimeoutDuration time.Duration = 15 * time.Second + defaultCallTimeoutDuration time.Duration = 10 * time.Second // UsePersistentConfig caches client config to avoid reloads. UsePersistentConfig = true diff --git a/internal/client/config_test.go b/internal/client/config_test.go index c92593af..a9a3386d 100644 --- a/internal/client/config_test.go +++ b/internal/client/config_test.go @@ -5,18 +5,18 @@ package client_test import ( "errors" + "log/slog" "os" "testing" "time" "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "k8s.io/cli-runtime/pkg/genericclioptions" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestCallTimeout(t *testing.T) { @@ -29,7 +29,7 @@ func TestCallTimeout(t *testing.T) { e: 1 * time.Minute, }, "default": { - e: 15 * time.Second, + e: 10 * time.Second, }, } diff --git a/internal/client/gvr.go b/internal/client/gvr.go index ebb7fffd..1d4a4b68 100644 --- a/internal/client/gvr.go +++ b/internal/client/gvr.go @@ -5,11 +5,12 @@ package client import ( "fmt" + "log/slog" "path" "strings" + "github.com/derailed/k9s/internal/slogs" "github.com/fvbommel/sortorder" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -40,7 +41,7 @@ func NewGVR(gvr string) GVR { case 1: r = tokens[0] default: - log.Error().Err(fmt.Errorf("can't parse GVR %q", gvr)).Msg("GVR init failed!") + slog.Error("GVR init failed!", slogs.Error, fmt.Errorf("can't parse GVR %q", gvr)) } return GVR{raw: gvr, g: g, v: v, r: r, sr: sr} @@ -186,7 +187,7 @@ func Can(verbs []string, v string) bool { for _, verb := range verbs { candidates, err := mapVerb(v) if err != nil { - log.Error().Err(err).Msgf("verb mapping failed") + slog.Error("Access verb mapping failed", slogs.Error, err) return false } for _, c := range candidates { diff --git a/internal/client/helpers.go b/internal/client/helpers.go index afea4b3a..d2dbabd5 100644 --- a/internal/client/helpers.go +++ b/internal/client/helpers.go @@ -4,12 +4,14 @@ package client import ( + "log/slog" + "os" "os/user" "path" "regexp" "strings" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -81,7 +83,8 @@ func MetaFQN(m metav1.ObjectMeta) string { func mustHomeDir() string { usr, err := user.Current() if err != nil { - log.Fatal().Err(err).Msg("Die getting user home directory") + slog.Error("Die getting user home directory", slogs.Error, err) + os.Exit(1) } return usr.HomeDir } diff --git a/internal/config/alias.go b/internal/config/alias.go index f189e86e..8172ba97 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -5,14 +5,14 @@ package config import ( "errors" - "fmt" "io/fs" + "log/slog" "os" "sync" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/json" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "gopkg.in/yaml.v2" ) @@ -120,7 +120,7 @@ func (a *Aliases) Load(path string) error { f, err := EnsureAliasesCfgFile() if err != nil { - log.Error().Err(err).Msgf("Unable to gen config aliases") + slog.Error("Unable to gen config aliases", slogs.Error, err) } // load global alias file @@ -146,7 +146,7 @@ func (a *Aliases) LoadFile(path string) error { return err } if err := data.JSONValidator.Validate(json.AliasesSchema, bb); err != nil { - return fmt.Errorf("validation failed for %q: %w", path, err) + slog.Warn("Aliases validation failed", slogs.Error, err) } var aa Aliases @@ -193,7 +193,7 @@ func (a *Aliases) loadDefaultAliases() { // Save alias to disk. func (a *Aliases) Save() error { - log.Debug().Msg("[Config] Saving Aliases...") + slog.Debug("Saving Aliases...") return a.SaveAliases(AppAliasesFile) } diff --git a/internal/config/config.go b/internal/config/config.go index a5da1dfa..a0bfa8b1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,13 +7,14 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "os" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/json" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/view/cmd" - "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" "k8s.io/cli-runtime/pkg/genericclioptions" ) @@ -33,6 +34,16 @@ func NewConfig(ks data.KubeSettings) *Config { } } +// ActiveClusterName returns the corresponding cluster name. +func (c *Config) ActiveClusterName(contextName string) (string, error) { + ct, err := c.settings.GetContext(contextName) + if err != nil { + return "", err + } + + return ct.Cluster, nil +} + // ContextHotkeysPath returns a context specific hotkeys file spec. func (c *Config) ContextHotkeysPath() string { ct, err := c.K9s.ActiveContext() @@ -82,7 +93,7 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c return fmt.Errorf("unable to activate context %q: %w", n, err) } } - log.Debug().Msgf("Active Context %q", c.K9s.ActiveContextName()) + slog.Debug("Using active context", slogs.Context, c.K9s.ActiveContextName()) var ns string switch { @@ -113,7 +124,7 @@ func (c *Config) Reset() { c.K9s.Reset() } -func (c *Config) SetCurrentContext(n string) (*data.Context, error) { +func (c *Config) ActivateContext(n string) (*data.Context, error) { ct, err := c.K9s.ActivateContext(n) if err != nil { return nil, fmt.Errorf("set current context failed. %w", err) @@ -132,7 +143,7 @@ func (c *Config) CurrentContext() (*data.Context, error) { func (c *Config) ActiveNamespace() string { ns, err := c.K9s.ActiveContextNamespace() if err != nil { - log.Error().Err(err).Msgf("Unable to assert active namespace. Using default") + slog.Error("Unable to assert active namespace. Using default", slogs.Error, err) ns = client.DefaultNamespace } @@ -145,7 +156,7 @@ func (c *Config) FavNamespaces() []string { if err != nil { return nil } - ct.Validate(c.conn, c.settings) + ct.Validate(c.conn, c.K9s.getActiveContextName(), ct.ClusterName) return ct.Namespace.Favorites } @@ -153,7 +164,7 @@ func (c *Config) FavNamespaces() []string { // SetActiveNamespace set the active namespace in the current context. func (c *Config) SetActiveNamespace(ns string) error { if ns == client.NotNamespaced { - log.Debug().Msgf("[SetNS] No namespace given. skipping!") + slog.Debug("No namespace given. skipping!", slogs.Namespace, ns) return nil } ct, err := c.K9s.ActiveContext() @@ -251,8 +262,13 @@ func (c *Config) Load(path string, force bool) error { // Save configuration to disk. func (c *Config) Save(force bool) error { - c.Validate() - if err := c.K9s.Save(force); err != nil { + contextName := c.K9s.ActiveContextName() + clusterName, err := c.ActiveClusterName(contextName) + if err != nil { + return fmt.Errorf("unable to locate associated cluster for context %q: %w", contextName, err) + } + c.Validate(contextName, clusterName) + if err := c.K9s.Save(contextName, clusterName, force); err != nil { return err } if _, err := os.Stat(AppConfigFile); errors.Is(err, fs.ErrNotExist) { @@ -267,21 +283,23 @@ func (c *Config) SaveFile(path string) error { if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil { return err } + cfg, err := yaml.Marshal(c) if err != nil { - log.Error().Msgf("[Config] Unable to save K9s config file: %v", err) + slog.Error("Unable to save K9s config file", slogs.Error, err) return err } + slog.Info("[CONFIG] Saving K9s config to disk", slogs.Path, path) return os.WriteFile(path, cfg, data.DefaultFileMod) } // Validate the configuration. -func (c *Config) Validate() { +func (c *Config) Validate(contextName, clusterName string) { if c.K9s == nil { c.K9s = NewK9s(c.conn, c.settings) } - c.K9s.Validate(c.conn, c.settings) + c.K9s.Validate(c.conn, contextName, clusterName) } // Dump for debug... diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 3910eb5c..38cd3235 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -6,6 +6,7 @@ package config_test import ( "errors" "fmt" + "log/slog" "os" "path/filepath" "testing" @@ -16,13 +17,12 @@ import ( "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/mock" m "github.com/petergtz/pegomock" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "k8s.io/cli-runtime/pkg/genericclioptions" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestConfigSave(t *testing.T) { @@ -324,7 +324,7 @@ Invalid type. Expected: boolean, given: string`, } } -func TestConfigSetCurrentContext(t *testing.T) { +func TestConfigActivateContext(t *testing.T) { uu := map[string]struct { cl, ct string err string @@ -344,7 +344,7 @@ func TestConfigSetCurrentContext(t *testing.T) { u := uu[k] t.Run(k, func(t *testing.T) { cfg := mock.NewMockConfig() - ct, err := cfg.SetCurrentContext(u.ct) + ct, err := cfg.ActivateContext(u.ct) if err != nil { assert.Equal(t, u.err, err.Error()) return @@ -529,7 +529,7 @@ func TestConfigValidate(t *testing.T) { cfg.SetConnection(mock.NewMockConnection()) assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) - cfg.Validate() + cfg.Validate("ct-1-1", "cl-1") } func TestConfigLoad(t *testing.T) { @@ -556,7 +556,7 @@ func TestConfigSaveFile(t *testing.T) { cfg.K9s.ReadOnly = true cfg.K9s.Logger.TailCount = 500 cfg.K9s.Logger.BufferSize = 800 - cfg.Validate() + cfg.Validate("ct-1-1", "cl-1") path := filepath.Join("/tmp", "k9s.yaml") assert.NoError(t, cfg.SaveFile(path)) @@ -571,7 +571,7 @@ func TestConfigReset(t *testing.T) { cfg := mock.NewMockConfig() assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) cfg.Reset() - cfg.Validate() + cfg.Validate("ct-1-1", "cl-1") path := filepath.Join("/tmp", "k9s.yaml") assert.NoError(t, cfg.SaveFile(path)) diff --git a/internal/config/data/config.go b/internal/config/data/config.go index a03020f8..bc894406 100644 --- a/internal/config/data/config.go +++ b/internal/config/data/config.go @@ -26,6 +26,7 @@ func NewConfig(ct *api.Context) *Config { } } +// Merge merges configs and updates receiver. func (c *Config) Merge(c1 *Config) { if c1 == nil { return @@ -36,14 +37,14 @@ func (c *Config) Merge(c1 *Config) { } // Validate ensures config is in norms. -func (c *Config) Validate(conn client.Connection, ks KubeSettings) { +func (c *Config) Validate(conn client.Connection, contextName, clusterName string) { c.mx.Lock() defer c.mx.Unlock() if c.Context == nil { c.Context = NewContext() } - c.Context.Validate(conn, ks) + c.Context.Validate(conn, contextName, clusterName) } // Dump used for debugging. diff --git a/internal/config/data/context.go b/internal/config/data/context.go index 358a1aae..1e5c29c5 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -13,24 +13,22 @@ import ( // Context tracks K9s context configuration. type Context struct { - ClusterName string `yaml:"cluster,omitempty"` - ReadOnly *bool `yaml:"readOnly,omitempty"` - Skin string `yaml:"skin,omitempty"` - Namespace *Namespace `yaml:"namespace"` - View *View `yaml:"view"` - FeatureGates FeatureGates `yaml:"featureGates"` - PortForwardAddress string `yaml:"portForwardAddress"` - Proxy *Proxy `yaml:"proxy"` - mx sync.RWMutex + ClusterName string `yaml:"cluster,omitempty"` + ReadOnly *bool `yaml:"readOnly,omitempty"` + Skin string `yaml:"skin,omitempty"` + Namespace *Namespace `yaml:"namespace"` + View *View `yaml:"view"` + FeatureGates FeatureGates `yaml:"featureGates"` + Proxy *Proxy `yaml:"proxy"` + mx sync.RWMutex } // NewContext creates a new cluster configuration. func NewContext() *Context { return &Context{ - Namespace: NewNamespace(), - View: NewView(), - PortForwardAddress: defaultPFAddress(), - FeatureGates: NewFeatureGates(), + Namespace: NewNamespace(), + View: NewView(), + FeatureGates: NewFeatureGates(), } } @@ -71,19 +69,11 @@ func (c *Context) GetClusterName() string { } // Validate ensures a context config is tip top. -func (c *Context) Validate(conn client.Connection, ks KubeSettings) { +func (c *Context) Validate(conn client.Connection, contextName, clusterName string) { c.mx.Lock() defer c.mx.Unlock() - if a := os.Getenv(envPFAddress); a != "" { - c.PortForwardAddress = a - } - if c.PortForwardAddress == "" { - c.PortForwardAddress = defaultPFAddress() - } - if cl, err := ks.CurrentClusterName(); err == nil { - c.ClusterName = cl - } + c.ClusterName = clusterName if b := os.Getenv(envFGNodeShell); b != "" { c.FeatureGates.NodeShell = defaultFGNodeShell() } diff --git a/internal/config/data/context_test.go b/internal/config/data/context_test.go index d66c8d56..30318ead 100644 --- a/internal/config/data/context_test.go +++ b/internal/config/data/context_test.go @@ -13,7 +13,7 @@ import ( func TestClusterValidate(t *testing.T) { c := data.NewContext() - c.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + c.Validate(mock.NewMockConnection(), "ct-1", "cl-1") assert.Equal(t, "po", c.View.Active) assert.Equal(t, "default", c.Namespace.Active) @@ -23,7 +23,7 @@ func TestClusterValidate(t *testing.T) { func TestClusterValidateEmpty(t *testing.T) { c := data.NewContext() - c.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + c.Validate(mock.NewMockConnection(), "ct-1", "cl-1") assert.Equal(t, "po", c.View.Active) assert.Equal(t, "default", c.Namespace.Active) diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go index 3b045578..6165e5ad 100644 --- a/internal/config/data/dir.go +++ b/internal/config/data/dir.go @@ -7,12 +7,13 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "os" "path/filepath" "sync" "github.com/derailed/k9s/internal/config/json" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "gopkg.in/yaml.v2" "k8s.io/client-go/tools/clientcmd/api" ) @@ -31,18 +32,19 @@ func NewDir(root string) *Dir { } // Load loads context configuration. -func (d *Dir) Load(n string, ct *api.Context) (*Config, error) { +func (d *Dir) Load(contextName string, ct *api.Context) (*Config, error) { if ct == nil { return nil, errors.New("api.Context must not be nil") } - var path = filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, n), MainConfigFile) + path := filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, contextName), MainConfigFile) + slog.Debug("[CONFIG] Loading context config from disk", slogs.Path, path, slogs.Cluster, ct.Cluster, slogs.Context, contextName) f, err := os.Stat(path) if errors.Is(err, fs.ErrPermission) { return nil, err } if errors.Is(err, fs.ErrNotExist) || (f != nil && f.Size() == 0) { - log.Debug().Msgf("Context config not found! Generating... %q", path) + slog.Debug("Context config not found! Generating..", slogs.Path, path) return d.genConfig(path, ct) } if err != nil { @@ -89,7 +91,10 @@ func (d *Dir) loadConfig(path string) (*Config, error) { return nil, err } if err := JSONValidator.Validate(json.ContextSchema, bb); err != nil { - return nil, fmt.Errorf("validation failed for %q: %w", path, err) + slog.Warn("Validation failed. Please update your config and restart!", + slogs.Path, path, + slogs.Error, err, + ) } var cfg Config diff --git a/internal/config/data/dir_test.go b/internal/config/data/dir_test.go index b780a585..e4e5118c 100644 --- a/internal/config/data/dir_test.go +++ b/internal/config/data/dir_test.go @@ -4,20 +4,20 @@ package data_test import ( + "log/slog" "os" "strings" "testing" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/mock" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v2" "k8s.io/cli-runtime/pkg/genericclioptions" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestDirLoad(t *testing.T) { diff --git a/internal/config/data/helpers.go b/internal/config/data/helpers.go index bff321a0..af086e5b 100644 --- a/internal/config/data/helpers.go +++ b/internal/config/data/helpers.go @@ -11,11 +11,7 @@ import ( "regexp" ) -const ( - envPFAddress = "K9S_DEFAULT_PF_ADDRESS" - envFGNodeShell = "K9S_FEATURE_GATE_NODE_SHELL" - defaultPortFwdAddress = "localhost" -) +const envFGNodeShell = "K9S_FEATURE_GATE_NODE_SHELL" var invalidPathCharsRX = regexp.MustCompile(`[:/]+`) @@ -29,14 +25,6 @@ func SanitizeFileName(name string) string { return invalidPathCharsRX.ReplaceAllString(name, "-") } -func defaultPFAddress() string { - if a := os.Getenv(envPFAddress); a != "" { - return a - } - - return defaultPortFwdAddress -} - func defaultFGNodeShell() bool { if a := os.Getenv(envFGNodeShell); a != "" { return a == "true" diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index 9b0d5ebf..cf9aba0f 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -4,11 +4,12 @@ package data import ( + "log/slog" "slices" "sync" "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) const ( @@ -67,7 +68,10 @@ func (n *Namespace) Validate(c client.Connection) { } for _, ns := range n.Favorites { if !c.IsValidNamespace(ns) { - log.Debug().Msgf("[Namespace] Invalid favorite found '%s' - %t", ns, n.isAllNamespaces()) + slog.Debug("Invalid favorite found", + slogs.Namespace, ns, + slogs.AllNS, n.isAllNamespaces(), + ) n.rmFavNS(ns) } } @@ -136,7 +140,7 @@ func (n *Namespace) rmFavNS(ns string) { func (n *Namespace) trimFavNs() { if len(n.Favorites) > MaxFavoritesNS { - log.Debug().Msgf("[Namespace] Number of favorite exceeds hard limit of %v. Trimming.", MaxFavoritesNS) + slog.Debug("Number of favorite exceeds hard limit. Trimming.", slogs.Max, MaxFavoritesNS) n.Favorites = n.Favorites[:MaxFavoritesNS] } } diff --git a/internal/config/data/testdata/configs/aws_ct.yaml b/internal/config/data/testdata/configs/aws_ct.yaml index 0f81b93b..a0f86f9e 100644 --- a/internal/config/data/testdata/configs/aws_ct.yaml +++ b/internal/config/data/testdata/configs/aws_ct.yaml @@ -9,4 +9,3 @@ k9s: active: po featureGates: nodeShell: false - portForwardAddress: localhost diff --git a/internal/config/data/testdata/configs/ct-1-1.yaml b/internal/config/data/testdata/configs/ct-1-1.yaml index 091b9071..e961fdbd 100644 --- a/internal/config/data/testdata/configs/ct-1-1.yaml +++ b/internal/config/data/testdata/configs/ct-1-1.yaml @@ -13,4 +13,3 @@ k9s: active: dp featureGates: nodeShell: true - portForwardAddress: localhost diff --git a/internal/config/data/testdata/configs/ct-1-2.yaml b/internal/config/data/testdata/configs/ct-1-2.yaml index e7bd28f5..863ac8ad 100644 --- a/internal/config/data/testdata/configs/ct-1-2.yaml +++ b/internal/config/data/testdata/configs/ct-1-2.yaml @@ -11,4 +11,3 @@ k9s: active: po featureGates: nodeShell: false - portForwardAddress: localhost diff --git a/internal/config/data/testdata/configs/ct-2-1.yaml b/internal/config/data/testdata/configs/ct-2-1.yaml index 5a2e25be..230b2a7e 100644 --- a/internal/config/data/testdata/configs/ct-2-1.yaml +++ b/internal/config/data/testdata/configs/ct-2-1.yaml @@ -12,4 +12,3 @@ k9s: active: svc featureGates: nodeShell: true - portForwardAddress: fred diff --git a/internal/config/data/testdata/configs/def_ct.yaml b/internal/config/data/testdata/configs/def_ct.yaml index a69eb346..9fcf13a2 100644 --- a/internal/config/data/testdata/configs/def_ct.yaml +++ b/internal/config/data/testdata/configs/def_ct.yaml @@ -9,4 +9,3 @@ k9s: active: po featureGates: nodeShell: false - portForwardAddress: localhost diff --git a/internal/config/data/testdata/data/k9s/cl-1/ct-1-1/config.yaml b/internal/config/data/testdata/data/k9s/cl-1/ct-1-1/config.yaml index 091b9071..e961fdbd 100644 --- a/internal/config/data/testdata/data/k9s/cl-1/ct-1-1/config.yaml +++ b/internal/config/data/testdata/data/k9s/cl-1/ct-1-1/config.yaml @@ -13,4 +13,3 @@ k9s: active: dp featureGates: nodeShell: true - portForwardAddress: localhost diff --git a/internal/config/data/testdata/data/k9s/cl-1/ct-1-2/config.yaml b/internal/config/data/testdata/data/k9s/cl-1/ct-1-2/config.yaml index e7bd28f5..863ac8ad 100644 --- a/internal/config/data/testdata/data/k9s/cl-1/ct-1-2/config.yaml +++ b/internal/config/data/testdata/data/k9s/cl-1/ct-1-2/config.yaml @@ -11,4 +11,3 @@ k9s: active: po featureGates: nodeShell: false - portForwardAddress: localhost diff --git a/internal/config/data/testdata/data/k9s/cl-2/ct-2-1/config.yaml b/internal/config/data/testdata/data/k9s/cl-2/ct-2-1/config.yaml index 5a2e25be..230b2a7e 100644 --- a/internal/config/data/testdata/data/k9s/cl-2/ct-2-1/config.yaml +++ b/internal/config/data/testdata/data/k9s/cl-2/ct-2-1/config.yaml @@ -12,4 +12,3 @@ k9s: active: svc featureGates: nodeShell: true - portForwardAddress: fred diff --git a/internal/config/files.go b/internal/config/files.go index 2b246d51..831a5739 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -7,13 +7,14 @@ import ( _ "embed" "errors" "io/fs" + "log/slog" "os" "path/filepath" "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/slogs" "github.com/adrg/xdg" - "github.com/rs/zerolog/log" ) const ( @@ -126,19 +127,31 @@ func initK9sEnvLocs() error { AppDumpsDir = filepath.Join(AppConfigDir, "screen-dumps") if err := data.EnsureFullPath(AppDumpsDir, data.DefaultDirMod); err != nil { - log.Warn().Err(err).Msgf("Unable to create screen-dumps dir: %s", AppDumpsDir) + slog.Warn("Unable to create screen-dumps dir", + slogs.Dir, AppDumpsDir, + slogs.Error, err, + ) } AppBenchmarksDir = filepath.Join(AppConfigDir, "benchmarks") if err := data.EnsureFullPath(AppBenchmarksDir, data.DefaultDirMod); err != nil { - log.Warn().Err(err).Msgf("Unable to create benchmarks dir: %s", AppBenchmarksDir) + slog.Warn("Unable to create benchmarks dir", + slogs.Dir, AppBenchmarksDir, + slogs.Error, err, + ) } AppSkinsDir = filepath.Join(AppConfigDir, "skins") if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil { - log.Warn().Err(err).Msgf("Unable to create skins dir: %s", AppSkinsDir) + slog.Warn("Unable to create skins dir", + slogs.Dir, AppSkinsDir, + slogs.Error, err, + ) } AppContextsDir = filepath.Join(AppConfigDir, "clusters") if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil { - log.Warn().Err(err).Msgf("Unable to create clusters dir: %s", AppContextsDir) + slog.Warn("Unable to create clusters dir", + slogs.Dir, AppContextsDir, + slogs.Error, err, + ) } AppConfigFile = filepath.Join(AppConfigDir, data.MainConfigFile) @@ -170,7 +183,7 @@ func initXDGLocs() error { AppSkinsDir = filepath.Join(AppConfigDir, "skins") if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil { - log.Warn().Err(err).Msgf("No skins dir detected") + slog.Warn("No skins dir detected", slogs.Error, err) } AppDumpsDir, err = xdg.StateFile(filepath.Join(AppName, "screen-dumps")) @@ -180,7 +193,10 @@ func initXDGLocs() error { AppBenchmarksDir, err = xdg.StateFile(filepath.Join(AppName, "benchmarks")) if err != nil { - log.Warn().Err(err).Msgf("No benchmarks dir detected") + slog.Warn("No benchmarks dir detected", + slogs.Dir, AppBenchmarksDir, + slogs.Error, err, + ) } dataDir, err := xdg.DataFile(AppName) @@ -189,7 +205,10 @@ func initXDGLocs() error { } AppContextsDir = filepath.Join(dataDir, "clusters") if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil { - log.Warn().Err(err).Msgf("No context dir detected") + slog.Warn("No context dir detected", + slogs.Dir, AppContextsDir, + slogs.Error, err, + ) } return nil diff --git a/internal/config/helpers.go b/internal/config/helpers.go index 165885d2..cb0d7f4f 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -4,11 +4,17 @@ package config import ( + "log/slog" "os" "os/user" "path/filepath" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" +) + +const ( + envPFAddress = "K9S_DEFAULT_PF_ADDRESS" + defaultPortFwdAddress = "localhost" ) // IsBoolSet checks if a bool ptr is set. @@ -54,7 +60,16 @@ func MustK9sUser() string { if envUsr != "" { return envUsr } - log.Fatal().Err(err).Msg("Die on retrieving user info") + slog.Error("Die on retrieving user info", slogs.Error, err) + os.Exit(1) } return usr.Username } + +func defaultPFAddress() string { + if a := os.Getenv(envPFAddress); a != "" { + return a + } + + return defaultPortFwdAddress +} diff --git a/internal/config/hotkey.go b/internal/config/hotkey.go index 65a651e4..b1695085 100644 --- a/internal/config/hotkey.go +++ b/internal/config/hotkey.go @@ -5,12 +5,13 @@ package config import ( "errors" - "fmt" "io/fs" + "log/slog" "os" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/json" + "github.com/derailed/k9s/internal/slogs" "gopkg.in/yaml.v2" ) @@ -57,7 +58,10 @@ func (h HotKeys) LoadHotKeys(path string) error { return err } if err := data.JSONValidator.Validate(json.HotkeysSchema, bb); err != nil { - return fmt.Errorf("validation failed for %q: %w", path, err) + slog.Warn("Validation failed. Please update your config and restart.", + slogs.Path, path, + slogs.Error, err, + ) } var hh HotKeys diff --git a/internal/config/json/schemas/context.json b/internal/config/json/schemas/context.json index b9545b1a..71a6a7dc 100644 --- a/internal/config/json/schemas/context.json +++ b/internal/config/json/schemas/context.json @@ -10,7 +10,6 @@ "cluster": { "type": "string" }, "readOnly": {"type": "boolean"}, "skin": { "type": "string" }, - "portForwardAddress": { "type": "string" }, "proxy": { "oneOf": [ { "type": "null" }, diff --git a/internal/config/json/schemas/k9s.json b/internal/config/json/schemas/k9s.json index e6fc4d83..6c842cf7 100644 --- a/internal/config/json/schemas/k9s.json +++ b/internal/config/json/schemas/k9s.json @@ -15,6 +15,7 @@ "noExitOnCtrlC": { "type": "boolean" }, "skipLatestRevCheck": { "type": "boolean" }, "disablePodCounting": { "type": "boolean" }, + "portForwardAddress": { "type": "string" }, "ui": { "type": "object", "additionalProperties": false, diff --git a/internal/config/json/testdata/context/cool.yaml b/internal/config/json/testdata/context/cool.yaml index fd2c6771..d37bc63f 100644 --- a/internal/config/json/testdata/context/cool.yaml +++ b/internal/config/json/testdata/context/cool.yaml @@ -12,4 +12,3 @@ k9s: active: pod featureGates: nodeShell: false - portForwardAddress: localhost diff --git a/internal/config/json/testdata/context/toast.yaml b/internal/config/json/testdata/context/toast.yaml index 997914b4..37d78d35 100644 --- a/internal/config/json/testdata/context/toast.yaml +++ b/internal/config/json/testdata/context/toast.yaml @@ -13,4 +13,3 @@ k9s: fred: blee featureGates: nodeShell: false - portForwardAddress: localhost diff --git a/internal/config/json/validator.go b/internal/config/json/validator.go index e9bb0211..5c6e44bd 100644 --- a/internal/config/json/validator.go +++ b/internal/config/json/validator.go @@ -8,9 +8,10 @@ import ( _ "embed" "errors" "fmt" + "log/slog" "slices" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "github.com/xeipuuv/gojsonschema" "gopkg.in/yaml.v3" ) @@ -89,9 +90,15 @@ func NewValidator() *Validator { func (v *Validator) register() { v.loader = gojsonschema.NewSchemaLoader() v.loader.Validate = true + + clog := slog.With(slogs.Subsys, "schema") + for k, s := range v.schemas { if err := v.loader.AddSchema(k, s); err != nil { - log.Error().Err(err).Msgf("schema initialization failed: %q", k) + clog.Error("Schema initialization failed", + slogs.SchemaFile, k, + slogs.Error, err, + ) } } } diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 5cc2ec41..8579b682 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "net/http" "net/url" "os" @@ -15,7 +16,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config/data" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) // K9s tracks K9s configuration options. @@ -26,6 +27,7 @@ type K9s struct { MaxConnRetry int `json:"maxConnRetry" yaml:"maxConnRetry"` ReadOnly bool `json:"readOnly" yaml:"readOnly"` NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"` + PortForwardAddress string `yaml:"portForwardAddress"` UI UI `json:"ui" yaml:"ui"` SkipLatestRevCheck bool `json:"skipLatestRevCheck" yaml:"skipLatestRevCheck"` DisablePodCounting bool `json:"disablePodCounting" yaml:"disablePodCounting"` @@ -46,24 +48,40 @@ type K9s struct { conn client.Connection ks data.KubeSettings mx sync.RWMutex + contextSwitch bool } // NewK9s create a new K9s configuration. func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s { return &K9s{ - RefreshRate: defaultRefreshRate, - MaxConnRetry: defaultMaxConnRetry, - ScreenDumpDir: AppDumpsDir, - Logger: NewLogger(), - Thresholds: NewThreshold(), - ShellPod: NewShellPod(), - ImageScans: NewImageScans(), - dir: data.NewDir(AppContextsDir), - conn: conn, - ks: ks, + RefreshRate: defaultRefreshRate, + MaxConnRetry: defaultMaxConnRetry, + ScreenDumpDir: AppDumpsDir, + Logger: NewLogger(), + Thresholds: NewThreshold(), + PortForwardAddress: defaultPFAddress(), + ShellPod: NewShellPod(), + ImageScans: NewImageScans(), + dir: data.NewDir(AppContextsDir), + conn: conn, + ks: ks, } } +func (k *K9s) ToggleContextSwitch(b bool) { + k.mx.Lock() + defer k.mx.Unlock() + + k.contextSwitch = b +} + +func (k *K9s) getContextSwitch() bool { + k.mx.Lock() + defer k.mx.Unlock() + + return k.contextSwitch +} + func (k *K9s) resetConnection(conn client.Connection) { k.mx.Lock() defer k.mx.Unlock() @@ -72,17 +90,15 @@ func (k *K9s) resetConnection(conn client.Connection) { } // Save saves the k9s config to disk. -func (k *K9s) Save(force bool) error { - if k.getActiveConfig() == nil { - log.Warn().Msgf("Save failed. no active config detected") - return nil - } +func (k *K9s) Save(contextName, clusterName string, force bool) error { path := filepath.Join( AppContextsDir, - data.SanitizeContextSubpath(k.activeConfig.Context.GetClusterName(), k.getActiveContextName()), + data.SanitizeContextSubpath(clusterName, contextName), data.MainConfigFile, ) + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) || force { + slog.Debug("[CONFIG] Saving context config to disk", slogs.Path, path, slogs.Cluster, k.getActiveConfig().Context.GetClusterName(), slogs.Context, k.getActiveContextName()) return k.dir.Save(path, k.getActiveConfig()) } @@ -168,12 +184,9 @@ func (k *K9s) ActiveContext() (*data.Context, error) { if cfg := k.getActiveConfig(); cfg != nil && cfg.Context != nil { return cfg.Context, nil } - ct, err := k.ActivateContext(k.getActiveContextName()) - if err != nil { - return nil, err - } + ct, err := k.ActivateContext(k.ActiveContextName()) - return ct, nil + return ct, err } func (k *K9s) setActiveConfig(c *data.Config) { @@ -205,14 +218,14 @@ func (k *K9s) getActiveContextName() string { } // ActivateContext initializes the active context if not present. -func (k *K9s) ActivateContext(n string) (*data.Context, error) { - k.setActiveContextName(n) - ct, err := k.ks.GetContext(n) +func (k *K9s) ActivateContext(contextName string) (*data.Context, error) { + k.setActiveContextName(contextName) + ct, err := k.ks.GetContext(contextName) if err != nil { return nil, err } - cfg, err := k.dir.Load(n, ct) + cfg, err := k.dir.Load(contextName, ct) if err != nil { return nil, err } @@ -220,7 +233,7 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { if cfg.Context.Proxy != nil { k.ks.SetProxy(func(*http.Request) (*url.URL, error) { - log.Debug().Msgf("[Proxy]: %s", cfg.Context.Proxy.Address) + slog.Debug("Using proxy address", slogs.Address, cfg.Context.Proxy.Address) return url.Parse(cfg.Context.Proxy.Address) }) @@ -229,25 +242,25 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { // already has an API connection object so we just set the proxy to // avoid recreation using client.InitConnection k.conn.Config().SetProxy(func(*http.Request) (*url.URL, error) { - log.Debug().Msgf("[Proxy]: %s", cfg.Context.Proxy.Address) + slog.Debug("Setting proxy address", slogs.Address, cfg.Context.Proxy.Address) return url.Parse(cfg.Context.Proxy.Address) }) if !k.conn.CheckConnectivity() { - return nil, fmt.Errorf("unable to connect to context %q", n) + return nil, fmt.Errorf("unable to connect to context %q", contextName) } } } - k.Validate(k.conn, k.ks) + k.Validate(k.conn, contextName, ct.Cluster) // If the context specifies a namespace, use it! if ns := ct.Namespace; ns != client.BlankNamespace { k.getActiveConfig().Context.Namespace.Active = ns - } else if k.activeConfig.Context.Namespace.Active == "" { + } else if k.getActiveConfig().Context.Namespace.Active == "" { k.getActiveConfig().Context.Namespace.Active = client.DefaultNamespace } if k.getActiveConfig().Context == nil { - return nil, fmt.Errorf("context activation failed for: %s", n) + return nil, fmt.Errorf("context activation failed for: %s", contextName) } return k.getActiveConfig().Context, nil @@ -255,6 +268,10 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { // Reload reloads the context config from disk. func (k *K9s) Reload() error { + // Switching context skipping reload... + if k.getContextSwitch() { + return nil + } ct, err := k.ks.GetContext(k.getActiveContextName()) if err != nil { return err @@ -265,7 +282,7 @@ func (k *K9s) Reload() error { return err } k.setActiveConfig(cfg) - k.getActiveConfig().Validate(k.conn, k.ks) + k.getActiveConfig().Validate(k.conn, k.getActiveContextName(), ct.Cluster) return nil } @@ -340,7 +357,7 @@ func (k *K9s) IsReadOnly() bool { } // Validate the current configuration. -func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { +func (k *K9s) Validate(c client.Connection, contextName, clusterName string) { if k.RefreshRate <= 0 { k.RefreshRate = defaultRefreshRate } @@ -348,16 +365,21 @@ func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { k.MaxConnRetry = defaultMaxConnRetry } + if a := os.Getenv(envPFAddress); a != "" { + k.PortForwardAddress = a + } + if k.PortForwardAddress == "" { + k.PortForwardAddress = defaultPFAddress() + } + if k.getActiveConfig() == nil { - if n, err := ks.CurrentContextName(); err == nil { - _, _ = k.ActivateContext(n) - } + _, _ = k.ActivateContext(contextName) } k.ShellPod = k.ShellPod.Validate() k.Logger = k.Logger.Validate() k.Thresholds = k.Thresholds.Validate() if cfg := k.getActiveConfig(); cfg != nil { - cfg.Validate(c, ks) + cfg.Validate(c, contextName, clusterName) } } diff --git a/internal/config/mock/test_helpers.go b/internal/config/mock/test_helpers.go index c8f954fb..952de7f8 100644 --- a/internal/config/mock/test_helpers.go +++ b/internal/config/mock/test_helpers.go @@ -36,6 +36,9 @@ func EnsureDir(d string) error { } func NewMockConfig() *config.Config { + if _, err := os.Stat("/tmp/test"); errors.Is(err, os.ErrExist) { + os.RemoveAll("/tmp/test") + } config.AppContextsDir = "/tmp/test" cl, ct := "cl-1", "ct-1-1" flags := genericclioptions.ConfigFlags{ diff --git a/internal/config/plugin.go b/internal/config/plugin.go index fc14d1fc..2f6773e2 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -7,14 +7,15 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "os" "path/filepath" "strings" + "github.com/adrg/xdg" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/json" - - "github.com/adrg/xdg" + "github.com/derailed/k9s/internal/slogs" "gopkg.in/yaml.v2" ) @@ -113,7 +114,10 @@ func (p *Plugins) load(path string) error { return err } if err := data.JSONValidator.Validate(json.PluginsSchema, bb); err != nil { - return fmt.Errorf("validation failed for %q: %w", path, err) + slog.Warn("Validation failed. Please update your config and restart!", + slogs.Path, path, + slogs.Error, err, + ) } var pp Plugins if err := yaml.Unmarshal(bb, &pp); err != nil { diff --git a/internal/config/testdata/configs/default.yaml b/internal/config/testdata/configs/default.yaml index 937972f0..bf3a9f91 100644 --- a/internal/config/testdata/configs/default.yaml +++ b/internal/config/testdata/configs/default.yaml @@ -5,6 +5,7 @@ k9s: maxConnRetry: 5 readOnly: false noExitOnCtrlC: false + portForwardAddress: localhost ui: enableMouse: false headless: false diff --git a/internal/config/testdata/configs/expected.yaml b/internal/config/testdata/configs/expected.yaml index e7c20911..b83455ad 100644 --- a/internal/config/testdata/configs/expected.yaml +++ b/internal/config/testdata/configs/expected.yaml @@ -5,6 +5,7 @@ k9s: maxConnRetry: 5 readOnly: true noExitOnCtrlC: false + portForwardAddress: localhost ui: enableMouse: false headless: false diff --git a/internal/config/testdata/configs/k9s.yaml b/internal/config/testdata/configs/k9s.yaml index c33ed9f6..5af410fd 100644 --- a/internal/config/testdata/configs/k9s.yaml +++ b/internal/config/testdata/configs/k9s.yaml @@ -5,6 +5,7 @@ k9s: maxConnRetry: 5 readOnly: false noExitOnCtrlC: false + portForwardAddress: localhost ui: enableMouse: false headless: false diff --git a/internal/config/testdata/views/views.yaml b/internal/config/testdata/views/views.yaml index b6debac3..5ce6a64d 100644 --- a/internal/config/testdata/views/views.yaml +++ b/internal/config/testdata/views/views.yaml @@ -5,3 +5,15 @@ views: - NAME - AGE - IP + + v1/pods@default: + columns: + - NAME + - IP + - AGE + + v1/pods@ns*: + columns: + - AGE + - NAME + - IP diff --git a/internal/config/views.go b/internal/config/views.go index 86b1ef3c..11b3ceae 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -8,13 +8,16 @@ import ( "errors" "fmt" "io/fs" + "log/slog" + "maps" "os" + "regexp" "slices" "strings" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/json" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "gopkg.in/yaml.v2" ) @@ -22,6 +25,9 @@ import ( type ViewConfigListener interface { // ViewSettingsChanged notifies listener the view configuration changed. ViewSettingsChanged(*ViewSetting) + + // GetNamespace return the view namespace + GetNamespace() string } // ViewSetting represents a view configuration. @@ -50,13 +56,19 @@ func (v *ViewSetting) SortCol() (string, bool, error) { return tt[0], tt[1] == "asc", nil } +// Equals checks if two view settings are equal. func (v *ViewSetting) Equals(vs *ViewSetting) bool { + if v == nil && vs == nil { + return true + } if v == nil || vs == nil { return false } + if c := slices.Compare(v.Columns, vs.Columns); c != 0 { return false } + return cmp.Compare(v.SortColumn, vs.SortColumn) == 0 } @@ -91,7 +103,10 @@ func (v *CustomView) Load(path string) error { return err } if err := data.JSONValidator.Validate(json.ViewsSchema, bb); err != nil { - return fmt.Errorf("validation failed for %q: %w", path, err) + slog.Warn("Validation failed. Please update your config and restart!", + slogs.Path, path, + slogs.Error, err, + ) } var in CustomView if err := yaml.Unmarshal(bb, &in); err != nil { @@ -116,11 +131,41 @@ func (v *CustomView) RemoveListener(gvr string) { func (v *CustomView) fireConfigChanged() { for gvr, list := range v.listeners { - if vs, ok := v.Views[gvr]; ok { - log.Debug().Msgf("Reloading custom view settings for %s", gvr) - list.ViewSettingsChanged(&vs) - } else { + if vs := v.getVS(gvr, list.GetNamespace()); vs == nil { list.ViewSettingsChanged(nil) + } else { + slog.Debug("Reloading custom view settings", slogs.GVR, gvr) + list.ViewSettingsChanged(vs) } } } + +func (v *CustomView) getVS(gvr, ns string) *ViewSetting { + k := gvr + if ns != "" { + k += "@" + ns + } + + for key := range maps.Keys(v.Views) { + if !strings.HasPrefix(key, gvr) { + continue + } + + switch { + case key == k: + vs := v.Views[key] + return &vs + case strings.Contains(key, "@"): + tt := strings.Split(key, "@") + if len(tt) != 2 { + break + } + if rx, err := regexp.Compile(tt[1]); err == nil && rx.MatchString(k) { + vs := v.Views[key] + return &vs + } + } + } + + return nil +} diff --git a/internal/config/views_int_test.go b/internal/config/views_int_test.go new file mode 100644 index 00000000..63e532ce --- /dev/null +++ b/internal/config/views_int_test.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCustomView_getVS(t *testing.T) { + uu := map[string]struct { + cv *CustomView + gvr, ns string + e *ViewSetting + }{ + "empty": {}, + + "gvr": { + gvr: "v1/pods", + e: &ViewSetting{ + Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"}, + }, + }, + + "gvr+ns": { + gvr: "v1/pods", + ns: "default", + e: &ViewSetting{ + Columns: []string{"NAME", "IP", "AGE"}, + }, + }, + + "rx": { + gvr: "v1/pods", + ns: "ns-fred", + e: &ViewSetting{ + Columns: []string{"AGE", "NAME", "IP"}, + }, + }, + + "toast-no-ns": { + gvr: "v1/pods", + ns: "zorg", + }, + + "toast-no-res": { + gvr: "v1/services", + ns: "zorg", + }, + } + + v := NewCustomView() + assert.NoError(t, v.Load("testdata/views/views.yaml")) + for k, u := range uu { + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, v.getVS(u.gvr, u.ns)) + }) + } +} diff --git a/internal/config/views_test.go b/internal/config/views_test.go index 902eda50..b5e22cb0 100644 --- a/internal/config/views_test.go +++ b/internal/config/views_test.go @@ -4,37 +4,117 @@ package config_test import ( + "log/slog" "testing" "github.com/derailed/k9s/internal/config" "github.com/stretchr/testify/assert" ) -func TestViewSettingsLoad(t *testing.T) { - cfg := config.NewCustomView() - - assert.Nil(t, cfg.Load("testdata/views/views.yaml")) - assert.Equal(t, 1, len(cfg.Views)) - assert.Equal(t, 4, len(cfg.Views["v1/pods"].Columns)) +func init() { + slog.SetDefault(slog.New(slog.DiscardHandler)) } -func TestViewSetting_Equals(t *testing.T) { - tests := []struct { - v1, v2 *config.ViewSetting - equals bool +func TestCustomViewLoad(t *testing.T) { + uu := map[string]struct { + cv *config.CustomView + path string + key string + e []string }{ - {nil, nil, false}, - {&config.ViewSetting{}, nil, false}, - {nil, &config.ViewSetting{}, false}, - {&config.ViewSetting{}, &config.ViewSetting{}, true}, - {&config.ViewSetting{Columns: []string{"A"}}, &config.ViewSetting{}, false}, - {&config.ViewSetting{Columns: []string{"A"}}, &config.ViewSetting{Columns: []string{"A"}}, true}, - {&config.ViewSetting{Columns: []string{"A"}}, &config.ViewSetting{Columns: []string{"B"}}, false}, - {&config.ViewSetting{SortColumn: "A"}, &config.ViewSetting{SortColumn: "B"}, false}, - {&config.ViewSetting{SortColumn: "A"}, &config.ViewSetting{SortColumn: "A"}, true}, + "empty": {}, + + "gvr": { + path: "testdata/views/views.yaml", + key: "v1/pods", + e: []string{"NAMESPACE", "NAME", "AGE", "IP"}, + }, + + "gvr+ns": { + path: "testdata/views/views.yaml", + key: "v1/pods@default", + e: []string{"NAME", "IP", "AGE"}, + }, } - for _, tt := range tests { - assert.Equalf(t, tt.equals, tt.v1.Equals(tt.v2), "%#v and %#v", tt.v1, tt.v2) + for k, u := range uu { + t.Run(k, func(t *testing.T) { + cfg := config.NewCustomView() + + assert.NoError(t, cfg.Load(u.path)) + assert.Equal(t, u.e, cfg.Views[u.key].Columns) + }) + } +} + +func TestViewSettingEquals(t *testing.T) { + uu := map[string]struct { + v1, v2 *config.ViewSetting + e bool + }{ + "v1-nil-v2-nil": { + e: true, + }, + + "v1-v2-empty": { + v1: new(config.ViewSetting), + v2: new(config.ViewSetting), + e: true, + }, + + "v1-nil": { + v1: new(config.ViewSetting), + }, + + "nil-v2": { + v2: new(config.ViewSetting), + }, + + "v1-v2-blank": { + v1: &config.ViewSetting{ + Columns: []string{"A"}, + }, + v2: new(config.ViewSetting), + }, + + "v1-v2-nil": { + v1: &config.ViewSetting{ + Columns: []string{"A"}, + }, + }, + + "same": { + v1: &config.ViewSetting{ + Columns: []string{"A", "B", "C"}, + }, + v2: &config.ViewSetting{ + Columns: []string{"A", "B", "C"}, + }, + e: true, + }, + + "order": { + v1: &config.ViewSetting{ + Columns: []string{"C", "A", "B"}, + }, + v2: &config.ViewSetting{ + Columns: []string{"A", "B", "C"}, + }, + }, + + "delta": { + v1: &config.ViewSetting{ + Columns: []string{"A", "B", "C"}, + }, + v2: &config.ViewSetting{ + Columns: []string{"B"}, + }, + }, + } + + for k, u := range uu { + t.Run(k, func(t *testing.T) { + assert.Equalf(t, u.e, u.v1.Equals(u.v2), "%#v and %#v", u.v1, u.v2) + }) } } diff --git a/internal/dao/cluster.go b/internal/dao/cluster.go index dab4fbf1..8b0b1b7c 100644 --- a/internal/dao/cluster.go +++ b/internal/dao/cluster.go @@ -6,12 +6,14 @@ package dao import ( "context" "errors" + "fmt" + "log/slog" "sync" "time" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) // RefScanner represents a resource reference scanner. @@ -55,7 +57,7 @@ func scanners() map[string]RefScanner { // ScanForRefs scans cluster resources for resource references. func ScanForRefs(ctx context.Context, f Factory) (Refs, error) { defer func(t time.Time) { - log.Debug().Msgf("Cluster Scan %v", time.Since(t)) + slog.Debug("Cluster Scan", slogs.Elapsed, time.Since(t)) }(time.Now()) gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR) @@ -68,7 +70,7 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) { } wait, ok := ctx.Value(internal.KeyWait).(bool) if !ok { - log.Error().Msgf("expecting Context Wait Key") + slog.Warn("Expecting context Wait key. Using default") } ss := scanners() @@ -81,7 +83,10 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) { s.Init(f, client.NewGVR(kind)) refs, err := s.Scan(ctx, gvr, fqn, wait) if err != nil { - log.Error().Err(err).Msgf("scan failed for %T", s) + slog.Error("Reference scan failed for", + slogs.RefType, fmt.Sprintf("%T", s), + slogs.Error, err, + ) return } select { @@ -108,7 +113,7 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) { // ScanForSARefs scans cluster resources for serviceaccount refs. func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) { defer func(t time.Time) { - log.Debug().Msgf("SA Cluster Scan %v", time.Since(t)) + slog.Debug("Time to scan Cluster SA", slogs.Elapsed, time.Since(t)) }(time.Now()) fqn, ok := ctx.Value(internal.KeyPath).(string) @@ -130,7 +135,10 @@ func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) { s.Init(f, client.NewGVR(kind)) refs, err := s.ScanSA(ctx, fqn, wait) if err != nil { - log.Error().Err(err).Msgf("scan failed for %T", s) + slog.Error("ServiceAccount scan failed", + slogs.RefType, fmt.Sprintf("%T", s), + slogs.Error, err, + ) return } select { diff --git a/internal/dao/context.go b/internal/dao/context.go index 671ed34f..de655be2 100644 --- a/internal/dao/context.go +++ b/internal/dao/context.go @@ -5,10 +5,11 @@ package dao import ( "context" + "log/slog" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "k8s.io/apimachinery/pkg/runtime" ) @@ -53,7 +54,7 @@ func (c *Context) List(_ context.Context, _ string) ([]runtime.Object, error) { func (c *Context) MustCurrentContextName() string { cl, err := c.config().CurrentContextName() if err != nil { - log.Fatal().Err(err).Msg("Fetching current context") + slog.Error("Fetching current context", slogs.Error, err) } return cl } diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index 31533495..ba5514c3 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -7,10 +7,11 @@ import ( "context" "errors" "fmt" + "log/slog" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" batchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -200,7 +201,10 @@ func (c *CronJob) Scan(ctx context.Context, gvr client.GVR, fqn string, wait boo case SecGVR: found, err := hasSecret(c.Factory, &cj.Spec.JobTemplate.Spec.Template.Spec, cj.Namespace, n, wait) if err != nil { - log.Warn().Err(err).Msgf("locate secret %q", fqn) + slog.Warn("Failed to locate secret", + slogs.FQN, fqn, + slogs.Error, err, + ) continue } if !found { diff --git a/internal/dao/describe.go b/internal/dao/describe.go index d990dfd4..541f453c 100644 --- a/internal/dao/describe.go +++ b/internal/dao/describe.go @@ -4,8 +4,10 @@ package dao import ( + "log/slog" + "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "k8s.io/kubectl/pkg/describe" ) @@ -14,13 +16,19 @@ func Describe(c client.Connection, gvr client.GVR, path string) (string, error) mapper := RestMapper{Connection: c} m, err := mapper.ToRESTMapper() if err != nil { - log.Error().Err(err).Msgf("No REST mapper for resource %s", gvr) + slog.Error("No REST mapper for resource", + slogs.GVR, gvr, + slogs.Error, err, + ) return "", err } gvk, err := m.KindFor(gvr.GVR()) if err != nil { - log.Error().Err(err).Msgf("No GVK for resource %s", gvr) + slog.Error("No GVK for resource %s", + slogs.GVR, gvr, + slogs.Error, err, + ) return "", err } @@ -30,12 +38,19 @@ func Describe(c client.Connection, gvr client.GVR, path string) (string, error) } mapping, err := mapper.ResourceFor(gvr.AsResourceName(), gvk.Kind) if err != nil { - log.Error().Err(err).Msgf("Unable to find mapper for %s %s", gvr, n) + slog.Error("Unable to find mapper", + slogs.GVR, gvr, + slogs.ResName, n, + slogs.Error, err, + ) return "", err } d, err := describe.Describer(c.Config().Flags(), mapping) if err != nil { - log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping) + slog.Error("Unable to find describer", + slogs.GVR, gvr.AsResourceName(), + slogs.Error, err, + ) return "", err } diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 01f9513d..c4d432a3 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -7,10 +7,11 @@ import ( "context" "errors" "fmt" + "log/slog" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -215,7 +216,10 @@ func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait case SecGVR: found, err := hasSecret(d.Factory, &dp.Spec.Template.Spec, dp.Namespace, n, wait) if err != nil { - log.Warn().Err(err).Msgf("scanning secret %q", fqn) + slog.Warn("Fail to locate secret", + slogs.FQN, fqn, + slogs.Error, err, + ) continue } if !found { diff --git a/internal/dao/ds.go b/internal/dao/ds.go index 3bbd7827..dcbadaed 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -7,13 +7,14 @@ import ( "context" "errors" "fmt" + "log/slog" "strings" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/watch" - "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -235,7 +236,10 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait b case SecGVR: found, err := hasSecret(d.Factory, &ds.Spec.Template.Spec, ds.Namespace, n, wait) if err != nil { - log.Warn().Err(err).Msgf("locate secret %q", fqn) + slog.Warn("Unable to locate secret", + slogs.FQN, fqn, + slogs.Error, err, + ) continue } if !found { diff --git a/internal/dao/helm_chart.go b/internal/dao/helm_chart.go index 819a9c63..3a20d918 100644 --- a/internal/dao/helm_chart.go +++ b/internal/dao/helm_chart.go @@ -6,11 +6,12 @@ package dao import ( "context" "fmt" + "log/slog" "os" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render/helm" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "gopkg.in/yaml.v2" "helm.sh/helm/v3/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -163,6 +164,9 @@ func ensureHelmConfig(flags *genericclioptions.ConfigFlags, ns string) (*action. return cfg, err } -func helmLogger(fmt string, args ...interface{}) { - log.Debug().Msgf("[Helm] "+fmt, args...) +func helmLogger(fmat string, args ...interface{}) { + slog.Debug("Log", + slogs.Log, fmt.Sprintf(fmat, args...), + slogs.Subsys, "helm", + ) } diff --git a/internal/dao/helpers.go b/internal/dao/helpers.go index 552d3137..734e4757 100644 --- a/internal/dao/helpers.go +++ b/internal/dao/helpers.go @@ -7,10 +7,11 @@ import ( "bytes" "errors" "fmt" + "log/slog" "math" "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -35,7 +36,10 @@ func GetDefaultContainer(m metav1.ObjectMeta, spec v1.PodSpec) (string, bool) { return defaultContainer, true } } - log.Warn().Msg(defaultContainer + " container not found. " + defaultContainerAnnotation + " annotation will be ignored") + slog.Warn("Container not found. Annotation ignored", + slogs.CO, defaultContainer, + slogs.Annotation, defaultContainerAnnotation, + ) return "", false } @@ -43,7 +47,7 @@ func GetDefaultContainer(m metav1.ObjectMeta, spec v1.PodSpec) (string, bool) { func extractFQN(o runtime.Object) string { u, ok := o.(*unstructured.Unstructured) if !ok { - log.Error().Err(fmt.Errorf("expecting unstructured but got %T", o)) + slog.Error("Expecting unstructured", slogs.ResType, fmt.Sprintf("%T", o)) return client.NA } @@ -93,7 +97,7 @@ func ToYAML(o runtime.Object, showManaged bool) (string, error) { } err := p.PrintObj(o, &buff) if err != nil { - log.Error().Msgf("Marshal Error %v", err) + slog.Error("Marshal failed", slogs.Error, err) return "", err } diff --git a/internal/dao/job.go b/internal/dao/job.go index 0c3ba1a1..5afb933c 100644 --- a/internal/dao/job.go +++ b/internal/dao/job.go @@ -7,11 +7,12 @@ import ( "context" "errors" "fmt" + "log/slog" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -159,7 +160,10 @@ func (j *Job) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) ( case SecGVR: found, err := hasSecret(j.Factory, &job.Spec.Template.Spec, job.Namespace, n, wait) if err != nil { - log.Warn().Err(err).Msgf("locate secret %q", fqn) + slog.Warn("Locate secret failed", + slogs.FQN, fqn, + slogs.Error, err, + ) continue } if !found { diff --git a/internal/dao/log_items_test.go b/internal/dao/log_items_test.go index 787fddfe..a4f08214 100644 --- a/internal/dao/log_items_test.go +++ b/internal/dao/log_items_test.go @@ -5,16 +5,16 @@ package dao_test import ( "fmt" + "log/slog" "testing" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestLogItemsFilter(t *testing.T) { diff --git a/internal/dao/node.go b/internal/dao/node.go index d348255b..09795372 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -8,11 +8,12 @@ import ( "errors" "fmt" "io" + "log/slog" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -37,16 +38,23 @@ type Node struct { } // ToggleCordon toggles cordon/uncordon a node. -func (n *Node) ToggleCordon(path string, cordon bool) error { - log.Debug().Msgf("CORDON %q::%t -- %q", path, cordon, n.gvr.GVK()) - o, err := FetchNode(context.Background(), n.Factory, path) +func (n *Node) ToggleCordon(fqn string, cordon bool) error { + slog.Debug("Toggle cordon on node", + slogs.GVR, n.GVR(), + slogs.FQN, fqn, + slogs.Bool, cordon, + ) + o, err := FetchNode(context.Background(), n.Factory, fqn) if err != nil { return err } h, err := drain.NewCordonHelperFromRuntimeObject(o, scheme.Scheme, n.gvr.GVK()) if err != nil { - log.Debug().Msgf("BOOM %v", err) + slog.Debug("Fail to toggle cordon on node", + slogs.FQN, fqn, + slogs.Error, err, + ) return err } @@ -174,7 +182,10 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) { if shouldCountPods { podCount, err = n.CountPods(name) if err != nil { - log.Error().Err(err).Msgf("unable to get pods count for %s", name) + slog.Error("Unable to get pods count", + slogs.ResName, name, + slogs.Error, err, + ) } } res = append(res, &render.NodeWithMetrics{ diff --git a/internal/dao/ofaas.go b/internal/dao/ofaas.go deleted file mode 100644 index 0030aa4e..00000000 --- a/internal/dao/ofaas.go +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package dao - -// BOZO!! Revamp with latest - -// import ( -// "bytes" -// "context" -// "encoding/json" -// "errors" -// "fmt" -// "io" -// "net/http" -// "net/url" -// "os" -// "path" -// "strings" -// "time" - -// "github.com/derailed/k9s/internal/client" -// "github.com/derailed/k9s/internal/render" -// "github.com/openfaas/faas-cli/proxy" -// "github.com/openfaas/faas/gateway/requests" -// "github.com/rs/zerolog/log" -// "k8s.io/apimachinery/pkg/runtime" -// "sigs.k8s.io/yaml" -// ) - -// const ( -// oFaasGatewayEnv = "OPENFAAS_GATEWAY" -// oFaasJWTTokenEnv = "OPENFAAS_JWT_TOKEN" -// oFaasTLSInsecure = "OPENFAAS_TLS_INSECURE" -// ) - -// var ( -// _ Accessor = (*OpenFaas)(nil) -// _ Nuker = (*OpenFaas)(nil) -// _ Describer = (*OpenFaas)(nil) -// ) - -// // OpenFaas represents a faas gateway connection. -// type OpenFaas struct { -// NonResource -// } - -// // IsOpenFaasEnabled returns true if a gateway url is set in the environment. -// func IsOpenFaasEnabled() bool { -// return os.Getenv(oFaasGatewayEnv) != "" -// } - -// func getOpenFAASFlags() (string, string, bool) { -// gw, token := os.Getenv(oFaasGatewayEnv), os.Getenv(oFaasJWTTokenEnv) -// tlsInsecure := false -// if os.Getenv(oFaasTLSInsecure) == "true" { -// tlsInsecure = true -// } - -// return gw, token, tlsInsecure -// } - -// // Get returns a function by name. -// func (f *OpenFaas) Get(ctx context.Context, path string) (runtime.Object, error) { -// ns, n := client.Namespaced(path) - -// oo, err := f.List(ctx, ns) -// if err != nil { -// return nil, err -// } - -// var found runtime.Object -// for _, o := range oo { -// r, ok := o.(render.OpenFaasRes) -// if !ok { -// continue -// } -// if r.Function.Name == n { -// found = o -// break -// } -// } - -// if found == nil { -// return nil, fmt.Errorf("unable to locate function %q", path) -// } - -// return found, nil -// } - -// // List returns a collection of functions. -// func (f *OpenFaas) List(_ context.Context, ns string) ([]runtime.Object, error) { -// if !IsOpenFaasEnabled() { -// return nil, errors.New("OpenFAAS is not enabled on this cluster") -// } - -// gw, token, tls := getOpenFAASFlags() -// ff, err := proxy.ListFunctionsToken(gw, tls, token, ns) -// if err != nil { -// return nil, err -// } - -// oo := make([]runtime.Object, 0, len(ff)) -// for _, f := range ff { -// oo = append(oo, render.OpenFaasRes{Function: f}) -// } - -// return oo, nil -// } - -// // Delete removes a function. -// func (f *OpenFaas) Delete(path string, _, _ bool) error { -// gw, token, tls := getOpenFAASFlags() -// ns, n := client.Namespaced(path) - -// // BOZO!! openfaas spews to stdout. Not good for us... -// return deleteFunctionToken(gw, n, tls, token, ns) -// } - -// // ToYAML dumps a function to yaml. -// func (f *OpenFaas) ToYAML(path string, _ bool) (string, error) { -// return f.Describe(path) -// } - -// // Describe describes a function. -// func (f *OpenFaas) Describe(path string) (string, error) { -// o, err := f.Get(context.Background(), path) -// if err != nil { -// return "", err -// } - -// fn, ok := o.(render.OpenFaasRes) -// if !ok { -// return "", fmt.Errorf("expecting OpenFaasRes but got %T", o) -// } - -// raw, err := json.Marshal(fn) -// if err != nil { -// return "", err -// } - -// bytes, err := yaml.JSONToYAML(raw) -// if err != nil { -// return "", err -// } - -// return string(bytes), nil -// } - -// // BOZO!! Meow! openfaas fn prints to stdout have to dup ;(. -// func deleteFunctionToken(gateway string, functionName string, tlsInsecure bool, token string, namespace string) error { -// defaultCommandTimeout := 60 * time.Second - -// gateway = strings.TrimRight(gateway, "/") -// delReq := requests.DeleteFunctionRequest{FunctionName: functionName} -// reqBytes, _ := json.Marshal(&delReq) -// reader := bytes.NewReader(reqBytes) - -// c := proxy.MakeHTTPClient(&defaultCommandTimeout, tlsInsecure) - -// deleteEndpoint, err := createSystemEndpoint(gateway, namespace) -// if err != nil { -// return err -// } - -// req, err := http.NewRequestWithContext(context.Background(), "DELETE", deleteEndpoint, reader) -// if err != nil { -// return err -// } -// req.Header.Set("Content-Type", "application/json") - -// if len(token) > 0 { -// proxy.SetToken(req, token) -// } else { -// proxy.SetAuth(req, gateway) -// } - -// delRes, delErr := c.Do(req) -// if delErr != nil { -// return delErr -// } - -// if delRes.Body != nil { -// defer func() { -// if err := delRes.Body.Close(); err != nil { -// log.Error().Err(err).Msgf("closing delete-gtw body") -// } -// }() -// } - -// switch delRes.StatusCode { -// case http.StatusOK, http.StatusCreated, http.StatusAccepted: -// return nil -// case http.StatusNotFound: -// return fmt.Errorf("no function named %s found", functionName) -// case http.StatusUnauthorized: -// return fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") -// default: -// bytesOut, err := io.ReadAll(delRes.Body) -// if err != nil { -// return err -// } -// return fmt.Errorf("server returned unexpected status code %d %s", delRes.StatusCode, string(bytesOut)) -// } -// } - -// func createSystemEndpoint(gateway, namespace string) (string, error) { -// const systemPath = "/system/functions" - -// gatewayURL, err := url.Parse(gateway) -// if err != nil { -// return "", fmt.Errorf("invalid gateway URL: %w", err) -// } -// gatewayURL.Path = path.Join(gatewayURL.Path, systemPath) -// if len(namespace) > 0 { -// q := gatewayURL.Query() -// q.Set("namespace", namespace) -// gatewayURL.RawQuery = q.Encode() -// } -// return gatewayURL.String(), nil -// } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index f4ee453d..2ed3f6bd 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -9,15 +9,16 @@ import ( "errors" "fmt" "io" + "log/slog" "sync" "time" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/watch" "github.com/derailed/tview" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -287,7 +288,10 @@ func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) ( case SecGVR: found, err := hasSecret(p.Factory, &pod.Spec, pod.Namespace, n, wait) if err != nil { - log.Warn().Err(err).Msgf("locate secret %q", fqn) + slog.Warn("Locate secret failed", + slogs.FQN, fqn, + slogs.Error, err, + ) continue } if !found { @@ -332,30 +336,33 @@ func tailLogs(ctx context.Context, logger Logger, opts *LogOptions) LogChan { go func() { defer wg.Done() podOpts := opts.ToPodLogOptions() - var stream io.ReadCloser for r := 0; r < logRetryCount; r++ { - var e error req, err := logger.Logs(opts.Path, podOpts) if err == nil { // This call will block if nothing is in the stream!! - if stream, err = req.Stream(ctx); err == nil { + if stream, e := req.Stream(ctx); e == nil { wg.Add(1) go readLogs(ctx, &wg, stream, out, opts) return + } else { + slog.Error("Stream logs failed", + slogs.Error, e, + slogs.Container, opts.Info(), + ) } - e = fmt.Errorf("stream logs failed %w for %s", err, opts.Info()) - log.Error().Err(e).Msg("logs-stream") } else { - e = fmt.Errorf("stream logs failed %w for %s", err, opts.Info()) - log.Error().Err(e).Msg("log-request") + slog.Error("Log request failed", + slogs.Container, opts.Info(), + slogs.Error, err, + ) } select { case <-ctx.Done(): return default: - if e != nil { - out <- opts.ToErrLogItem(e) + if err != nil { + out <- opts.ToErrLogItem(err) } time.Sleep(logRetryWait) } @@ -372,12 +379,15 @@ func tailLogs(ctx context.Context, logger Logger, opts *LogOptions) LogChan { func readLogs(ctx context.Context, wg *sync.WaitGroup, stream io.ReadCloser, out chan<- *LogItem, opts *LogOptions) { defer func() { if err := stream.Close(); err != nil { - log.Error().Err(err).Msgf("Fail to close stream %s", opts.Info()) + slog.Error("Fail to close stream", + slogs.Container, opts.Info(), + slogs.Error, err, + ) } wg.Done() }() - log.Debug().Msgf(">>> LOG-READER PROCESSING %#v", opts) + slog.Debug("Processing logs", slogs.Options, opts.Info()) r := bufio.NewReader(stream) for { var item *LogItem @@ -387,11 +397,14 @@ func readLogs(ctx context.Context, wg *sync.WaitGroup, stream io.ReadCloser, out if errors.Is(err, io.EOF) { e := fmt.Errorf("stream closed %w for %s", err, opts.Info()) item = opts.ToErrLogItem(e) - log.Warn().Err(e).Msg("log-reader EOF") + slog.Warn("Log reader EOF", + slogs.Container, opts.Info(), + slogs.Error, e, + ) } else { e := fmt.Errorf("stream canceled %w for %s", err, opts.Info()) item = opts.ToErrLogItem(e) - log.Warn().Err(e).Msg("log-reader canceled") + slog.Warn("Log stream canceled") } } select { @@ -488,7 +501,6 @@ func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) { if err != nil { continue } - log.Debug().Msgf("Pod status: %q", render.PodStatus(&pod)) switch render.PodStatus(&pod) { case render.PhaseCompleted: fallthrough @@ -506,16 +518,20 @@ func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) { fallthrough case render.PhaseOOMKilled: // !!BOZO!! Might need to bump timeout otherwise rev limit if too many?? - log.Debug().Msgf("Sanitizing %s:%s", pod.Namespace, pod.Name) fqn := client.FQN(pod.Namespace, pod.Name) + slog.Debug("Sanitizing resource", slogs.FQN, fqn) if err := p.Delete(ctx, fqn, nil, 0); err != nil { - log.Debug().Msgf("Aborted! Sanitizer deleted %d pods", count) + slog.Debug("Aborted! Sanitizer delete failed", + slogs.FQN, fqn, + slogs.Count, count, + slogs.Error, err, + ) return count, err } count++ } } - log.Debug().Msgf("Sanitizer deleted %d pods", count) + slog.Debug("Sanitizer deleted pods", slogs.Count, count) return count, nil } diff --git a/internal/dao/popeye.go b/internal/dao/popeye.go index 7cb08e9a..f23476d0 100644 --- a/internal/dao/popeye.go +++ b/internal/dao/popeye.go @@ -22,7 +22,6 @@ package dao // "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" // ) diff --git a/internal/dao/port_forward.go b/internal/dao/port_forward.go index 3a0b6280..8d9cc424 100644 --- a/internal/dao/port_forward.go +++ b/internal/dao/port_forward.go @@ -6,6 +6,7 @@ package dao import ( "context" "fmt" + "log/slog" "regexp" "strings" @@ -13,7 +14,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -45,7 +46,7 @@ func (p *PortForward) List(ctx context.Context, _ string) ([]runtime.Object, err config, err := config.NewBench(benchFile) if err != nil { - log.Debug().Msgf("No custom benchmark config file found: %q", benchFile) + slog.Debug("No custom benchmark config file found", slogs.FileName, benchFile) } ff, cc := p.getFactory().Forwarders(), config.Benchmarks.Containers @@ -92,7 +93,10 @@ func BenchConfigFor(benchFile, path string) config.BenchConfig { def := config.DefaultBenchSpec() cust, err := config.NewBench(benchFile) if err != nil { - log.Debug().Msgf("No custom benchmark config file found. Using default: %q", benchFile) + slog.Debug("No custom benchmark config file found. Using default", + slogs.FileName, benchFile, + slogs.Error, err, + ) return def } if b, ok := cust.Benchmarks.Containers[PodToKey(path)]; ok { diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index 54aed487..c42bbb69 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -12,7 +12,6 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/port" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -101,7 +100,6 @@ func (p *PortForwarder) Container() string { // Stop terminates a port forward. func (p *PortForwarder) Stop() { - log.Debug().Msgf("<<< Stopping PortForward %s", p.ID()) p.active = false if p.stopChan != nil { close(p.stopChan) diff --git a/internal/dao/rbac.go b/internal/dao/rbac.go index 7e688aee..afcd4ce6 100644 --- a/internal/dao/rbac.go +++ b/internal/dao/rbac.go @@ -6,11 +6,12 @@ package dao import ( "context" "fmt" + "log/slog" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -121,9 +122,9 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { return asRuntimeObjects(parseRules(client.ClusterScope, "-", role.Rules)), nil } -func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) { - log.Debug().Msgf("LOAD-CR %q", path) - o, err := r.getFactory().Get(crGVR, path, true, labels.Everything()) +func (r *Rbac) loadClusterRole(fqn string) ([]runtime.Object, error) { + slog.Debug("LOAD-CR", slogs.FQN, fqn) + o, err := r.getFactory().Get(crGVR, fqn, true, labels.Everything()) if err != nil { return nil, err } @@ -165,6 +166,9 @@ func parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Policies { pp := make(render.Policies, 0, len(rules)) for _, rule := range rules { for _, grp := range rule.APIGroups { + if grp == "" { + grp = "core" + } for _, res := range rule.Resources { for _, na := range rule.ResourceNames { pp = pp.Upsert(render.NewPolicyRes(ns, binding, FQN(res, na), grp, rule.Verbs)) diff --git a/internal/dao/rbac_policy.go b/internal/dao/rbac_policy.go index 3b9fe7d8..067b45b3 100644 --- a/internal/dao/rbac_policy.go +++ b/internal/dao/rbac_policy.go @@ -6,11 +6,12 @@ package dao import ( "context" "fmt" + "log/slog" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -102,7 +103,10 @@ func (p *Policy) loadRoleBinding(kind, name string) (render.Policies, error) { rows := make(render.Policies, 0, len(crs)) for _, cr := range crs { if rbNs, ok := rbsMap["ClusterRole:"+cr.Name]; ok { - log.Debug().Msgf("Loading rules for clusterrole %q:%q", rbNs, cr.Name) + slog.Debug("Loading rules for clusterrole", + slogs.Namespace, rbNs, + slogs.ResName, cr.Name, + ) rows = append(rows, parseRules(rbNs, "CR:"+cr.Name, cr.Rules)...) } } @@ -115,7 +119,10 @@ func (p *Policy) loadRoleBinding(kind, name string) (render.Policies, error) { if _, ok := rbsMap["Role:"+ro.Name]; !ok { continue } - log.Debug().Msgf("Loading rules for role %q:%q", ro.Namespace, ro.Name) + slog.Debug("Loading rules for role", + slogs.Namespace, ro.Namespace, + slogs.ResName, ro.Name, + ) rows = append(rows, parseRules(ro.Namespace, "RO:"+ro.Name, ro.Rules)...) } diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 74cf9446..ef04efb3 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -5,13 +5,14 @@ package dao import ( "fmt" + "log/slog" "slices" "sort" "strings" "sync" "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -97,7 +98,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { r, ok := m[gvr] if !ok { r = new(Scaler) - log.Debug().Msgf("No DAO registry entry for %q. Using generics!", gvr) + slog.Debug("No DAO registry entry. Using generics!", slogs.GVR, gvr) } r.Init(f, gvr) @@ -348,7 +349,7 @@ func loadRBAC(m ResourceMetas) { func loadPreferred(f Factory, m ResourceMetas) error { if f.Client() == nil || !f.Client().ConnectionOK() { - log.Error().Msgf("Load cluster resources - No API server connection") + slog.Error("Load cluster resources - No API server connection") return nil } @@ -358,7 +359,7 @@ func loadPreferred(f Factory, m ResourceMetas) error { } rr, err := dial.ServerPreferredResources() if err != nil { - log.Debug().Err(err).Msgf("Failed to load preferred resources") + slog.Debug("Failed to load preferred resources", slogs.Error, err) } for _, r := range rr { for _, res := range r.APIResources { @@ -400,7 +401,7 @@ func loadCRDs(f Factory, m ResourceMetas) { oo, err := f.List(crdGVR, client.ClusterScope, true, labels.Everything()) if err != nil { - log.Warn().Err(err).Msgf("Fail CRDs load") + slog.Warn("CRDs load Fail", slogs.Error, err) return } @@ -408,7 +409,7 @@ func loadCRDs(f Factory, m ResourceMetas) { var crd apiext.CustomResourceDefinition err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crd) if err != nil { - log.Err(err).Msg("boom") + slog.Error("CRD conversion failed", slogs.Error, err) continue } gvr, version, ok := newGVRFromCRD(&crd) diff --git a/internal/dao/scalable.go b/internal/dao/scalable.go index 7664b535..80c0fac5 100644 --- a/internal/dao/scalable.go +++ b/internal/dao/scalable.go @@ -5,14 +5,14 @@ package dao import ( "context" + "log/slog" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/slogs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/dynamic" "k8s.io/client-go/restmapper" "k8s.io/client-go/scale" - - "github.com/derailed/k9s/internal/client" ) var _ Scalable = (*Scaler)(nil) @@ -59,7 +59,10 @@ func (s *Scaler) Scale(ctx context.Context, path string, replicas int32) error { return err } - log.Debug().Msgf("%s scaled to %d", path, updatedScale.Spec.Replicas) + slog.Debug("Scaled resource", + slogs.FQN, path, + slogs.Replicas, updatedScale.Spec.Replicas, + ) return nil } diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 4e352f16..153d8296 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -7,11 +7,12 @@ import ( "context" "errors" "fmt" + "log/slog" "strings" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -236,7 +237,10 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait case SecGVR: found, err := hasSecret(s.Factory, &sts.Spec.Template.Spec, sts.Namespace, n, wait) if err != nil { - log.Warn().Err(err).Msgf("locate secret %q", fqn) + slog.Warn("Locate secret failed", + slogs.FQN, fqn, + slogs.Error, err, + ) continue } if !found { diff --git a/internal/dao/workload.go b/internal/dao/workload.go index 604c6ca9..5b1b8d6e 100644 --- a/internal/dao/workload.go +++ b/internal/dao/workload.go @@ -8,13 +8,14 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "strconv" "strings" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -206,12 +207,18 @@ func isReady(s string) bool { } r, err := strconv.Atoi(tt[0]) if err != nil { - log.Error().Msgf("invalid ready count: %q", tt[0]) + slog.Error("Invalid ready count", + slogs.Error, err, + slogs.Count, tt[0], + ) return false } c, err := strconv.Atoi(tt[1]) if err != nil { - log.Error().Msgf("invalid expected count: %q", tt[1]) + slog.Error("invalid expected count: %q", + slogs.Error, err, + slogs.Count, tt[1], + ) return false } diff --git a/internal/model/cluster_info.go b/internal/model/cluster_info.go index 62f6e7f1..d3aed24e 100644 --- a/internal/model/cluster_info.go +++ b/internal/model/cluster_info.go @@ -8,15 +8,16 @@ import ( "encoding/json" "errors" "io" + "log/slog" "net/http" "sync" "time" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" - "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/util/cache" ) @@ -107,7 +108,7 @@ func (c *ClusterInfo) fetchK9sLatestRev() string { latestRev, err := fetchLatestRev() if err != nil { - log.Warn().Msgf("k9s latest rev fetch failed %s", err) + slog.Warn("k9s latest rev fetch failed", slogs.Error, err) } else { c.cache.Add(k9sLatestRevKey, latestRev, cacheExpiry) } @@ -206,7 +207,7 @@ func (c *ClusterInfo) fireNoMetaChanged(data ClusterMeta) { // Helpers... func fetchLatestRev() (string, error) { - log.Debug().Msgf("Fetching latest k9s rev...") + slog.Debug("Fetching latest k9s rev...") ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -234,7 +235,7 @@ func fetchLatestRev() (string, error) { } if v, ok := m["name"]; ok { - log.Debug().Msgf("K9s latest rev: %q", v.(string)) + slog.Debug("K9s latest rev", slogs.Revision, v.(string)) return v.(string), nil } diff --git a/internal/model/cluster_info_test.go b/internal/model/cluster_info_test.go index 48426ba5..ca0c637e 100644 --- a/internal/model/cluster_info_test.go +++ b/internal/model/cluster_info_test.go @@ -4,15 +4,15 @@ package model_test import ( + "log/slog" "testing" "github.com/derailed/k9s/internal/model" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestClusterMetaDelta(t *testing.T) { diff --git a/internal/model/describe.go b/internal/model/describe.go index 2d5d845a..501426b4 100644 --- a/internal/model/describe.go +++ b/internal/model/describe.go @@ -6,6 +6,7 @@ package model import ( "context" "fmt" + "log/slog" "reflect" "strings" "sync/atomic" @@ -15,7 +16,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "github.com/sahilm/fuzzy" ) @@ -113,7 +114,7 @@ func (d *Describe) Watch(ctx context.Context) error { } func (d *Describe) updater(ctx context.Context) { - defer log.Debug().Msgf("Describe canceled -- %q", d.gvr) + defer slog.Debug("Describe canceled", slogs.GVR, d.gvr) backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval) delay := defaultReaderRefreshRate @@ -125,7 +126,7 @@ func (d *Describe) updater(ctx context.Context) { if err := d.refresh(ctx); err != nil { d.fireResourceFailed(err) if delay = backOff.NextBackOff(); delay == backoff.Stop { - log.Error().Err(err).Msgf("Describe gave up!") + slog.Error("Describe gave up!", slogs.Error, err) return } } else { @@ -138,13 +139,16 @@ func (d *Describe) updater(ctx context.Context) { func (d *Describe) refresh(ctx context.Context) error { if !atomic.CompareAndSwapInt32(&d.inUpdate, 0, 1) { - log.Debug().Msgf("Dropping update...") + slog.Debug("Dropping update...") return nil } defer atomic.StoreInt32(&d.inUpdate, 0) if err := d.reconcile(ctx); err != nil { - log.Error().Err(err).Msgf("reconcile failed %q", d.gvr) + slog.Error("reconcile failed", + slogs.GVR, d.gvr, + slogs.Error, err, + ) d.fireResourceFailed(err) return err } @@ -170,7 +174,7 @@ func (d *Describe) reconcile(ctx context.Context) error { // Describe describes a given resource. func (d *Describe) describe(ctx context.Context, gvr client.GVR, path string) (string, error) { defer func(t time.Time) { - log.Debug().Msgf("Describe model elapsed: %v", time.Since(t)) + slog.Debug("Describe model elapsed", slogs.Elapsed, time.Since(t)) }(time.Now()) meta, err := getMeta(ctx, gvr) diff --git a/internal/model/flash.go b/internal/model/flash.go index d4bb7c8d..727af7f2 100644 --- a/internal/model/flash.go +++ b/internal/model/flash.go @@ -6,9 +6,10 @@ package model import ( "context" "fmt" + "log/slog" "time" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) const ( @@ -86,7 +87,7 @@ func (f *Flash) Infof(fmat string, args ...interface{}) { // Warn displays a warning flash message. func (f *Flash) Warn(msg string) { - log.Warn().Msg(msg) + slog.Warn(msg) f.SetMessage(FlashWarn, msg) } @@ -97,7 +98,7 @@ func (f *Flash) Warnf(fmat string, args ...interface{}) { // Err displays an error flash message. func (f *Flash) Err(err error) { - log.Error().Msg(err.Error()) + slog.Error("Flash failed", slogs.Error, err) f.SetMessage(FlashErr, err.Error()) } @@ -110,7 +111,10 @@ func (f *Flash) Errf(fmat string, args ...interface{}) { err = e } } - log.Error().Err(err).Msgf(fmat, args...) + slog.Error("Flashing error", + slogs.Error, err, + slogs.Message, fmt.Sprintf(fmat, args...), + ) f.SetMessage(FlashErr, fmt.Sprintf(fmat, args...)) } diff --git a/internal/model/log.go b/internal/model/log.go index f78e5a27..19495847 100644 --- a/internal/model/log.go +++ b/internal/model/log.go @@ -6,6 +6,7 @@ package model import ( "context" "fmt" + "log/slog" "sync" "time" @@ -14,7 +15,7 @@ import ( "github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) // LogsListener represents a log model listener. @@ -164,7 +165,7 @@ func (l *Log) Restart(ctx context.Context) { // Start starts logging. func (l *Log) Start(ctx context.Context) { if err := l.load(ctx); err != nil { - log.Error().Err(err).Msgf("Tail logs failed!") + slog.Error("Tail logs failed!", slogs.Error, err) l.fireLogError(err) } } @@ -219,7 +220,6 @@ func (l *Log) cancel() { defer l.mx.Unlock() if l.cancelFn != nil { l.cancelFn() - log.Debug().Msgf("!!! LOG-MODEL CANCELED !!!") l.cancelFn = nil } } @@ -231,7 +231,7 @@ func (l *Log) load(ctx context.Context) error { } loggable, ok := accessor.(dao.Loggable) if !ok { - return fmt.Errorf("Resource %s is not Loggable", l.gvr) + return fmt.Errorf("resource %s is not Loggable", l.gvr) } l.cancel() @@ -240,7 +240,7 @@ func (l *Log) load(ctx context.Context) error { cc, err := loggable.TailLogs(ctx, l.logOptions) if err != nil { - log.Error().Err(err).Msgf("Tail logs failed") + slog.Error("Tail logs failed", slogs.Error, err) l.cancel() l.fireLogError(err) } @@ -288,8 +288,6 @@ func (l *Log) ToggleAllContainers(ctx context.Context) { } func (l *Log) updateLogs(ctx context.Context, c dao.LogChan) { - defer log.Debug().Msgf("<<< LOG-MODEL UPDATER DONE %s!!!!", l.logOptions.Info()) - log.Debug().Msgf(">>> START LOG-MODEL UPDATER %s", l.logOptions.Info()) for { select { case item, ok := <-c: diff --git a/internal/model/pulse.go b/internal/model/pulse.go index 0b62a1a7..dd974c9a 100644 --- a/internal/model/pulse.go +++ b/internal/model/pulse.go @@ -6,13 +6,14 @@ package model import ( "context" "fmt" + "log/slog" "sync/atomic" "time" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/health" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "k8s.io/apimachinery/pkg/runtime" ) @@ -53,7 +54,7 @@ func (p *Pulse) Watch(ctx context.Context) { } func (p *Pulse) updater(ctx context.Context) { - defer log.Debug().Msgf("Pulse canceled -- %q", p.gvr) + defer slog.Debug("Pulse canceled", slogs.GVR, p.gvr) rate := initRefreshRate for { @@ -77,13 +78,13 @@ func (p *Pulse) Refresh(ctx context.Context) { func (p *Pulse) refresh(ctx context.Context) { if !atomic.CompareAndSwapInt32(&p.inUpdate, 0, 1) { - log.Debug().Msgf("Dropping update...") + slog.Debug("Dropping update...") return } defer atomic.StoreInt32(&p.inUpdate, 0) if err := p.reconcile(ctx); err != nil { - log.Error().Err(err).Msg("Reconcile failed") + slog.Error("Reconcile failed", slogs.Error, err) p.firePulseFailed(err) return } diff --git a/internal/model/pulse_health.go b/internal/model/pulse_health.go index d2be69c9..8e2027e8 100644 --- a/internal/model/pulse_health.go +++ b/internal/model/pulse_health.go @@ -6,13 +6,14 @@ package model import ( "context" "fmt" + "log/slog" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/health" "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -72,7 +73,7 @@ func (h *PulseHealth) checkMetrics(ctx context.Context) (health.Checks, error) { nmx, err := dial.FetchNodesMetrics(ctx) if err != nil { - log.Error().Err(err).Msgf("Fetching metrics") + slog.Error("Fetching metrics", slogs.Error, err) return nil, err } diff --git a/internal/model/rev_values.go b/internal/model/rev_values.go index a0b8cb98..059fa0ef 100644 --- a/internal/model/rev_values.go +++ b/internal/model/rev_values.go @@ -5,6 +5,7 @@ package model import ( "context" + "log/slog" "strings" "sync/atomic" "time" @@ -13,7 +14,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "github.com/sahilm/fuzzy" ) @@ -48,7 +49,7 @@ func getHelmHistDao() *dao.HelmHistory { func getRevValues(path, rev string) []string { vals, err := getHelmHistDao().GetValues(path, true) if err != nil { - log.Error().Err(err).Msgf("Failed to get Helm values") + slog.Error("Failed to get Helm values", slogs.Error, err) } return strings.Split(string(vals), "\n") } @@ -133,7 +134,7 @@ func (v *RevValues) Watch(ctx context.Context) error { } func (v *RevValues) updater(ctx context.Context) { - defer log.Debug().Msgf("YAML canceled -- %q", v.gvr) + defer slog.Debug("YAML canceled", slogs.GVR, v.gvr) backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval) delay := defaultReaderRefreshRate @@ -145,7 +146,7 @@ func (v *RevValues) updater(ctx context.Context) { if err := v.refresh(ctx); err != nil { v.fireResourceFailed(err) if delay = backOff.NextBackOff(); delay == backoff.Stop { - log.Error().Err(err).Msgf("giving up retrieving chart values") + slog.Error("Giving up retrieving chart values", slogs.Error, err) return } } else { @@ -158,7 +159,7 @@ func (v *RevValues) updater(ctx context.Context) { func (v *RevValues) refresh(ctx context.Context) error { if !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) { - log.Debug().Msgf("Dropping update...") + slog.Debug("Dropping update...") return nil } defer atomic.StoreInt32(&v.inUpdate, 0) diff --git a/internal/model/stack.go b/internal/model/stack.go index 55221b85..d3d8446c 100644 --- a/internal/model/stack.go +++ b/internal/model/stack.go @@ -4,9 +4,11 @@ package model import ( + "fmt" + "log/slog" "sync" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) const ( @@ -188,9 +190,9 @@ func (s *Stack) notify(a StackAction, c Component) { // Dump prints out the stack. func (s *Stack) Dump() { - log.Debug().Msgf("--- Stack Dump %p---", s) + slog.Debug("Stack Dump", slogs.Stack, fmt.Sprintf("%p", s)) for i, c := range s.components { - log.Debug().Msgf("%d -- %s -- %#v", i, c.Name(), c) + slog.Debug(fmt.Sprintf("%d -- %s -- %#v", i, c.Name(), c)) } - log.Debug().Msg("------------------") + slog.Debug("------------------") } diff --git a/internal/model/stack_test.go b/internal/model/stack_test.go index 5a3eca62..6d8c756e 100644 --- a/internal/model/stack_test.go +++ b/internal/model/stack_test.go @@ -5,17 +5,17 @@ package model_test import ( "context" + "log/slog" "testing" "github.com/derailed/k9s/internal/model" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestStackClear(t *testing.T) { diff --git a/internal/model/table.go b/internal/model/table.go index ab135c24..2c0c8dc3 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -6,6 +6,7 @@ package model import ( "context" "fmt" + "log/slog" "sync" "sync/atomic" "time" @@ -16,7 +17,7 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model1" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -63,7 +64,7 @@ func (t *Table) SetViewSetting(ctx context.Context, vs *config.ViewSetting) { if ctx != context.Background() { if err := t.reconcile(ctx); err != nil { - log.Err(err).Msgf("refresh failed for gvr: %s", t.gvr) + slog.Error("Refresh failed", slogs.GVR, t.gvr) } } } @@ -209,13 +210,13 @@ func (t *Table) updater(ctx context.Context) { rate = t.refreshRate err := backoff.Retry(func() error { if err := t.refresh(ctx); err != nil { - log.Err(err).Msgf("refresh failed for gvr: %s", t.gvr) + slog.Error("Refresh failed", slogs.GVR, t.gvr) return err } return nil }, backoff.WithContext(bf, ctx)) if err != nil { - log.Warn().Err(err).Msgf("reconciler exited") + slog.Warn("Reconciler exited", slogs.Error, err) t.fireTableLoadFailed(err) return } @@ -224,12 +225,8 @@ func (t *Table) updater(ctx context.Context) { } func (t *Table) refresh(ctx context.Context) error { - defer func(ti time.Time) { - log.Trace().Msgf("Refresh [%s](%d) %s ", t.gvr, t.data.RowCount(), time.Since(ti)) - }(time.Now()) - if !atomic.CompareAndSwapInt32(&t.inUpdate, 0, 1) { - log.Debug().Msgf("Dropping update...") + slog.Debug("Dropping update...") return nil } defer atomic.StoreInt32(&t.inUpdate, 0) diff --git a/internal/model/tree.go b/internal/model/tree.go index 6f32d13c..f97b5852 100644 --- a/internal/model/tree.go +++ b/internal/model/tree.go @@ -6,6 +6,7 @@ package model import ( "context" "fmt" + "log/slog" "regexp" "strings" "sync/atomic" @@ -15,8 +16,8 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/xray" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -162,7 +163,7 @@ func (t *Tree) ToYAML(ctx context.Context, gvr, path string) (string, error) { } func (t *Tree) updater(ctx context.Context) { - defer log.Debug().Msgf("Tree-model canceled -- %q", t.gvr) + defer slog.Debug("Tree-model canceled", slogs.GVR, t.gvr) rate := initTreeRefreshRate for { @@ -179,13 +180,13 @@ func (t *Tree) updater(ctx context.Context) { func (t *Tree) refresh(ctx context.Context) { if !atomic.CompareAndSwapInt32(&t.inUpdate, 0, 1) { - log.Debug().Msgf("Dropping update...") + slog.Debug("Dropping update...") return } defer atomic.StoreInt32(&t.inUpdate, 0) if err := t.reconcile(ctx); err != nil { - log.Error().Err(err).Msg("Reconcile failed") + slog.Error("Reconcile failed", slogs.Error, err) t.fireTreeLoadFailed(err) return } diff --git a/internal/model/values.go b/internal/model/values.go index eafe098d..41515f65 100644 --- a/internal/model/values.go +++ b/internal/model/values.go @@ -6,6 +6,7 @@ package model import ( "context" "fmt" + "log/slog" "strings" "sync/atomic" "time" @@ -14,7 +15,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "github.com/sahilm/fuzzy" ) @@ -162,7 +163,7 @@ func (v *Values) Watch(ctx context.Context) error { } func (v *Values) updater(ctx context.Context) { - defer log.Debug().Msgf("YAML canceled -- %q", v.gvr) + defer slog.Debug("YAML canceled", slogs.GVR, v.gvr) backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval) delay := defaultReaderRefreshRate @@ -174,7 +175,7 @@ func (v *Values) updater(ctx context.Context) { if err := v.refresh(ctx); err != nil { v.fireResourceFailed(err) if delay = backOff.NextBackOff(); delay == backoff.Stop { - log.Error().Err(err).Msgf("giving up retrieving chart values") + slog.Error("Giving up retrieving chart values", slogs.Error, err) return } } else { @@ -187,7 +188,7 @@ func (v *Values) updater(ctx context.Context) { func (v *Values) refresh(ctx context.Context) error { if !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) { - log.Debug().Msgf("Dropping update...") + slog.Debug("Dropping update...") return nil } defer atomic.StoreInt32(&v.inUpdate, 0) diff --git a/internal/model/yaml.go b/internal/model/yaml.go index 6ba1ff86..b6ecc2b6 100644 --- a/internal/model/yaml.go +++ b/internal/model/yaml.go @@ -6,6 +6,7 @@ package model import ( "context" "fmt" + "log/slog" "reflect" "strings" "sync/atomic" @@ -15,7 +16,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "github.com/sahilm/fuzzy" ) @@ -121,7 +122,7 @@ func (y *YAML) Watch(ctx context.Context) error { } func (y *YAML) updater(ctx context.Context) { - defer log.Debug().Msgf("YAML canceled -- %q", y.gvr) + defer slog.Debug("YAML canceled", slogs.GVR, y.gvr) backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval) delay := defaultReaderRefreshRate @@ -133,7 +134,7 @@ func (y *YAML) updater(ctx context.Context) { if err := y.refresh(ctx); err != nil { y.fireResourceFailed(err) if delay = backOff.NextBackOff(); delay == backoff.Stop { - log.Error().Err(err).Msgf("YAML gave up!") + slog.Error("YAML gave up!", slogs.Error, err) return } } else { @@ -146,7 +147,7 @@ func (y *YAML) updater(ctx context.Context) { func (y *YAML) refresh(ctx context.Context) error { if !atomic.CompareAndSwapInt32(&y.inUpdate, 0, 1) { - log.Debug().Msgf("Dropping update...") + slog.Debug("Dropping update...", slogs.GVR, y.gvr) return nil } defer atomic.StoreInt32(&y.inUpdate, 0) diff --git a/internal/model1/header.go b/internal/model1/header.go index 9e1ab6f2..4355d2b5 100644 --- a/internal/model1/header.go +++ b/internal/model1/header.go @@ -5,9 +5,10 @@ package model1 import ( "fmt" + "log/slog" "reflect" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) const ageCol = "AGE" @@ -16,6 +17,7 @@ type Attrs struct { Align int Decorator DecoratorFunc Wide bool + Show bool MX bool MXC, MXM bool Time bool @@ -34,18 +36,20 @@ func (a Attrs) Merge(b Attrs) Attrs { if a.Align == 0 { a.Align = b.Align } - if !a.Wide { + + if !a.Hide { + a.Hide = b.Hide + } + if !a.Show && !a.Wide { a.Wide = b.Wide } + if !a.Time { a.Time = b.Time } if !a.Capacity { a.Capacity = b.Capacity } - if !a.Hide { - a.Hide = b.Hide - } return a } @@ -107,7 +111,7 @@ func (h Header) MapIndices(cols []string, wide bool) []int { for _, col := range cols { idx, ok := h.IndexOf(col, true) if !ok { - log.Warn().Msgf("Column %q not found on resource", col) + slog.Warn("Column not found on resource", slogs.ColName, col) } ii, cc[idx] = append(ii, idx), struct{}{} } @@ -134,7 +138,7 @@ func (h Header) Customize(cols []string, wide bool) Header { for _, c := range cols { idx, ok := h.IndexOf(c, true) if !ok { - log.Warn().Msgf("Column %s is not available on this resource", c) + slog.Warn("Column is not available on this resource", slogs.ColName, c) cc = append(cc, HeaderColumn{Name: c}) continue } @@ -232,8 +236,8 @@ func (h Header) IndexOf(colName string, includeWide bool) (int, bool) { // Dump for debugging. func (h Header) Dump() { - log.Debug().Msgf("HEADER") + slog.Debug("HEADER") for i, c := range h { - log.Debug().Msgf("%d %q -- %t", i, c.Name, c.Wide) + slog.Debug(fmt.Sprintf("%d %q -- %t", i, c.Name, c.Wide)) } } diff --git a/internal/model1/row_event.go b/internal/model1/row_event.go index 1c5e065b..b4921b74 100644 --- a/internal/model1/row_event.go +++ b/internal/model1/row_event.go @@ -5,9 +5,8 @@ package model1 import ( "fmt" + "log/slog" "sort" - - "github.com/rs/zerolog/log" ) type ReRangeFn func(int, RowEvent) bool @@ -274,11 +273,11 @@ func (r *RowEvents) Sort(ns string, sortCol int, isDuration, numCol, isCapacity, r.reindex() } -// For debguging... +// For debugging... func (re RowEvents) Dump(msg string) { - log.Debug().Msg(msg) + slog.Debug("[DEBUG] RowEvents" + msg) for _, r := range re.events { - log.Debug().Msgf("!!YO!! %#v", r) + slog.Debug(fmt.Sprintf(" %#v", r)) } } diff --git a/internal/model1/table_data.go b/internal/model1/table_data.go index 3c70a8d5..dc25afb2 100644 --- a/internal/model1/table_data.go +++ b/internal/model1/table_data.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "log/slog" "regexp" "strings" "sync" @@ -14,7 +15,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "github.com/sahilm/fuzzy" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -155,7 +156,7 @@ func (t *TableData) Filter(f FilterOpts) *TableData { if err == nil { td.rowEvents = rr } else { - log.Error().Err(err).Msg("rx filter failed") + slog.Error("RX filter failed", slogs.Error, err) } return td @@ -206,12 +207,11 @@ func (t *TableData) fuzzyFilter(q string) *RowEvents { mm := fuzzy.Find(q, ss) rr := NewRowEvents(t.RowCount() / 2) for _, m := range mm { - re, ok := t.rowEvents.At(m.Index) - if !ok { - log.Error().Msgf("unable to find event for index in fuzzfilter: %d", m.Index) - continue + if re, ok := t.rowEvents.At(m.Index); !ok { + slog.Error("Unable to find event for index in fuzzfilter", slogs.Index, m.Index) + } else { + rr.Add(re) } - rr.Add(re) } return rr @@ -474,7 +474,10 @@ func (t *TableData) Delete(newKeys map[string]struct{}) { }) for _, id := range victims { if err := t.rowEvents.Delete(id); err != nil { - log.Error().Err(err).Msgf("table delete failed: %q", id) + slog.Error("Table delete failed", + slogs.Error, err, + slogs.Message, id, + ) } } } diff --git a/internal/model1/table_data_test.go b/internal/model1/table_data_test.go index dddbdf36..185f2cac 100644 --- a/internal/model1/table_data_test.go +++ b/internal/model1/table_data_test.go @@ -4,16 +4,16 @@ package model1 import ( + "log/slog" "testing" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestTableDataComputeSortCol(t *testing.T) { diff --git a/internal/perf/benchmark.go b/internal/perf/benchmark.go index 5440e53a..80544b11 100644 --- a/internal/perf/benchmark.go +++ b/internal/perf/benchmark.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "io" + "log/slog" "net/http" "os" "path/filepath" @@ -16,11 +17,11 @@ import ( "time" "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/rakyll/hey/requester" - "github.com/rs/zerolog/log" ) const ( @@ -59,7 +60,7 @@ func (b *Benchmark) init(base, version string) error { req.SetBasicAuth(b.config.Auth.User, b.config.Auth.Password) } req.Header = b.config.HTTP.Headers - log.Debug().Msgf("Benchmarking Request %s", req.URL.String()) + slog.Debug("Benchmarking Request", slogs.URL, req.URL.String()) ua := req.UserAgent() if ua == "" { @@ -73,8 +74,7 @@ func (b *Benchmark) init(base, version string) error { } req.Header.Set("User-Agent", ua) - log.Debug().Msgf("Using bench config N:%d--C:%d", b.config.N, b.config.C) - + slog.Debug(fmt.Sprintf("Using bench config N:%d--C:%d", b.config.N, b.config.C)) b.worker = &requester.Work{ Request: req, RequestBody: []byte(b.config.HTTP.Body), @@ -108,7 +108,10 @@ func (b *Benchmark) Canceled() bool { // Run starts a benchmark. func (b *Benchmark) Run(cluster, context string, done func()) { - log.Debug().Msgf("Running benchmark on context %s", cluster) + slog.Debug("Running benchmark", + slogs.Cluster, cluster, + slogs.Context, context, + ) buff := new(bytes.Buffer) b.worker.Writer = buff // this call will block until the benchmark is complete or times out. @@ -116,7 +119,7 @@ func (b *Benchmark) Run(cluster, context string, done func()) { b.worker.Stop() if buff.Len() > 0 { if err := b.save(cluster, context, buff); err != nil { - log.Error().Err(err).Msg("Saving Benchmark") + slog.Error("Saving Benchmark", slogs.Error, err) } } done() @@ -141,7 +144,10 @@ func (b *Benchmark) save(cluster, context string, r io.Reader) error { } defer func() { if e := f.Close(); e != nil { - log.Error().Err(e).Msgf("Benchmark file close failed: %q", bf) + slog.Error("Benchmark file close failed", + slogs.Error, e, + slogs.Path, bf, + ) } }() if _, err = io.Copy(f, r); err != nil { diff --git a/internal/port/tunnel.go b/internal/port/tunnel.go index 758675ad..585228a6 100644 --- a/internal/port/tunnel.go +++ b/internal/port/tunnel.go @@ -5,12 +5,16 @@ package port import ( "fmt" + "log/slog" "net" + + "github.com/derailed/k9s/internal/slogs" ) // PortTunnels represents a collection of tunnels. type PortTunnels []PortTunnel +// CheckAvailable checks if all port tunnels are available. func (t PortTunnels) CheckAvailable() error { for _, pt := range t { if !IsPortFree(pt) { @@ -26,6 +30,7 @@ type PortTunnel struct { Address, Container, LocalPort, ContainerPort string } +// NewPortTunnel returns a new instance. func NewPortTunnel(a, co, lp, cp string) PortTunnel { return PortTunnel{ Address: a, @@ -45,13 +50,17 @@ func (t PortTunnel) PortMap() string { if t.LocalPort == "" { t.LocalPort = t.ContainerPort } + return t.LocalPort + ":" + t.ContainerPort } +// IsPortFree checks if a address/port pair is available on host. func IsPortFree(t PortTunnel) bool { s, err := net.Listen("tcp", fmt.Sprintf("%s:%s", t.Address, t.LocalPort)) if err != nil { + slog.Warn("Port is not available", slogs.Port, t.LocalPort, slogs.Address, t.Address) return false } + return s.Close() == nil } diff --git a/internal/render/alias.go b/internal/render/alias.go index b7faa76e..6b7f841e 100644 --- a/internal/render/alias.go +++ b/internal/render/alias.go @@ -5,6 +5,7 @@ package render import ( "fmt" + "slices" "strings" "github.com/derailed/k9s/internal/client" @@ -36,13 +37,14 @@ func (Alias) Render(o interface{}, ns string, r *model1.Row) error { return fmt.Errorf("expected AliasRes, but got %T", o) } + slices.Sort(a.Aliases) gvr := client.NewGVR(a.GVR) r.ID = gvr.String() r.Fields = append(r.Fields, gvr.R(), gvr.G(), gvr.V(), - strings.Join(a.Aliases, ","), + strings.Join(a.Aliases, " "), ) return nil diff --git a/internal/render/alias_test.go b/internal/render/alias_test.go index a71b39e7..b29bca0f 100644 --- a/internal/render/alias_test.go +++ b/internal/render/alias_test.go @@ -76,7 +76,7 @@ func TestAliasRender(t *testing.T) { assert.Nil(t, a.Render(o, "fred/v1/blee", &r)) assert.Equal(t, model1.Row{ ID: "fred/v1/blee", - Fields: model1.Fields{"blee", "fred", "v1", "a,b,c"}, + Fields: model1.Fields{"blee", "fred", "v1", "a b c"}, }, r) } diff --git a/internal/render/base.go b/internal/render/base.go index 34bb5e31..091db7ba 100644 --- a/internal/render/base.go +++ b/internal/render/base.go @@ -4,9 +4,11 @@ package render import ( + "log/slog" + "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model1" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) // DecoratorFunc decorates a string. @@ -48,7 +50,7 @@ func (b *Base) SetViewSetting(vs *config.ViewSetting) { } specs, err := NewColsSpecs(cols...).parseSpecs() if err != nil { - log.Error().Err(err).Msg("unable to grok custom columns") + slog.Error("Unable to grok custom columns", slogs.Error, err) return } b.specs = specs diff --git a/internal/render/benchmark_int_test.go b/internal/render/benchmark_int_test.go index a7d4a387..bb6c42ed 100644 --- a/internal/render/benchmark_int_test.go +++ b/internal/render/benchmark_int_test.go @@ -4,16 +4,16 @@ package render import ( + "log/slog" "os" "testing" "github.com/derailed/k9s/internal/model1" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestAugmentRow(t *testing.T) { diff --git a/internal/render/container.go b/internal/render/container.go index 37cd38cc..c05242a0 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -87,8 +87,8 @@ func (Container) defaultHeader() model1.Header { model1.HeaderColumn{Name: "PROBES(L:R:S)"}, model1.HeaderColumn{Name: "CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, - model1.HeaderColumn{Name: "CPU/R:L", Attrs: model1.Attrs{Align: tview.AlignRight}}, - model1.HeaderColumn{Name: "MEM/R:L", Attrs: model1.Attrs{Align: tview.AlignRight}}, + model1.HeaderColumn{Name: "CPU/RL", Attrs: model1.Attrs{Align: tview.AlignRight}}, + model1.HeaderColumn{Name: "MEM/RL", Attrs: model1.Attrs{Align: tview.AlignRight}}, model1.HeaderColumn{Name: "%CPU/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "%CPU/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "%MEM/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, diff --git a/internal/render/context.go b/internal/render/context.go index 06a622ad..2aa17368 100644 --- a/internal/render/context.go +++ b/internal/render/context.go @@ -5,11 +5,12 @@ package render import ( "fmt" + "log/slog" + "os" "strings" "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/tools/clientcmd/api" @@ -88,8 +89,8 @@ func NewNamedContext(c ContextNamer, n string, ctx *api.Context) *NamedContext { func (c *NamedContext) IsCurrentContext(n string) bool { cl, err := c.Config.CurrentContextName() if err != nil { - log.Fatal().Err(err).Msg("Fetching current context") - return false + slog.Error("Fail to retrieve current context. Exiting!") + os.Exit(1) } return cl == n } diff --git a/internal/render/crd.go b/internal/render/crd.go index efcb6af9..a5bb5af7 100644 --- a/internal/render/crd.go +++ b/internal/render/crd.go @@ -6,11 +6,12 @@ package render import ( "errors" "fmt" + "log/slog" "strings" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/model1" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -84,7 +85,7 @@ func (c CustomResourceDefinition) defaultRow(raw *unstructured.Unstructured, r * } } if len(versions) == 0 { - log.Warn().Msgf("unable to assert CRD versions for %s", crd.Name) + slog.Warn("Unable to assert CRD versions", slogs.FQN, crd.Name) } r.ID = client.MetaFQN(crd.ObjectMeta) diff --git a/internal/render/cust_col.go b/internal/render/cust_col.go index b7ceefc3..ae26a267 100644 --- a/internal/render/cust_col.go +++ b/internal/render/cust_col.go @@ -5,14 +5,16 @@ package render import ( "fmt" + "log/slog" "regexp" "github.com/derailed/k9s/internal/model1" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/tview" "k8s.io/kubectl/pkg/cmd/get" ) -var fullRX = regexp.MustCompile(`^([\w\s%\/-]+)\:?([\w\d\S\W]*?)\|?([N|T|W|R|L|H]{0,3})$`) +var fullRX = regexp.MustCompile(`^([\w\s%\/-]+)\:?([\w\d\S\W]*?)\|?([N|T|W|V|R|L|H]{0,3})$`) type colAttr byte @@ -20,6 +22,7 @@ const ( number colAttr = 'N' age colAttr = 'T' wide colAttr = 'W' + show colAttr = 'V' alignLeft colAttr = 'L' alignRight colAttr = 'R' hide colAttr = 'H' @@ -32,6 +35,7 @@ type colAttrs struct { mxm bool time bool wide bool + show bool hide bool capacity bool } @@ -46,7 +50,9 @@ func newColFlags(flags string) colAttrs { case hide: c.hide = true case wide: - c.wide = true + c.wide, c.show = true, false + case show: + c.show, c.wide = true, false case alignLeft: c.align = tview.AlignLeft case alignRight: @@ -55,6 +61,8 @@ func newColFlags(flags string) colAttrs { c.time = true case number: c.capacity, c.align = true, tview.AlignRight + default: + slog.Warn("Unknown column attribute", slogs.Attr, b) } } @@ -69,8 +77,6 @@ type colDef struct { spec string } -// TAG:.spec.containers[0].image|split(":")|.[-1]|TW - func parse(s string) (colDef, error) { mm := fullRX.FindStringSubmatch(s) if len(mm) == 4 { @@ -95,6 +101,7 @@ func (c colDef) toHeaderCol() model1.HeaderColumn { Attrs: model1.Attrs{ Align: c.align, Wide: c.wide, + Show: c.show, Time: c.time, MX: c.mx, MXC: c.mxc, diff --git a/internal/render/cust_cols.go b/internal/render/cust_cols.go index 7fde0e72..b3026ffc 100644 --- a/internal/render/cust_cols.go +++ b/internal/render/cust_cols.go @@ -5,12 +5,13 @@ package render import ( "fmt" + "log/slog" "reflect" "strings" "time" "github.com/derailed/k9s/internal/model1" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -150,7 +151,7 @@ func hydrate(o runtime.Object, cc ColumnSpecs, parsers []*jsonpath.JSONPath, rh Header: cc[idx].Header, Value: NAValue, } - log.Warn().Msgf("unable to find column %s", cc[idx].Header.Name) + slog.Warn("Unable to find column %s", slogs.Name, cc[idx].Header.Name) continue } var v string diff --git a/internal/render/helpers.go b/internal/render/helpers.go index 92dce48a..5ce26018 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -5,16 +5,17 @@ package render import ( "context" + "log/slog" "sort" "strconv" "strings" "time" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/vul" "github.com/derailed/tview" "github.com/mattn/go-runewidth" - "github.com/rs/zerolog/log" "golang.org/x/text/language" "golang.org/x/text/message" v1 "k8s.io/api/core/v1" @@ -72,7 +73,7 @@ func AsStatus(err error) string { func asSelector(s *metav1.LabelSelector) string { sel, err := metav1.LabelSelectorAsSelector(s) if err != nil { - log.Error().Err(err).Msg("Selector conversion failed") + slog.Error("Selector conversion failed", slogs.Error, err) return NAValue } diff --git a/internal/render/pod.go b/internal/render/pod.go index bff3a716..6e93b1d2 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -106,8 +106,8 @@ func (Pod) defaultHeader() model1.Header { model1.HeaderColumn{Name: "LAST RESTART", Attrs: model1.Attrs{Align: tview.AlignRight, Time: true, Wide: true}}, model1.HeaderColumn{Name: "CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, - model1.HeaderColumn{Name: "CPU/R:L", Attrs: model1.Attrs{Align: tview.AlignRight, Wide: true}}, - model1.HeaderColumn{Name: "MEM/R:L", Attrs: model1.Attrs{Align: tview.AlignRight, Wide: true}}, + model1.HeaderColumn{Name: "CPU/RL", Attrs: model1.Attrs{Align: tview.AlignRight, Wide: true}}, + model1.HeaderColumn{Name: "MEM/RL", Attrs: model1.Attrs{Align: tview.AlignRight, Wide: true}}, model1.HeaderColumn{Name: "%CPU/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "%CPU/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "%MEM/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, diff --git a/internal/render/policy.go b/internal/render/policy.go index 2bcd82a5..2e8297a6 100644 --- a/internal/render/policy.go +++ b/internal/render/policy.go @@ -5,11 +5,13 @@ package render import ( "fmt" + "log/slog" + "strings" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/model1" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -78,14 +80,16 @@ func (Policy) Render(o interface{}, gvr string, r *model1.Row) error { // Helpers... func cleanseResource(r string) string { - if r == "" { + if r == "" || r[0] == '/' { return r } - if r[0] == '/' { + tt := strings.Split(r, "/") + switch len(tt) { + case 2, 3: + return strings.TrimPrefix(r, tt[0]+"/") + default: return r } - _, n := client.Namespaced(r) - return n } // PolicyRes represents a rbac policy rule. @@ -158,7 +162,7 @@ func (pp Policies) Upsert(p PolicyRes) Policies { } p, err := pp[idx].Merge(p) if err != nil { - log.Error().Err(err).Msg("policy upsert failed") + slog.Error("Policy upsert failed", slogs.Error, err) return pp } pp[idx] = p diff --git a/internal/render/policy_int_test.go b/internal/render/policy_int_test.go new file mode 100644 index 00000000..b16d5264 --- /dev/null +++ b/internal/render/policy_int_test.go @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package render + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_cleanseResource(t *testing.T) { + uu := map[string]struct { + r, e string + }{ + "empty": {}, + "single": { + r: "fred", + e: "fred", + }, + "grp/res": { + r: "fred/blee", + e: "blee", + }, + "grp/res/sub": { + r: "fred/blee/bob", + e: "blee/bob", + }, + } + + for k, u := range uu { + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, cleanseResource(u.r)) + }) + } +} diff --git a/internal/render/table.go b/internal/render/table.go index 0bc07e92..21c268be 100644 --- a/internal/render/table.go +++ b/internal/render/table.go @@ -7,11 +7,11 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "strings" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/model1" - "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -132,7 +132,7 @@ func (t *Table) defaultRow(row *metav1.TableRow, ns string, r *model1.Row) error if d, ok := age.(string); ok { r.Fields = append(r.Fields, d) } else if t.ageIndex > 0 { - log.Warn().Msgf("No Duration detected on age field") + slog.Warn("No Duration detected on age field") r.Fields = append(r.Fields, NAValue) } diff --git a/internal/slogs/child.go b/internal/slogs/child.go new file mode 100644 index 00000000..e252ae0a --- /dev/null +++ b/internal/slogs/child.go @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package slogs + +import "log/slog" + +// CLog returns a child logger. +func CLog(subsys string) *slog.Logger { + return slog.With(Subsys, subsys) +} diff --git a/internal/slogs/keys.go b/internal/slogs/keys.go new file mode 100644 index 00000000..abdd01c4 --- /dev/null +++ b/internal/slogs/keys.go @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package slogs + +const ( + // Error tracks an error logger key. + Error = "error" + + // Stack tracks a stack logger key. + Stack = "stack" + + // Subsys tracks a subsystem logger key. + Subsys = "subsys" + + // SchemaFile tracks a schema file logger key. + SchemaFile = "schema-file" + + // RefType tracks a reference type. + RefType = "ref-type" + + // GVR tracks a group version resource logger key. + GVR = "gvr" + + // AuthorSpec tracks an author spec logger key. + AuthSpec = "auth-spec" + + // AuthStatus tracks an auth status logger key. + AuthStatus = "auth-status" + + // AuthReason tracks an auth reason logger key. + AuthReason = "auth-reason" + + // Ports tracks a ports logger key. + Port = "port" + + // Address tracks an address logger key. + Address = "address" + + // ResName tracks a resource name logger key. + ResName = "res-name" + + // Verb tracks a verb logger key. + Verb = "verb" + + // ResType tracks a resource type logger key. + ResType = "res-type" + + // View tracks a view logger key. + View = "view" + + // GOR tracks a gor logger key. + GOR = "gor" + + // Action tracks an action logger key. + Action = "action" + + // Page tracks a page logger key. + Page = "page" + + // Skin tracks a skin logger key. + Skin = "skin" + + // CmdHist tracks a command history logger key. + CmdHist = "cmd-hist" + + // Image tracks an image logger key. + Image = "image" + + // FQN tracks a fully qualified name logger key. + FQN = "fqn" + + // ConfigName tracks a config name logger key. + ConfigName = "config-name" + + // CompName tracks a component name logger key. + CompName = "comp-name" + + // Command tracks a command logger key. + Command = "cmd" + + // Context tracks a context logger key. + Context = "context" + // Cluster tracks a cluster logger key. + Cluster = "cluster" + + // Container tracks a container logger key. + Container = "container" + + // Options tracks an options logger key. + Options = "options" + + // Count tracks a count logger key. + Count = "count" + + // MaxRetries tracks a max retries logger key. + MaxRetries = "max-retries" + + // Retry tracks a retry logger key. + Retry = "retry" + + // Message tracks a message logger key. + Message = "message" + + // Index tracks an index logger key. + Index = "index" + + // Path tracks a path logger key. + Path = "path" + + // Dir tracks a directory logger key. + Dir = "dir" + + // FileName tracks a file name logger key. + FileName = "file-name" + + // Key tracks a key logger key. + Key = "key" + + // Component tracks a component logger key. + Component = "component" + + // RowID tracks a row id logger key. + RowID = "row-id" + + // Cell tracks a cell logger key. + Cell = "cell" + + // HeaderSize tracks a header size logger key. + HeaderSize = "row-size" + + // Namespace tracks a namespace logger key. + Namespace = "ns" + + // AllNS tracks all namespaces logger key. + AllNS = "all-ns" + + // Max tracks a max logger key. + Max = "max" + + // Elapsed tracks an elapsed logger key. + Elapsed = "elapsed" + + // Log tracks a log logger key. + Log = "log" + + // CO tracks a container logger key. + CO = "container" + + // Annotation tracks an annotation logger key. + Annotation = "annotation" + + // Bool tracks a boolean logger key. + Bool = "bool" + + // Replicas tracks a replicas logger key. + Replicas = "replicas" + + // Revision tracks a revision logger key. + Revision = "revision" + + // ColName tracks a column name logger key. + ColName = "col-name" + + // URL tracks a URL logger key. + URL = "url" + + // Attr tracks an attribute logger key. + Attr = "attr" + + // Name tracks a name logger key. + Name = "name" + + // Matches tracks a matches logger key. + Matches = "matches" + + // Line tracks a line logger key. + Line = "line" + + // Sig tracks a signal logger key. + Sig = "signal" + + // Bin tracks a binary logger key. + Bin = "binary" + + // Args tracks an arguments logger key. + Args = "args" + + // PodPhase tracks a pod phase logger key. + PodPhase = "pod-phase" + + // ShellPodCfg tracks a shell pod config logger key. + ShellPodCfg = "shell-pod-cfg" + + // PFID tracks a port forward id logger key. + PFID = "port-fwd-id" + + // PFTunnel tracks a port forward tunnel logger key. + PFTunnel = "port-fwd-tunnel" + + // Config tracks a config logger key. + Config = "config" + + // ResKind tracks a resource kind logger key. + ResKind = "res-kind" + + // ResGrpVersion tracks a resource group version logger key. + ResGrpVersion = "res-grp-version" + + // ID tracks an id logger key. + ID = "id" +) diff --git a/internal/ui/action.go b/internal/ui/action.go index f270e67b..305d3624 100644 --- a/internal/ui/action.go +++ b/internal/ui/action.go @@ -4,12 +4,13 @@ package ui import ( + "log/slog" "sort" "sync" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) type ( @@ -213,7 +214,7 @@ func (a *KeyActions) Hints() model.MenuHints { }, ) } else { - log.Error().Msgf("Unable to locate KeyName for %#v", k) + slog.Error("Unable to locate key name", slogs.Key, k) } } diff --git a/internal/ui/app.go b/internal/ui/app.go index acefef97..cbfb1a64 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -4,15 +4,16 @@ package ui import ( + "log/slog" "os" "sync" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" ) // App represents an application. @@ -127,11 +128,11 @@ func (a *App) StylesChanged(s *config.Styles) { if h, ok := f.ItemAt(0).(*tview.Flex); ok { h.SetBackgroundColor(s.BgColor()) } else { - log.Error().Msgf("Header not found") + slog.Warn("Header not found", slogs.Subsys, "styles", slogs.Component, "app") } } } else { - log.Error().Msgf("Main not found") + slog.Error("Main panel not found", slogs.Subsys, "styles", slogs.Component, "app") } } @@ -151,13 +152,13 @@ func (a *App) bindKeys() { } // BailOut exits the application. -func (a *App) BailOut() { +func (a *App) BailOut(exitCode int) { if err := a.Config.Save(true); err != nil { - log.Error().Err(err).Msg("config save failed!") + slog.Error("Config save failed!", slogs.Error, err) } a.Stop() - os.Exit(0) + os.Exit(exitCode) } // ResetPrompt reset the prompt model and marks buffer as active. diff --git a/internal/ui/config.go b/internal/ui/config.go index 487a1cc9..98019b19 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -7,15 +7,16 @@ import ( "context" "errors" "io/fs" + "log/slog" "os" "path/filepath" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model1" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/config" "github.com/fsnotify/fsnotify" - "github.com/rs/zerolog/log" ) // Synchronizer manages ui event queue. @@ -55,17 +56,17 @@ func (c *Configurator) CustomViewsWatcher(ctx context.Context, s synchronizer) e if evt.Name == config.AppViewsFile && evt.Op != fsnotify.Chmod { s.QueueUpdateDraw(func() { if err := c.RefreshCustomViews(); err != nil { - log.Warn().Err(err).Msgf("Custom views refresh failed") + slog.Warn("Custom views refresh failed", slogs.Error, err) } }) } case err := <-w.Errors: - log.Warn().Err(err).Msg("CustomView watcher failed") + slog.Warn("CustomView watcher failed", slogs.Error, err) return case <-ctx.Done(): - log.Debug().Msgf("CustomViewWatcher CANCELED `%s!!", config.AppViewsFile) + slog.Debug("CustomViewWatcher canceled", slogs.FileName, config.AppViewsFile) if err := w.Close(); err != nil { - log.Error().Err(err).Msg("Closing CustomView watcher") + slog.Error("Closing CustomView watcher", slogs.Error, err) } return } @@ -104,29 +105,29 @@ func (c *Configurator) SkinsDirWatcher(ctx context.Context, s synchronizer) erro select { case evt := <-w.Events: if evt.Op != fsnotify.Chmod && filepath.Base(evt.Name) == filepath.Base(c.skinFile) { - log.Debug().Msgf("Skin changed: %s", c.skinFile) + slog.Debug("Skin file changed detected", slogs.FileName, c.skinFile) s.QueueUpdateDraw(func() { c.RefreshStyles(s) }) } case err := <-w.Errors: - log.Info().Err(err).Msg("Skin watcher failed") + slog.Warn("Skin watcher failed", slogs.Error, err) return case <-ctx.Done(): - log.Debug().Msgf("SkinWatcher CANCELED `%s!!", c.skinFile) + slog.Debug("SkinWatcher canceled", slogs.FileName, c.skinFile) if err := w.Close(); err != nil { - log.Error().Err(err).Msg("Closing Skin watcher") + slog.Error("Closing Skin watcher", slogs.Error, err) } return } } }() - log.Debug().Msgf("SkinWatcher watching %q", config.AppSkinsDir) + slog.Debug("SkinWatcher initialized", slogs.Dir, config.AppSkinsDir) return w.Add(config.AppSkinsDir) } -// ConfigWatcher watches for skin settings changes. +// ConfigWatcher watches for config settings changes. func (c *Configurator) ConfigWatcher(ctx context.Context, s synchronizer) error { w, err := fsnotify.NewWatcher() if err != nil { @@ -138,16 +139,16 @@ func (c *Configurator) ConfigWatcher(ctx context.Context, s synchronizer) error select { case evt := <-w.Events: if evt.Has(fsnotify.Create) || evt.Has(fsnotify.Write) { - log.Debug().Msgf("ConfigWatcher file changed: %s", evt.Name) + slog.Debug("ConfigWatcher file changed", slogs.FileName, evt.Name) if evt.Name == config.AppConfigFile { if err := c.Config.Load(evt.Name, false); err != nil { - log.Error().Err(err).Msgf("k9s config reload failed") + slog.Error("K9s config reload failed", slogs.Error, err) s.Flash().Warn("k9s config reload failed. Check k9s logs!") s.Logo().Warn("K9s config reload failed!") } } else { if err := c.Config.K9s.Reload(); err != nil { - log.Error().Err(err).Msgf("k9s context config reload failed") + slog.Error("K9s context config reload failed", slogs.Error, err) s.Flash().Warn("Context config reload failed. Check k9s logs!") s.Logo().Warn("Context config reload failed!") } @@ -157,19 +158,19 @@ func (c *Configurator) ConfigWatcher(ctx context.Context, s synchronizer) error }) } case err := <-w.Errors: - log.Info().Err(err).Msg("ConfigWatcher failed") + slog.Warn("ConfigWatcher failed", slogs.Error, err) return case <-ctx.Done(): - log.Debug().Msg("ConfigWatcher CANCELED") + slog.Debug("ConfigWatcher canceled") if err := w.Close(); err != nil { - log.Error().Err(err).Msg("Canceling ConfigWatcher") + slog.Error("Canceling ConfigWatcher", slogs.Error, err) } return } } }() - log.Debug().Msgf("ConfigWatcher watching: %q", config.AppConfigFile) + slog.Debug("ConfigWatcher watching", slogs.FileName, config.AppConfigFile) if err := w.Add(config.AppConfigFile); err != nil { return err } @@ -179,7 +180,7 @@ func (c *Configurator) ConfigWatcher(ctx context.Context, s synchronizer) error return nil } ctConfigFile := filepath.Join(config.AppContextConfig(cl, ct)) - log.Debug().Msgf("ConfigWatcher watching: %q", ctConfigFile) + slog.Debug("ConfigWatcher watching", slogs.FileName, ctConfigFile) return w.Add(ctConfigFile) } @@ -193,14 +194,17 @@ func (c *Configurator) activeSkin() (string, bool) { if ct, err := c.Config.K9s.ActiveContext(); err == nil && ct.Skin != "" { if _, err := os.Stat(config.SkinFileFromName(ct.Skin)); err == nil { skin = ct.Skin - log.Debug().Msgf("[Skin] Loading context skin (%q) from %q", skin, c.Config.K9s.ActiveContextName()) + slog.Debug("Loading context skin", + slogs.Skin, skin, + slogs.Context, c.Config.K9s.ActiveContextName(), + ) } } if sk := c.Config.K9s.UI.Skin; skin == "" && sk != "" { if _, err := os.Stat(config.SkinFileFromName(sk)); err == nil { skin = sk - log.Debug().Msgf("[Skin] Loading global skin (%q)", skin) + slog.Debug("Loading global skin", slogs.Skin, skin) } } @@ -237,7 +241,11 @@ func (c *Configurator) RefreshStyles(s synchronizer) { } // !!BOZO!! Lame move out! if bc, err := config.EnsureBenchmarksCfgFile(cl, ct); err != nil { - log.Warn().Err(err).Msgf("No benchmark config file found: %q@%q", cl, ct) + slog.Warn("No benchmark config file found", + slogs.Cluster, cl, + slogs.Context, ct, + slogs.Error, err, + ) } else { c.BenchFile = bc } @@ -246,19 +254,26 @@ func (c *Configurator) RefreshStyles(s synchronizer) { func (c *Configurator) loadSkinFile(s synchronizer) { skin, ok := c.activeSkin() if !ok { - log.Debug().Msgf("No custom skin found. Using stock skin") + slog.Debug("No custom skin found. Using stock skin") c.updateStyles("") return } skinFile := config.SkinFileFromName(skin) - log.Debug().Msgf("Loading skin file: %q", skinFile) + slog.Debug("Loading skin file", slogs.Skin, skinFile) if err := c.Styles.Load(skinFile); err != nil { if errors.Is(err, os.ErrNotExist) { - log.Warn().Msgf("Skin file %q not found in skins dir: %s", filepath.Base(skinFile), config.AppSkinsDir) + slog.Warn("Skin file not found in skins dir", + slogs.Skin, filepath.Base(skinFile), + slogs.Dir, config.AppSkinsDir, + slogs.Error, err, + ) c.updateStyles("") } else { - log.Error().Msgf("Failed to parse skin file -- %s: %s.", filepath.Base(skinFile), err) + slog.Error("Failed to parse skin file", + slogs.Path, filepath.Base(skinFile), + slogs.Error, err, + ) c.updateStyles(skinFile) } } else { diff --git a/internal/ui/crumbs_test.go b/internal/ui/crumbs_test.go index 644f2745..dc43b5ba 100644 --- a/internal/ui/crumbs_test.go +++ b/internal/ui/crumbs_test.go @@ -5,6 +5,7 @@ package ui_test import ( "context" + "log/slog" "testing" "github.com/derailed/k9s/internal/config" @@ -12,12 +13,11 @@ import ( "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestNewCrumbs(t *testing.T) { diff --git a/internal/ui/flash.go b/internal/ui/flash.go index f1884641..0ba37d5b 100644 --- a/internal/ui/flash.go +++ b/internal/ui/flash.go @@ -5,12 +5,12 @@ package ui import ( "context" + "log/slog" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" ) const ( @@ -55,7 +55,7 @@ func (f *Flash) StylesChanged(s *config.Styles) { // Watch watches for flash changes. func (f *Flash) Watch(ctx context.Context, c model.FlashChan) { - defer log.Debug().Msgf("Flash Watch Canceled!") + defer slog.Debug("Flash Watch Canceled!") for { select { case <-ctx.Done(): diff --git a/internal/ui/pages.go b/internal/ui/pages.go index eed87e5d..af18fd3e 100644 --- a/internal/ui/pages.go +++ b/internal/ui/pages.go @@ -5,10 +5,11 @@ package ui import ( "fmt" + "log/slog" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/tview" - "github.com/rs/zerolog/log" ) // Pages represents a stack of view pages. @@ -72,9 +73,9 @@ func (p *Pages) delete(c model.Component) { // Dump for debug. func (p *Pages) Dump() { - log.Debug().Msgf("Dumping Pages %p", p) + slog.Debug("Dumping Pages", slogs.Page, p) for i, c := range p.Stack.Peek() { - log.Debug().Msgf("%d -- %s -- %#v", i, componentID(c), p.GetPrimitive(componentID(c))) + slog.Debug(fmt.Sprintf("%d -- %s -- %#v", i, componentID(c), p.GetPrimitive(componentID(c)))) } } @@ -102,7 +103,7 @@ func (p *Pages) StackTop(top model.Component) { func componentID(c model.Component) string { if c.Name() == "" { - log.Error().Msg("Component has no name") + slog.Error("Component has no name", slogs.Component, fmt.Sprintf("%T", c)) } return fmt.Sprintf("%s-%p", c.Name(), c) } diff --git a/internal/ui/prompt.go b/internal/ui/prompt.go index 9e8b7ea6..3f43fd28 100644 --- a/internal/ui/prompt.go +++ b/internal/ui/prompt.go @@ -104,8 +104,6 @@ func NewPrompt(app *App, noIcons bool, styles *config.Styles) *Prompt { p.SetDynamicColors(true) p.SetBorder(true) p.SetBorderPadding(0, 0, 1, 1) - p.SetBackgroundColor(styles.K9s.Prompt.BgColor.Color()) - p.SetTextColor(styles.K9s.Prompt.FgColor.Color()) styles.AddListener(&p) p.SetInputCapture(p.keyboard) @@ -236,6 +234,7 @@ func (p *Prompt) write(text, suggest string) { if suggest != "" { txt += fmt.Sprintf("[%s::-]%s", p.styles.Prompt().SuggestColor, suggest) } + p.StylesChanged(p.styles) fmt.Fprintf(p, defaultPrompt, p.icon, txt) } diff --git a/internal/ui/table.go b/internal/ui/table.go index fdf68b7d..de4c5151 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -6,6 +6,7 @@ package ui import ( "context" "fmt" + "log/slog" "sync" "github.com/derailed/k9s/internal" @@ -15,12 +16,10 @@ import ( "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/vul" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" - "golang.org/x/text/cases" - "golang.org/x/text/language" ) const maxTruncate = 50 @@ -270,6 +269,14 @@ func (t *Table) Update(data *model1.TableData, hasMetrics bool) *model1.TableDat return t.doUpdate(t.filtered(data)) } +func (t *Table) GetNamespace() string { + if t.GetModel() != nil { + return t.GetModel().GetNamespace() + } + + return client.NamespaceAll +} + func (t *Table) doUpdate(data *model1.TableData) *model1.TableData { if client.IsAllNamespaces(data.GetNamespace()) { t.actions.Add( @@ -323,7 +330,7 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) { cdata.RowsRange(func(row int, re model1.RowEvent) bool { ore, ok := data.FindRow(re.Row.ID) if !ok { - log.Error().Msgf("unable to find original re: %q", re.Row.ID) + slog.Error("Unable to find original row event", slogs.RowID, re.Row.ID) return true } t.buildRow(row+1, re, ore, cdata.Header(), pads, isNamespaced) @@ -346,7 +353,11 @@ func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads M ns := t.GetModel().GetNamespace() for c, field := range re.Row.Fields { if c >= len(h) { - log.Error().Msgf("field/header overflow detected for %q -- %d::%d. Check your mappings!", t.GVR(), c, len(h)) + slog.Error("Field/header overflow detected. Check your mappings!", + slogs.GVR, t.GVR(), + slogs.Cell, c, + slogs.HeaderSize, len(h), + ) continue } if h[c].Hide || (!t.wide && h[c].Wide) { @@ -505,7 +516,6 @@ func (t *Table) styleTitle() string { rc-- } - base := cases.Title(language.Und, cases.NoLower).String(t.gvr.R()) ns := t.GetModel().GetNamespace() if client.IsClusterWide(ns) || ns == client.NotNamespaced { ns = client.NamespaceAll @@ -524,9 +534,9 @@ func (t *Table) styleTitle() string { } var title string if ns == client.ClusterScope { - title = SkinTitle(fmt.Sprintf(TitleFmt, base, render.AsThousands(rc)), t.styles.Frame()) + title = SkinTitle(fmt.Sprintf(TitleFmt, t.gvr, render.AsThousands(rc)), t.styles.Frame()) } else { - title = SkinTitle(fmt.Sprintf(NSTitleFmt, base, ns, render.AsThousands(rc)), t.styles.Frame()) + title = SkinTitle(fmt.Sprintf(NSTitleFmt, t.gvr, ns, render.AsThousands(rc)), t.styles.Frame()) } buff := t.cmdBuff.GetText() diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 479ed2d0..30c4359f 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -6,11 +6,13 @@ package ui import ( "context" "fmt" + "log/slog" + "os" "strings" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/config" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) const ( @@ -39,7 +41,8 @@ const ( func mustExtractStyles(ctx context.Context) *config.Styles { styles, ok := ctx.Value(internal.KeyStyles).(*config.Styles) if !ok { - log.Fatal().Msg("Expecting valid styles") + slog.Error("Expecting valid styles. Exiting!") + os.Exit(1) } return styles } @@ -48,7 +51,7 @@ func mustExtractStyles(ctx context.Context) *config.Styles { func TrimCell(tv *SelectTable, row, col int) string { c := tv.GetCell(row, col) if c == nil { - log.Error().Err(fmt.Errorf("No cell at location [%d:%d]", row, col)).Msg("Trim cell failed!") + slog.Error("Trim cell failed", slogs.Error, fmt.Errorf("no cell at [%d:%d]", row, col)) return "" } return strings.TrimSpace(c.Text) diff --git a/internal/view/actions.go b/internal/view/actions.go index fa4cf6ad..0b3cf767 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -6,13 +6,14 @@ package view import ( "errors" "fmt" + "log/slog" "strings" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) // AllScopes represents actions available for all views. @@ -80,12 +81,15 @@ func hotKeyActions(r Runner, aa *ui.KeyActions) error { errs = errors.Join(errs, fmt.Errorf("duplicate hotkey found for %q in %q", hk.ShortCut, k)) continue } - log.Debug().Msgf("Action %q has been overridden by hotkey in %q", hk.ShortCut, k) + slog.Debug("Action has been overridden by hotkey", + slogs.Action, hk.ShortCut, + slogs.Key, k, + ) } command, err := r.EnvFn()().Substitute(hk.Command) if err != nil { - log.Warn().Err(err).Msg("Invalid shortcut command") + slog.Warn("Invalid shortcut command", slogs.Error, err) continue } @@ -144,7 +148,10 @@ func pluginActions(r Runner, aa *ui.KeyActions) error { errs = errors.Join(errs, fmt.Errorf("duplicate plugin key found for %q in %q", plugin.ShortCut, k)) continue } - log.Debug().Msgf("Action %q has been overridden by plugin in %q", plugin.ShortCut, k) + slog.Debug("Action has been overridden by plugin action", + slogs.Action, plugin.ShortCut, + slogs.Key, k, + ) } if plugin.Dangerous && ro { @@ -178,7 +185,7 @@ func pluginAction(r Runner, p config.Plugin) ui.ActionHandler { for i, a := range p.Args { arg, err := r.EnvFn()().Substitute(a) if err != nil { - log.Error().Err(err).Msg("Plugin Args match failed") + slog.Error("Plugin Args match failed", slogs.Error, err) return nil } args[i] = arg diff --git a/internal/view/actions_test.go b/internal/view/actions_test.go index 76f8abaf..051927fc 100644 --- a/internal/view/actions_test.go +++ b/internal/view/actions_test.go @@ -4,14 +4,14 @@ package view import ( + "log/slog" "testing" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) func init() { - zerolog.SetGlobalLevel(zerolog.Disabled) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestHasAll(t *testing.T) { diff --git a/internal/view/app.go b/internal/view/app.go index 8128c9bc..3ddac265 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "log/slog" "os" "os/signal" "runtime" @@ -21,6 +22,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/k9s/internal/view/cmd" @@ -28,7 +30,6 @@ import ( "github.com/derailed/k9s/internal/watch" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" ) // ExitStatus indicates UI exit conditions. @@ -146,10 +147,10 @@ func (a *App) stopImgScanner() { func (a *App) initImgScanner(version string) { defer func(t time.Time) { - log.Debug().Msgf("Scanner init time %s", time.Since(t)) + slog.Debug("Scanner init time", slogs.Elapsed, time.Since(t)) }(time.Now()) - vul.ImgScanner = vul.NewImageScanner(a.Config.K9s.ImageScans) + vul.ImgScanner = vul.NewImageScanner(a.Config.K9s.ImageScans, slog.Default()) go vul.ImgScanner.Init("k9s", version) } @@ -183,7 +184,7 @@ func (a *App) initSignals() { func (a *App) suggestCommand() model.SuggestionFunc { contextNames, err := a.contextNames() if err != nil { - log.Error().Err(err).Msg("failed to list contexts") + slog.Error("Failed to list contexts", slogs.Error, err) } return func(s string) (entries sort.StringSlice) { @@ -203,7 +204,7 @@ func (a *App) suggestCommand() model.SuggestionFunc { namespaceNames, err := a.factory.Client().ValidNamespaceNames() if err != nil { - log.Error().Err(err).Msg("failed to list namespaces") + slog.Error("Failed to obtain list of namespaces", slogs.Error, err) } entries = append(entries, cmd.SuggestSubCommand(s, namespaceNames, contextNames)...) if len(entries) == 0 { @@ -215,9 +216,6 @@ func (a *App) suggestCommand() model.SuggestionFunc { } func (a *App) contextNames() ([]string, error) { - if !a.Conn().ConnectionOK() { - return nil, errors.New("no connection") - } contexts, err := a.factory.Client().Config().Contexts() if err != nil { return nil, err @@ -254,10 +252,11 @@ func (a *App) bindKeys() { } func (a *App) dumpGOR(evt *tcell.EventKey) *tcell.EventKey { - log.Debug().Msgf("GOR %d", runtime.NumGoroutine()) - // bb := make([]byte, 5_000_000) - // runtime.Stack(bb, true) - // log.Debug().Msgf("GOR\n%s", string(bb)) + slog.Debug("GOR", slogs.GOR, runtime.NumGoroutine()) + bb := make([]byte, 5_000_000) + runtime.Stack(bb, true) + slog.Debug("GOR stack", slogs.Stack, string(bb)) + return evt } @@ -270,7 +269,8 @@ func (a *App) toggleHeader(header, logo bool) { a.showHeader, a.showLogo = header, logo flex, ok := a.Main.GetPrimitive("main").(*tview.Flex) if !ok { - log.Fatal().Msg("Expecting valid flex view") + slog.Error("Expecting flex view main panel. Exiting!") + os.Exit(1) } if a.showHeader { flex.RemoveItemAtIndex(0) @@ -285,7 +285,8 @@ func (a *App) toggleCrumbs(flag bool) { a.showCrumbs = flag flex, ok := a.Main.GetPrimitive("main").(*tview.Flex) if !ok { - log.Fatal().Msg("Expecting valid flex view") + slog.Error("Expecting valid flex view main panel. Exiting!") + os.Exit(1) } if a.showCrumbs { if _, ok := flex.ItemAt(2).(*ui.Crumbs); !ok { @@ -341,20 +342,20 @@ func (a *App) Resume() { if a.Config.K9s.UI.Reactive { if err := a.ConfigWatcher(ctx, a); err != nil { - log.Warn().Err(err).Msgf("ConfigWatcher failed") + slog.Warn("ConfigWatcher failed", slogs.Error, err) } if err := a.SkinsDirWatcher(ctx, a); err != nil { - log.Warn().Err(err).Msgf("SkinsWatcher failed") + slog.Warn("SkinsWatcher failed", slogs.Error, err) } if err := a.CustomViewsWatcher(ctx, a); err != nil { - log.Warn().Err(err).Msgf("CustomView watcher failed") + slog.Warn("CustomView watcher failed", slogs.Error, err) } } } func (a *App) clusterUpdater(ctx context.Context) { if err := a.refreshCluster(ctx); err != nil { - log.Error().Err(err).Msgf("Cluster updater failed!") + slog.Error("Cluster updater failed!", slogs.Error, err) return } @@ -363,13 +364,13 @@ func (a *App) clusterUpdater(ctx context.Context) { for { select { case <-ctx.Done(): - log.Debug().Msg("ClusterInfo updater canceled!") + slog.Debug("ClusterInfo updater canceled!") return case <-time.After(delay): if err := a.refreshCluster(ctx); err != nil { - log.Error().Err(err).Msgf("ClusterUpdater failed") + slog.Error("Cluster updates failed. Giving up ;(", slogs.Error, err) if delay = bf.NextBackOff(); delay == backoff.Stop { - a.BailOut() + a.BailOut(1) return } } else { @@ -400,9 +401,12 @@ func (a *App) refreshCluster(context.Context) error { count, maxConnRetry := atomic.LoadInt32(&a.conRetry), int32(a.Config.K9s.MaxConnRetry) if count >= maxConnRetry { - log.Error().Msgf("Conn check failed (%d/%d). Bailing out!", count, maxConnRetry) + slog.Error("Conn check failed. Bailing out!", + slogs.Retry, count, + slogs.MaxRetries, maxConnRetry, + ) ExitStatus = fmt.Sprintf("Lost K8s connection (%d). Bailing out!", count) - a.BailOut() + a.BailOut(1) } if count > 0 { a.Status(model.FlashWarn, fmt.Sprintf("Dial K8s Toast [%d/%d]", count, maxConnRetry)) @@ -412,7 +416,7 @@ func (a *App) refreshCluster(context.Context) error { // Reload alias go func() { if err := a.command.Reset(a.Config.ContextAliasesPath(), false); err != nil { - log.Warn().Err(err).Msgf("Command reset failed") + slog.Warn("Command reset failed", slogs.Error, err) a.QueueUpdateDraw(func() { a.Logo().Warn("Aliases load failed!") }) @@ -439,18 +443,16 @@ func (a *App) switchNS(ns string) error { } func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { - name, ok := ci.HasContext() - if !ok || a.Config.ActiveContextName() == name { - if !force { - return nil - } + contextName, ok := ci.HasContext() + if (!ok || a.Config.ActiveContextName() == contextName) && !force { + return nil } a.Halt() defer a.Resume() { a.Config.Reset() - ct, err := a.Config.K9s.ActivateContext(name) + ct, err := a.Config.ActivateContext(contextName) if err != nil { return err } @@ -465,7 +467,7 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { } ns := a.Config.ActiveNamespace() if !a.Conn().IsValidNamespace(ns) { - log.Warn().Msgf("Unable to validate namespace: %q. Using %q as active namespace", ns, ns) + slog.Warn("Unable to validate namespace", slogs.Namespace, ns) if err := a.Config.SetActiveNamespace(ns); err != nil { return err } @@ -473,17 +475,19 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { a.Flash().Infof("Using %q namespace", ns) if err := a.Config.Save(true); err != nil { - log.Error().Err(err).Msg("config save failed!") - } else { - log.Debug().Msgf("Saved context config for: %q", name) + slog.Error("Fail to save config to disk", slogs.Subsys, "config", slogs.Error, err) } a.initFactory(ns) if err := a.command.Reset(a.Config.ContextAliasesPath(), true); err != nil { return err } - log.Debug().Msgf("--> Switching Context %q -- %q -- %q", name, ns, a.Config.ActiveView()) - a.Flash().Infof("Switching context to %q::%q", name, ns) + slog.Debug("Switching Context", + slogs.Context, contextName, + slogs.Namespace, ns, + slogs.View, a.Config.ActiveView(), + ) + a.Flash().Infof("Switching context to %q::%q", contextName, ns) a.ReloadStyles() a.gotoResource(a.Config.ActiveView(), "", true, true) a.clusterModel.Reset(a.factory) @@ -498,24 +502,20 @@ func (a *App) initFactory(ns string) { } // BailOut exists the application. -func (a *App) BailOut() { +func (a *App) BailOut(exitCode int) { defer func() { if err := recover(); err != nil { - log.Error().Msgf("Bailing out %v", err) + slog.Error("Bailout failed", slogs.Error, err) } }() - if err := a.Config.Save(true); err != nil { - log.Error().Err(err).Msg("config save failed!") - } - if err := nukeK9sShell(a); err != nil { - log.Error().Err(err).Msgf("nuking k9s shell pod") + slog.Error("Unable to nuke k9s shell pod", slogs.Error, err) } a.stopImgScanner() a.factory.Terminate() - a.App.BailOut() + a.App.BailOut(exitCode) } // Run starts the application loop. @@ -646,7 +646,7 @@ func (a *App) cowCmd(msg string) { } func (a *App) dirCmd(path string, pushCmd bool) error { - log.Debug().Msgf("DIR PATH %q", path) + slog.Debug("Exec Dir command", slogs.Path, path) _, err := os.Stat(path) if err != nil { return err @@ -670,7 +670,7 @@ func (a *App) quitCmd(evt *tcell.EventKey) *tcell.EventKey { } if !a.Config.K9s.NoExitOnCtrlC { - a.BailOut() + a.BailOut(0) } // overwrite the default ctrl-c behavior of tview @@ -765,7 +765,10 @@ func (a *App) gotoResource(c, path string, clearStack bool, pushCmd bool) { func (a *App) inject(c model.Component, clearStack bool) error { ctx := context.WithValue(context.Background(), internal.KeyApp, a) if err := c.Init(ctx); err != nil { - log.Error().Err(err).Msgf("Component init failed for %q", c.Name()) + slog.Error("Component init failed", + slogs.Error, err, + slogs.CompName, c.Name(), + ) return err } if clearStack { diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go index 535b39f8..386241e7 100644 --- a/internal/view/benchmark.go +++ b/internal/view/benchmark.go @@ -5,6 +5,7 @@ package view import ( "context" + "log/slog" "os" "path/filepath" "strings" @@ -13,9 +14,10 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) // Benchmark represents a service benchmark results view. @@ -71,8 +73,10 @@ func fileToSubject(path string) string { func benchDir(cfg *config.Config) string { ct, err := cfg.K9s.ActiveContext() if err != nil { - log.Error().Err(err).Msgf("no active context located") + slog.Error("No active context located", slogs.Error, err) + return render.MissingValue } + return filepath.Join( config.AppBenchmarksDir, data.SanitizeFileName(ct.ClusterName), diff --git a/internal/view/browser.go b/internal/view/browser.go index 52b6a2cb..e691b8d0 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -6,6 +6,7 @@ package view import ( "context" "fmt" + "log/slog" "sort" "strconv" "strings" @@ -18,10 +19,10 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model1" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -60,6 +61,7 @@ func (b *Browser) getUpdating() bool { // Init watches all running pods in given namespace. func (b *Browser) Init(ctx context.Context) error { var err error + b.meta, err = dao.MetaAccess.MetaFor(b.GVR()) if err != nil { return err @@ -151,7 +153,7 @@ func (b *Browser) Start() { ns = n } if err := b.app.switchNS(ns); err != nil { - log.Error().Err(err).Msgf("ns switch failed") + slog.Error("Unable to switch namespace", slogs.Error, err) } b.Stop() @@ -205,7 +207,10 @@ func (b *Browser) BufferActive(state bool, k model.BufferKind) { return } if err := b.GetModel().Refresh(b.GetContext()); err != nil { - log.Error().Err(err).Msgf("Refresh failed for %s", b.GVR()) + slog.Error("Model refresh failed", + slogs.GVR, b.GVR(), + slogs.Error, err, + ) } data := b.GetModel().Peek() cdata := b.Update(data, b.App().Conn().HasMetrics()) @@ -462,7 +467,7 @@ func editRes(app *App, gvr client.GVR, path string) error { func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { i, err := strconv.Atoi(string(evt.Rune())) if err != nil { - log.Error().Err(err).Msgf("Fail to switch namespace") + slog.Error("Unable to convert keystroke", slogs.Error, err) return nil } ns := b.namespaces[i] @@ -487,7 +492,7 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { b.SelectRow(1, 0, true) b.app.CmdBuff().Reset() if err := b.app.Config.SetActiveNamespace(b.GetModel().GetNamespace()); err != nil { - log.Error().Err(err).Msg("Config save NS failed!") + slog.Error("Unable to set active namespace during ns switch", slogs.Error, err) } return nil @@ -561,11 +566,11 @@ func (b *Browser) refreshActions() { b.Actions().Merge(aa) if err := pluginActions(b, b.Actions()); err != nil { - log.Warn().Msgf("Plugins load failed: %s", err) + slog.Warn("Plugins load failed", slogs.Error, err) b.app.Logo().Warn("Plugins load failed!") } if err := hotKeyActions(b, b.Actions()); err != nil { - log.Warn().Msgf("Hotkeys load failed: %s", err) + slog.Warn("Hotkeys load failed", slogs.Error, err) b.app.Logo().Warn("HotKeys load failed!") } b.app.Menu().HydrateMenu(b.Hints()) @@ -591,7 +596,11 @@ func (b *Browser) namespaceActions(aa *ui.KeyActions) { b.namespaces[index] = ns index++ } else { - log.Warn().Msgf("No number key available for favorite namespace %s (%d of %d). Skipping...", ns, index, len(favNamespaces)) + slog.Warn("No number key available for favorite namespace. Skipping...", + slogs.Namespace, ns, + slogs.Index, index, + slogs.Max, len(favNamespaces), + ) break } } diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go index 3ff7cd83..f11c541e 100644 --- a/internal/view/cluster_info.go +++ b/internal/view/cluster_info.go @@ -5,15 +5,16 @@ package view import ( "fmt" + "log/slog" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" ) var _ model.ClusterInfoListener = (*ClusterInfo)(nil) @@ -55,7 +56,7 @@ func (c *ClusterInfo) hasMetrics() bool { if mx { auth, err := c.app.Conn().CanI("", "metrics.k8s.io/v1beta1/nodes", "", client.ListAccess) if err != nil { - log.Warn().Err(err).Msgf("No nodes metrics access") + slog.Warn("No nodes metrics access", slogs.Error, err) } mx = auth } diff --git a/internal/view/cmd/helpers_test.go b/internal/view/cmd/helpers_test.go index da4f821d..204bbe67 100644 --- a/internal/view/cmd/helpers_test.go +++ b/internal/view/cmd/helpers_test.go @@ -4,14 +4,14 @@ package cmd import ( + "log/slog" "testing" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func Test_toLabels(t *testing.T) { diff --git a/internal/view/command.go b/internal/view/command.go index 3a6fb670..e3695da7 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -6,16 +6,16 @@ package view import ( "errors" "fmt" + "log/slog" "regexp" "runtime/debug" "strings" "sync" - "github.com/rs/zerolog/log" - "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/view/cmd" ) @@ -47,7 +47,7 @@ func (c *Command) AliasesFor(s string) []string { func (c *Command) Init(path string) error { c.alias = dao.NewAlias(c.app.factory) if _, err := c.alias.Ensure(path); err != nil { - log.Error().Err(err).Msgf("Alias ensure failed!") + slog.Error("Ensure aliases failed", slogs.Error, err) return err } customViewers = loadCustomViewers() @@ -164,9 +164,9 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool, pushCmd b if context, ok := p.HasContext(); ok { if context != c.app.Config.ActiveContextName() { if err := c.app.Config.Save(true); err != nil { - log.Error().Err(err).Msg("config save failed!") + slog.Error("Config save failed during command exec", slogs.Error, err) } else { - log.Debug().Msgf("Saved context config for: %q", context) + slog.Debug("Successfully saved config", slogs.Context, context) } } res, err := dao.AccessorFor(c.app.factory, client.NewGVR("contexts")) @@ -178,7 +178,7 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool, pushCmd b return errors.New("expecting a switchable resource") } if err := switcher.Switch(context); err != nil { - log.Error().Err(err).Msgf("Context switch failed") + slog.Error("Unable to switch context", slogs.Error, err) return err } if err := c.app.switchContext(p, false); err != nil { @@ -226,7 +226,10 @@ func (c *Command) defaultCmd(isRoot bool) error { if err := c.run(p, "", true, true); err != nil { p = p.Reset(defCmd) - log.Error().Err(fmt.Errorf("Command failed. Using default command: %s", p.GetLine())) + slog.Error("Command exec failed. Using default command", + slogs.Command, p.GetLine(), + slogs.Error, err, + ) return c.run(p, "", true, true) } @@ -242,7 +245,7 @@ func (c *Command) specialCmd(p *cmd.Interpreter, pushCmd bool) bool { c.app.cowCmd(msg) } case p.IsBailCmd(): - c.app.BailOut() + c.app.BailOut(0) case p.IsHelpCmd(): _ = c.app.helpCmd(nil) case p.IsAliasCmd(): @@ -323,10 +326,10 @@ func (c *Command) componentFor(gvr client.GVR, fqn string, v *MetaViewer) Resour func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component, clearStack bool, pushCmd bool) (err error) { defer func() { if e := recover(); e != nil { - log.Error().Msgf("Something bad happened! %#v", e) + slog.Error("Failure detected during command exec", slogs.Error, e) c.app.Content.Dump() - log.Debug().Msgf("History %v", c.app.cmdHistory.List()) - log.Error().Msg(string(debug.Stack())) + slog.Debug("Dumping history buffer", slogs.CmdHist, c.app.cmdHistory.List()) + slog.Error("Dumping stack", slogs.Stack, debug.Stack()) p := cmd.NewInterpreter("pod") cmds := c.app.cmdHistory.List() diff --git a/internal/view/context.go b/internal/view/context.go index e4788229..cd339091 100644 --- a/internal/view/context.go +++ b/internal/view/context.go @@ -6,14 +6,15 @@ package view import ( "errors" "fmt" + "log/slog" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/slogs" "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" ) const ( @@ -103,7 +104,10 @@ func (c *Context) showRenameModal(name string, ok func(form *tview.Form, context } func (c *Context) useCtx(app *App, model ui.Tabular, gvr client.GVR, path string) { - log.Debug().Msgf("SWITCH CTX %q--%q", gvr, path) + slog.Debug("Using context", + slogs.GVR, gvr, + slogs.FQN, path, + ) if err := useContext(app, path); err != nil { app.Flash().Err(err) return @@ -124,8 +128,17 @@ func useContext(app *App, name string) error { if !ok { return errors.New("expecting a switchable resource") } + + app.Config.K9s.ToggleContextSwitch(true) + defer app.Config.K9s.ToggleContextSwitch(false) + + // Save config prior to context switch... + if err := app.Config.Save(true); err != nil { + slog.Error("Fail to save config to disk", slogs.Subsys, "config", slogs.Error, err) + } + if err := switcher.Switch(name); err != nil { - log.Error().Err(err).Msgf("Context switch failed") + slog.Error("Context switch failed during use command", slogs.Error, err) return err } diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go index 05a78fe3..0862e041 100644 --- a/internal/view/cronjob.go +++ b/internal/view/cronjob.go @@ -6,16 +6,17 @@ package view import ( "context" "fmt" + "log/slog" "strings" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -44,9 +45,9 @@ func NewCronJob(gvr client.GVR) ResourceViewer { return &c } -func (c *CronJob) showJobs(app *App, model ui.Tabular, gvr client.GVR, path string) { - log.Debug().Msgf("Showing Jobs %q:%q -- %q", model.GetNamespace(), gvr, path) - o, err := app.factory.Get(gvr.String(), path, true, labels.Everything()) +func (c *CronJob) showJobs(app *App, _ ui.Tabular, gvr client.GVR, fqn string) { + slog.Debug("Showing Jobs", slogs.GVR, gvr, slogs.FQN, fqn) + o, err := app.factory.Get(gvr.String(), fqn, true, labels.Everything()) if err != nil { app.Flash().Err(err) return @@ -59,16 +60,20 @@ func (c *CronJob) showJobs(app *App, model ui.Tabular, gvr client.GVR, path stri return } + ns, _ := client.Namespaced(fqn) + if err := app.Config.SetActiveNamespace(ns); err != nil { + slog.Error("Unable to set active namespace during show pods", slogs.Error, err) + } v := NewJob(client.NewGVR("batch/v1/jobs")) - v.SetContextFn(jobCtx(path, string(cj.UID))) + v.SetContextFn(jobCtx(fqn, string(cj.UID))) if err := app.inject(v, false); err != nil { app.Flash().Err(err) } } -func jobCtx(path, uid string) ContextFunc { +func jobCtx(fqn, uid string) ContextFunc { return func(ctx context.Context) context.Context { - ctx = context.WithValue(ctx, internal.KeyPath, path) + ctx = context.WithValue(ctx, internal.KeyPath, fqn) return context.WithValue(ctx, internal.KeyUID, uid) } } diff --git a/internal/view/env.go b/internal/view/env.go index 8ac71558..ebcc214a 100644 --- a/internal/view/env.go +++ b/internal/view/env.go @@ -5,12 +5,13 @@ package view import ( "fmt" + "log/slog" "regexp" "sort" "strconv" "strings" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" ) // Env represent K9s and K8s available environment variables. @@ -50,7 +51,10 @@ func (e Env) Substitute(arg string) (string, error) { key, inverse := keyFromSubmatch(m) v, ok := e[strings.ToUpper(key)] if !ok { - log.Warn().Msgf("no k9s environment matching key %q:%q", m[0], key) + slog.Warn("No k9s environment matching key", + slogs.Matches, matches, + slogs.Key, key, + ) continue } if b, err := strconv.ParseBool(v); err == nil { diff --git a/internal/view/exec.go b/internal/view/exec.go index d7d0fa04..6c8e9a26 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "os/exec" "os/signal" @@ -17,13 +18,13 @@ import ( "time" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui/dialog" "github.com/fatih/color" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" @@ -85,7 +86,7 @@ func runK(a *App, opts shellOpts) error { return fmt.Errorf("unable to run command") } for v := range stChan { - log.Debug().Msgf(" - %s", v) + slog.Debug("stdout", slogs.Line, v) } var errs error for e := range errChan { @@ -183,19 +184,19 @@ func execute(opts shellOpts, statusChan chan<- string) error { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) go func(cancel context.CancelFunc) { - defer log.Debug().Msgf("SIGNAL_GOR - BAILED!!") + defer slog.Debug("Got signal canceled") select { case sig := <-sigChan: - log.Debug().Msgf("Command canceled with signal! %#v", sig) + slog.Debug("Command canceled with signal", slogs.Sig, sig) cancel() case <-ctx.Done(): - log.Debug().Msgf("SIGNAL Context CANCELED!") + slog.Debug("Signal context canceled!") } }(cancel) cmds := make([]*exec.Cmd, 0, 1) cmd := exec.CommandContext(ctx, opts.binary, opts.args...) - log.Debug().Msgf("RUNNING> %s", opts) + slog.Debug("Exec command", slogs.Command, opts) if env := os.Getenv("K9S_EDITOR"); env != "" { // There may be situations where the user sets the editor as the binary @@ -218,14 +219,17 @@ func execute(opts shellOpts, statusChan chan<- string) error { continue } cmd := exec.CommandContext(ctx, tokens[0], tokens[1:]...) - log.Debug().Msgf("\t| %s", cmd) + slog.Debug("Exec command", slogs.Command, cmd) cmds = append(cmds, cmd) } var o, e bytes.Buffer err := pipe(ctx, opts, statusChan, &o, &e, cmds...) if err != nil { - log.Err(err).Msgf("Command failed") + slog.Error("Exec failed", + slogs.Error, err, + slogs.Command, cmds, + ) return errors.Join(err, fmt.Errorf("%s", e.String())) } @@ -235,11 +239,11 @@ func execute(opts shellOpts, statusChan chan<- string) error { func runKu(a *App, opts shellOpts) (string, error) { bin, err := exec.LookPath("kubectl") if errors.Is(err, exec.ErrDot) { - log.Error().Err(err).Msgf("kubectl command must not be in the current working directory") + slog.Error("Kubectl exec can not reside in current working directory", slogs.Error, err) return "", err } if err != nil { - log.Error().Err(err).Msgf("kubectl command is not in your path") + slog.Error("Kubectl exec not found", slogs.Error, err) return "", err } var args []string @@ -266,7 +270,10 @@ func oneShoot(opts shellOpts) (string, error) { clearScreen() } - log.Debug().Msgf("Running command> %s %s", opts.binary, strings.Join(opts.args, " ")) + slog.Debug("Executing command", + slogs.Bin, opts.binary, + slogs.Args, strings.Join(opts.args, " "), + ) cmd := exec.Command(opts.binary, opts.args...) var err error @@ -348,7 +355,7 @@ func sshIn(a *App, fqn, co string) error { } args = append(args, "sh", "-c", shellCheck) } - log.Debug().Msgf("ARGS %#v", args) + slog.Debug("Running command with args", slogs.Args, args) c := color.New(color.BgGreen).Add(color.FgBlack).Add(color.Bold) err = runK(a, shellOpts{clear: true, banner: c.Sprintf(bannerFmt, fqn, co), args: args}) @@ -416,7 +423,10 @@ func launchShellPod(ctx context.Context, a *App, node string) error { if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod); err != nil { return err } - log.Debug().Msgf("Checking shell pod [%d] %v", i, pod.Status.Phase) + slog.Debug("Checking k9s shell pod retries", + slogs.Retry, i, + slogs.PodPhase, pod.Status.Phase, + ) if pod.Status.Phase == v1.PodRunning { return nil } @@ -439,7 +449,7 @@ func k9sShellPod(node string, cfg config.ShellPod) *v1.Pod { var grace int64 var priv bool = true - log.Debug().Msgf("Shell Config %#v", cfg) + slog.Debug("Shell pod config", slogs.ShellPodCfg, cfg) c := v1.Container{ Name: k9sShell, Image: cfg.Image, @@ -518,7 +528,7 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *byt go func() { cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, w, e if err := cmd.Run(); err != nil { - log.Error().Err(err).Msgf("Command failed: %s", err) + slog.Error("Command exec failed", slogs.Error, err) } else { for _, l := range strings.Split(w.String(), "\n") { if l != "" { @@ -526,7 +536,7 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *byt } } statusChan <- fmt.Sprintf("Command completed successfully: %q", render.Truncate(cmd.String(), 20)) - log.Info().Msgf("Command completed successfully: %q", cmd.String()) + slog.Info("Command ran successfully", slogs.Command, cmd.String()) } close(statusChan) }() @@ -535,9 +545,9 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *byt cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr _, _ = cmd.Stdout.Write([]byte(opts.banner)) - log.Debug().Msgf("Running Start") + slog.Debug("Exec started") err := cmd.Run() - log.Debug().Msgf("Running Done: %v", err) + slog.Debug("Running exec done", slogs.Error, err) if err == nil { statusChan <- fmt.Sprintf("Command completed successfully: %q", cmd.String()) } @@ -557,7 +567,7 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *byt cmds[last].Stdout = os.Stdout for _, cmd := range cmds { - log.Debug().Msgf("Starting CMD %s", cmd) + slog.Debug("Starting command", slogs.Command, cmd) if err := cmd.Start(); err != nil { return err } diff --git a/internal/view/helpers.go b/internal/view/helpers.go index ad8fb4c5..e533c9ee 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "log/slog" "os" "strconv" "strings" @@ -19,11 +20,11 @@ import ( "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "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" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -47,7 +48,11 @@ func aliasesFor(m v1.APIResource, aa []string) map[string]struct{} { } func clipboardWrite(text string) error { - return clipboard.WriteAll(text) + if text != "" { + return clipboard.WriteAll(text) + } + + return nil } func sanitizeEsc(s string) string { @@ -149,7 +154,7 @@ func showPods(app *App, path, labelSel, fieldSel string) { ns, _ := client.Namespaced(path) if err := app.Config.SetActiveNamespace(ns); err != nil { - log.Error().Err(err).Msg("Config NS set failed!") + slog.Error("Unable to set active namespace during show pods", slogs.Error, err) } if err := app.inject(v, false); err != nil { app.Flash().Err(err) diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go index 5c2ddbe2..f7eefcdb 100644 --- a/internal/view/helpers_test.go +++ b/internal/view/helpers_test.go @@ -6,6 +6,7 @@ package view import ( "context" "errors" + "log/slog" "testing" "github.com/derailed/k9s/internal" @@ -15,14 +16,13 @@ import ( "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog" "github.com/sahilm/fuzzy" "github.com/stretchr/testify/assert" "k8s.io/cli-runtime/pkg/genericclioptions" ) func init() { - zerolog.SetGlobalLevel(zerolog.Disabled) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestParsePFAnn(t *testing.T) { diff --git a/internal/view/image_extender.go b/internal/view/image_extender.go index 10d99dda..96d364c9 100644 --- a/internal/view/image_extender.go +++ b/internal/view/image_extender.go @@ -6,13 +6,14 @@ package view import ( "context" "fmt" + "log/slog" "strings" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" corev1 "k8s.io/api/core/v1" ) @@ -94,8 +95,8 @@ func (s *ImageExtender) showImageDialog(path string) error { return nil } -func (s *ImageExtender) makeSetImageForm(sel string) (*tview.Form, error) { - podSpec, err := s.getPodSpec(sel) +func (s *ImageExtender) makeSetImageForm(fqn string) (*tview.Form, error) { + podSpec, err := s.getPodSpec(fqn) if err != nil { return nil, err } @@ -126,12 +127,15 @@ func (s *ImageExtender) makeSetImageForm(sel string) (*tview.Form, error) { } ctx, cancel := context.WithTimeout(context.Background(), s.App().Conn().Config().CallTimeout()) defer cancel() - if err := s.setImages(ctx, sel, imageSpecsModified); err != nil { - log.Error().Err(err).Msgf("PodSpec %s image update failed", sel) + if err := s.setImages(ctx, fqn, imageSpecsModified); err != nil { + slog.Error("Unable to set image name", + slogs.FQN, fqn, + slogs.Error, err, + ) s.App().Flash().Err(err) return } - s.App().Flash().Infof("Resource %s:%s image updated successfully", s.GVR(), sel) + s.App().Flash().Infof("Resource %s:%s image updated successfully", s.GVR(), fqn) }). AddButton("Cancel", func() { s.dismissDialog() diff --git a/internal/view/live_view.go b/internal/view/live_view.go index bc24d4d2..e63d88e0 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -6,16 +6,17 @@ package view import ( "context" "fmt" + "log/slog" "strconv" "strings" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" "github.com/sahilm/fuzzy" ) @@ -233,12 +234,12 @@ func (v *LiveView) Start() { ctx, v.cancel = context.WithCancel(v.defaultCtx()) if err := v.model.Watch(ctx); err != nil { - log.Error().Err(err).Msgf("LiveView watcher failed") + slog.Error("LiveView watcher failed", slogs.Error, err) } return } if err := v.model.Refresh(v.defaultCtx()); err != nil { - log.Error().Err(err).Msgf("refresh failed") + slog.Error("LiveView refresh failed", slogs.Error, err) } } diff --git a/internal/view/log.go b/internal/view/log.go index d5682e78..e56d94ef 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "log/slog" "os" "path/filepath" "strings" @@ -19,10 +20,10 @@ import ( "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" ) const ( @@ -113,13 +114,13 @@ func (l *Log) InCmdMode() bool { // LogCanceled indicates no more logs are coming. func (l *Log) LogCanceled() { - log.Debug().Msgf("LOGS_CANCELED!!!") + slog.Debug("Logs watcher canceled!") l.Flush([][]byte{[]byte("\nšŸ [red::b]Stream exited! No more logs...")}) } // LogStop disables log flushes. func (l *Log) LogStop() { - log.Debug().Msgf("LOG_STOP!!!") + slog.Debug("Logs watcher stopped!") l.mx.Lock() defer l.mx.Unlock() @@ -149,7 +150,7 @@ func (l *Log) LogFailed(err error) { l.logs.Clear() } if _, err = l.ansiWriter.Write([]byte(tview.Escape(color.Colorize(err.Error(), color.Red)))); err != nil { - log.Error().Err(err).Msgf("Writing log error") + slog.Error("Log line write failed", slogs.Error, err) } }) } @@ -204,7 +205,6 @@ func (l *Log) cancel() { l.mx.Lock() defer l.mx.Unlock() if l.cancelFn != nil { - log.Debug().Msgf("!!! LOG-VIEWER CANCELED !!!") l.cancelFn() l.cancelFn = nil } @@ -430,12 +430,18 @@ func saveData(dir, fqn, logs string) (string, error) { mod := os.O_CREATE | os.O_WRONLY file, err := os.OpenFile(path, mod, 0600) if err != nil { - log.Error().Err(err).Msgf("Log file save failed: %q", path) + slog.Error("Unable to save log file", + slogs.Path, path, + slogs.Error, err, + ) return "", nil } defer func() { if err := file.Close(); err != nil { - log.Error().Err(err).Msg("Closing Log file") + slog.Error("Closing Log file failed", + slogs.Path, path, + slogs.Error, err, + ) } }() if _, err := file.WriteString(logs); err != nil { diff --git a/internal/view/node.go b/internal/view/node.go index 31a20015..165c7f17 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -6,15 +6,16 @@ package view import ( "context" "fmt" + "log/slog" "time" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -68,7 +69,7 @@ func (n *Node) bindDangerousKeys(aa *ui.KeyActions) { }) ct, err := n.App().Config.K9s.ActiveContext() if err != nil { - log.Error().Err(err).Msgf("No active context located") + slog.Error("No active context located", slogs.Error, err) return } if ct.FeatureGates.NodeShell { diff --git a/internal/view/owner_extender.go b/internal/view/owner_extender.go index 3ed99b3e..1425e88c 100644 --- a/internal/view/owner_extender.go +++ b/internal/view/owner_extender.go @@ -6,16 +6,17 @@ package view import ( "context" "fmt" + "log/slog" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" "github.com/go-errors/errors" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -46,7 +47,10 @@ func (v *OwnerExtender) ownerCmd(evt *tcell.EventKey) *tcell.EventKey { } if err := v.findOwnerFor(path); err != nil { - log.Warn().Msgf("Unable to jump to the owner of resource %q: %s", path, err) + slog.Warn("Unable to jump to owner of resource", + slogs.FQN, path, + slogs.Error, err, + ) v.App().Flash().Warnf("Unable to jump owner: %s", err) } return nil diff --git a/internal/view/pf.go b/internal/view/pf.go index 252ea533..eaa7c86a 100644 --- a/internal/view/pf.go +++ b/internal/view/pf.go @@ -6,6 +6,7 @@ package view import ( "context" "fmt" + "log/slog" "regexp" "time" @@ -14,10 +15,10 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/perf" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) // PortForward presents active portforward viewer. @@ -95,7 +96,7 @@ func (p *PortForward) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey { cfg.Name = path r, _ := p.GetTable().GetSelection() - log.Debug().Msgf("PF NS %q", p.GetTable().GetModel().GetNamespace()) + slog.Debug("Port forward namespace", slogs.Namespace, p.GetTable().GetModel().GetNamespace()) col := 3 if client.IsAllNamespaces(p.GetTable().GetModel().GetNamespace()) { col = 4 @@ -112,7 +113,7 @@ func (p *PortForward) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey { p.App().Status(model.FlashWarn, "Benchmark in progress...") go func() { if err := p.runBenchmark(); err != nil { - log.Error().Err(err).Msgf("Benchmark run failed") + slog.Error("Benchmark run failed", slogs.Error, err) } }() @@ -120,7 +121,7 @@ func (p *PortForward) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey { } func (p *PortForward) runBenchmark() error { - log.Debug().Msg("Bench starting...") + slog.Debug("Bench starting...") ct, err := p.App().Config.K9s.ActiveContext() if err != nil { @@ -128,7 +129,7 @@ func (p *PortForward) runBenchmark() error { } name := p.App().Config.K9s.ActiveContextName() p.bench.Run(ct.ClusterName, name, func() { - log.Debug().Msgf("Benchmark %q Completed!", name) + slog.Debug("Benchmark Completed!", slogs.Name, name) p.App().QueueUpdate(func() { if p.bench.Canceled() { p.App().Status(model.FlashInfo, "Benchmark canceled") diff --git a/internal/view/pf_dialog.go b/internal/view/pf_dialog.go index 27f0d29b..5fdbd81b 100644 --- a/internal/view/pf_dialog.go +++ b/internal/view/pf_dialog.go @@ -5,14 +5,15 @@ package view import ( "fmt" + "log/slog" "math" "strconv" "strings" "github.com/derailed/k9s/internal/port" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" - "github.com/rs/zerolog/log" ) const portForwardKey = "portforward" @@ -33,15 +34,12 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe SetFieldTextColor(styles.FieldFgColor.Color()). SetFieldBackgroundColor(styles.BgColor.Color()) - ct, err := v.App().Config.K9s.ActiveContext() - if err != nil { - log.Error().Err(err).Msgf("No active context detected") - return - } - pf, err := aa.PreferredPorts(ports) if err != nil { - log.Warn().Err(err).Msgf("unable to resolve ports on %s", path) + slog.Warn("Unable to resolve preferred ports", + slogs.FQN, path, + slogs.Error, err, + ) } p1, p2 := pf.ToPortSpec(ports) @@ -61,7 +59,7 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe if loField.GetText() == "" { loField.SetPlaceholder("Enter a local port") } - address := ct.PortForwardAddress + address := v.App().Config.K9s.PortForwardAddress f.AddInputField("Address:", address, fieldLen, nil, func(h string) { address = h }) diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 2e19cc26..6a0072a7 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -6,16 +6,17 @@ package view import ( "context" "fmt" + "log/slog" "strings" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/port" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/watch" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -159,7 +160,10 @@ func startFwdCB(v ResourceViewer, path string, pts port.PortTunnels) error { if err != nil { return err } - log.Debug().Msgf(">>> Starting port forward %q -- %#v", pf.ID(), pt) + slog.Debug(">>> Starting port forward", + slogs.PFID, pf.ID(), + slogs.PFTunnel, pt, + ) go runForward(v, pf, fwd) tt = append(tt, pt.LocalPort) } @@ -173,10 +177,6 @@ func startFwdCB(v ResourceViewer, path string, pts port.PortTunnels) error { } func showFwdDialog(v ResourceViewer, path string, cb PortForwardCB) error { - ct, err := v.App().Config.CurrentContext() - if err != nil { - return err - } mm, anns, err := fetchPodPorts(v.App().factory, path) if err != nil { return err @@ -196,7 +196,7 @@ func showFwdDialog(v ResourceViewer, path string, cb PortForwardCB) error { return err } - pts, err := pfs.ToTunnels(ct.PortForwardAddress, ports, port.IsPortFree) + pts, err := pfs.ToTunnels(v.App().Config.K9s.PortForwardAddress, ports, port.IsPortFree) if err != nil { return err } @@ -210,7 +210,7 @@ func showFwdDialog(v ResourceViewer, path string, cb PortForwardCB) error { } func fetchPodPorts(f *watch.Factory, path string) (map[string][]v1.ContainerPort, map[string]string, error) { - log.Debug().Msgf("Fetching ports on pod %q", path) + slog.Debug("Fetching ports on pod", slogs.FQN, path) o, err := f.Get("v1/pods", path, true, labels.Everything()) if err != nil { return nil, nil, err diff --git a/internal/view/pod.go b/internal/view/pod.go index 879bd6b4..df219d8f 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "os" "strings" @@ -17,11 +18,11 @@ import ( "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" "github.com/fatih/color" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -392,7 +393,7 @@ func resumeShellIn(a *App, c model.Component, path, co string) { func shellIn(a *App, fqn, co string) { os, err := getPodOS(a.factory, fqn) if err != nil { - log.Warn().Err(err).Msgf("os detect failed") + slog.Warn("OS detect failed", slogs.Error, err) } args := computeShellArgs(fqn, co, a.Conn().Config().Flags().KubeConfig, os) @@ -513,10 +514,13 @@ func fetchPod(f dao.Factory, path string) (*v1.Pod, error) { return &pod, nil } -func podIsRunning(f dao.Factory, path string) bool { - po, err := fetchPod(f, path) +func podIsRunning(f dao.Factory, fqn string) bool { + po, err := fetchPod(f, fqn) if err != nil { - log.Error().Err(err).Msg("unable to fetch pod") + slog.Error("Unable to fetch pod", + slogs.FQN, fqn, + slogs.Error, err, + ) return false } diff --git a/internal/view/sanitizer.go b/internal/view/sanitizer.go index 100e3f82..4497e8d5 100644 --- a/internal/view/sanitizer.go +++ b/internal/view/sanitizer.go @@ -6,6 +6,7 @@ package view import ( "context" "fmt" + "log/slog" "strings" "github.com/derailed/k9s/internal" @@ -13,11 +14,11 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/xray" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" "golang.org/x/text/cases" "golang.org/x/text/language" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -82,7 +83,7 @@ func (s *Sanitizer) Init(ctx context.Context) error { 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()) + slog.Error("No ref field found on node", slogs.FQN, n.GetText()) return } s.SetSelectedItem(spec.AsPath()) @@ -141,7 +142,7 @@ func (s *Sanitizer) selectedSpec() *xray.NodeSpec { ref, ok := node.GetReference().(xray.NodeSpec) if !ok { - log.Error().Msgf("Expecting a NodeSpec!") + slog.Error("Expecting a NodeSpec", slogs.RefType, fmt.Sprintf("%T", node.GetReference())) return nil } @@ -290,7 +291,7 @@ func (s *Sanitizer) update(node *xray.TreeNode) { 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()) + slog.Error("Expecting a NodeSpec", slogs.RefType, fmt.Sprintf("%T", node.GetReference())) return false } // BOZO!! Figure this out expand/collapse but the root diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go index fd4b7d3a..86ae6f06 100644 --- a/internal/view/scale_extender.go +++ b/internal/view/scale_extender.go @@ -6,14 +6,15 @@ package view import ( "context" "fmt" + "log/slog" "strconv" "strings" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" ) @@ -37,7 +38,10 @@ func (s *ScaleExtender) bindKeys(aa *ui.KeyActions) { meta, err := dao.MetaAccess.MetaFor(s.GVR()) if err != nil { - log.Error().Err(err).Msgf("Unable to retrieve meta information for %s", s.GVR()) + slog.Error("No meta information found", + slogs.GVR, s.GVR(), + slogs.Error, err, + ) return } @@ -127,13 +131,13 @@ func (s *ScaleExtender) replicasFromScaleSubresource(sel string) (string, error) return strconv.Itoa(int(replicas)), nil } -func (s *ScaleExtender) makeScaleForm(sels []string) (*tview.Form, error) { +func (s *ScaleExtender) makeScaleForm(fqns []string) (*tview.Form, error) { factor := "0" - if len(sels) == 1 { + if len(fqns) == 1 { // If the CRD resource supports scaling, then first try to // read the replicas directly from the CRD. if meta, _ := dao.MetaAccess.MetaFor(s.GVR()); dao.IsScalable(meta) { - replicas, err := s.replicasFromScaleSubresource(sels[0]) + replicas, err := s.replicasFromScaleSubresource(fqns[0]) if err == nil && len(replicas) != 0 { factor = replicas } @@ -142,7 +146,7 @@ func (s *ScaleExtender) makeScaleForm(sels []string) (*tview.Form, error) { // For built-in resources or cases where we can't get the replicas from the CRD, we can // only try to get the number of copies from the READY field. if factor == "0" { - replicas, err := s.replicasFromReady(sels[0]) + replicas, err := s.replicasFromReady(fqns[0]) if err != nil { return nil, err } @@ -176,17 +180,17 @@ func (s *ScaleExtender) makeScaleForm(sels []string) (*tview.Form, error) { } ctx, cancel := context.WithTimeout(context.Background(), s.App().Conn().Config().CallTimeout()) defer cancel() - for _, sel := range sels { - if err := s.scale(ctx, sel, count); err != nil { - log.Error().Err(err).Msgf("DP %s scaling failed", sel) + for _, fqn := range fqns { + if err := s.scale(ctx, fqn, count); err != nil { + slog.Error("Unable to scale resource", slogs.FQN, fqn) s.App().Flash().Err(err) return } } - if len(sels) != 1 { - s.App().Flash().Infof("[%d] %s scaled successfully", len(sels), singularize(s.GVR().R())) + if len(fqns) != 1 { + s.App().Flash().Infof("[%d] %s scaled successfully", len(fqns), singularize(s.GVR().R())) } else { - s.App().Flash().Infof("%s %s scaled successfully", s.GVR().R(), sels[0]) + s.App().Flash().Infof("%s %s scaled successfully", s.GVR().R(), fqns[0]) } }) f.AddButton("Cancel", func() { diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go index 83bbca09..c36b54a7 100644 --- a/internal/view/screen_dump.go +++ b/internal/view/screen_dump.go @@ -5,13 +5,14 @@ package view import ( "context" + "log/slog" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) // ScreenDump presents a directory listing viewer. @@ -45,7 +46,7 @@ func (s *ScreenDump) dirContext(ctx context.Context) context.Context { } func (s *ScreenDump) edit(app *App, _ ui.Tabular, _ client.GVR, path string) { - log.Debug().Msgf("ScreenDump selection is %q", path) + slog.Debug("ScreenDump selection", slogs.FQN, path) s.Stop() defer s.Start() diff --git a/internal/view/svc.go b/internal/view/svc.go index 9c92a8ef..c2f2459e 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -6,6 +6,7 @@ package view import ( "errors" "fmt" + "log/slog" "strings" "time" @@ -15,9 +16,9 @@ import ( "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/perf" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -100,7 +101,7 @@ func (s *Service) getExternalPort(svc *v1.Service) (string, error) { func (s *Service) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey { if s.bench != nil { - log.Debug().Msg(">>> Benchmark canceled!!") + slog.Debug(">>> Benchmark canceled!!") s.App().Status(model.FlashErr, "Benchmark Canceled!") s.bench.Cancel() s.App().ClearStatus(true) @@ -114,7 +115,7 @@ func (s *Service) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey { cust, err := config.NewBench(s.App().BenchFile) if err != nil { - log.Debug().Msgf("No bench config file found %s", s.App().BenchFile) + slog.Debug("No bench config file found", slogs.FileName, s.App().BenchFile) } cfg, ok := cust.Benchmarks.Services[path] @@ -123,7 +124,7 @@ func (s *Service) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } cfg.Name = path - log.Debug().Msgf("Benchmark config %#v", cfg) + slog.Debug("Benchmark config", slogs.Config, cfg) svc, err := fetchService(s.App().factory, path) if err != nil { @@ -170,7 +171,7 @@ func (s *Service) runBenchmark(port string, cfg config.BenchConfig) error { } s.App().Status(model.FlashWarn, "Benchmark in progress...") - log.Debug().Msg("Benchmark starting...") + slog.Debug("Benchmark starting...") ct, err := s.App().Config.K9s.ActiveContext() if err != nil { @@ -184,7 +185,7 @@ func (s *Service) runBenchmark(port string, cfg config.BenchConfig) error { } func (s *Service) benchDone() { - log.Debug().Msg("Bench Completed!") + slog.Debug("Bench Completed!") s.App().QueueUpdate(func() { if s.bench.Canceled() { s.App().Status(model.FlashInfo, "Benchmark canceled") diff --git a/internal/view/table.go b/internal/view/table.go index 0956f625..9900df8d 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -5,6 +5,7 @@ package view import ( "context" + "log/slog" "path/filepath" "strings" "time" @@ -14,9 +15,9 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) // Table represents a table viewer. @@ -55,7 +56,7 @@ func (t *Table) Init(ctx context.Context) (err error) { t.Table.Init(ctx) if !t.app.Config.K9s.UI.Reactive { if err := t.app.RefreshCustomViews(); err != nil { - log.Warn().Err(err).Msg("CustomViews load failed") + slog.Warn("CustomViews load failed", slogs.Error, err) t.app.Logo().Warn("Views load failed!") } } diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go index 34dfc81e..ca4154a8 100644 --- a/internal/view/table_helper.go +++ b/internal/view/table_helper.go @@ -6,6 +6,7 @@ package view import ( "encoding/csv" "fmt" + "log/slog" "os" "path/filepath" "strings" @@ -14,8 +15,8 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/model1" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" - "github.com/rs/zerolog/log" ) func computeFilename(dumpPath, ns, title, path string) (string, error) { @@ -51,7 +52,7 @@ func saveTable(dir, title, path string, data *model1.TableData) (string, error) if err != nil { return "", err } - log.Debug().Msgf("Saving Table to %s", fPath) + slog.Debug("Saving table to disk", slogs.FileName, fPath) mod := os.O_CREATE | os.O_WRONLY out, err := os.OpenFile(fPath, mod, 0600) @@ -60,7 +61,10 @@ func saveTable(dir, title, path string, data *model1.TableData) (string, error) } defer func() { if err := out.Close(); err != nil { - log.Error().Err(err).Msg("Closing file") + slog.Error("Closing file failed", + slogs.Path, fPath, + slogs.Error, err, + ) } }() diff --git a/internal/view/value_extender.go b/internal/view/value_extender.go index 8d3a244c..d772454b 100644 --- a/internal/view/value_extender.go +++ b/internal/view/value_extender.go @@ -5,13 +5,14 @@ package view import ( "context" + "log/slog" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) // ValueExtender adds values actions to a given viewer. @@ -61,7 +62,7 @@ func showValues(ctx context.Context, app *App, path string, gvr client.GVR) { } if err := vm.Refresh(ctx); err != nil { - log.Error().Err(err).Msgf("values refresh failed") + slog.Error("Values viewer refresh failed", slogs.Error, err) return nil } diff --git a/internal/view/workload.go b/internal/view/workload.go index 2b7acabf..fea30e19 100644 --- a/internal/view/workload.go +++ b/internal/view/workload.go @@ -6,16 +6,17 @@ package view import ( "context" "fmt" + "log/slog" "strings" "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/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -69,7 +70,7 @@ func (w *Workload) bindKeys(aa *ui.KeyActions) { func parsePath(path string) (client.GVR, string, bool) { tt := strings.Split(path, "|") if len(tt) != 3 { - log.Error().Msgf("unable to parse path: %q", path) + slog.Error("Unable to parse workload path", slogs.Path, path) return client.NewGVR(""), client.FQN("", ""), false } diff --git a/internal/view/xray.go b/internal/view/xray.go index 2fff2783..c797e071 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -6,6 +6,7 @@ package view import ( "context" "fmt" + "log/slog" "regexp" "strings" "time" @@ -16,12 +17,12 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/k9s/internal/xray" "github.com/derailed/tcell/v2" "github.com/derailed/tview" - "github.com/rs/zerolog/log" "github.com/sahilm/fuzzy" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -89,7 +90,7 @@ func (x *Xray) Init(ctx context.Context) error { x.SetChangedFunc(func(n *tview.TreeNode) { spec, ok := n.GetReference().(xray.NodeSpec) if !ok { - log.Error().Msgf("No ref found on node %s", n.GetText()) + slog.Error("No ref found on node", slogs.FQN, n.GetText()) return } x.SetSelectedItem(spec.AsPath()) @@ -134,10 +135,10 @@ func (x *Xray) refreshActions() { defer func() { if err := pluginActions(x, aa); err != nil { - log.Warn().Err(err).Msg("Plugins load failed") + slog.Warn("Plugins load failed", slogs.Error, err) } if err := hotKeyActions(x, aa); err != nil { - log.Warn().Err(err).Msg("HotKeys load failed") + slog.Warn("HotKeys load failed", slogs.Error, err) } x.Actions().Merge(aa) @@ -157,7 +158,10 @@ func (x *Xray) refreshActions() { var err error x.meta, err = dao.MetaAccess.MetaFor(client.NewGVR(gvr)) if err != nil { - log.Warn().Msgf("NO meta for %q -- %s", gvr, err) + slog.Warn("No meta found!", + slogs.GVR, gvr, + slogs.Error, err, + ) return } @@ -212,7 +216,10 @@ func (x *Xray) selectedSpec() *xray.NodeSpec { ref, ok := node.GetReference().(xray.NodeSpec) if !ok { - log.Error().Msgf("Expecting a NodeSpec!") + slog.Error("Expecting a NodeSpec", + slogs.Path, node.GetText(), + slogs.RefType, fmt.Sprintf("%T", node.GetReference()), + ) return nil } @@ -375,7 +382,10 @@ func (x *Xray) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { gvr := client.NewGVR(spec.GVR()) meta, err := dao.MetaAccess.MetaFor(gvr) if err != nil { - log.Warn().Msgf("NO meta for %q -- %s", spec.GVR(), err) + slog.Warn("No meta found!", + slogs.GVR, spec.GVR(), + slogs.Error, err, + ) return nil } x.resourceDelete(gvr, spec, fmt.Sprintf("Delete %s %s?", meta.SingularName, spec.Path())) @@ -535,7 +545,10 @@ func (x *Xray) update(node *xray.TreeNode) { 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()) + slog.Error("Expecting a NodeSpec", + slogs.FQN, node.GetText(), + slogs.RefType, fmt.Sprintf("%T", node.GetReference()), + ) return false } // BOZO!! Figure this out expand/collapse but the root @@ -679,7 +692,10 @@ func (x *Xray) resourceDelete(gvr client.GVR, spec *xray.NodeSpec, msg string) { x.app.Flash().Infof("Delete resource %s %s", spec.GVR(), spec.Path()) accessor, err := dao.AccessorFor(x.app.factory, gvr) if err != nil { - log.Error().Err(err).Msgf("No accessor") + slog.Error("No accessor found", + slogs.GVR, gvr, + slogs.Error, err, + ) return } diff --git a/internal/view/yaml.go b/internal/view/yaml.go index a7364e4c..3c6114c9 100644 --- a/internal/view/yaml.go +++ b/internal/view/yaml.go @@ -5,6 +5,7 @@ package view import ( "fmt" + "log/slog" "os" "path/filepath" "regexp" @@ -13,8 +14,8 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/slogs" "github.com/derailed/tview" - "github.com/rs/zerolog/log" ) var ( @@ -74,12 +75,18 @@ func saveYAML(dir, name, raw string) (string, error) { mod := os.O_CREATE | os.O_WRONLY file, err := os.OpenFile(fpath, mod, 0600) if err != nil { - log.Error().Err(err).Msgf("YAML create %s", fpath) + slog.Error("Unable to open YAML file", + slogs.Path, fpath, + slogs.Error, err, + ) return "", nil } defer func() { if err := file.Close(); err != nil { - log.Error().Err(err).Msg("Closing yaml file") + slog.Error("Closing yaml file failed", + slogs.Path, fpath, + slogs.Error, err, + ) } }() if _, err := file.Write([]byte(raw)); err != nil { diff --git a/internal/vul/scanner.go b/internal/vul/scanner.go index 4cac816c..7628de55 100644 --- a/internal/vul/scanner.go +++ b/internal/vul/scanner.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "log/slog" "sync" "time" @@ -27,7 +28,7 @@ import ( "github.com/anchore/grype/grype/vex" "github.com/anchore/syft/syft" "github.com/derailed/k9s/internal/config" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -47,13 +48,15 @@ type imageScanner struct { mx sync.RWMutex initialized bool config config.ImageScans + log *slog.Logger } // NewImageScanner returns a new instance. -func NewImageScanner(cfg config.ImageScans) *imageScanner { +func NewImageScanner(cfg config.ImageScans, l *slog.Logger) *imageScanner { return &imageScanner{ scans: make(Scans), config: cfg, + log: l.With(slogs.Subsys, "vul"), } } @@ -93,12 +96,12 @@ func (s *imageScanner) Init(name, version string) { s.opts.DB.AutoUpdate, ) if err != nil { - log.Error().Err(err).Msgf("VulDb load failed") + s.log.Error("VulDb load failed", slogs.Error, err) return } if err := validateDBLoad(err, s.dbStatus); err != nil { - log.Error().Err(err).Msgf("VulDb validate failed") + s.log.Error("VulDb validate failed", slogs.Error, err) return } @@ -150,19 +153,25 @@ func (s *imageScanner) Enqueue(ctx context.Context, images ...string) { } func (s *imageScanner) scanWorker(ctx context.Context, img string) { - defer log.Debug().Msgf("ScanWorker bailing out!") + defer s.log.Debug("ScanWorker bailing out!") - log.Debug().Msgf("ScanWorker processing: %q", img) + s.log.Debug("ScanWorker processing image", slogs.Image, img) sc := newScan(img) s.setScan(img, sc) if err := s.scan(ctx, img, sc); err != nil { - log.Warn().Err(err).Msgf("Scan failed for img %s --", img) + s.log.Warn("Scan failed for image", + slogs.Image, img, + slogs.Error, err, + ) } } func (s *imageScanner) scan(_ context.Context, img string, sc *Scan) error { defer func(t time.Time) { - log.Debug().Msgf("ScanTime %q: %v", img, time.Since(t)) + s.log.Debug("Time to run vulscan", + slogs.Image, img, + slogs.Elapsed, time.Since(t), + ) }(time.Now()) var errs error diff --git a/internal/watch/factory.go b/internal/watch/factory.go index 014ecfb1..75a5ea86 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -5,11 +5,11 @@ package watch import ( "fmt" + "log/slog" "strings" "sync" "time" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -18,6 +18,7 @@ import ( "k8s.io/client-go/informers" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/slogs" ) const ( @@ -48,10 +49,10 @@ func (f *Factory) Start(ns string) { f.mx.Lock() defer f.mx.Unlock() - log.Debug().Msgf("Factory START with ns %q", ns) + slog.Debug("Factory started", slogs.Namespace, ns) f.stopChan = make(chan struct{}) for ns, fac := range f.factories { - log.Debug().Msgf("Starting factory in ns %q", ns) + slog.Debug("Starting factory for ns", slogs.Namespace, ns) fac.Start(f.stopChan) } } @@ -163,7 +164,11 @@ func (f *Factory) WaitForCacheSync() { for ns, fac := range f.factories { m := fac.WaitForCacheSync(f.stopChan) for k, v := range m { - log.Debug().Msgf("CACHE `%q Loaded %t:%s", ns, v, k) + slog.Debug("CACHE `%q Loaded %t:%s", + slogs.Namespace, ns, + slogs.ResGrpVersion, v, + slogs.ResKind, k, + ) } } } @@ -216,7 +221,10 @@ func (f *Factory) ForResource(ns, gvr string) (informers.GenericInformer, error) } inf := fact.ForResource(toGVR(gvr)) if inf == nil { - log.Error().Err(fmt.Errorf("MEOW! No informer for %q:%q", ns, gvr)) + slog.Error("No informer found", + slogs.GVR, gvr, + slogs.Namespace, ns, + ) return inf, nil } @@ -262,7 +270,10 @@ func (f *Factory) AddForwarder(pf Forwarder) { // DeleteForwarder deletes portforward for a given container. func (f *Factory) DeleteForwarder(path string) { count := f.forwarders.Kill(path) - log.Warn().Msgf("Deleted (%d) portforward for %q", count, path) + slog.Warn("Deleted portforward", + slogs.Count, count, + slogs.GVR, path, + ) } // Forwarders returns all portforwards. @@ -289,12 +300,12 @@ func (f *Factory) ValidatePortForwards() { for k, fwd := range f.forwarders { tokens := strings.Split(k, ":") if len(tokens) != 2 { - log.Error().Msgf("Invalid fwd keys %q", k) + slog.Error("Invalid port-forward key", slogs.Key, k) return } paths := strings.Split(tokens[0], "|") if len(paths) < 1 { - log.Error().Msgf("Invalid path %q", tokens[0]) + slog.Error("Invalid port-forward path", slogs.Path, tokens[0]) } o, err := f.Get("v1/pods", paths[0], false, labels.Everything()) if err != nil { diff --git a/internal/watch/forwarders.go b/internal/watch/forwarders.go index 0c16f38d..0133c99e 100644 --- a/internal/watch/forwarders.go +++ b/internal/watch/forwarders.go @@ -4,11 +4,13 @@ package watch import ( + "fmt" + "log/slog" "strings" "time" "github.com/derailed/k9s/internal/port" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" "k8s.io/client-go/tools/portforward" ) @@ -83,7 +85,7 @@ func (ff Forwarders) IsContainerForwarded(fqn, co string) bool { // DeleteAll stops and delete all port-forwards. func (ff Forwarders) DeleteAll() { for k, f := range ff { - log.Debug().Msgf("Deleting forwarder %s", f.ID()) + slog.Debug("Deleting forwarder", slogs.ID, f.ID()) f.Stop() delete(ff, k) } @@ -101,7 +103,7 @@ func (ff Forwarders) Kill(path string) int { for k, f := range ff { if k == path || strings.HasPrefix(k, prefix) { stats++ - log.Debug().Msgf("Stop + Delete port-forward %s", k) + slog.Debug("Stop and delete port-forward", slogs.Name, k) f.Stop() delete(ff, k) } @@ -112,8 +114,8 @@ func (ff Forwarders) Kill(path string) int { // Dump for debug! func (ff Forwarders) Dump() { - log.Debug().Msgf("----------- PORT-FORWARDS --------------") + slog.Debug("----------- PORT-FORWARDS --------------") for k, f := range ff { - log.Debug().Msgf(" %s -- %s", k, f) + slog.Debug(fmt.Sprintf(" %s -- %s", k, f)) } } diff --git a/internal/watch/forwarders_test.go b/internal/watch/forwarders_test.go index eae8ae63..4ebbff7d 100644 --- a/internal/watch/forwarders_test.go +++ b/internal/watch/forwarders_test.go @@ -4,18 +4,18 @@ package watch_test import ( + "log/slog" "testing" "time" "github.com/derailed/k9s/internal/port" "github.com/derailed/k9s/internal/watch" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "k8s.io/client-go/tools/portforward" ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestIsPodForwarded(t *testing.T) { diff --git a/internal/watch/helper.go b/internal/watch/helper.go index 2381f4b4..67e79455 100644 --- a/internal/watch/helper.go +++ b/internal/watch/helper.go @@ -4,10 +4,11 @@ package watch import ( + "fmt" + "log/slog" "path" "strings" - "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -32,22 +33,22 @@ func namespaced(n string) (string, string) { // DumpFactory for debug. func DumpFactory(f *Factory) { - log.Debug().Msgf("----------- FACTORIES -------------") + slog.Debug("----------- FACTORIES -------------") for ns := range f.factories { - log.Debug().Msgf(" Factory for NS %q", ns) + slog.Debug(fmt.Sprintf(" Factory for NS %q", ns)) } - log.Debug().Msgf("-----------------------------------") + slog.Debug("-----------------------------------") } // DebugFactory for debug. func DebugFactory(f *Factory, ns string, gvr string) { - log.Debug().Msgf("----------- DEBUG FACTORY (%s) -------------", gvr) + slog.Debug(fmt.Sprintf("----------- DEBUG FACTORY (%s) -------------", gvr)) fac, ok := f.factories[ns] if !ok { return } inf := fac.ForResource(toGVR(gvr)) for i, k := range inf.Informer().GetStore().ListKeys() { - log.Debug().Msgf("%d -- %s", i, k) + slog.Debug(fmt.Sprintf("%d -- %s", i, k)) } } diff --git a/internal/xray/container.go b/internal/xray/container.go index 50a60860..9818c204 100644 --- a/internal/xray/container.go +++ b/internal/xray/container.go @@ -6,12 +6,13 @@ package xray import ( "context" "fmt" + "log/slog" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal/slogs" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" ) @@ -95,7 +96,10 @@ func validate(f dao.Factory, n *TreeNode, optional *bool) { res, err := f.Get(n.GVR, n.ID, true, labels.Everything()) if err != nil || res == nil { if optional == nil || !*optional { - log.Warn().Err(err).Msgf("Missing ref %q::%q", n.GVR, n.ID) + slog.Warn("Missing ref", + slogs.GVR, n.GVR, + slogs.ID, n.ID, + ) n.Extras[StatusKey] = MissingRefStatus } return diff --git a/internal/xray/container_test.go b/internal/xray/container_test.go index 09d51b73..0c78100b 100644 --- a/internal/xray/container_test.go +++ b/internal/xray/container_test.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "os" "testing" @@ -16,7 +17,6 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/watch" "github.com/derailed/k9s/internal/xray" - "github.com/rs/zerolog" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -26,7 +26,7 @@ import ( ) func init() { - zerolog.SetGlobalLevel(zerolog.FatalLevel) + slog.SetDefault(slog.New(slog.DiscardHandler)) } func TestCOConfigMapRefs(t *testing.T) { diff --git a/internal/xray/tree_node.go b/internal/xray/tree_node.go index 7a62e48e..44b86970 100644 --- a/internal/xray/tree_node.go +++ b/internal/xray/tree_node.go @@ -5,6 +5,7 @@ package xray import ( "fmt" + "log/slog" "reflect" "sort" "strings" @@ -12,7 +13,6 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/fvbommel/sortorder" - "github.com/rs/zerolog/log" ) const ( @@ -352,10 +352,10 @@ func (t *TreeNode) Dump() { func dump(n *TreeNode, level int) { if n == nil { - log.Debug().Msgf("NO DATA!!") + slog.Debug("NO DATA!!") return } - log.Debug().Msgf("%s%s::%s\n", strings.Repeat(" ", level), n.GVR, n.ID) + slog.Debug(fmt.Sprintf("%s%s::%s\n", strings.Repeat(" ", level), n.GVR, n.ID)) for _, c := range n.Children { dump(c, level+1) } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 0cf21c95..c662218b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core22 -version: 'v0.40.5' +version: 'v0.40.6' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session.