Rel v0.40.6 (#3185)

* update deps

* docs

* fix#3161 - add V attr

* add alias sort

* fix#3139 - cpu/mem with rx

* fix#3147 - prompt styles

* fix#3138 - rbac subres

* update deps

* mv pfAddress to main config + relax validation

* fix#3179 - gvr title

* fix#3178 - jobs in wrong ns

* fix#3163 - cust views with ns

* fix#3162 - ctx switch save in wrong context

* switch over to slog logger

* add exit code

* update default timeout

* rel notes

* update docs

* rel notes
mine
Fernand Galiana 2025-03-09 13:58:44 -06:00 committed by GitHub
parent 4867f5361d
commit a8b75ef1e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
159 changed files with 1893 additions and 1031 deletions

View File

@ -78,6 +78,62 @@ linters-settings:
- errors - errors
reason: "Go 1.20+ has support for combining multiple errors, see https://go.dev/doc/go1.20#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: issues:
# default is true. Enables skipping of directories: # default is true. Enables skipping of directories:
@ -124,3 +180,5 @@ linters:
- misspell - misspell
- prealloc - prealloc
- typecheck - typecheck
- sloglint

View File

@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
else else
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
endif endif
VERSION ?= v0.40.5 VERSION ?= v0.40.6
IMG_NAME := derailed/k9s IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION} IMAGE := ${IMG_NAME}:${VERSION}

View File

@ -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. Please refer to our [K9s documentation](https://k9scli.io) site for installation, usage, customization and tips.
---
## Slack Channel ## Slack Channel
Wanna discuss K9s features with your fellow `K9sers` or simply show your support for this tool? 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/) * Channel: [K9sersSlack](https://k9sers.slack.com/)
* Invite: [K9slackers Invite](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) * 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.
<br/>
<a href="https://panfactum.com"><img src="assets/sponsors/panfactum.png" alt="panfactum"></a>
<br/>
<br/>
> NOTE! K9s neither vouches for nor endorses these companies or products.
---
## Installation ## Installation
K9s is available on Linux, macOS and Windows platforms. 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) [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 * `T` -> time column indicator
* `N` -> number 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. * `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 * `H` -> Hides the column
* `L` -> Left align (default) * `L` -> Left align (default)
* `R` -> Right align * `R` -> Right align
@ -674,6 +692,14 @@ views:
- NODE - NODE
- STATUS - STATUS
- READY - 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: v1/services:
columns: columns:
- AGE - AGE

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,99 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
# 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)
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -5,12 +5,13 @@ package cmd
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/color"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -60,13 +61,13 @@ func getScreenDumpDirForInfo() string {
f, err := os.ReadFile(config.AppConfigFile) f, err := os.ReadFile(config.AppConfigFile)
if err != nil { 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 return config.AppDumpsDir
} }
var cfg config.Config var cfg config.Config
if err := yaml.Unmarshal(f, &cfg); err != nil { 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 return config.AppDumpsDir
} }
if cfg.K9s == nil { if cfg.K9s == nil {

View File

@ -6,22 +6,25 @@ package cmd
import ( import (
"errors" "errors"
"fmt" "fmt"
"log/slog"
"os" "os"
"runtime/debug" "runtime/debug"
"strings" "strings"
"time"
"github.com/derailed/k9s/internal/config/data"
"k8s.io/client-go/tools/clientcmd/api"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/color"
"github.com/derailed/k9s/internal/config" "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/derailed/k9s/internal/view"
"github.com/lmittmann/tint"
// "github.com/MatusOllah/slogcolor"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd/api"
) )
const ( const (
@ -78,35 +81,37 @@ func run(cmd *cobra.Command, args []string) error {
if err := config.InitLocs(); err != nil { if err := config.InitLocs(); err != nil {
return err return err
} }
file, err := os.OpenFile( logFile, err := os.OpenFile(
*k9sFlags.LogFile, *k9sFlags.LogFile,
os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.O_CREATE|os.O_APPEND|os.O_WRONLY,
data.DefaultFileMod, data.DefaultFileMod,
) )
if err != nil { 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() { defer func() {
if file != nil { if logFile != nil {
_ = file.Close() _ = logFile.Close()
} }
}() }()
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Error().Msgf("Boom! %v", err) slog.Error("Boom!! k9s init failed", slogs.Error, err)
log.Error().Msg(string(debug.Stack())) slog.Error("", slogs.Stack, string(debug.Stack()))
printLogo(color.Red) printLogo(color.Red)
fmt.Printf("%s", color.Colorize("Boom!! ", color.Red)) fmt.Printf("%s", color.Colorize("Boom!! ", color.Red))
fmt.Printf("%v.\n", err) fmt.Printf("%v.\n", err)
} }
}() }()
log.Logger = log.Output(zerolog.ConsoleWriter{Out: file}) slog.SetDefault(slog.New(tint.NewHandler(logFile, &tint.Options{
zerolog.SetGlobalLevel(parseLevel(*k9sFlags.LogLevel)) Level: parseLevel(*k9sFlags.LogLevel),
TimeFormat: time.Kitchen,
})))
cfg, err := loadConfiguration() cfg, err := loadConfiguration()
if err != nil { 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) app := view.NewApp(cfg)
if err := app.Init(version, *k9sFlags.RefreshRate); err != nil { 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) { func loadConfiguration() (*config.Config, error) {
log.Info().Msg("🐶 K9s starting up...") slog.Info("🐶 K9s starting up...")
k8sCfg := client.NewConfig(k8sFlags) k8sCfg := client.NewConfig(k8sFlags)
k9sCfg := config.NewConfig(k8sCfg) k9sCfg := config.NewConfig(k8sCfg)
@ -134,50 +139,43 @@ func loadConfiguration() (*config.Config, error) {
} }
k9sCfg.K9s.Override(k9sFlags) k9sCfg.K9s.Override(k9sFlags)
if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { 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) errs = errors.Join(errs, err)
} }
conn, err := client.InitConnection(k8sCfg) conn, err := client.InitConnection(k8sCfg, slog.Default())
if err != nil { if err != nil {
errs = errors.Join(errs, err) errs = errors.Join(errs, err)
} }
// Try to access server version if that fail. Connectivity issue? // Try to access server version if that fail. Connectivity issue?
if !conn.CheckConnectivity() { if !conn.CheckConnectivity() {
errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName())) errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()))
} }
if !conn.ConnectionOK() { if !conn.ConnectionOK() {
slog.Warn("💣 Kubernetes connectivity toast!")
errs = errors.Join(errs, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName())) errs = errors.Join(errs, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()))
} else {
slog.Info("✅ Kubernetes connectivity OK")
} }
k9sCfg.SetConnection(conn) k9sCfg.SetConnection(conn)
log.Info().Msg("✅ Kubernetes connectivity")
if err := k9sCfg.Save(false); err != nil { 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) errs = errors.Join(errs, err)
} }
return k9sCfg, errs return k9sCfg, errs
} }
func parseLevel(level string) zerolog.Level { func parseLevel(level string) slog.Level {
switch level { switch level {
case "trace":
return zerolog.TraceLevel
case "debug": case "debug":
return zerolog.DebugLevel return slog.LevelDebug
case "warn": case "warn":
return zerolog.WarnLevel return slog.LevelWarn
case "error": case "error":
return zerolog.ErrorLevel return slog.LevelError
case "fatal":
return zerolog.FatalLevel
default: default:
return zerolog.InfoLevel return slog.LevelInfo
} }
} }
@ -193,7 +191,7 @@ func initK9sFlags() {
k9sFlags.LogLevel, k9sFlags.LogLevel,
"logLevel", "l", "logLevel", "l",
config.DefaultLogLevel, config.DefaultLogLevel,
"Specify a log level (error, warn, info, debug, trace)", "Specify a log level (error, warn, info, debug)",
) )
rootCmd.Flags().StringVarP( rootCmd.Flags().StringVarP(
k9sFlags.LogFile, k9sFlags.LogFile,
@ -265,7 +263,7 @@ func initK8sFlags() {
rootCmd.Flags().StringVar( rootCmd.Flags().StringVar(
k8sFlags.Timeout, k8sFlags.Timeout,
"request-timeout", "request-timeout",
"", "5s",
"The length of time to wait before giving up on a single server request", "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) { _ = rootCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, s string) ([]string, cobra.ShellCompDirective) {
conn := client.NewConfig(k8sFlags) 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 { if nss, err := c.ValidNamespaceNames(); err == nil {
return filterFlagCompletions(nss, s) return filterFlagCompletions(nss, s)
} }
@ -391,7 +389,7 @@ func k8sFlagCompletion[T any](picker k8sPickerFn[T]) completeFn {
conn := client.NewConfig(k8sFlags) conn := client.NewConfig(k8sFlags)
cfg, err := conn.RawConfig() cfg, err := conn.RawConfig()
if err != nil { 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) return filterFlagCompletions(picker(&cfg), toComplete)

40
go.mod
View File

@ -1,12 +1,12 @@
module github.com/derailed/k9s module github.com/derailed/k9s
go 1.23.2 go 1.24.0
require ( require (
github.com/adrg/xdg v0.5.3 github.com/adrg/xdg v0.5.3
github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837 github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837
github.com/anchore/grype v0.86.1 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/atotto/clipboard v0.1.4
github.com/cenkalti/backoff/v4 v4.3.0 github.com/cenkalti/backoff/v4 v4.3.0
github.com/derailed/popeye v0.11.3 github.com/derailed/popeye v0.11.3
@ -16,12 +16,12 @@ require (
github.com/fsnotify/fsnotify v1.8.0 github.com/fsnotify/fsnotify v1.8.0
github.com/fvbommel/sortorder v1.1.0 github.com/fvbommel/sortorder v1.1.0
github.com/go-errors/errors v1.5.1 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-colorable v0.1.14
github.com/mattn/go-runewidth v0.0.16 github.com/mattn/go-runewidth v0.0.16
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/petergtz/pegomock v2.9.0+incompatible github.com/petergtz/pegomock v2.9.0+incompatible
github.com/rakyll/hey v0.1.4 github.com/rakyll/hey v0.1.4
github.com/rs/zerolog v1.33.0
github.com/sahilm/fuzzy v0.1.1 github.com/sahilm/fuzzy v0.1.1
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
@ -69,7 +69,7 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.11.7 // indirect github.com/Microsoft/hcsshim v0.11.7 // indirect
github.com/OneOfOne/xxhash v1.2.8 // 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/acobaugh/osrelease v0.1.0 // indirect
github.com/agext/levenshtein v1.2.1 // indirect github.com/agext/levenshtein v1.2.1 // indirect
github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51 // 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-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // 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/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/anchore/stereoscope v0.0.13 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // 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/becheran/wildmatch-go v1.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // 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/blang/semver/v4 v4.0.0 // indirect
github.com/bmatcuk/doublestar/v2 v2.0.4 // 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/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // 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/distribution/reference v0.6.0 // indirect
github.com/docker/cli v27.5.0+incompatible // indirect github.com/docker/cli v27.5.0+incompatible // indirect
github.com/docker/distribution v2.8.3+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/docker-credential-helpers v0.8.2 // indirect
github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // 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/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect
github.com/fatih/camelcase v1.0.0 // 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/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // 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/glebarez/sqlite v1.11.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // 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-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-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.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/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.21 // 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/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect github.com/pkg/profile v1.7.0 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // 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/prometheus/procfs v0.15.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // 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/rubenv/sql-migrate v1.7.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saferwall/pe v1.5.6 // 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/sdk/metric v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.33.0 // indirect go.opentelemetry.io/otel/trace v1.33.0 // indirect
go.uber.org/multierr v1.11.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/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.22.0 // indirect golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/net v0.35.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.29.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.28.0 // indirect golang.org/x/term v0.29.0 // indirect
golang.org/x/time v0.8.0 // indirect golang.org/x/time v0.8.0 // indirect
golang.org/x/tools v0.29.0 // indirect golang.org/x/tools v0.29.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // 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/component-base v0.32.2 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
modernc.org/libc v1.55.3 // indirect modernc.org/libc v1.61.13 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.0 // indirect modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.34.5 // indirect modernc.org/sqlite v1.35.0 // indirect
oras.land/oras-go v1.2.5 // indirect oras.land/oras-go v1.2.5 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/kustomize/api v0.18.0 // indirect sigs.k8s.io/kustomize/api v0.18.0 // indirect

121
go.sum
View File

@ -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.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= 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.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= 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 h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= 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= 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/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 h1:HWpzCOCwjKkwkIEEC5lcKI4yl6GhTF3+Z12tXWYtMoI=
github.com/anchore/grype v0.86.1/go.mod h1:k3VnXfi+e/OGx1mTUL733gy3fyB4W/AdHP8fSyQML9w= 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.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY=
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/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 h1:9Ivkh7k+vOeG3JHrt44jOg/8UdZrCvMsSjLQ7trHBig=
github.com/anchore/stereoscope v0.0.13/go.mod h1:QfhhFc2pezp5aX/dVJ5qnBFpBUv5+KUTphwaQLxMUig= 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.20.0 h1:4nVM/eiqrb2GJCkW+d1xv8M5mxply8vVblpWOvVCgN8=
github.com/anchore/syft v1.19.0/go.mod h1:QyTWjG0LzowbJVNQj5ZX8UVx17eTkU73Xl7jAf6upE8= 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/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 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 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 h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= 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/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 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 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 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI=
github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= 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.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= 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 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= 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= 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 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 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/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.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 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 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/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/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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/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 h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 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 v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM=
github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 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 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= 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= 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/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 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= 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.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= 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 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY=
github.com/elliotchance/phpserialize v1.4.0/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs= 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= 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/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 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= 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.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 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= 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/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 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= 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.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ=
github.com/gkampitakis/go-snaps v0.5.8/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY= 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 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= 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-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 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-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.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= 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 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-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/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/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 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 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 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg=
github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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-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-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-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 h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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-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-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-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.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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/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 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= 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 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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 h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 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 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= 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 h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 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= 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/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 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ=
github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo= 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 h1:FvA4bwjKpPqik5WsQ8+4z4DKWgA1tO1RTTtNKr5oYNA=
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554/go.mod h1:n73K/hcuJ50MiVznXyN4rde6fZY7naGKWBXOLFTyc94= 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= 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/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 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 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.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= 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.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/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= 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/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 h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= 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.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
github.com/sanity-io/litter v1.5.6/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= 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 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= 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= 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-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-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.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 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.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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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.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.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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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-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-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-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-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-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.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.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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/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 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 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.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= 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 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= 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.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= modernc.org/sqlite v1.35.0 h1:yQps4fegMnZFdphtzlfQTCNBWtS0CZv48pRpW3RFHRw=
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= modernc.org/sqlite v1.35.0/go.mod h1:9cr2sicr7jIaWTBKQmAxQLfBv9LL0su4ZTEV+utt3ic=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= 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 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo=

View File

@ -7,13 +7,14 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
authorizationv1 "k8s.io/api/authorization/v1" authorizationv1 "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/cache" "k8s.io/apimachinery/pkg/util/cache"
@ -34,7 +35,9 @@ const (
cacheNSKey = "validNamespaces" cacheNSKey = "validNamespaces"
) )
var supportedMetricsAPIVersions = []string{"v1beta1"} var (
supportedMetricsAPIVersions = []string{"v1beta1"}
)
// NamespaceNames tracks a collection of namespace names. // NamespaceNames tracks a collection of namespace names.
type NamespaceNames map[string]struct{} type NamespaceNames map[string]struct{}
@ -50,6 +53,7 @@ type APIClient struct {
mx sync.RWMutex mx sync.RWMutex
cache *cache.LRUExpireCache cache *cache.LRUExpireCache
connOK bool connOK bool
log *slog.Logger
} }
// NewTestAPIClient for testing ONLY!! // NewTestAPIClient for testing ONLY!!
@ -62,15 +66,16 @@ func NewTestAPIClient() *APIClient {
// InitConnection initialize connection from command line args. // InitConnection initialize connection from command line args.
// Checks for connectivity with the api server. // Checks for connectivity with the api server.
func InitConnection(config *Config) (*APIClient, error) { func InitConnection(config *Config, log *slog.Logger) (*APIClient, error) {
a := APIClient{ a := APIClient{
config: config, config: config,
cache: cache.NewLRUExpireCache(cacheSize), cache: cache.NewLRUExpireCache(cacheSize),
connOK: true, connOK: true,
log: log.With(slogs.Subsys, "client"),
} }
err := a.supportsMetricsResources() err := a.supportsMetricsResources()
if err != nil { 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) { if err == nil || errors.Is(err, noMetricServerErr) || errors.Is(err, metricsUnsupportedErr) {
return &a, nil return &a, nil
@ -113,7 +118,7 @@ func makeCacheKey(ns, gvr, n string, vv []string) string {
func (a *APIClient) ActiveContext() string { func (a *APIClient) ActiveContext() string {
c, err := a.config.CurrentContextName() c, err := a.config.CurrentContextName()
if err != nil { if err != nil {
log.Error().Msgf("Unable to located active cluster") slog.Error("unable to located active cluster", slogs.Error, err)
return "" return ""
} }
return c 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() dial, err := a.Dial()
if err != nil { if err != nil {
return false, err 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 { for _, v := range verbs {
sar.Spec.ResourceAttributes.Verb = v sar.Spec.ResourceAttributes.Verb = v
resp, err := client.Create(ctx, sar, metav1.CreateOptions{}) 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 { if resp != nil {
log.Trace().Msgf(" Spec: %#v", resp.Spec) clog.Debug("[CAN] reps",
log.Trace().Msgf(" Auth: %t [%q]", resp.Status.Allowed, resp.Status.Reason) slogs.AuthStatus, resp.Status.Allowed,
slogs.AuthReason, resp.Status.Reason,
)
} }
log.Trace().Msgf(" <<%v>>", err)
if err != nil { if err != nil {
log.Warn().Err(err).Msgf(" Dial Failed!") clog.Warn("Auth request failed", slogs.Error, err)
a.cache.Add(key, false, cacheExpiry) a.cache.Add(key, false, cacheExpiry)
return auth, err return auth, err
} }
@ -220,7 +233,10 @@ func (a *APIClient) ServerVersion() (*version.Info, error) {
func (a *APIClient) IsValidNamespace(ns string) bool { func (a *APIClient) IsValidNamespace(ns string) bool {
ok, err := a.isValidNamespace(ns) ok, err := a.isValidNamespace(ns)
if err != nil { 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 return ok
@ -287,16 +303,15 @@ func (a *APIClient) CheckConnectivity() bool {
}() }()
cfg, err := a.config.RESTConfig() cfg, err := a.config.RESTConfig()
if err != nil { if err != nil {
log.Error().Err(err).Msgf("restConfig load failed") slog.Error("RestConfig load failed", slogs.Error, err)
a.connOK = false a.connOK = false
return a.connOK return a.connOK
} }
cfg.Timeout = a.config.CallTimeout() cfg.Timeout = a.config.CallTimeout()
client, err := kubernetes.NewForConfig(cfg) client, err := kubernetes.NewForConfig(cfg)
if err != nil { 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) a.setConnOK(false)
return a.getConnOK() return a.getConnOK()
} }
@ -307,7 +322,7 @@ func (a *APIClient) CheckConnectivity() bool {
a.reset() a.reset()
} }
} else { } else {
log.Error().Err(err).Msgf("can't connect to cluster") slog.Error("Unable to fetch server version", slogs.Error, err)
a.setConnOK(false) a.setConnOK(false)
} }
@ -541,13 +556,13 @@ func (a *APIClient) invalidateCache() error {
// SwitchContext handles kubeconfig context switches. // SwitchContext handles kubeconfig context switches.
func (a *APIClient) SwitchContext(name string) error { 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 { if err := a.config.SwitchContext(name); err != nil {
return err return err
} }
if !a.CheckConnectivity() { 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 { } else if err := a.invalidateCache(); err != nil {
return err return err
} }
@ -597,7 +612,7 @@ func (a *APIClient) supportsMetricsResources() error {
dial, err := a.Dial() dial, err := a.Dial()
if err != nil { 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 return err
} }
apiGroups, err := dial.Discovery().ServerGroups() apiGroups, err := dial.Discovery().ServerGroups()

View File

@ -19,7 +19,7 @@ import (
) )
const ( const (
defaultCallTimeoutDuration time.Duration = 15 * time.Second defaultCallTimeoutDuration time.Duration = 10 * time.Second
// UsePersistentConfig caches client config to avoid reloads. // UsePersistentConfig caches client config to avoid reloads.
UsePersistentConfig = true UsePersistentConfig = true

View File

@ -5,18 +5,18 @@ package client_test
import ( import (
"errors" "errors"
"log/slog"
"os" "os"
"testing" "testing"
"time" "time"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
) )
func init() { func init() {
zerolog.SetGlobalLevel(zerolog.FatalLevel) slog.SetDefault(slog.New(slog.DiscardHandler))
} }
func TestCallTimeout(t *testing.T) { func TestCallTimeout(t *testing.T) {
@ -29,7 +29,7 @@ func TestCallTimeout(t *testing.T) {
e: 1 * time.Minute, e: 1 * time.Minute,
}, },
"default": { "default": {
e: 15 * time.Second, e: 10 * time.Second,
}, },
} }

View File

@ -5,11 +5,12 @@ package client
import ( import (
"fmt" "fmt"
"log/slog"
"path" "path"
"strings" "strings"
"github.com/derailed/k9s/internal/slogs"
"github.com/fvbommel/sortorder" "github.com/fvbommel/sortorder"
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
@ -40,7 +41,7 @@ func NewGVR(gvr string) GVR {
case 1: case 1:
r = tokens[0] r = tokens[0]
default: 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} 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 { for _, verb := range verbs {
candidates, err := mapVerb(v) candidates, err := mapVerb(v)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("verb mapping failed") slog.Error("Access verb mapping failed", slogs.Error, err)
return false return false
} }
for _, c := range candidates { for _, c := range candidates {

View File

@ -4,12 +4,14 @@
package client package client
import ( import (
"log/slog"
"os"
"os/user" "os/user"
"path" "path"
"regexp" "regexp"
"strings" "strings"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -81,7 +83,8 @@ func MetaFQN(m metav1.ObjectMeta) string {
func mustHomeDir() string { func mustHomeDir() string {
usr, err := user.Current() usr, err := user.Current()
if err != nil { 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 return usr.HomeDir
} }

View File

@ -5,14 +5,14 @@ package config
import ( import (
"errors" "errors"
"fmt"
"io/fs" "io/fs"
"log/slog"
"os" "os"
"sync" "sync"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -120,7 +120,7 @@ func (a *Aliases) Load(path string) error {
f, err := EnsureAliasesCfgFile() f, err := EnsureAliasesCfgFile()
if err != nil { 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 // load global alias file
@ -146,7 +146,7 @@ func (a *Aliases) LoadFile(path string) error {
return err return err
} }
if err := data.JSONValidator.Validate(json.AliasesSchema, bb); err != nil { 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 var aa Aliases
@ -193,7 +193,7 @@ func (a *Aliases) loadDefaultAliases() {
// Save alias to disk. // Save alias to disk.
func (a *Aliases) Save() error { func (a *Aliases) Save() error {
log.Debug().Msg("[Config] Saving Aliases...") slog.Debug("Saving Aliases...")
return a.SaveAliases(AppAliasesFile) return a.SaveAliases(AppAliasesFile)
} }

View File

@ -7,13 +7,14 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"log/slog"
"os" "os"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/k9s/internal/view/cmd"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"k8s.io/cli-runtime/pkg/genericclioptions" "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. // ContextHotkeysPath returns a context specific hotkeys file spec.
func (c *Config) ContextHotkeysPath() string { func (c *Config) ContextHotkeysPath() string {
ct, err := c.K9s.ActiveContext() 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) 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 var ns string
switch { switch {
@ -113,7 +124,7 @@ func (c *Config) Reset() {
c.K9s.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) ct, err := c.K9s.ActivateContext(n)
if err != nil { if err != nil {
return nil, fmt.Errorf("set current context failed. %w", err) 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 { func (c *Config) ActiveNamespace() string {
ns, err := c.K9s.ActiveContextNamespace() ns, err := c.K9s.ActiveContextNamespace()
if err != nil { 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 ns = client.DefaultNamespace
} }
@ -145,7 +156,7 @@ func (c *Config) FavNamespaces() []string {
if err != nil { if err != nil {
return nil return nil
} }
ct.Validate(c.conn, c.settings) ct.Validate(c.conn, c.K9s.getActiveContextName(), ct.ClusterName)
return ct.Namespace.Favorites return ct.Namespace.Favorites
} }
@ -153,7 +164,7 @@ func (c *Config) FavNamespaces() []string {
// SetActiveNamespace set the active namespace in the current context. // SetActiveNamespace set the active namespace in the current context.
func (c *Config) SetActiveNamespace(ns string) error { func (c *Config) SetActiveNamespace(ns string) error {
if ns == client.NotNamespaced { if ns == client.NotNamespaced {
log.Debug().Msgf("[SetNS] No namespace given. skipping!") slog.Debug("No namespace given. skipping!", slogs.Namespace, ns)
return nil return nil
} }
ct, err := c.K9s.ActiveContext() ct, err := c.K9s.ActiveContext()
@ -251,8 +262,13 @@ func (c *Config) Load(path string, force bool) error {
// Save configuration to disk. // Save configuration to disk.
func (c *Config) Save(force bool) error { func (c *Config) Save(force bool) error {
c.Validate() contextName := c.K9s.ActiveContextName()
if err := c.K9s.Save(force); err != nil { 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 return err
} }
if _, err := os.Stat(AppConfigFile); errors.Is(err, fs.ErrNotExist) { 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 { if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil {
return err return err
} }
cfg, err := yaml.Marshal(c) cfg, err := yaml.Marshal(c)
if err != nil { 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 return err
} }
slog.Info("[CONFIG] Saving K9s config to disk", slogs.Path, path)
return os.WriteFile(path, cfg, data.DefaultFileMod) return os.WriteFile(path, cfg, data.DefaultFileMod)
} }
// Validate the configuration. // Validate the configuration.
func (c *Config) Validate() { func (c *Config) Validate(contextName, clusterName string) {
if c.K9s == nil { if c.K9s == nil {
c.K9s = NewK9s(c.conn, c.settings) c.K9s = NewK9s(c.conn, c.settings)
} }
c.K9s.Validate(c.conn, c.settings) c.K9s.Validate(c.conn, contextName, clusterName)
} }
// Dump for debug... // Dump for debug...

View File

@ -6,6 +6,7 @@ package config_test
import ( import (
"errors" "errors"
"fmt" "fmt"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -16,13 +17,12 @@ import (
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/config/mock"
m "github.com/petergtz/pegomock" m "github.com/petergtz/pegomock"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
) )
func init() { func init() {
zerolog.SetGlobalLevel(zerolog.FatalLevel) slog.SetDefault(slog.New(slog.DiscardHandler))
} }
func TestConfigSave(t *testing.T) { 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 { uu := map[string]struct {
cl, ct string cl, ct string
err string err string
@ -344,7 +344,7 @@ func TestConfigSetCurrentContext(t *testing.T) {
u := uu[k] u := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
cfg := mock.NewMockConfig() cfg := mock.NewMockConfig()
ct, err := cfg.SetCurrentContext(u.ct) ct, err := cfg.ActivateContext(u.ct)
if err != nil { if err != nil {
assert.Equal(t, u.err, err.Error()) assert.Equal(t, u.err, err.Error())
return return
@ -529,7 +529,7 @@ func TestConfigValidate(t *testing.T) {
cfg.SetConnection(mock.NewMockConnection()) cfg.SetConnection(mock.NewMockConnection())
assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.Validate() cfg.Validate("ct-1-1", "cl-1")
} }
func TestConfigLoad(t *testing.T) { func TestConfigLoad(t *testing.T) {
@ -556,7 +556,7 @@ func TestConfigSaveFile(t *testing.T) {
cfg.K9s.ReadOnly = true cfg.K9s.ReadOnly = true
cfg.K9s.Logger.TailCount = 500 cfg.K9s.Logger.TailCount = 500
cfg.K9s.Logger.BufferSize = 800 cfg.K9s.Logger.BufferSize = 800
cfg.Validate() cfg.Validate("ct-1-1", "cl-1")
path := filepath.Join("/tmp", "k9s.yaml") path := filepath.Join("/tmp", "k9s.yaml")
assert.NoError(t, cfg.SaveFile(path)) assert.NoError(t, cfg.SaveFile(path))
@ -571,7 +571,7 @@ func TestConfigReset(t *testing.T) {
cfg := mock.NewMockConfig() cfg := mock.NewMockConfig()
assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))
cfg.Reset() cfg.Reset()
cfg.Validate() cfg.Validate("ct-1-1", "cl-1")
path := filepath.Join("/tmp", "k9s.yaml") path := filepath.Join("/tmp", "k9s.yaml")
assert.NoError(t, cfg.SaveFile(path)) assert.NoError(t, cfg.SaveFile(path))

View File

@ -26,6 +26,7 @@ func NewConfig(ct *api.Context) *Config {
} }
} }
// Merge merges configs and updates receiver.
func (c *Config) Merge(c1 *Config) { func (c *Config) Merge(c1 *Config) {
if c1 == nil { if c1 == nil {
return return
@ -36,14 +37,14 @@ func (c *Config) Merge(c1 *Config) {
} }
// Validate ensures config is in norms. // 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() c.mx.Lock()
defer c.mx.Unlock() defer c.mx.Unlock()
if c.Context == nil { if c.Context == nil {
c.Context = NewContext() c.Context = NewContext()
} }
c.Context.Validate(conn, ks) c.Context.Validate(conn, contextName, clusterName)
} }
// Dump used for debugging. // Dump used for debugging.

View File

@ -13,24 +13,22 @@ import (
// Context tracks K9s context configuration. // Context tracks K9s context configuration.
type Context struct { type Context struct {
ClusterName string `yaml:"cluster,omitempty"` ClusterName string `yaml:"cluster,omitempty"`
ReadOnly *bool `yaml:"readOnly,omitempty"` ReadOnly *bool `yaml:"readOnly,omitempty"`
Skin string `yaml:"skin,omitempty"` Skin string `yaml:"skin,omitempty"`
Namespace *Namespace `yaml:"namespace"` Namespace *Namespace `yaml:"namespace"`
View *View `yaml:"view"` View *View `yaml:"view"`
FeatureGates FeatureGates `yaml:"featureGates"` FeatureGates FeatureGates `yaml:"featureGates"`
PortForwardAddress string `yaml:"portForwardAddress"` Proxy *Proxy `yaml:"proxy"`
Proxy *Proxy `yaml:"proxy"` mx sync.RWMutex
mx sync.RWMutex
} }
// NewContext creates a new cluster configuration. // NewContext creates a new cluster configuration.
func NewContext() *Context { func NewContext() *Context {
return &Context{ return &Context{
Namespace: NewNamespace(), Namespace: NewNamespace(),
View: NewView(), View: NewView(),
PortForwardAddress: defaultPFAddress(), FeatureGates: NewFeatureGates(),
FeatureGates: NewFeatureGates(),
} }
} }
@ -71,19 +69,11 @@ func (c *Context) GetClusterName() string {
} }
// Validate ensures a context config is tip top. // 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() c.mx.Lock()
defer c.mx.Unlock() defer c.mx.Unlock()
if a := os.Getenv(envPFAddress); a != "" { c.ClusterName = clusterName
c.PortForwardAddress = a
}
if c.PortForwardAddress == "" {
c.PortForwardAddress = defaultPFAddress()
}
if cl, err := ks.CurrentClusterName(); err == nil {
c.ClusterName = cl
}
if b := os.Getenv(envFGNodeShell); b != "" { if b := os.Getenv(envFGNodeShell); b != "" {
c.FeatureGates.NodeShell = defaultFGNodeShell() c.FeatureGates.NodeShell = defaultFGNodeShell()
} }

View File

@ -13,7 +13,7 @@ import (
func TestClusterValidate(t *testing.T) { func TestClusterValidate(t *testing.T) {
c := data.NewContext() 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, "po", c.View.Active)
assert.Equal(t, "default", c.Namespace.Active) assert.Equal(t, "default", c.Namespace.Active)
@ -23,7 +23,7 @@ func TestClusterValidate(t *testing.T) {
func TestClusterValidateEmpty(t *testing.T) { func TestClusterValidateEmpty(t *testing.T) {
c := data.NewContext() 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, "po", c.View.Active)
assert.Equal(t, "default", c.Namespace.Active) assert.Equal(t, "default", c.Namespace.Active)

View File

@ -7,12 +7,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/tools/clientcmd/api"
) )
@ -31,18 +32,19 @@ func NewDir(root string) *Dir {
} }
// Load loads context configuration. // 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 { if ct == nil {
return nil, errors.New("api.Context must not be 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) f, err := os.Stat(path)
if errors.Is(err, fs.ErrPermission) { if errors.Is(err, fs.ErrPermission) {
return nil, err return nil, err
} }
if errors.Is(err, fs.ErrNotExist) || (f != nil && f.Size() == 0) { 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) return d.genConfig(path, ct)
} }
if err != nil { if err != nil {
@ -89,7 +91,10 @@ func (d *Dir) loadConfig(path string) (*Config, error) {
return nil, err return nil, err
} }
if err := JSONValidator.Validate(json.ContextSchema, bb); err != nil { 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 var cfg Config

View File

@ -4,20 +4,20 @@
package data_test package data_test
import ( import (
"log/slog"
"os" "os"
"strings" "strings"
"testing" "testing"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/config/mock"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
) )
func init() { func init() {
zerolog.SetGlobalLevel(zerolog.FatalLevel) slog.SetDefault(slog.New(slog.DiscardHandler))
} }
func TestDirLoad(t *testing.T) { func TestDirLoad(t *testing.T) {

View File

@ -11,11 +11,7 @@ import (
"regexp" "regexp"
) )
const ( const envFGNodeShell = "K9S_FEATURE_GATE_NODE_SHELL"
envPFAddress = "K9S_DEFAULT_PF_ADDRESS"
envFGNodeShell = "K9S_FEATURE_GATE_NODE_SHELL"
defaultPortFwdAddress = "localhost"
)
var invalidPathCharsRX = regexp.MustCompile(`[:/]+`) var invalidPathCharsRX = regexp.MustCompile(`[:/]+`)
@ -29,14 +25,6 @@ func SanitizeFileName(name string) string {
return invalidPathCharsRX.ReplaceAllString(name, "-") return invalidPathCharsRX.ReplaceAllString(name, "-")
} }
func defaultPFAddress() string {
if a := os.Getenv(envPFAddress); a != "" {
return a
}
return defaultPortFwdAddress
}
func defaultFGNodeShell() bool { func defaultFGNodeShell() bool {
if a := os.Getenv(envFGNodeShell); a != "" { if a := os.Getenv(envFGNodeShell); a != "" {
return a == "true" return a == "true"

View File

@ -4,11 +4,12 @@
package data package data
import ( import (
"log/slog"
"slices" "slices"
"sync" "sync"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
) )
const ( const (
@ -67,7 +68,10 @@ func (n *Namespace) Validate(c client.Connection) {
} }
for _, ns := range n.Favorites { for _, ns := range n.Favorites {
if !c.IsValidNamespace(ns) { 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) n.rmFavNS(ns)
} }
} }
@ -136,7 +140,7 @@ func (n *Namespace) rmFavNS(ns string) {
func (n *Namespace) trimFavNs() { func (n *Namespace) trimFavNs() {
if len(n.Favorites) > MaxFavoritesNS { 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] n.Favorites = n.Favorites[:MaxFavoritesNS]
} }
} }

View File

@ -9,4 +9,3 @@ k9s:
active: po active: po
featureGates: featureGates:
nodeShell: false nodeShell: false
portForwardAddress: localhost

View File

@ -13,4 +13,3 @@ k9s:
active: dp active: dp
featureGates: featureGates:
nodeShell: true nodeShell: true
portForwardAddress: localhost

View File

@ -11,4 +11,3 @@ k9s:
active: po active: po
featureGates: featureGates:
nodeShell: false nodeShell: false
portForwardAddress: localhost

View File

@ -12,4 +12,3 @@ k9s:
active: svc active: svc
featureGates: featureGates:
nodeShell: true nodeShell: true
portForwardAddress: fred

View File

@ -9,4 +9,3 @@ k9s:
active: po active: po
featureGates: featureGates:
nodeShell: false nodeShell: false
portForwardAddress: localhost

View File

@ -13,4 +13,3 @@ k9s:
active: dp active: dp
featureGates: featureGates:
nodeShell: true nodeShell: true
portForwardAddress: localhost

View File

@ -11,4 +11,3 @@ k9s:
active: po active: po
featureGates: featureGates:
nodeShell: false nodeShell: false
portForwardAddress: localhost

View File

@ -12,4 +12,3 @@ k9s:
active: svc active: svc
featureGates: featureGates:
nodeShell: true nodeShell: true
portForwardAddress: fred

View File

@ -7,13 +7,14 @@ import (
_ "embed" _ "embed"
"errors" "errors"
"io/fs" "io/fs"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/slogs"
"github.com/adrg/xdg" "github.com/adrg/xdg"
"github.com/rs/zerolog/log"
) )
const ( const (
@ -126,19 +127,31 @@ func initK9sEnvLocs() error {
AppDumpsDir = filepath.Join(AppConfigDir, "screen-dumps") AppDumpsDir = filepath.Join(AppConfigDir, "screen-dumps")
if err := data.EnsureFullPath(AppDumpsDir, data.DefaultDirMod); err != nil { 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") AppBenchmarksDir = filepath.Join(AppConfigDir, "benchmarks")
if err := data.EnsureFullPath(AppBenchmarksDir, data.DefaultDirMod); err != nil { 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") AppSkinsDir = filepath.Join(AppConfigDir, "skins")
if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil { 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") AppContextsDir = filepath.Join(AppConfigDir, "clusters")
if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil { 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) AppConfigFile = filepath.Join(AppConfigDir, data.MainConfigFile)
@ -170,7 +183,7 @@ func initXDGLocs() error {
AppSkinsDir = filepath.Join(AppConfigDir, "skins") AppSkinsDir = filepath.Join(AppConfigDir, "skins")
if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil { 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")) AppDumpsDir, err = xdg.StateFile(filepath.Join(AppName, "screen-dumps"))
@ -180,7 +193,10 @@ func initXDGLocs() error {
AppBenchmarksDir, err = xdg.StateFile(filepath.Join(AppName, "benchmarks")) AppBenchmarksDir, err = xdg.StateFile(filepath.Join(AppName, "benchmarks"))
if err != nil { 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) dataDir, err := xdg.DataFile(AppName)
@ -189,7 +205,10 @@ func initXDGLocs() error {
} }
AppContextsDir = filepath.Join(dataDir, "clusters") AppContextsDir = filepath.Join(dataDir, "clusters")
if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil { 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 return nil

View File

@ -4,11 +4,17 @@
package config package config
import ( import (
"log/slog"
"os" "os"
"os/user" "os/user"
"path/filepath" "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. // IsBoolSet checks if a bool ptr is set.
@ -54,7 +60,16 @@ func MustK9sUser() string {
if envUsr != "" { if envUsr != "" {
return 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 return usr.Username
} }
func defaultPFAddress() string {
if a := os.Getenv(envPFAddress); a != "" {
return a
}
return defaultPortFwdAddress
}

View File

@ -5,12 +5,13 @@ package config
import ( import (
"errors" "errors"
"fmt"
"io/fs" "io/fs"
"log/slog"
"os" "os"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -57,7 +58,10 @@ func (h HotKeys) LoadHotKeys(path string) error {
return err return err
} }
if err := data.JSONValidator.Validate(json.HotkeysSchema, bb); err != nil { 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 var hh HotKeys

View File

@ -10,7 +10,6 @@
"cluster": { "type": "string" }, "cluster": { "type": "string" },
"readOnly": {"type": "boolean"}, "readOnly": {"type": "boolean"},
"skin": { "type": "string" }, "skin": { "type": "string" },
"portForwardAddress": { "type": "string" },
"proxy": { "proxy": {
"oneOf": [ "oneOf": [
{ "type": "null" }, { "type": "null" },

View File

@ -15,6 +15,7 @@
"noExitOnCtrlC": { "type": "boolean" }, "noExitOnCtrlC": { "type": "boolean" },
"skipLatestRevCheck": { "type": "boolean" }, "skipLatestRevCheck": { "type": "boolean" },
"disablePodCounting": { "type": "boolean" }, "disablePodCounting": { "type": "boolean" },
"portForwardAddress": { "type": "string" },
"ui": { "ui": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,

View File

@ -12,4 +12,3 @@ k9s:
active: pod active: pod
featureGates: featureGates:
nodeShell: false nodeShell: false
portForwardAddress: localhost

View File

@ -13,4 +13,3 @@ k9s:
fred: blee fred: blee
featureGates: featureGates:
nodeShell: false nodeShell: false
portForwardAddress: localhost

View File

@ -8,9 +8,10 @@ import (
_ "embed" _ "embed"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"slices" "slices"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"github.com/xeipuuv/gojsonschema" "github.com/xeipuuv/gojsonschema"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -89,9 +90,15 @@ func NewValidator() *Validator {
func (v *Validator) register() { func (v *Validator) register() {
v.loader = gojsonschema.NewSchemaLoader() v.loader = gojsonschema.NewSchemaLoader()
v.loader.Validate = true v.loader.Validate = true
clog := slog.With(slogs.Subsys, "schema")
for k, s := range v.schemas { for k, s := range v.schemas {
if err := v.loader.AddSchema(k, s); err != nil { 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,
)
} }
} }
} }

View File

@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"log/slog"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -15,7 +16,7 @@ import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
) )
// K9s tracks K9s configuration options. // K9s tracks K9s configuration options.
@ -26,6 +27,7 @@ type K9s struct {
MaxConnRetry int `json:"maxConnRetry" yaml:"maxConnRetry"` MaxConnRetry int `json:"maxConnRetry" yaml:"maxConnRetry"`
ReadOnly bool `json:"readOnly" yaml:"readOnly"` ReadOnly bool `json:"readOnly" yaml:"readOnly"`
NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"` NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"`
PortForwardAddress string `yaml:"portForwardAddress"`
UI UI `json:"ui" yaml:"ui"` UI UI `json:"ui" yaml:"ui"`
SkipLatestRevCheck bool `json:"skipLatestRevCheck" yaml:"skipLatestRevCheck"` SkipLatestRevCheck bool `json:"skipLatestRevCheck" yaml:"skipLatestRevCheck"`
DisablePodCounting bool `json:"disablePodCounting" yaml:"disablePodCounting"` DisablePodCounting bool `json:"disablePodCounting" yaml:"disablePodCounting"`
@ -46,24 +48,40 @@ type K9s struct {
conn client.Connection conn client.Connection
ks data.KubeSettings ks data.KubeSettings
mx sync.RWMutex mx sync.RWMutex
contextSwitch bool
} }
// NewK9s create a new K9s configuration. // NewK9s create a new K9s configuration.
func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s { func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s {
return &K9s{ return &K9s{
RefreshRate: defaultRefreshRate, RefreshRate: defaultRefreshRate,
MaxConnRetry: defaultMaxConnRetry, MaxConnRetry: defaultMaxConnRetry,
ScreenDumpDir: AppDumpsDir, ScreenDumpDir: AppDumpsDir,
Logger: NewLogger(), Logger: NewLogger(),
Thresholds: NewThreshold(), Thresholds: NewThreshold(),
ShellPod: NewShellPod(), PortForwardAddress: defaultPFAddress(),
ImageScans: NewImageScans(), ShellPod: NewShellPod(),
dir: data.NewDir(AppContextsDir), ImageScans: NewImageScans(),
conn: conn, dir: data.NewDir(AppContextsDir),
ks: ks, 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) { func (k *K9s) resetConnection(conn client.Connection) {
k.mx.Lock() k.mx.Lock()
defer k.mx.Unlock() defer k.mx.Unlock()
@ -72,17 +90,15 @@ func (k *K9s) resetConnection(conn client.Connection) {
} }
// Save saves the k9s config to disk. // Save saves the k9s config to disk.
func (k *K9s) Save(force bool) error { func (k *K9s) Save(contextName, clusterName string, force bool) error {
if k.getActiveConfig() == nil {
log.Warn().Msgf("Save failed. no active config detected")
return nil
}
path := filepath.Join( path := filepath.Join(
AppContextsDir, AppContextsDir,
data.SanitizeContextSubpath(k.activeConfig.Context.GetClusterName(), k.getActiveContextName()), data.SanitizeContextSubpath(clusterName, contextName),
data.MainConfigFile, data.MainConfigFile,
) )
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) || force { 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()) 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 { if cfg := k.getActiveConfig(); cfg != nil && cfg.Context != nil {
return cfg.Context, nil return cfg.Context, nil
} }
ct, err := k.ActivateContext(k.getActiveContextName()) ct, err := k.ActivateContext(k.ActiveContextName())
if err != nil {
return nil, err
}
return ct, nil return ct, err
} }
func (k *K9s) setActiveConfig(c *data.Config) { func (k *K9s) setActiveConfig(c *data.Config) {
@ -205,14 +218,14 @@ func (k *K9s) getActiveContextName() string {
} }
// ActivateContext initializes the active context if not present. // ActivateContext initializes the active context if not present.
func (k *K9s) ActivateContext(n string) (*data.Context, error) { func (k *K9s) ActivateContext(contextName string) (*data.Context, error) {
k.setActiveContextName(n) k.setActiveContextName(contextName)
ct, err := k.ks.GetContext(n) ct, err := k.ks.GetContext(contextName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg, err := k.dir.Load(n, ct) cfg, err := k.dir.Load(contextName, ct)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -220,7 +233,7 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) {
if cfg.Context.Proxy != nil { if cfg.Context.Proxy != nil {
k.ks.SetProxy(func(*http.Request) (*url.URL, error) { 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) 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 // already has an API connection object so we just set the proxy to
// avoid recreation using client.InitConnection // avoid recreation using client.InitConnection
k.conn.Config().SetProxy(func(*http.Request) (*url.URL, error) { 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) return url.Parse(cfg.Context.Proxy.Address)
}) })
if !k.conn.CheckConnectivity() { 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 the context specifies a namespace, use it!
if ns := ct.Namespace; ns != client.BlankNamespace { if ns := ct.Namespace; ns != client.BlankNamespace {
k.getActiveConfig().Context.Namespace.Active = ns 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 k.getActiveConfig().Context.Namespace.Active = client.DefaultNamespace
} }
if k.getActiveConfig().Context == nil { 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 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. // Reload reloads the context config from disk.
func (k *K9s) Reload() error { func (k *K9s) Reload() error {
// Switching context skipping reload...
if k.getContextSwitch() {
return nil
}
ct, err := k.ks.GetContext(k.getActiveContextName()) ct, err := k.ks.GetContext(k.getActiveContextName())
if err != nil { if err != nil {
return err return err
@ -265,7 +282,7 @@ func (k *K9s) Reload() error {
return err return err
} }
k.setActiveConfig(cfg) k.setActiveConfig(cfg)
k.getActiveConfig().Validate(k.conn, k.ks) k.getActiveConfig().Validate(k.conn, k.getActiveContextName(), ct.Cluster)
return nil return nil
} }
@ -340,7 +357,7 @@ func (k *K9s) IsReadOnly() bool {
} }
// Validate the current configuration. // 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 { if k.RefreshRate <= 0 {
k.RefreshRate = defaultRefreshRate k.RefreshRate = defaultRefreshRate
} }
@ -348,16 +365,21 @@ func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) {
k.MaxConnRetry = defaultMaxConnRetry k.MaxConnRetry = defaultMaxConnRetry
} }
if a := os.Getenv(envPFAddress); a != "" {
k.PortForwardAddress = a
}
if k.PortForwardAddress == "" {
k.PortForwardAddress = defaultPFAddress()
}
if k.getActiveConfig() == nil { if k.getActiveConfig() == nil {
if n, err := ks.CurrentContextName(); err == nil { _, _ = k.ActivateContext(contextName)
_, _ = k.ActivateContext(n)
}
} }
k.ShellPod = k.ShellPod.Validate() k.ShellPod = k.ShellPod.Validate()
k.Logger = k.Logger.Validate() k.Logger = k.Logger.Validate()
k.Thresholds = k.Thresholds.Validate() k.Thresholds = k.Thresholds.Validate()
if cfg := k.getActiveConfig(); cfg != nil { if cfg := k.getActiveConfig(); cfg != nil {
cfg.Validate(c, ks) cfg.Validate(c, contextName, clusterName)
} }
} }

View File

@ -36,6 +36,9 @@ func EnsureDir(d string) error {
} }
func NewMockConfig() *config.Config { func NewMockConfig() *config.Config {
if _, err := os.Stat("/tmp/test"); errors.Is(err, os.ErrExist) {
os.RemoveAll("/tmp/test")
}
config.AppContextsDir = "/tmp/test" config.AppContextsDir = "/tmp/test"
cl, ct := "cl-1", "ct-1-1" cl, ct := "cl-1", "ct-1-1"
flags := genericclioptions.ConfigFlags{ flags := genericclioptions.ConfigFlags{

View File

@ -7,14 +7,15 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/adrg/xdg"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs"
"github.com/adrg/xdg"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -113,7 +114,10 @@ func (p *Plugins) load(path string) error {
return err return err
} }
if err := data.JSONValidator.Validate(json.PluginsSchema, bb); err != nil { 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 var pp Plugins
if err := yaml.Unmarshal(bb, &pp); err != nil { if err := yaml.Unmarshal(bb, &pp); err != nil {

View File

@ -5,6 +5,7 @@ k9s:
maxConnRetry: 5 maxConnRetry: 5
readOnly: false readOnly: false
noExitOnCtrlC: false noExitOnCtrlC: false
portForwardAddress: localhost
ui: ui:
enableMouse: false enableMouse: false
headless: false headless: false

View File

@ -5,6 +5,7 @@ k9s:
maxConnRetry: 5 maxConnRetry: 5
readOnly: true readOnly: true
noExitOnCtrlC: false noExitOnCtrlC: false
portForwardAddress: localhost
ui: ui:
enableMouse: false enableMouse: false
headless: false headless: false

View File

@ -5,6 +5,7 @@ k9s:
maxConnRetry: 5 maxConnRetry: 5
readOnly: false readOnly: false
noExitOnCtrlC: false noExitOnCtrlC: false
portForwardAddress: localhost
ui: ui:
enableMouse: false enableMouse: false
headless: false headless: false

View File

@ -5,3 +5,15 @@ views:
- NAME - NAME
- AGE - AGE
- IP - IP
v1/pods@default:
columns:
- NAME
- IP
- AGE
v1/pods@ns*:
columns:
- AGE
- NAME
- IP

View File

@ -8,13 +8,16 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"log/slog"
"maps"
"os" "os"
"regexp"
"slices" "slices"
"strings" "strings"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -22,6 +25,9 @@ import (
type ViewConfigListener interface { type ViewConfigListener interface {
// ViewSettingsChanged notifies listener the view configuration changed. // ViewSettingsChanged notifies listener the view configuration changed.
ViewSettingsChanged(*ViewSetting) ViewSettingsChanged(*ViewSetting)
// GetNamespace return the view namespace
GetNamespace() string
} }
// ViewSetting represents a view configuration. // ViewSetting represents a view configuration.
@ -50,13 +56,19 @@ func (v *ViewSetting) SortCol() (string, bool, error) {
return tt[0], tt[1] == "asc", nil return tt[0], tt[1] == "asc", nil
} }
// Equals checks if two view settings are equal.
func (v *ViewSetting) Equals(vs *ViewSetting) bool { func (v *ViewSetting) Equals(vs *ViewSetting) bool {
if v == nil && vs == nil {
return true
}
if v == nil || vs == nil { if v == nil || vs == nil {
return false return false
} }
if c := slices.Compare(v.Columns, vs.Columns); c != 0 { if c := slices.Compare(v.Columns, vs.Columns); c != 0 {
return false return false
} }
return cmp.Compare(v.SortColumn, vs.SortColumn) == 0 return cmp.Compare(v.SortColumn, vs.SortColumn) == 0
} }
@ -91,7 +103,10 @@ func (v *CustomView) Load(path string) error {
return err return err
} }
if err := data.JSONValidator.Validate(json.ViewsSchema, bb); err != nil { 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 var in CustomView
if err := yaml.Unmarshal(bb, &in); err != nil { if err := yaml.Unmarshal(bb, &in); err != nil {
@ -116,11 +131,41 @@ func (v *CustomView) RemoveListener(gvr string) {
func (v *CustomView) fireConfigChanged() { func (v *CustomView) fireConfigChanged() {
for gvr, list := range v.listeners { for gvr, list := range v.listeners {
if vs, ok := v.Views[gvr]; ok { if vs := v.getVS(gvr, list.GetNamespace()); vs == nil {
log.Debug().Msgf("Reloading custom view settings for %s", gvr)
list.ViewSettingsChanged(&vs)
} else {
list.ViewSettingsChanged(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
}

View File

@ -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))
})
}
}

View File

@ -4,37 +4,117 @@
package config_test package config_test
import ( import (
"log/slog"
"testing" "testing"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestViewSettingsLoad(t *testing.T) { func init() {
cfg := config.NewCustomView() slog.SetDefault(slog.New(slog.DiscardHandler))
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 TestViewSetting_Equals(t *testing.T) { func TestCustomViewLoad(t *testing.T) {
tests := []struct { uu := map[string]struct {
v1, v2 *config.ViewSetting cv *config.CustomView
equals bool path string
key string
e []string
}{ }{
{nil, nil, false}, "empty": {},
{&config.ViewSetting{}, nil, false},
{nil, &config.ViewSetting{}, false}, "gvr": {
{&config.ViewSetting{}, &config.ViewSetting{}, true}, path: "testdata/views/views.yaml",
{&config.ViewSetting{Columns: []string{"A"}}, &config.ViewSetting{}, false}, key: "v1/pods",
{&config.ViewSetting{Columns: []string{"A"}}, &config.ViewSetting{Columns: []string{"A"}}, true}, e: []string{"NAMESPACE", "NAME", "AGE", "IP"},
{&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}, "gvr+ns": {
path: "testdata/views/views.yaml",
key: "v1/pods@default",
e: []string{"NAME", "IP", "AGE"},
},
} }
for _, tt := range tests { for k, u := range uu {
assert.Equalf(t, tt.equals, tt.v1.Equals(tt.v2), "%#v and %#v", tt.v1, tt.v2) 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)
})
} }
} }

View File

@ -6,12 +6,14 @@ package dao
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"log/slog"
"sync" "sync"
"time" "time"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
) )
// RefScanner represents a resource reference scanner. // RefScanner represents a resource reference scanner.
@ -55,7 +57,7 @@ func scanners() map[string]RefScanner {
// ScanForRefs scans cluster resources for resource references. // ScanForRefs scans cluster resources for resource references.
func ScanForRefs(ctx context.Context, f Factory) (Refs, error) { func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
defer func(t time.Time) { 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()) }(time.Now())
gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR) 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) wait, ok := ctx.Value(internal.KeyWait).(bool)
if !ok { if !ok {
log.Error().Msgf("expecting Context Wait Key") slog.Warn("Expecting context Wait key. Using default")
} }
ss := scanners() ss := scanners()
@ -81,7 +83,10 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
s.Init(f, client.NewGVR(kind)) s.Init(f, client.NewGVR(kind))
refs, err := s.Scan(ctx, gvr, fqn, wait) refs, err := s.Scan(ctx, gvr, fqn, wait)
if err != nil { 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 return
} }
select { select {
@ -108,7 +113,7 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) {
// ScanForSARefs scans cluster resources for serviceaccount refs. // ScanForSARefs scans cluster resources for serviceaccount refs.
func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) { func ScanForSARefs(ctx context.Context, f Factory) (Refs, error) {
defer func(t time.Time) { 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()) }(time.Now())
fqn, ok := ctx.Value(internal.KeyPath).(string) 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)) s.Init(f, client.NewGVR(kind))
refs, err := s.ScanSA(ctx, fqn, wait) refs, err := s.ScanSA(ctx, fqn, wait)
if err != nil { 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 return
} }
select { select {

View File

@ -5,10 +5,11 @@ package dao
import ( import (
"context" "context"
"log/slog"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -53,7 +54,7 @@ func (c *Context) List(_ context.Context, _ string) ([]runtime.Object, error) {
func (c *Context) MustCurrentContextName() string { func (c *Context) MustCurrentContextName() string {
cl, err := c.config().CurrentContextName() cl, err := c.config().CurrentContextName()
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Fetching current context") slog.Error("Fetching current context", slogs.Error, err)
} }
return cl return cl
} }

View File

@ -7,10 +7,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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: case SecGVR:
found, err := hasSecret(c.Factory, &cj.Spec.JobTemplate.Spec.Template.Spec, cj.Namespace, n, wait) found, err := hasSecret(c.Factory, &cj.Spec.JobTemplate.Spec.Template.Spec, cj.Namespace, n, wait)
if err != nil { 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 continue
} }
if !found { if !found {

View File

@ -4,8 +4,10 @@
package dao package dao
import ( import (
"log/slog"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"k8s.io/kubectl/pkg/describe" "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} mapper := RestMapper{Connection: c}
m, err := mapper.ToRESTMapper() m, err := mapper.ToRESTMapper()
if err != nil { 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 return "", err
} }
gvk, err := m.KindFor(gvr.GVR()) gvk, err := m.KindFor(gvr.GVR())
if err != nil { 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 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) mapping, err := mapper.ResourceFor(gvr.AsResourceName(), gvk.Kind)
if err != nil { 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 return "", err
} }
d, err := describe.Describer(c.Config().Flags(), mapping) d, err := describe.Describer(c.Config().Flags(), mapping)
if err != nil { 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 return "", err
} }

View File

@ -7,10 +7,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/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: case SecGVR:
found, err := hasSecret(d.Factory, &dp.Spec.Template.Spec, dp.Namespace, n, wait) found, err := hasSecret(d.Factory, &dp.Spec.Template.Spec, dp.Namespace, n, wait)
if err != nil { 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 continue
} }
if !found { if !found {

View File

@ -7,13 +7,14 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"strings" "strings"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/watch" "github.com/derailed/k9s/internal/watch"
"github.com/rs/zerolog/log"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/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: case SecGVR:
found, err := hasSecret(d.Factory, &ds.Spec.Template.Spec, ds.Namespace, n, wait) found, err := hasSecret(d.Factory, &ds.Spec.Template.Spec, ds.Namespace, n, wait)
if err != nil { 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 continue
} }
if !found { if !found {

View File

@ -6,11 +6,12 @@ package dao
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"os" "os"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render/helm" "github.com/derailed/k9s/internal/render/helm"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -163,6 +164,9 @@ func ensureHelmConfig(flags *genericclioptions.ConfigFlags, ns string) (*action.
return cfg, err return cfg, err
} }
func helmLogger(fmt string, args ...interface{}) { func helmLogger(fmat string, args ...interface{}) {
log.Debug().Msgf("[Helm] "+fmt, args...) slog.Debug("Log",
slogs.Log, fmt.Sprintf(fmat, args...),
slogs.Subsys, "helm",
)
} }

View File

@ -7,10 +7,11 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"math" "math"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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 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 return "", false
} }
@ -43,7 +47,7 @@ func GetDefaultContainer(m metav1.ObjectMeta, spec v1.PodSpec) (string, bool) {
func extractFQN(o runtime.Object) string { func extractFQN(o runtime.Object) string {
u, ok := o.(*unstructured.Unstructured) u, ok := o.(*unstructured.Unstructured)
if !ok { 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 return client.NA
} }
@ -93,7 +97,7 @@ func ToYAML(o runtime.Object, showManaged bool) (string, error) {
} }
err := p.PrintObj(o, &buff) err := p.PrintObj(o, &buff)
if err != nil { if err != nil {
log.Error().Msgf("Marshal Error %v", err) slog.Error("Marshal failed", slogs.Error, err)
return "", err return "", err
} }

View File

@ -7,11 +7,12 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "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: case SecGVR:
found, err := hasSecret(j.Factory, &job.Spec.Template.Spec, job.Namespace, n, wait) found, err := hasSecret(j.Factory, &job.Spec.Template.Spec, job.Namespace, n, wait)
if err != nil { if err != nil {
log.Warn().Err(err).Msgf("locate secret %q", fqn) slog.Warn("Locate secret failed",
slogs.FQN, fqn,
slogs.Error, err,
)
continue continue
} }
if !found { if !found {

View File

@ -5,16 +5,16 @@ package dao_test
import ( import (
"fmt" "fmt"
"log/slog"
"testing" "testing"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func init() { func init() {
zerolog.SetGlobalLevel(zerolog.FatalLevel) slog.SetDefault(slog.New(slog.DiscardHandler))
} }
func TestLogItemsFilter(t *testing.T) { func TestLogItemsFilter(t *testing.T) {

View File

@ -8,11 +8,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log/slog"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
@ -37,16 +38,23 @@ type Node struct {
} }
// ToggleCordon toggles cordon/uncordon a node. // ToggleCordon toggles cordon/uncordon a node.
func (n *Node) ToggleCordon(path string, cordon bool) error { func (n *Node) ToggleCordon(fqn string, cordon bool) error {
log.Debug().Msgf("CORDON %q::%t -- %q", path, cordon, n.gvr.GVK()) slog.Debug("Toggle cordon on node",
o, err := FetchNode(context.Background(), n.Factory, path) slogs.GVR, n.GVR(),
slogs.FQN, fqn,
slogs.Bool, cordon,
)
o, err := FetchNode(context.Background(), n.Factory, fqn)
if err != nil { if err != nil {
return err return err
} }
h, err := drain.NewCordonHelperFromRuntimeObject(o, scheme.Scheme, n.gvr.GVK()) h, err := drain.NewCordonHelperFromRuntimeObject(o, scheme.Scheme, n.gvr.GVK())
if err != nil { 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 return err
} }
@ -174,7 +182,10 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
if shouldCountPods { if shouldCountPods {
podCount, err = n.CountPods(name) podCount, err = n.CountPods(name)
if err != nil { 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{ res = append(res, &render.NodeWithMetrics{

View File

@ -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
// }

View File

@ -9,15 +9,16 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log/slog"
"sync" "sync"
"time" "time"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/watch" "github.com/derailed/k9s/internal/watch"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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: case SecGVR:
found, err := hasSecret(p.Factory, &pod.Spec, pod.Namespace, n, wait) found, err := hasSecret(p.Factory, &pod.Spec, pod.Namespace, n, wait)
if err != nil { if err != nil {
log.Warn().Err(err).Msgf("locate secret %q", fqn) slog.Warn("Locate secret failed",
slogs.FQN, fqn,
slogs.Error, err,
)
continue continue
} }
if !found { if !found {
@ -332,30 +336,33 @@ func tailLogs(ctx context.Context, logger Logger, opts *LogOptions) LogChan {
go func() { go func() {
defer wg.Done() defer wg.Done()
podOpts := opts.ToPodLogOptions() podOpts := opts.ToPodLogOptions()
var stream io.ReadCloser
for r := 0; r < logRetryCount; r++ { for r := 0; r < logRetryCount; r++ {
var e error
req, err := logger.Logs(opts.Path, podOpts) req, err := logger.Logs(opts.Path, podOpts)
if err == nil { if err == nil {
// This call will block if nothing is in the stream!! // 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) wg.Add(1)
go readLogs(ctx, &wg, stream, out, opts) go readLogs(ctx, &wg, stream, out, opts)
return 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 { } else {
e = fmt.Errorf("stream logs failed %w for %s", err, opts.Info()) slog.Error("Log request failed",
log.Error().Err(e).Msg("log-request") slogs.Container, opts.Info(),
slogs.Error, err,
)
} }
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
default: default:
if e != nil { if err != nil {
out <- opts.ToErrLogItem(e) out <- opts.ToErrLogItem(err)
} }
time.Sleep(logRetryWait) 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) { func readLogs(ctx context.Context, wg *sync.WaitGroup, stream io.ReadCloser, out chan<- *LogItem, opts *LogOptions) {
defer func() { defer func() {
if err := stream.Close(); err != nil { 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() wg.Done()
}() }()
log.Debug().Msgf(">>> LOG-READER PROCESSING %#v", opts) slog.Debug("Processing logs", slogs.Options, opts.Info())
r := bufio.NewReader(stream) r := bufio.NewReader(stream)
for { for {
var item *LogItem 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) { if errors.Is(err, io.EOF) {
e := fmt.Errorf("stream closed %w for %s", err, opts.Info()) e := fmt.Errorf("stream closed %w for %s", err, opts.Info())
item = opts.ToErrLogItem(e) 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 { } else {
e := fmt.Errorf("stream canceled %w for %s", err, opts.Info()) e := fmt.Errorf("stream canceled %w for %s", err, opts.Info())
item = opts.ToErrLogItem(e) item = opts.ToErrLogItem(e)
log.Warn().Err(e).Msg("log-reader canceled") slog.Warn("Log stream canceled")
} }
} }
select { select {
@ -488,7 +501,6 @@ func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) {
if err != nil { if err != nil {
continue continue
} }
log.Debug().Msgf("Pod status: %q", render.PodStatus(&pod))
switch render.PodStatus(&pod) { switch render.PodStatus(&pod) {
case render.PhaseCompleted: case render.PhaseCompleted:
fallthrough fallthrough
@ -506,16 +518,20 @@ func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) {
fallthrough fallthrough
case render.PhaseOOMKilled: case render.PhaseOOMKilled:
// !!BOZO!! Might need to bump timeout otherwise rev limit if too many?? // !!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) fqn := client.FQN(pod.Namespace, pod.Name)
slog.Debug("Sanitizing resource", slogs.FQN, fqn)
if err := p.Delete(ctx, fqn, nil, 0); err != nil { 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 return count, err
} }
count++ count++
} }
} }
log.Debug().Msgf("Sanitizer deleted %d pods", count) slog.Debug("Sanitizer deleted pods", slogs.Count, count)
return count, nil return count, nil
} }

View File

@ -22,7 +22,6 @@ package dao
// "github.com/derailed/popeye/pkg" // "github.com/derailed/popeye/pkg"
// "github.com/derailed/popeye/pkg/config" // "github.com/derailed/popeye/pkg/config"
// "github.com/derailed/popeye/types" // "github.com/derailed/popeye/types"
// "github.com/rs/zerolog/log"
// "k8s.io/apimachinery/pkg/runtime" // "k8s.io/apimachinery/pkg/runtime"
// ) // )

View File

@ -6,6 +6,7 @@ package dao
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"regexp" "regexp"
"strings" "strings"
@ -13,7 +14,7 @@ import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/render" "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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "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) config, err := config.NewBench(benchFile)
if err != nil { 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 ff, cc := p.getFactory().Forwarders(), config.Benchmarks.Containers
@ -92,7 +93,10 @@ func BenchConfigFor(benchFile, path string) config.BenchConfig {
def := config.DefaultBenchSpec() def := config.DefaultBenchSpec()
cust, err := config.NewBench(benchFile) cust, err := config.NewBench(benchFile)
if err != nil { 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 return def
} }
if b, ok := cust.Benchmarks.Containers[PodToKey(path)]; ok { if b, ok := cust.Benchmarks.Containers[PodToKey(path)]; ok {

View File

@ -12,7 +12,6 @@ import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/port" "github.com/derailed/k9s/internal/port"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -101,7 +100,6 @@ func (p *PortForwarder) Container() string {
// Stop terminates a port forward. // Stop terminates a port forward.
func (p *PortForwarder) Stop() { func (p *PortForwarder) Stop() {
log.Debug().Msgf("<<< Stopping PortForward %s", p.ID())
p.active = false p.active = false
if p.stopChan != nil { if p.stopChan != nil {
close(p.stopChan) close(p.stopChan)

View File

@ -6,11 +6,12 @@ package dao
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "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 return asRuntimeObjects(parseRules(client.ClusterScope, "-", role.Rules)), nil
} }
func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) { func (r *Rbac) loadClusterRole(fqn string) ([]runtime.Object, error) {
log.Debug().Msgf("LOAD-CR %q", path) slog.Debug("LOAD-CR", slogs.FQN, fqn)
o, err := r.getFactory().Get(crGVR, path, true, labels.Everything()) o, err := r.getFactory().Get(crGVR, fqn, true, labels.Everything())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -165,6 +166,9 @@ func parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Policies {
pp := make(render.Policies, 0, len(rules)) pp := make(render.Policies, 0, len(rules))
for _, rule := range rules { for _, rule := range rules {
for _, grp := range rule.APIGroups { for _, grp := range rule.APIGroups {
if grp == "" {
grp = "core"
}
for _, res := range rule.Resources { for _, res := range rule.Resources {
for _, na := range rule.ResourceNames { for _, na := range rule.ResourceNames {
pp = pp.Upsert(render.NewPolicyRes(ns, binding, FQN(res, na), grp, rule.Verbs)) pp = pp.Upsert(render.NewPolicyRes(ns, binding, FQN(res, na), grp, rule.Verbs))

View File

@ -6,11 +6,12 @@ package dao
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "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)) rows := make(render.Policies, 0, len(crs))
for _, cr := range crs { for _, cr := range crs {
if rbNs, ok := rbsMap["ClusterRole:"+cr.Name]; ok { 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)...) 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 { if _, ok := rbsMap["Role:"+ro.Name]; !ok {
continue 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)...) rows = append(rows, parseRules(ro.Namespace, "RO:"+ro.Name, ro.Rules)...)
} }

View File

@ -5,13 +5,14 @@ package dao
import ( import (
"fmt" "fmt"
"log/slog"
"slices" "slices"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"github.com/derailed/k9s/internal/client" "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" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "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] r, ok := m[gvr]
if !ok { if !ok {
r = new(Scaler) 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) r.Init(f, gvr)
@ -348,7 +349,7 @@ func loadRBAC(m ResourceMetas) {
func loadPreferred(f Factory, m ResourceMetas) error { func loadPreferred(f Factory, m ResourceMetas) error {
if f.Client() == nil || !f.Client().ConnectionOK() { 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 return nil
} }
@ -358,7 +359,7 @@ func loadPreferred(f Factory, m ResourceMetas) error {
} }
rr, err := dial.ServerPreferredResources() rr, err := dial.ServerPreferredResources()
if err != nil { 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 _, r := range rr {
for _, res := range r.APIResources { 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()) oo, err := f.List(crdGVR, client.ClusterScope, true, labels.Everything())
if err != nil { if err != nil {
log.Warn().Err(err).Msgf("Fail CRDs load") slog.Warn("CRDs load Fail", slogs.Error, err)
return return
} }
@ -408,7 +409,7 @@ func loadCRDs(f Factory, m ResourceMetas) {
var crd apiext.CustomResourceDefinition var crd apiext.CustomResourceDefinition
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crd) err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crd)
if err != nil { if err != nil {
log.Err(err).Msg("boom") slog.Error("CRD conversion failed", slogs.Error, err)
continue continue
} }
gvr, version, ok := newGVRFromCRD(&crd) gvr, version, ok := newGVRFromCRD(&crd)

View File

@ -5,14 +5,14 @@ package dao
import ( import (
"context" "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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
"k8s.io/client-go/restmapper" "k8s.io/client-go/restmapper"
"k8s.io/client-go/scale" "k8s.io/client-go/scale"
"github.com/derailed/k9s/internal/client"
) )
var _ Scalable = (*Scaler)(nil) var _ Scalable = (*Scaler)(nil)
@ -59,7 +59,10 @@ func (s *Scaler) Scale(ctx context.Context, path string, replicas int32) error {
return err 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 return nil
} }

View File

@ -7,11 +7,12 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"strings" "strings"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/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: case SecGVR:
found, err := hasSecret(s.Factory, &sts.Spec.Template.Spec, sts.Namespace, n, wait) found, err := hasSecret(s.Factory, &sts.Spec.Template.Spec, sts.Namespace, n, wait)
if err != nil { if err != nil {
log.Warn().Err(err).Msgf("locate secret %q", fqn) slog.Warn("Locate secret failed",
slogs.FQN, fqn,
slogs.Error, err,
)
continue continue
} }
if !found { if !found {

View File

@ -8,13 +8,14 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"strconv" "strconv"
"strings" "strings"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -206,12 +207,18 @@ func isReady(s string) bool {
} }
r, err := strconv.Atoi(tt[0]) r, err := strconv.Atoi(tt[0])
if err != nil { 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 return false
} }
c, err := strconv.Atoi(tt[1]) c, err := strconv.Atoi(tt[1])
if err != nil { 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 return false
} }

View File

@ -8,15 +8,16 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"io" "io"
"log/slog"
"net/http" "net/http"
"sync" "sync"
"time" "time"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/util/cache" "k8s.io/apimachinery/pkg/util/cache"
) )
@ -107,7 +108,7 @@ func (c *ClusterInfo) fetchK9sLatestRev() string {
latestRev, err := fetchLatestRev() latestRev, err := fetchLatestRev()
if err != nil { if err != nil {
log.Warn().Msgf("k9s latest rev fetch failed %s", err) slog.Warn("k9s latest rev fetch failed", slogs.Error, err)
} else { } else {
c.cache.Add(k9sLatestRevKey, latestRev, cacheExpiry) c.cache.Add(k9sLatestRevKey, latestRev, cacheExpiry)
} }
@ -206,7 +207,7 @@ func (c *ClusterInfo) fireNoMetaChanged(data ClusterMeta) {
// Helpers... // Helpers...
func fetchLatestRev() (string, error) { 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) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() defer cancel()
@ -234,7 +235,7 @@ func fetchLatestRev() (string, error) {
} }
if v, ok := m["name"]; ok { 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 return v.(string), nil
} }

View File

@ -4,15 +4,15 @@
package model_test package model_test
import ( import (
"log/slog"
"testing" "testing"
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func init() { func init() {
zerolog.SetGlobalLevel(zerolog.FatalLevel) slog.SetDefault(slog.New(slog.DiscardHandler))
} }
func TestClusterMetaDelta(t *testing.T) { func TestClusterMetaDelta(t *testing.T) {

View File

@ -6,6 +6,7 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"reflect" "reflect"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -15,7 +16,7 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
) )
@ -113,7 +114,7 @@ func (d *Describe) Watch(ctx context.Context) error {
} }
func (d *Describe) updater(ctx context.Context) { 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) backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval)
delay := defaultReaderRefreshRate delay := defaultReaderRefreshRate
@ -125,7 +126,7 @@ func (d *Describe) updater(ctx context.Context) {
if err := d.refresh(ctx); err != nil { if err := d.refresh(ctx); err != nil {
d.fireResourceFailed(err) d.fireResourceFailed(err)
if delay = backOff.NextBackOff(); delay == backoff.Stop { if delay = backOff.NextBackOff(); delay == backoff.Stop {
log.Error().Err(err).Msgf("Describe gave up!") slog.Error("Describe gave up!", slogs.Error, err)
return return
} }
} else { } else {
@ -138,13 +139,16 @@ func (d *Describe) updater(ctx context.Context) {
func (d *Describe) refresh(ctx context.Context) error { func (d *Describe) refresh(ctx context.Context) error {
if !atomic.CompareAndSwapInt32(&d.inUpdate, 0, 1) { if !atomic.CompareAndSwapInt32(&d.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...") slog.Debug("Dropping update...")
return nil return nil
} }
defer atomic.StoreInt32(&d.inUpdate, 0) defer atomic.StoreInt32(&d.inUpdate, 0)
if err := d.reconcile(ctx); err != nil { 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) d.fireResourceFailed(err)
return err return err
} }
@ -170,7 +174,7 @@ func (d *Describe) reconcile(ctx context.Context) error {
// Describe describes a given resource. // Describe describes a given resource.
func (d *Describe) describe(ctx context.Context, gvr client.GVR, path string) (string, error) { func (d *Describe) describe(ctx context.Context, gvr client.GVR, path string) (string, error) {
defer func(t time.Time) { 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()) }(time.Now())
meta, err := getMeta(ctx, gvr) meta, err := getMeta(ctx, gvr)

View File

@ -6,9 +6,10 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"time" "time"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
) )
const ( const (
@ -86,7 +87,7 @@ func (f *Flash) Infof(fmat string, args ...interface{}) {
// Warn displays a warning flash message. // Warn displays a warning flash message.
func (f *Flash) Warn(msg string) { func (f *Flash) Warn(msg string) {
log.Warn().Msg(msg) slog.Warn(msg)
f.SetMessage(FlashWarn, msg) f.SetMessage(FlashWarn, msg)
} }
@ -97,7 +98,7 @@ func (f *Flash) Warnf(fmat string, args ...interface{}) {
// Err displays an error flash message. // Err displays an error flash message.
func (f *Flash) Err(err error) { func (f *Flash) Err(err error) {
log.Error().Msg(err.Error()) slog.Error("Flash failed", slogs.Error, err)
f.SetMessage(FlashErr, err.Error()) f.SetMessage(FlashErr, err.Error())
} }
@ -110,7 +111,10 @@ func (f *Flash) Errf(fmat string, args ...interface{}) {
err = e 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...)) f.SetMessage(FlashErr, fmt.Sprintf(fmat, args...))
} }

View File

@ -6,6 +6,7 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"sync" "sync"
"time" "time"
@ -14,7 +15,7 @@ import (
"github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/color"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
) )
// LogsListener represents a log model listener. // LogsListener represents a log model listener.
@ -164,7 +165,7 @@ func (l *Log) Restart(ctx context.Context) {
// Start starts logging. // Start starts logging.
func (l *Log) Start(ctx context.Context) { func (l *Log) Start(ctx context.Context) {
if err := l.load(ctx); err != nil { 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) l.fireLogError(err)
} }
} }
@ -219,7 +220,6 @@ func (l *Log) cancel() {
defer l.mx.Unlock() defer l.mx.Unlock()
if l.cancelFn != nil { if l.cancelFn != nil {
l.cancelFn() l.cancelFn()
log.Debug().Msgf("!!! LOG-MODEL CANCELED !!!")
l.cancelFn = nil l.cancelFn = nil
} }
} }
@ -231,7 +231,7 @@ func (l *Log) load(ctx context.Context) error {
} }
loggable, ok := accessor.(dao.Loggable) loggable, ok := accessor.(dao.Loggable)
if !ok { if !ok {
return fmt.Errorf("Resource %s is not Loggable", l.gvr) return fmt.Errorf("resource %s is not Loggable", l.gvr)
} }
l.cancel() l.cancel()
@ -240,7 +240,7 @@ func (l *Log) load(ctx context.Context) error {
cc, err := loggable.TailLogs(ctx, l.logOptions) cc, err := loggable.TailLogs(ctx, l.logOptions)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Tail logs failed") slog.Error("Tail logs failed", slogs.Error, err)
l.cancel() l.cancel()
l.fireLogError(err) l.fireLogError(err)
} }
@ -288,8 +288,6 @@ func (l *Log) ToggleAllContainers(ctx context.Context) {
} }
func (l *Log) updateLogs(ctx context.Context, c dao.LogChan) { 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 { for {
select { select {
case item, ok := <-c: case item, ok := <-c:

View File

@ -6,13 +6,14 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/health" "github.com/derailed/k9s/internal/health"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -53,7 +54,7 @@ func (p *Pulse) Watch(ctx context.Context) {
} }
func (p *Pulse) updater(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 rate := initRefreshRate
for { for {
@ -77,13 +78,13 @@ func (p *Pulse) Refresh(ctx context.Context) {
func (p *Pulse) refresh(ctx context.Context) { func (p *Pulse) refresh(ctx context.Context) {
if !atomic.CompareAndSwapInt32(&p.inUpdate, 0, 1) { if !atomic.CompareAndSwapInt32(&p.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...") slog.Debug("Dropping update...")
return return
} }
defer atomic.StoreInt32(&p.inUpdate, 0) defer atomic.StoreInt32(&p.inUpdate, 0)
if err := p.reconcile(ctx); err != nil { if err := p.reconcile(ctx); err != nil {
log.Error().Err(err).Msg("Reconcile failed") slog.Error("Reconcile failed", slogs.Error, err)
p.firePulseFailed(err) p.firePulseFailed(err)
return return
} }

View File

@ -6,13 +6,14 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/health" "github.com/derailed/k9s/internal/health"
"github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/render" "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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -72,7 +73,7 @@ func (h *PulseHealth) checkMetrics(ctx context.Context) (health.Checks, error) {
nmx, err := dial.FetchNodesMetrics(ctx) nmx, err := dial.FetchNodesMetrics(ctx)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Fetching metrics") slog.Error("Fetching metrics", slogs.Error, err)
return nil, err return nil, err
} }

View File

@ -5,6 +5,7 @@ package model
import ( import (
"context" "context"
"log/slog"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
@ -13,7 +14,7 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
) )
@ -48,7 +49,7 @@ func getHelmHistDao() *dao.HelmHistory {
func getRevValues(path, rev string) []string { func getRevValues(path, rev string) []string {
vals, err := getHelmHistDao().GetValues(path, true) vals, err := getHelmHistDao().GetValues(path, true)
if err != nil { 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") 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) { 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) backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval)
delay := defaultReaderRefreshRate delay := defaultReaderRefreshRate
@ -145,7 +146,7 @@ func (v *RevValues) updater(ctx context.Context) {
if err := v.refresh(ctx); err != nil { if err := v.refresh(ctx); err != nil {
v.fireResourceFailed(err) v.fireResourceFailed(err)
if delay = backOff.NextBackOff(); delay == backoff.Stop { 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 return
} }
} else { } else {
@ -158,7 +159,7 @@ func (v *RevValues) updater(ctx context.Context) {
func (v *RevValues) refresh(ctx context.Context) error { func (v *RevValues) refresh(ctx context.Context) error {
if !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) { if !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...") slog.Debug("Dropping update...")
return nil return nil
} }
defer atomic.StoreInt32(&v.inUpdate, 0) defer atomic.StoreInt32(&v.inUpdate, 0)

View File

@ -4,9 +4,11 @@
package model package model
import ( import (
"fmt"
"log/slog"
"sync" "sync"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
) )
const ( const (
@ -188,9 +190,9 @@ func (s *Stack) notify(a StackAction, c Component) {
// Dump prints out the stack. // Dump prints out the stack.
func (s *Stack) Dump() { 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 { 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("------------------")
} }

View File

@ -5,17 +5,17 @@ package model_test
import ( import (
"context" "context"
"log/slog"
"testing" "testing"
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func init() { func init() {
zerolog.SetGlobalLevel(zerolog.FatalLevel) slog.SetDefault(slog.New(slog.DiscardHandler))
} }
func TestStackClear(t *testing.T) { func TestStackClear(t *testing.T) {

View File

@ -6,6 +6,7 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -16,7 +17,7 @@ import (
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/model1" "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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -63,7 +64,7 @@ func (t *Table) SetViewSetting(ctx context.Context, vs *config.ViewSetting) {
if ctx != context.Background() { if ctx != context.Background() {
if err := t.reconcile(ctx); err != nil { 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 rate = t.refreshRate
err := backoff.Retry(func() error { err := backoff.Retry(func() error {
if err := t.refresh(ctx); err != nil { 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 err
} }
return nil return nil
}, backoff.WithContext(bf, ctx)) }, backoff.WithContext(bf, ctx))
if err != nil { if err != nil {
log.Warn().Err(err).Msgf("reconciler exited") slog.Warn("Reconciler exited", slogs.Error, err)
t.fireTableLoadFailed(err) t.fireTableLoadFailed(err)
return return
} }
@ -224,12 +225,8 @@ func (t *Table) updater(ctx context.Context) {
} }
func (t *Table) refresh(ctx context.Context) error { 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) { if !atomic.CompareAndSwapInt32(&t.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...") slog.Debug("Dropping update...")
return nil return nil
} }
defer atomic.StoreInt32(&t.inUpdate, 0) defer atomic.StoreInt32(&t.inUpdate, 0)

View File

@ -6,6 +6,7 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"regexp" "regexp"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -15,8 +16,8 @@ import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/xray" "github.com/derailed/k9s/internal/xray"
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "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) { 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 rate := initTreeRefreshRate
for { for {
@ -179,13 +180,13 @@ func (t *Tree) updater(ctx context.Context) {
func (t *Tree) refresh(ctx context.Context) { func (t *Tree) refresh(ctx context.Context) {
if !atomic.CompareAndSwapInt32(&t.inUpdate, 0, 1) { if !atomic.CompareAndSwapInt32(&t.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...") slog.Debug("Dropping update...")
return return
} }
defer atomic.StoreInt32(&t.inUpdate, 0) defer atomic.StoreInt32(&t.inUpdate, 0)
if err := t.reconcile(ctx); err != nil { if err := t.reconcile(ctx); err != nil {
log.Error().Err(err).Msg("Reconcile failed") slog.Error("Reconcile failed", slogs.Error, err)
t.fireTreeLoadFailed(err) t.fireTreeLoadFailed(err)
return return
} }

View File

@ -6,6 +6,7 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
@ -14,7 +15,7 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
) )
@ -162,7 +163,7 @@ func (v *Values) Watch(ctx context.Context) error {
} }
func (v *Values) updater(ctx context.Context) { 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) backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval)
delay := defaultReaderRefreshRate delay := defaultReaderRefreshRate
@ -174,7 +175,7 @@ func (v *Values) updater(ctx context.Context) {
if err := v.refresh(ctx); err != nil { if err := v.refresh(ctx); err != nil {
v.fireResourceFailed(err) v.fireResourceFailed(err)
if delay = backOff.NextBackOff(); delay == backoff.Stop { 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 return
} }
} else { } else {
@ -187,7 +188,7 @@ func (v *Values) updater(ctx context.Context) {
func (v *Values) refresh(ctx context.Context) error { func (v *Values) refresh(ctx context.Context) error {
if !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) { if !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...") slog.Debug("Dropping update...")
return nil return nil
} }
defer atomic.StoreInt32(&v.inUpdate, 0) defer atomic.StoreInt32(&v.inUpdate, 0)

View File

@ -6,6 +6,7 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"reflect" "reflect"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -15,7 +16,7 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
) )
@ -121,7 +122,7 @@ func (y *YAML) Watch(ctx context.Context) error {
} }
func (y *YAML) updater(ctx context.Context) { 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) backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval)
delay := defaultReaderRefreshRate delay := defaultReaderRefreshRate
@ -133,7 +134,7 @@ func (y *YAML) updater(ctx context.Context) {
if err := y.refresh(ctx); err != nil { if err := y.refresh(ctx); err != nil {
y.fireResourceFailed(err) y.fireResourceFailed(err)
if delay = backOff.NextBackOff(); delay == backoff.Stop { if delay = backOff.NextBackOff(); delay == backoff.Stop {
log.Error().Err(err).Msgf("YAML gave up!") slog.Error("YAML gave up!", slogs.Error, err)
return return
} }
} else { } else {
@ -146,7 +147,7 @@ func (y *YAML) updater(ctx context.Context) {
func (y *YAML) refresh(ctx context.Context) error { func (y *YAML) refresh(ctx context.Context) error {
if !atomic.CompareAndSwapInt32(&y.inUpdate, 0, 1) { if !atomic.CompareAndSwapInt32(&y.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...") slog.Debug("Dropping update...", slogs.GVR, y.gvr)
return nil return nil
} }
defer atomic.StoreInt32(&y.inUpdate, 0) defer atomic.StoreInt32(&y.inUpdate, 0)

View File

@ -5,9 +5,10 @@ package model1
import ( import (
"fmt" "fmt"
"log/slog"
"reflect" "reflect"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
) )
const ageCol = "AGE" const ageCol = "AGE"
@ -16,6 +17,7 @@ type Attrs struct {
Align int Align int
Decorator DecoratorFunc Decorator DecoratorFunc
Wide bool Wide bool
Show bool
MX bool MX bool
MXC, MXM bool MXC, MXM bool
Time bool Time bool
@ -34,18 +36,20 @@ func (a Attrs) Merge(b Attrs) Attrs {
if a.Align == 0 { if a.Align == 0 {
a.Align = b.Align a.Align = b.Align
} }
if !a.Wide {
if !a.Hide {
a.Hide = b.Hide
}
if !a.Show && !a.Wide {
a.Wide = b.Wide a.Wide = b.Wide
} }
if !a.Time { if !a.Time {
a.Time = b.Time a.Time = b.Time
} }
if !a.Capacity { if !a.Capacity {
a.Capacity = b.Capacity a.Capacity = b.Capacity
} }
if !a.Hide {
a.Hide = b.Hide
}
return a return a
} }
@ -107,7 +111,7 @@ func (h Header) MapIndices(cols []string, wide bool) []int {
for _, col := range cols { for _, col := range cols {
idx, ok := h.IndexOf(col, true) idx, ok := h.IndexOf(col, true)
if !ok { 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{}{} ii, cc[idx] = append(ii, idx), struct{}{}
} }
@ -134,7 +138,7 @@ func (h Header) Customize(cols []string, wide bool) Header {
for _, c := range cols { for _, c := range cols {
idx, ok := h.IndexOf(c, true) idx, ok := h.IndexOf(c, true)
if !ok { 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}) cc = append(cc, HeaderColumn{Name: c})
continue continue
} }
@ -232,8 +236,8 @@ func (h Header) IndexOf(colName string, includeWide bool) (int, bool) {
// Dump for debugging. // Dump for debugging.
func (h Header) Dump() { func (h Header) Dump() {
log.Debug().Msgf("HEADER") slog.Debug("HEADER")
for i, c := range h { 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))
} }
} }

View File

@ -5,9 +5,8 @@ package model1
import ( import (
"fmt" "fmt"
"log/slog"
"sort" "sort"
"github.com/rs/zerolog/log"
) )
type ReRangeFn func(int, RowEvent) bool type ReRangeFn func(int, RowEvent) bool
@ -274,11 +273,11 @@ func (r *RowEvents) Sort(ns string, sortCol int, isDuration, numCol, isCapacity,
r.reindex() r.reindex()
} }
// For debguging... // For debugging...
func (re RowEvents) Dump(msg string) { func (re RowEvents) Dump(msg string) {
log.Debug().Msg(msg) slog.Debug("[DEBUG] RowEvents" + msg)
for _, r := range re.events { for _, r := range re.events {
log.Debug().Msgf("!!YO!! %#v", r) slog.Debug(fmt.Sprintf(" %#v", r))
} }
} }

View File

@ -7,6 +7,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
@ -14,7 +15,7 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -155,7 +156,7 @@ func (t *TableData) Filter(f FilterOpts) *TableData {
if err == nil { if err == nil {
td.rowEvents = rr td.rowEvents = rr
} else { } else {
log.Error().Err(err).Msg("rx filter failed") slog.Error("RX filter failed", slogs.Error, err)
} }
return td return td
@ -206,12 +207,11 @@ func (t *TableData) fuzzyFilter(q string) *RowEvents {
mm := fuzzy.Find(q, ss) mm := fuzzy.Find(q, ss)
rr := NewRowEvents(t.RowCount() / 2) rr := NewRowEvents(t.RowCount() / 2)
for _, m := range mm { for _, m := range mm {
re, ok := t.rowEvents.At(m.Index) if re, ok := t.rowEvents.At(m.Index); !ok {
if !ok { slog.Error("Unable to find event for index in fuzzfilter", slogs.Index, m.Index)
log.Error().Msgf("unable to find event for index in fuzzfilter: %d", m.Index) } else {
continue rr.Add(re)
} }
rr.Add(re)
} }
return rr return rr
@ -474,7 +474,10 @@ func (t *TableData) Delete(newKeys map[string]struct{}) {
}) })
for _, id := range victims { for _, id := range victims {
if err := t.rowEvents.Delete(id); err != nil { 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,
)
} }
} }
} }

View File

@ -4,16 +4,16 @@
package model1 package model1
import ( import (
"log/slog"
"testing" "testing"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func init() { func init() {
zerolog.SetGlobalLevel(zerolog.FatalLevel) slog.SetDefault(slog.New(slog.DiscardHandler))
} }
func TestTableDataComputeSortCol(t *testing.T) { func TestTableDataComputeSortCol(t *testing.T) {

View File

@ -8,6 +8,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"log/slog"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -16,11 +17,11 @@ import (
"time" "time"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/rakyll/hey/requester" "github.com/rakyll/hey/requester"
"github.com/rs/zerolog/log"
) )
const ( const (
@ -59,7 +60,7 @@ func (b *Benchmark) init(base, version string) error {
req.SetBasicAuth(b.config.Auth.User, b.config.Auth.Password) req.SetBasicAuth(b.config.Auth.User, b.config.Auth.Password)
} }
req.Header = b.config.HTTP.Headers 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() ua := req.UserAgent()
if ua == "" { if ua == "" {
@ -73,8 +74,7 @@ func (b *Benchmark) init(base, version string) error {
} }
req.Header.Set("User-Agent", ua) 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{ b.worker = &requester.Work{
Request: req, Request: req,
RequestBody: []byte(b.config.HTTP.Body), RequestBody: []byte(b.config.HTTP.Body),
@ -108,7 +108,10 @@ func (b *Benchmark) Canceled() bool {
// Run starts a benchmark. // Run starts a benchmark.
func (b *Benchmark) Run(cluster, context string, done func()) { 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) buff := new(bytes.Buffer)
b.worker.Writer = buff b.worker.Writer = buff
// this call will block until the benchmark is complete or times out. // 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() b.worker.Stop()
if buff.Len() > 0 { if buff.Len() > 0 {
if err := b.save(cluster, context, buff); err != nil { if err := b.save(cluster, context, buff); err != nil {
log.Error().Err(err).Msg("Saving Benchmark") slog.Error("Saving Benchmark", slogs.Error, err)
} }
} }
done() done()
@ -141,7 +144,10 @@ func (b *Benchmark) save(cluster, context string, r io.Reader) error {
} }
defer func() { defer func() {
if e := f.Close(); e != nil { 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 { if _, err = io.Copy(f, r); err != nil {

View File

@ -5,12 +5,16 @@ package port
import ( import (
"fmt" "fmt"
"log/slog"
"net" "net"
"github.com/derailed/k9s/internal/slogs"
) )
// PortTunnels represents a collection of tunnels. // PortTunnels represents a collection of tunnels.
type PortTunnels []PortTunnel type PortTunnels []PortTunnel
// CheckAvailable checks if all port tunnels are available.
func (t PortTunnels) CheckAvailable() error { func (t PortTunnels) CheckAvailable() error {
for _, pt := range t { for _, pt := range t {
if !IsPortFree(pt) { if !IsPortFree(pt) {
@ -26,6 +30,7 @@ type PortTunnel struct {
Address, Container, LocalPort, ContainerPort string Address, Container, LocalPort, ContainerPort string
} }
// NewPortTunnel returns a new instance.
func NewPortTunnel(a, co, lp, cp string) PortTunnel { func NewPortTunnel(a, co, lp, cp string) PortTunnel {
return PortTunnel{ return PortTunnel{
Address: a, Address: a,
@ -45,13 +50,17 @@ func (t PortTunnel) PortMap() string {
if t.LocalPort == "" { if t.LocalPort == "" {
t.LocalPort = t.ContainerPort t.LocalPort = t.ContainerPort
} }
return 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 { func IsPortFree(t PortTunnel) bool {
s, err := net.Listen("tcp", fmt.Sprintf("%s:%s", t.Address, t.LocalPort)) s, err := net.Listen("tcp", fmt.Sprintf("%s:%s", t.Address, t.LocalPort))
if err != nil { if err != nil {
slog.Warn("Port is not available", slogs.Port, t.LocalPort, slogs.Address, t.Address)
return false return false
} }
return s.Close() == nil return s.Close() == nil
} }

View File

@ -5,6 +5,7 @@ package render
import ( import (
"fmt" "fmt"
"slices"
"strings" "strings"
"github.com/derailed/k9s/internal/client" "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) return fmt.Errorf("expected AliasRes, but got %T", o)
} }
slices.Sort(a.Aliases)
gvr := client.NewGVR(a.GVR) gvr := client.NewGVR(a.GVR)
r.ID = gvr.String() r.ID = gvr.String()
r.Fields = append(r.Fields, r.Fields = append(r.Fields,
gvr.R(), gvr.R(),
gvr.G(), gvr.G(),
gvr.V(), gvr.V(),
strings.Join(a.Aliases, ","), strings.Join(a.Aliases, " "),
) )
return nil return nil

View File

@ -76,7 +76,7 @@ func TestAliasRender(t *testing.T) {
assert.Nil(t, a.Render(o, "fred/v1/blee", &r)) assert.Nil(t, a.Render(o, "fred/v1/blee", &r))
assert.Equal(t, model1.Row{ assert.Equal(t, model1.Row{
ID: "fred/v1/blee", ID: "fred/v1/blee",
Fields: model1.Fields{"blee", "fred", "v1", "a,b,c"}, Fields: model1.Fields{"blee", "fred", "v1", "a b c"},
}, r) }, r)
} }

View File

@ -4,9 +4,11 @@
package render package render
import ( import (
"log/slog"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/model1"
"github.com/rs/zerolog/log" "github.com/derailed/k9s/internal/slogs"
) )
// DecoratorFunc decorates a string. // DecoratorFunc decorates a string.
@ -48,7 +50,7 @@ func (b *Base) SetViewSetting(vs *config.ViewSetting) {
} }
specs, err := NewColsSpecs(cols...).parseSpecs() specs, err := NewColsSpecs(cols...).parseSpecs()
if err != nil { if err != nil {
log.Error().Err(err).Msg("unable to grok custom columns") slog.Error("Unable to grok custom columns", slogs.Error, err)
return return
} }
b.specs = specs b.specs = specs

View File

@ -4,16 +4,16 @@
package render package render
import ( import (
"log/slog"
"os" "os"
"testing" "testing"
"github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/model1"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func init() { func init() {
zerolog.SetGlobalLevel(zerolog.FatalLevel) slog.SetDefault(slog.New(slog.DiscardHandler))
} }
func TestAugmentRow(t *testing.T) { func TestAugmentRow(t *testing.T) {

View File

@ -87,8 +87,8 @@ func (Container) defaultHeader() model1.Header {
model1.HeaderColumn{Name: "PROBES(L:R:S)"}, model1.HeaderColumn{Name: "PROBES(L:R:S)"},
model1.HeaderColumn{Name: "CPU", Attrs: model1.Attrs{Align: tview.AlignRight, MX: 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: "MEM", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
model1.HeaderColumn{Name: "CPU/R:L", Attrs: model1.Attrs{Align: tview.AlignRight}}, model1.HeaderColumn{Name: "CPU/RL", Attrs: model1.Attrs{Align: tview.AlignRight}},
model1.HeaderColumn{Name: "MEM/R:L", 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/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: "%CPU/L", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},
model1.HeaderColumn{Name: "%MEM/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}}, model1.HeaderColumn{Name: "%MEM/R", Attrs: model1.Attrs{Align: tview.AlignRight, MX: true}},

View File

@ -5,11 +5,12 @@ package render
import ( import (
"fmt" "fmt"
"log/slog"
"os"
"strings" "strings"
"github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/model1"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/clientcmd/api" "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 { func (c *NamedContext) IsCurrentContext(n string) bool {
cl, err := c.Config.CurrentContextName() cl, err := c.Config.CurrentContextName()
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Fetching current context") slog.Error("Fail to retrieve current context. Exiting!")
return false os.Exit(1)
} }
return cl == n return cl == n
} }

View File

@ -6,11 +6,12 @@ package render
import ( import (
"errors" "errors"
"fmt" "fmt"
"log/slog"
"strings" "strings"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/model1" "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" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -84,7 +85,7 @@ func (c CustomResourceDefinition) defaultRow(raw *unstructured.Unstructured, r *
} }
} }
if len(versions) == 0 { 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) r.ID = client.MetaFQN(crd.ObjectMeta)

View File

@ -5,14 +5,16 @@ package render
import ( import (
"fmt" "fmt"
"log/slog"
"regexp" "regexp"
"github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/tview" "github.com/derailed/tview"
"k8s.io/kubectl/pkg/cmd/get" "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 type colAttr byte
@ -20,6 +22,7 @@ const (
number colAttr = 'N' number colAttr = 'N'
age colAttr = 'T' age colAttr = 'T'
wide colAttr = 'W' wide colAttr = 'W'
show colAttr = 'V'
alignLeft colAttr = 'L' alignLeft colAttr = 'L'
alignRight colAttr = 'R' alignRight colAttr = 'R'
hide colAttr = 'H' hide colAttr = 'H'
@ -32,6 +35,7 @@ type colAttrs struct {
mxm bool mxm bool
time bool time bool
wide bool wide bool
show bool
hide bool hide bool
capacity bool capacity bool
} }
@ -46,7 +50,9 @@ func newColFlags(flags string) colAttrs {
case hide: case hide:
c.hide = true c.hide = true
case wide: case wide:
c.wide = true c.wide, c.show = true, false
case show:
c.show, c.wide = true, false
case alignLeft: case alignLeft:
c.align = tview.AlignLeft c.align = tview.AlignLeft
case alignRight: case alignRight:
@ -55,6 +61,8 @@ func newColFlags(flags string) colAttrs {
c.time = true c.time = true
case number: case number:
c.capacity, c.align = true, tview.AlignRight 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 spec string
} }
// TAG:.spec.containers[0].image|split(":")|.[-1]|TW
func parse(s string) (colDef, error) { func parse(s string) (colDef, error) {
mm := fullRX.FindStringSubmatch(s) mm := fullRX.FindStringSubmatch(s)
if len(mm) == 4 { if len(mm) == 4 {
@ -95,6 +101,7 @@ func (c colDef) toHeaderCol() model1.HeaderColumn {
Attrs: model1.Attrs{ Attrs: model1.Attrs{
Align: c.align, Align: c.align,
Wide: c.wide, Wide: c.wide,
Show: c.show,
Time: c.time, Time: c.time,
MX: c.mx, MX: c.mx,
MXC: c.mxc, MXC: c.mxc,

Some files were not shown because too many files have changed in this diff Show More