ronantakizawa/codereview-bench huggingface dataset:
Repository: derailed/k9s
File: internal/dao/node.go (Go)

Code before:
	}
	if raw == nil {
		return nil, fmt.Errorf("unable to locate node %s", path)
	}

	var nmx *mv1beta1.NodeMetrics
	if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
		nmx, _ = client.DialMetrics(n.Client()).FetchNodeMetrics(ctx, path)
	}

	return &render.NodeWithMetrics{Raw: raw, MX: nmx}, nil
}

// List returns a collection of node resources.
func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
	oo, err := n.Resource.List(ctx, ns)
	if err != nil {
		return oo, err
	}

	var nmx client.NodesMetricsMap
	if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
		nmx, _ = client.DialMetrics(n.Client()).FetchNodesMetricsMap(ctx)
	}

	shouldCountPods := os.Getenv("K9S_DISABLE_POD_COUNTING") != "true"

	res := make([]runtime.Object, 0, len(oo))
	for _, o := range oo {
		u, ok := o.(*unstructured.Unstructured)
		if !ok {
			return res, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
		}

		fqn := extractFQN(o)
		_, name := client.Namespaced(fqn)
		podCount := -1
		if shouldCountPods {
			podCount, err = n.CountPods(name)
			if err != nil {
				log.Error().Err(err).Msgf("unable to get pods count for %s", name)
			}
		}
		res = append(res, &render.NodeWithMetrics{
			Raw:      u,
			MX:       nmx[name],
			PodCount: podCount,
		})
	}

	return res, nil

Review comment (suggestion):
Right not keen on putting env var. Best handled as a k9s config option as you've pointed out. 
We typically propagate these kind of configuration via the context that list takes as a param. 
Please take a peek at how we do this in the code ie dao.Alias and view.Alias as an example.


Code after:
	if raw == nil {
		return nil, fmt.Errorf("unable to locate node %s", path)
	}

	var nmx *mv1beta1.NodeMetrics
	if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
		nmx, _ = client.DialMetrics(n.Client()).FetchNodeMetrics(ctx, path)
	}

	return &render.NodeWithMetrics{Raw: raw, MX: nmx}, nil
}

// List returns a collection of node resources.
func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
	oo, err := n.Resource.List(ctx, ns)
	if err != nil {
		return oo, err
	}

	var nmx client.NodesMetricsMap
	if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
		nmx, _ = client.DialMetrics(n.Client()).FetchNodesMetricsMap(ctx)
	}

	shouldCountPods, _ := ctx.Value(internal.KeyPodCounting).(bool)

	res := make([]runtime.Object, 0, len(oo))
	for _, o := range oo {
		u, ok := o.(*unstructured.Unstructured)
		if !ok {
			return res, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
		}

		fqn := extractFQN(o)
		_, name := client.Namespaced(fqn)
		podCount := -1
		if shouldCountPods {
			podCount, err = n.CountPods(name)
			if err != nil {
				log.Error().Err(err).Msgf("unable to get pods count for %s", name)
			}
		}
		res = append(res, &render.NodeWithMetrics{
			Raw:      u,
			MX:       nmx[name],
			PodCount: podCount,
		})
	}

	return res, nil
}
Repository: derailed/k9s
File: internal/view/node.go (Go)

Code before:
	"github.com/derailed/k9s/internal/dao"
	"github.com/derailed/k9s/internal/ui"
	"github.com/derailed/k9s/internal/ui/dialog"
	"github.com/derailed/tcell/v2"
	"github.com/rs/zerolog/log"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Node represents a node view.
type Node struct {
	ResourceViewer
}

// NewNode returns a new node view.
func NewNode(gvr client.GVR) ResourceViewer {
	n := Node{
		ResourceViewer: NewBrowser(gvr),
	}
	n.AddBindKeysFn(n.bindKeys)
	n.GetTable().SetEnterFn(n.showPods)
	n.SetContextFn(n.aliasContext)

	return &n
}

func (n *Node) aliasContext(ctx context.Context) context.Context {
	return context.WithValue(ctx, internal.KeyPodCounting, !n.App().Config.K9s.DisablePodCounting)
}

func (n *Node) bindDangerousKeys(aa ui.KeyActions) {
	aa.Add(ui.KeyActions{
		ui.KeyC: ui.NewKeyAction("Cordon", n.toggleCordonCmd(true), true),
		ui.KeyU: ui.NewKeyAction("Uncordon", n.toggleCordonCmd(false), true),
		ui.KeyR: ui.NewKeyAction("Drain", n.drainCmd, true),
	})
	cl := n.App().Config.K9s.CurrentCluster
	if n.App().Config.K9s.Clusters[cl].FeatureGates.NodeShell {
		aa.Add(ui.KeyActions{
			ui.KeyS: ui.NewKeyAction("Shell", n.sshCmd, true),
		})
	}
}

func (n *Node) bindKeys(aa ui.KeyActions) {
	aa.Delete(ui.KeySpace, tcell.KeyCtrlSpace)

	if !n.App().Config.K9s.IsReadOnly() {
		n.bindDangerousKeys(aa)
	}

	aa.Add(ui.KeyActions{

Review comment (suggestion):
This method should be called something like `nodeContext` or the likes...

Code after:
	"github.com/derailed/k9s/internal/dao"
	"github.com/derailed/k9s/internal/ui"
	"github.com/derailed/k9s/internal/ui/dialog"
	"github.com/derailed/tcell/v2"
	"github.com/rs/zerolog/log"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Node represents a node view.
type Node struct {
	ResourceViewer
}

// NewNode returns a new node view.
func NewNode(gvr client.GVR) ResourceViewer {
	n := Node{
		ResourceViewer: NewBrowser(gvr),
	}
	n.AddBindKeysFn(n.bindKeys)
	n.GetTable().SetEnterFn(n.showPods)
	n.SetContextFn(n.nodeContext)

	return &n
}

func (n *Node) nodeContext(ctx context.Context) context.Context {
	return context.WithValue(ctx, internal.KeyPodCounting, !n.App().Config.K9s.DisablePodCounting)
}

func (n *Node) bindDangerousKeys(aa ui.KeyActions) {
	aa.Add(ui.KeyActions{
		ui.KeyC: ui.NewKeyAction("Cordon", n.toggleCordonCmd(true), true),
		ui.KeyU: ui.NewKeyAction("Uncordon", n.toggleCordonCmd(false), true),
		ui.KeyR: ui.NewKeyAction("Drain", n.drainCmd, true),
	})
	cl := n.App().Config.K9s.CurrentCluster
	if n.App().Config.K9s.Clusters[cl].FeatureGates.NodeShell {
		aa.Add(ui.KeyActions{
			ui.KeyS: ui.NewKeyAction("Shell", n.sshCmd, true),
		})
	}
}

func (n *Node) bindKeys(aa ui.KeyActions) {
	aa.Delete(ui.KeySpace, tcell.KeyCtrlSpace)

	if !n.App().Config.K9s.IsReadOnly() {
		n.bindDangerousKeys(aa)
	}

	aa.Add(ui.KeyActions{
Repository: derailed/k9s
File: internal/view/pod.go (Go)

Code before:
	var pod v1.Pod
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
	if err != nil {
		return nil, err
	}

	return &pod, nil
}

func podIsRunning(f dao.Factory, path string) bool {
	po, err := fetchPod(f, path)
	if err != nil {
		log.Error().Err(err).Msg("unable to fetch pod")
		return false
	}

	var re render.Pod
	return re.Phase(po) == render.Running
}

func getPodOS(f dao.Factory, fqn string) (string, error) {
	po, err := fetchPod(f, fqn)
	if err != nil {
		return "", err
	}
	if podOS, ok := po.Spec.NodeSelector[osBetaSelector]; ok {
		return podOS, nil
	}
	if podOS, ok := po.Spec.NodeSelector[osSelector]; ok {
		return podOS, nil
	}

	if len(po.Spec.NodeName) != 0 {
		node, err := dao.FetchNode(context.Background(), f, po.Spec.NodeName)
		if err != nil {
			return "", err
		}

		if nodeOS, ok := node.Labels[osBetaSelector]; ok {
			return nodeOS, nil
		}
		if nodeOS, ok := node.Labels[osSelector]; ok {
			return nodeOS, nil
		}
	}

	return "", fmt.Errorf("no os information available")
}

func resourceSorters(t *Table) ui.KeyActions {
	return ui.KeyActions{

Review comment (refactor):
I think we could use a helper here i.e `osFromSelector` or the like so that we can reuse for both pod nodeselector and node labels. 

Code after:
	// put the default container as the first entry
	defaultContainer, hasDefaultContainer := dao.GetDefaultContainer(meta, spec)
	if hasDefaultContainer {
		nn = append(nn, defaultContainer)
	}

	for _, c := range spec.Containers {
		if !hasDefaultContainer || c.Name != defaultContainer {
			nn = append(nn, c.Name)
		}
	}
	if !allContainers {
		return nn
	}
	for _, c := range spec.InitContainers {
		nn = append(nn, c.Name)
	}
	for _, c := range spec.EphemeralContainers {
		nn = append(nn, c.Name)
	}

	return nn
}

func fetchPod(f dao.Factory, path string) (*v1.Pod, error) {
	o, err := f.Get("v1/pods", path, true, labels.Everything())
	if err != nil {
		return nil, err
	}

	var pod v1.Pod
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
	if err != nil {
		return nil, err
	}

	return &pod, nil
}

func podIsRunning(f dao.Factory, path string) bool {
	po, err := fetchPod(f, path)
	if err != nil {
		log.Error().Err(err).Msg("unable to fetch pod")
		return false
	}

	var re render.Pod
	return re.Phase(po) == render.Running
}

func getPodOS(f dao.Factory, fqn string) (string, error) {
Repository: derailed/k9s
File: internal/view/app.go (Go)

Code before:
	cmds := strings.Split(command, " ")
	if len(cmds[0]) == 0 || len(cmds) != 2 {
		return nil
	}

	var suggests []string
	switch strings.ToLower(cmds[0]) {
	case "cow", "q", "q!", "qa", "Q", "quit", "?", "h", "help", "a", "alias", "x", "xray", "dir":
		return nil // ignore special commands
	case "ctx", "context", "contexts":
		ctxs, err := a.factory.Client().Config().Contexts()
		if err != nil {
			log.Error().Err(err).Msg("failed to list contexts")
			return nil
		}

		for contextName := range ctxs {
			if suggest, ok := shouldAddSuggest(cmds[1], contextName); ok {
				suggests = append(suggests, suggest)
			}
		}
	default:
		nss, err := a.factory.Client().ValidNamespaces()
		if err != nil {
			log.Error().Err(err).Msg("failed to list nss")
			return nil
		}

		if suggest, ok := shouldAddSuggest(cmds[1], client.NamespaceAll); ok {
			suggests = append(suggests, suggest)
		}

		for _, ns := range nss {
			if suggest, ok := shouldAddSuggest(cmds[1], ns.Name); ok {
				suggests = append(suggests, suggest)
			}
		}
	}

	return suggests
}

func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {
	if k, ok := a.HasAction(ui.AsKey(evt)); ok && !a.Content.IsTopDialog() {
		return k.Action(evt)
	}

	return evt
}

func (a *App) bindKeys() {

Review comment (suggestion):
wouldn't make sense to limit only at the search bar and within the given namespace?
from UX perspective I think we should allow only the resource type from current screen (only deployments, namespaces, stateful sets) and not mix all together

might be objective on this one, but I find it more organised this way

Code after:
			}
		}

		entries = append(entries, suggestSubCommand(s, namespaceNames, contextNames)...)
		if len(entries) == 0 {
			return nil
		}
		entries.Sort()
		return
	}
}

func (a *App) namespaceNames() ([]string, error) {
	namespaces, err := a.factory.Client().ValidNamespaces()
	if err != nil {
		return nil, err
	}

	namespaceNames := make([]string, 0, len(namespaces))
	for _, namespace := range namespaces {
		namespaceNames = append(namespaceNames, namespace.Name)
	}
	return namespaceNames, nil
}

func (a *App) contextNames() ([]string, error) {
	contexts, err := a.factory.Client().Config().Contexts()
	if err != nil {
		return nil, err
	}

	contextNames := make([]string, 0, len(contexts))
	for ctxName := range contexts {
		contextNames = append(contextNames, ctxName)
	}
	return contextNames, nil
}

func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {
	if k, ok := a.HasAction(ui.AsKey(evt)); ok && !a.Content.IsTopDialog() {
		return k.Action(evt)
	}

	return evt
}

func (a *App) bindKeys() {
	a.AddActions(ui.KeyActions{
		ui.KeyShift9:   ui.NewSharedKeyAction("DumpGOR", a.dumpGOR, false),
		tcell.KeyCtrlE: ui.NewSharedKeyAction("ToggleHeader", a.toggleHeaderCmd, false),
		tcell.KeyCtrlG: ui.NewSharedKeyAction("toggleCrumbs", a.toggleCrumbsCmd, false),
Repository: derailed/k9s
File: internal/view/app.go (Go)

Code before:
			}
			return a.cmdHistory.List()
		}

		s = strings.ToLower(s)
		for _, k := range a.command.alias.Aliases.Keys() {
			if suggest, ok := shouldAddSuggest(s, k); ok {
				entries = append(entries, suggest)
			}
		}

		entries = append(entries, a.suggestSubCommand(s)...)
		if len(entries) == 0 {
			return nil
		}
		entries.Sort()
		return
	}
}

func (a *App) suggestSubCommand(command string) []string {
	cmds := strings.Split(command, " ")
	if len(cmds[0]) == 0 || len(cmds) != 2 {
		return nil
	}

	var suggests []string
	switch strings.ToLower(cmds[0]) {
	case "cow", "q", "q!", "qa", "Q", "quit", "?", "h", "help", "a", "alias", "x", "xray", "dir":
		return nil // ignore special commands
	case "ctx", "context", "contexts":
		ctxs, err := a.factory.Client().Config().Contexts()
		if err != nil {
			log.Error().Err(err).Msg("failed to list contexts")
			return nil
		}

		for contextName := range ctxs {
			if suggest, ok := shouldAddSuggest(cmds[1], contextName); ok {
				suggests = append(suggests, suggest)
			}
		}
	default:
		nss, err := a.factory.Client().ValidNamespaces()
		if err != nil {
			log.Error().Err(err).Msg("failed to list nss")
			return nil
		}

		if suggest, ok := shouldAddSuggest(cmds[1], client.NamespaceAll); ok {
			suggests = append(suggests, suggest)

Review comment (refactor):
I have several suggestions here:
1. I think it might be a good idea to use strings.Fields instead to pick up potential typo ie `po   fred`
2. Best to make this call a helper and take in dependencies aka context, ns maps. So we don't have to refetch as there could be lots there. I think we should provide apis ie ContextNames and NamespaceNames which could return a map of names??
3. Would be great to put some tests on this call.

Code after:
	if err != nil {
		log.Error().Err(err).Msg("failed to list namespaces")
	}

	contextNames, err := a.contextNames()
	if err != nil {
		log.Error().Err(err).Msg("failed to list contexts")
	}

	return func(s string) (entries sort.StringSlice) {
		if s == "" {
			if a.cmdHistory.Empty() {
				return
			}
			return a.cmdHistory.List()
		}

		s = strings.ToLower(s)
		for _, k := range a.command.alias.Aliases.Keys() {
			if suggest, ok := shouldAddSuggest(s, k); ok {
				entries = append(entries, suggest)
			}
		}

		entries = append(entries, suggestSubCommand(s, namespaceNames, contextNames)...)
		if len(entries) == 0 {
			return nil
		}
		entries.Sort()
		return
	}
}

func (a *App) namespaceNames() ([]string, error) {
	namespaces, err := a.factory.Client().ValidNamespaces()
	if err != nil {
		return nil, err
	}

	namespaceNames := make([]string, 0, len(namespaces))
	for _, namespace := range namespaces {
		namespaceNames = append(namespaceNames, namespace.Name)
	}
	return namespaceNames, nil
}

func (a *App) contextNames() ([]string, error) {
	contexts, err := a.factory.Client().Config().Contexts()
	if err != nil {
		return nil, err
	}
Repository: derailed/k9s
File: internal/view/app.go (Go)

Code before:
			if suggest, ok := shouldAddSuggest(s, k); ok {
				entries = append(entries, suggest)
			}
		}

		entries = append(entries, a.suggestSubCommand(s)...)
		if len(entries) == 0 {
			return nil
		}
		entries.Sort()
		return
	}
}

func (a *App) suggestSubCommand(command string) []string {
	cmds := strings.Split(command, " ")
	if len(cmds[0]) == 0 || len(cmds) != 2 {
		return nil
	}

	var suggests []string
	switch strings.ToLower(cmds[0]) {
	case "cow", "q", "q!", "qa", "Q", "quit", "?", "h", "help", "a", "alias", "x", "xray", "dir":
		return nil // ignore special commands
	case "ctx", "context", "contexts":
		ctxs, err := a.factory.Client().Config().Contexts()
		if err != nil {
			log.Error().Err(err).Msg("failed to list contexts")
			return nil
		}

		for contextName := range ctxs {
			if suggest, ok := shouldAddSuggest(cmds[1], contextName); ok {
				suggests = append(suggests, suggest)
			}
		}
	default:
		nss, err := a.factory.Client().ValidNamespaces()
		if err != nil {
			log.Error().Err(err).Msg("failed to list nss")
			return nil
		}

		if suggest, ok := shouldAddSuggest(cmds[1], client.NamespaceAll); ok {
			suggests = append(suggests, suggest)
		}

		for _, ns := range nss {
			if suggest, ok := shouldAddSuggest(cmds[1], ns.Name); ok {
				suggests = append(suggests, suggest)
			}

Review comment (suggestion):
Don't think this is necessary to check for those since we only care about context or something with this shape aka `xxx ns` ie len = 2 or am I missing it?
I am also thinking we should intro a command type, so we can hang convenience like IsContext() or IsTuple()?? These could be reused in several places and also could open up listing multiple nss i.e `dp ns1 ns2` at some point in the future...

Code after:
		log.Error().Err(err).Msg("failed to list contexts")
	}

	return func(s string) (entries sort.StringSlice) {
		if s == "" {
			if a.cmdHistory.Empty() {
				return
			}
			return a.cmdHistory.List()
		}

		s = strings.ToLower(s)
		for _, k := range a.command.alias.Aliases.Keys() {
			if suggest, ok := shouldAddSuggest(s, k); ok {
				entries = append(entries, suggest)
			}
		}

		entries = append(entries, suggestSubCommand(s, namespaceNames, contextNames)...)
		if len(entries) == 0 {
			return nil
		}
		entries.Sort()
		return
	}
}

func (a *App) namespaceNames() ([]string, error) {
	namespaces, err := a.factory.Client().ValidNamespaces()
	if err != nil {
		return nil, err
	}

	namespaceNames := make([]string, 0, len(namespaces))
	for _, namespace := range namespaces {
		namespaceNames = append(namespaceNames, namespace.Name)
	}
	return namespaceNames, nil
}

func (a *App) contextNames() ([]string, error) {
	contexts, err := a.factory.Client().Config().Contexts()
	if err != nil {
		return nil, err
	}

	contextNames := make([]string, 0, len(contexts))
	for ctxName := range contexts {
		contextNames = append(contextNames, ctxName)
	}
	return contextNames, nil
Repository: derailed/k9s
File: internal/view/app.go (Go)

Code before:
		return
	}
}

func (a *App) suggestSubCommand(command string) []string {
	cmds := strings.Split(command, " ")
	if len(cmds[0]) == 0 || len(cmds) != 2 {
		return nil
	}

	var suggests []string
	switch strings.ToLower(cmds[0]) {
	case "cow", "q", "q!", "qa", "Q", "quit", "?", "h", "help", "a", "alias", "x", "xray", "dir":
		return nil // ignore special commands
	case "ctx", "context", "contexts":
		ctxs, err := a.factory.Client().Config().Contexts()
		if err != nil {
			log.Error().Err(err).Msg("failed to list contexts")
			return nil
		}

		for contextName := range ctxs {
			if suggest, ok := shouldAddSuggest(cmds[1], contextName); ok {
				suggests = append(suggests, suggest)
			}
		}
	default:
		nss, err := a.factory.Client().ValidNamespaces()
		if err != nil {
			log.Error().Err(err).Msg("failed to list nss")
			return nil
		}

		if suggest, ok := shouldAddSuggest(cmds[1], client.NamespaceAll); ok {
			suggests = append(suggests, suggest)
		}

		for _, ns := range nss {
			if suggest, ok := shouldAddSuggest(cmds[1], ns.Name); ok {
				suggests = append(suggests, suggest)
			}
		}
	}

	return suggests
}

func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {
	if k, ok := a.HasAction(ui.AsKey(evt)); ok && !a.Content.IsTopDialog() {
		return k.Action(evt)
	}

Review comment (suggestion):
Also it would be great to add fuzzy matching here (we do this with filter commands) so user could type `po ed` and suggest `fred, bleed`

Code after:

		s = strings.ToLower(s)
		for _, k := range a.command.alias.Aliases.Keys() {
			if suggest, ok := shouldAddSuggest(s, k); ok {
				entries = append(entries, suggest)
			}
		}

		entries = append(entries, suggestSubCommand(s, namespaceNames, contextNames)...)
		if len(entries) == 0 {
			return nil
		}
		entries.Sort()
		return
	}
}

func (a *App) namespaceNames() ([]string, error) {
	namespaces, err := a.factory.Client().ValidNamespaces()
	if err != nil {
		return nil, err
	}

	namespaceNames := make([]string, 0, len(namespaces))
	for _, namespace := range namespaces {
		namespaceNames = append(namespaceNames, namespace.Name)
	}
	return namespaceNames, nil
}

func (a *App) contextNames() ([]string, error) {
	contexts, err := a.factory.Client().Config().Contexts()
	if err != nil {
		return nil, err
	}

	contextNames := make([]string, 0, len(contexts))
	for ctxName := range contexts {
		contextNames = append(contextNames, ctxName)
	}
	return contextNames, nil
}

func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {
	if k, ok := a.HasAction(ui.AsKey(evt)); ok && !a.Content.IsTopDialog() {
		return k.Action(evt)
	}

	return evt
}
Repository: derailed/k9s
File: internal/config/shell_pod.go (Go)

Code before:
package config

import (
	"github.com/derailed/k9s/internal/client"
	v1 "k8s.io/api/core/v1"
)

const defaultDockerShellImage = "busybox:1.35.0"

// Limits represents resource limits.
type Limits map[v1.ResourceName]string

// ShellPod represents k9s shell configuration.
type ShellPod struct {
	Image            string                    `json:"image"`
	ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty" yaml:"imagePullSecrets,omitempty"`
	ImagePullPolicy  string                    `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"`
	Command          []string                  `json:"command,omitempty"`
	Args             []string                  `json:"args,omitempty"`
	Namespace        string                    `json:"namespace"`
	Limits           Limits                    `json:"resources,omitempty"`
	Labels           map[string]string         `json:"labels,omitempty"`
}

// NewShellPod returns a new instance.
func NewShellPod() *ShellPod {
	return &ShellPod{
		Image:     defaultDockerShellImage,
		Namespace: "default",
		Limits:    defaultLimits(),
	}
}

// Validate validates the configuration.
func (s *ShellPod) Validate(client.Connection, KubeSettings) {
	if s.Image == "" {
		s.Image = defaultDockerShellImage
	}
	if len(s.Limits) == 0 {
		s.Limits = defaultLimits()
	}
	if len(s.ImagePullSecrets) == 0 {

Review comment (suggestion):
Best to use the type v1.PullPolicy here vs string.

Code after:
package config

import (
	"github.com/derailed/k9s/internal/client"
	v1 "k8s.io/api/core/v1"
)

const defaultDockerShellImage = "busybox:1.35.0"

// Limits represents resource limits.
type Limits map[v1.ResourceName]string

// ShellPod represents k9s shell configuration.
type ShellPod struct {
	Image            string                    `json:"image"`
	ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty" yaml:"imagePullSecrets,omitempty"`
	ImagePullPolicy  v1.PullPolicy             `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"`
	Command          []string                  `json:"command,omitempty"`
	Args             []string                  `json:"args,omitempty"`
	Namespace        string                    `json:"namespace"`
	Limits           Limits                    `json:"resources,omitempty"`
	Labels           map[string]string         `json:"labels,omitempty"`
}

// NewShellPod returns a new instance.
func NewShellPod() *ShellPod {
	return &ShellPod{
		Image:     defaultDockerShellImage,
		Namespace: "default",
		Limits:    defaultLimits(),
	}
}

// Validate validates the configuration.
func (s *ShellPod) Validate(client.Connection, KubeSettings) {
	if s.Image == "" {
		s.Image = defaultDockerShellImage
	}
	if len(s.Limits) == 0 {
		s.Limits = defaultLimits()
	}
}