diff --git a/README.md b/README.md
index f1004cb7..af360d9f 100644
--- a/README.md
+++ b/README.md
@@ -1088,12 +1088,12 @@ to make this project a reality!
## Meet The Core Team!
+If you have chops in GO and K8s and would like to offer your time to help maintain and enhance this project, please reach out to me.
+
* [Fernand Galiana](https://github.com/derailed)
*
fernand@imhotep.io
*
[@kitesurfer](https://twitter.com/kitesurfer?lang=en)
-* [Aleksei Romanenko](https://github.com/slimus)
-
We always enjoy hearing from folks who benefit from our work!
## Contributions Guideline
diff --git a/internal/client/client.go b/internal/client/client.go
index 626e69e2..be29c54c 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -545,16 +545,13 @@ func (a *APIClient) SwitchContext(name string) error {
if err := a.config.SwitchContext(name); err != nil {
return err
}
- if err := a.invalidateCache(); err != nil {
- return err
- }
a.reset()
ResetMetrics()
// Need reload to pick up any kubeconfig changes.
a.config = NewConfig(a.config.flags)
- return nil
+ return a.invalidateCache()
}
func (a *APIClient) reset() {
diff --git a/internal/config/data/helpers_test.go b/internal/config/data/helpers_test.go
index 0b41d432..db95f28a 100644
--- a/internal/config/data/helpers_test.go
+++ b/internal/config/data/helpers_test.go
@@ -63,7 +63,7 @@ func TestHelperInList(t *testing.T) {
func TestEnsureDirPathNone(t *testing.T) {
var mod os.FileMode = 0744
- dir := filepath.Join("/tmp", "fred")
+ dir := filepath.Join("/tmp", "k9s-test")
os.Remove(dir)
path := filepath.Join(dir, "duh.yaml")
diff --git a/internal/config/data/proxy.go b/internal/config/data/proxy.go
index 6bce7543..a5b53f5d 100644
--- a/internal/config/data/proxy.go
+++ b/internal/config/data/proxy.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of K9s
+
package data
// Proxy tracks a context's proxy configuration.
diff --git a/internal/config/helpers.go b/internal/config/helpers.go
index a62b3cd9..53627cfa 100644
--- a/internal/config/helpers.go
+++ b/internal/config/helpers.go
@@ -13,7 +13,8 @@ import (
v1 "k8s.io/api/core/v1"
)
-func isBoolSet(b *bool) bool {
+// IsBoolSet checks if a bool ptr is set.
+func IsBoolSet(b *bool) bool {
return b != nil && *b
}
@@ -70,8 +71,3 @@ func MustK9sUser() string {
}
return usr.Username
}
-
-// IsBoolSet checks if a bool prt is set.
-func IsBoolSet(b *bool) bool {
- return b != nil && *b
-}
diff --git a/internal/config/k9s.go b/internal/config/k9s.go
index df2e231a..5cc2ec41 100644
--- a/internal/config/k9s.go
+++ b/internal/config/k9s.go
@@ -292,7 +292,7 @@ func (k *K9s) Override(k9sFlags *Flags) {
// IsHeadless returns headless setting.
func (k *K9s) IsHeadless() bool {
- if isBoolSet(k.manualHeadless) {
+ if IsBoolSet(k.manualHeadless) {
return true
}
@@ -301,7 +301,7 @@ func (k *K9s) IsHeadless() bool {
// IsLogoless returns logoless setting.
func (k *K9s) IsLogoless() bool {
- if isBoolSet(k.manualLogoless) {
+ if IsBoolSet(k.manualLogoless) {
return true
}
@@ -310,7 +310,7 @@ func (k *K9s) IsLogoless() bool {
// IsCrumbsless returns crumbsless setting.
func (k *K9s) IsCrumbsless() bool {
- if isBoolSet(k.manualCrumbsless) {
+ if IsBoolSet(k.manualCrumbsless) {
return true
}
diff --git a/internal/dao/container.go b/internal/dao/container.go
index 0fd1c469..14b20344 100644
--- a/internal/dao/container.go
+++ b/internal/dao/container.go
@@ -116,7 +116,7 @@ func getContainerStatus(kind string, name string, status v1.PodStatus) *v1.Conta
func (c *Container) fetchPod(fqn string) (*v1.Pod, error) {
o, err := c.getFactory().Get("v1/pods", fqn, true, labels.Everything())
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to locate pod %q: %w", fqn, err)
}
var po v1.Pod
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po)
diff --git a/internal/dao/generic.go b/internal/dao/generic.go
index e0aff2b6..e7d95ccc 100644
--- a/internal/dao/generic.go
+++ b/internal/dao/generic.go
@@ -43,15 +43,12 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error)
ns = client.BlankNamespace
}
- var (
- ll *unstructured.UnstructuredList
- err error
- )
dial, err := g.dynClient()
if err != nil {
return nil, err
}
+ var ll *unstructured.UnstructuredList
if client.IsClusterScoped(ns) {
ll, err = dial.List(ctx, metav1.ListOptions{LabelSelector: labelSel})
} else {
@@ -71,12 +68,13 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error)
// Get returns a given resource.
func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error) {
- var opts metav1.GetOptions
ns, n := client.Namespaced(path)
dial, err := g.dynClient()
if err != nil {
return nil, err
}
+
+ var opts metav1.GetOptions
if client.IsClusterScoped(ns) {
return dial.Get(ctx, n, opts)
}
diff --git a/internal/dao/node.go b/internal/dao/node.go
index 08fea0b1..d348255b 100644
--- a/internal/dao/node.go
+++ b/internal/dao/node.go
@@ -257,7 +257,7 @@ func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) {
return nil, fmt.Errorf("user is not authorized to list nodes")
}
- o, err := f.Get("v1/nodes", client.FQN(client.ClusterScope, path), false, labels.Everything())
+ o, err := f.Get("v1/nodes", client.FQN(client.ClusterScope, path), true, labels.Everything())
if err != nil {
return nil, err
}
diff --git a/internal/dao/registry.go b/internal/dao/registry.go
index f512bc60..ac092c88 100644
--- a/internal/dao/registry.go
+++ b/internal/dao/registry.go
@@ -424,7 +424,8 @@ func loadCRDs(f Factory, m ResourceMetas) {
var meta metav1.APIResource
meta.Kind = crd.Spec.Names.Kind
meta.Group = crd.Spec.Group
- meta.Name = crd.Name
+ meta.Name = strings.TrimSuffix(crd.Name, "."+meta.Group)
+
meta.SingularName = crd.Spec.Names.Singular
meta.ShortNames = crd.Spec.Names.ShortNames
meta.Namespaced = crd.Spec.Scope == apiext.NamespaceScoped
@@ -435,11 +436,6 @@ func loadCRDs(f Factory, m ResourceMetas) {
}
}
- // meta, errs := extractMeta(o)
- // if len(errs) > 0 {
- // log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs))
- // continue
- // }
meta.Categories = append(meta.Categories, crdCat)
gvr := client.NewGVRFromMeta(meta)
m[gvr] = meta
diff --git a/internal/render/pod.go b/internal/render/pod.go
index dc3395a2..a501cad6 100644
--- a/internal/render/pod.go
+++ b/internal/render/pod.go
@@ -138,8 +138,8 @@ func (p Pod) Render(o interface{}, ns string, row *model1.Row) error {
}
c, r := gatherCoMX(&po.Spec, ccmx)
phase := p.Phase(&po)
- row.ID = client.MetaFQN(po.ObjectMeta)
+ row.ID = client.MetaFQN(po.ObjectMeta)
row.Fields = model1.Fields{
po.Namespace,
po.Name,
@@ -254,7 +254,7 @@ func cosLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) {
for _, c := range cc {
limits := c.Resources.Limits
if len(limits) == 0 {
- return resource.Quantity{}, resource.Quantity{}
+ continue
}
if limits.Cpu() != nil {
cpu.Add(*limits.Cpu())
@@ -263,6 +263,7 @@ func cosLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) {
mem.Add(*limits.Memory())
}
}
+
return *cpu, *mem
}
diff --git a/internal/render/rs.go b/internal/render/rs.go
index 85d5dbed..1046014f 100644
--- a/internal/render/rs.go
+++ b/internal/render/rs.go
@@ -6,6 +6,7 @@ package render
import (
"fmt"
"strconv"
+ "strings"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/model1"
@@ -34,7 +35,9 @@ func (ReplicaSet) Header(ns string) model1.Header {
model1.HeaderColumn{Name: "DESIRED", Align: tview.AlignRight},
model1.HeaderColumn{Name: "CURRENT", Align: tview.AlignRight},
model1.HeaderColumn{Name: "READY", Align: tview.AlignRight},
- model1.HeaderColumn{Name: "LABELS", Wide: true},
+ model1.HeaderColumn{Name: "CONTAINERS", Wide: true},
+ model1.HeaderColumn{Name: "IMAGES", Wide: true},
+ model1.HeaderColumn{Name: "SELECTOR", Wide: true},
model1.HeaderColumn{Name: "VALID", Wide: true},
model1.HeaderColumn{Name: "AGE", Time: true},
}
@@ -46,12 +49,21 @@ func (r ReplicaSet) Render(o interface{}, ns string, row *model1.Row) error {
if !ok {
return fmt.Errorf("expected ReplicaSet, but got %T", o)
}
+
var rs appsv1.ReplicaSet
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &rs)
if err != nil {
return err
}
+ var (
+ cc = rs.Spec.Template.Spec.Containers
+ cos, imgs = make([]string, 0, len(cc)), make([]string, 0, len(cc))
+ )
+ for _, co := range cc {
+ cos, imgs = append(cos, co.Name), append(imgs, co.Image)
+ }
+
row.ID = client.MetaFQN(rs.ObjectMeta)
row.Fields = model1.Fields{
rs.Namespace,
@@ -60,6 +72,8 @@ func (r ReplicaSet) Render(o interface{}, ns string, row *model1.Row) error {
strconv.Itoa(int(*rs.Spec.Replicas)),
strconv.Itoa(int(rs.Status.Replicas)),
strconv.Itoa(int(rs.Status.ReadyReplicas)),
+ strings.Join(cos, ","),
+ strings.Join(imgs, ","),
mapToStr(rs.Labels),
AsStatus(r.diagnose(rs)),
ToAge(rs.GetCreationTimestamp()),
diff --git a/internal/view/app.go b/internal/view/app.go
index 4ac7e7c2..72f9e028 100644
--- a/internal/view/app.go
+++ b/internal/view/app.go
@@ -530,7 +530,7 @@ func (a *App) Run() error {
})
}()
- if err := a.command.defaultCmd(); err != nil {
+ if err := a.command.defaultCmd(true); err != nil {
return err
}
a.SetRunning(true)
diff --git a/internal/view/command.go b/internal/view/command.go
index bb81826c..c57dec80 100644
--- a/internal/view/command.go
+++ b/internal/view/command.go
@@ -209,19 +209,24 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error {
return c.exec(p, gvr, co, clearStack)
}
-func (c *Command) defaultCmd() error {
+func (c *Command) defaultCmd(isRoot bool) error {
if c.app.Conn() == nil || !c.app.Conn().ConnectionOK() {
return c.run(cmd.NewInterpreter("context"), "", true)
}
+ defCmd := "pod"
+ if isRoot {
+ defCmd = "ctx"
+ }
p := cmd.NewInterpreter(c.app.Config.ActiveView())
if p.IsBlank() {
- return c.run(p.Reset("pod"), "", true)
+ return c.run(p.Reset(defCmd), "", true)
}
if err := c.run(p, "", true); err != nil {
- log.Error().Err(err).Msgf("Default run command failed %q", p.GetLine())
- return c.run(p.Reset("pod"), "", true)
+ p = p.Reset(defCmd)
+ log.Error().Err(fmt.Errorf("Command failed. Using default command: %s", p.GetLine()))
+ return c.run(p, "", true)
}
return nil
diff --git a/internal/view/log.go b/internal/view/log.go
index be01ed5a..d5682e78 100644
--- a/internal/view/log.go
+++ b/internal/view/log.go
@@ -393,6 +393,7 @@ func (l *Log) toggleAllContainers(evt *tcell.EventKey) *tcell.EventKey {
func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
if !l.logs.cmdBuff.IsActive() {
+ fmt.Fprintln(l.ansiWriter)
return evt
}
@@ -451,7 +452,7 @@ func (l *Log) clearCmd(*tcell.EventKey) *tcell.EventKey {
func (l *Log) markCmd(*tcell.EventKey) *tcell.EventKey {
_, _, w, _ := l.GetRect()
- fmt.Fprintf(l.ansiWriter, "\n[%s:-:b]%s[-:-:-]", l.app.Styles.Views().Log.FgColor.String(), strings.Repeat("─", w-4))
+ fmt.Fprintf(l.ansiWriter, "\n[%s:-:b]%s[-:-:-]", l.app.Styles.Views().Log.FgColor.String(), strings.Repeat("-", w-4))
l.follow = true
return nil
diff --git a/internal/view/pf.go b/internal/view/pf.go
index 246bd9ed..252ea533 100644
--- a/internal/view/pf.go
+++ b/internal/view/pf.go
@@ -20,8 +20,6 @@ import (
"github.com/rs/zerolog/log"
)
-const promptPage = "prompt"
-
// PortForward presents active portforward viewer.
type PortForward struct {
ResourceViewer
diff --git a/internal/watch/factory.go b/internal/watch/factory.go
index 6c896afa..eccbd38f 100644
--- a/internal/watch/factory.go
+++ b/internal/watch/factory.go
@@ -21,7 +21,7 @@ import (
const (
defaultResync = 10 * time.Minute
- defaultWaitTime = 250 * time.Millisecond
+ defaultWaitTime = 500 * time.Millisecond
)
// Factory tracks various resource informers.
@@ -72,13 +72,13 @@ func (f *Factory) Terminate() {
// List returns a resource collection.
func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]runtime.Object, error) {
+ if client.IsAllNamespace(ns) {
+ ns = client.BlankNamespace
+ }
inf, err := f.CanForResource(ns, gvr, client.ListAccess)
if err != nil {
return nil, err
}
- if client.IsAllNamespace(ns) {
- ns = client.BlankNamespace
- }
var oo []runtime.Object
if client.IsClusterScoped(ns) {
@@ -110,6 +110,10 @@ func (f *Factory) HasSynced(gvr, ns string) (bool, error) {
// Get retrieves a given resource.
func (f *Factory) Get(gvr, fqn string, wait bool, sel labels.Selector) (runtime.Object, error) {
ns, n := namespaced(fqn)
+ if client.IsAllNamespace(ns) {
+ ns = client.BlankNamespace
+ }
+
inf, err := f.CanForResource(ns, gvr, []string{client.GetVerb})
if err != nil {
return nil, err
@@ -128,6 +132,7 @@ func (f *Factory) Get(gvr, fqn string, wait bool, sel labels.Selector) (runtime.
if client.IsClusterScoped(ns) {
return inf.Lister().Get(n)
}
+
return inf.Lister().ByNamespace(ns).Get(n)
}