Skip to content

Commit afa4070

Browse files
committed
dynamically check if a resource is scalable
1 parent ac11361 commit afa4070

File tree

8 files changed

+84
-77
lines changed

8 files changed

+84
-77
lines changed

internal/dao/registry.go

Lines changed: 37 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ package dao
55

66
import (
77
"fmt"
8+
"slices"
89
"sort"
910
"strings"
1011
"sync"
1112

12-
"github.com/derailed/k9s/internal/client"
1313
"github.com/rs/zerolog/log"
14+
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1415
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1516
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1617
"k8s.io/apimachinery/pkg/labels"
1718
"k8s.io/apimachinery/pkg/runtime"
1819
"k8s.io/apimachinery/pkg/runtime/schema"
20+
21+
"github.com/derailed/k9s/internal/client"
1922
)
2023

2124
const (
@@ -95,10 +98,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
9598

9699
r, ok := m[gvr]
97100
if !ok {
98-
r = new(Generic)
99-
if MetaAccess.IsScalable(gvr) {
100-
r = new(Scaler)
101-
}
101+
r = new(Scaler)
102102
log.Debug().Msgf("No DAO registry entry for %q. Using generics!", gvr)
103103
}
104104
r.Init(f, gvr)
@@ -144,12 +144,7 @@ func (m *Meta) GVK2GVR(gv schema.GroupVersion, kind string) (client.GVR, bool, b
144144

145145
// IsCRD checks if resource represents a CRD
146146
func IsCRD(r metav1.APIResource) bool {
147-
for _, c := range r.Categories {
148-
if c == crdCat {
149-
return true
150-
}
151-
}
152-
return false
147+
return slices.Contains(r.Categories, crdCat)
153148
}
154149

155150
// MetaFor returns a resource metadata for a given gvr.
@@ -166,24 +161,19 @@ func (m *Meta) MetaFor(gvr client.GVR) (metav1.APIResource, error) {
166161

167162
// IsK8sMeta checks for non resource meta.
168163
func IsK8sMeta(m metav1.APIResource) bool {
169-
for _, c := range m.Categories {
170-
if c == k9sCat || c == helmCat {
171-
return false
172-
}
173-
}
174-
175-
return true
164+
return !slices.ContainsFunc(m.Categories, func(category string) bool {
165+
return category == k9sCat || category == helmCat
166+
})
176167
}
177168

178169
// IsK9sMeta checks for non resource meta.
179170
func IsK9sMeta(m metav1.APIResource) bool {
180-
for _, c := range m.Categories {
181-
if c == k9sCat {
182-
return true
183-
}
184-
}
171+
return slices.Contains(m.Categories, k9sCat)
172+
}
185173

186-
return false
174+
// IsScalable check if the resource can be scaled
175+
func IsScalable(m metav1.APIResource) bool {
176+
return slices.Contains(m.Categories, scaleCat)
187177
}
188178

189179
// LoadResources hydrates server preferred+CRDs resource metadata.
@@ -196,22 +186,12 @@ func (m *Meta) LoadResources(f Factory) error {
196186
return err
197187
}
198188
loadNonResource(m.resMetas)
199-
loadCRDs(f, m.resMetas)
200189

201-
return nil
202-
}
203-
204-
// IsScalable check if the resource can be scaled
205-
func (m *Meta) IsScalable(gvr client.GVR) bool {
206-
if meta, ok := m.resMetas[gvr]; ok {
207-
for _, c := range meta.Categories {
208-
if c == scaleCat {
209-
return true
210-
}
211-
}
212-
}
190+
// We've actually loaded all the CRDs in loadPreferred, and we're now adding
191+
// some additional CRD properties on top of that.
192+
go m.loadCRDs(f)
213193

214-
return false
194+
return nil
215195
}
216196

217197
// BOZO!! Need countermeasures for direct commands!
@@ -419,11 +399,16 @@ func isDeprecated(gvr client.GVR) bool {
419399
return ok
420400
}
421401

422-
func loadCRDs(f Factory, m ResourceMetas) {
402+
// loadCRDs Wait for the cache to synced and then add some additional properties to CRD.
403+
func (m *Meta) loadCRDs(f Factory) {
423404
if f.Client() == nil || !f.Client().ConnectionOK() {
424405
return
425406
}
426-
oo, err := f.List(crdGVR, client.ClusterScope, false, labels.Everything())
407+
408+
// we must block until all CRDs caches were synced
409+
f.WaitForCacheSync()
410+
411+
oo, err := f.List(crdGVR, client.ClusterScope, true, labels.Everything())
427412
if err != nil {
428413
log.Warn().Err(err).Msgf("Fail CRDs load")
429414
return
@@ -440,25 +425,28 @@ func loadCRDs(f Factory, m ResourceMetas) {
440425
var meta metav1.APIResource
441426
meta.Kind = crd.Spec.Names.Kind
442427
meta.Group = crd.Spec.Group
443-
meta.Name = crd.Name
428+
meta.Name = crd.Spec.Names.Plural
444429
meta.SingularName = crd.Spec.Names.Singular
445430
meta.ShortNames = crd.Spec.Names.ShortNames
446431
meta.Namespaced = crd.Spec.Scope == apiext.NamespaceScoped
447432
for _, v := range crd.Spec.Versions {
448433
if v.Served && !v.Deprecated {
434+
if v.Subresources != nil && v.Subresources.Scale != nil && !slices.Contains(meta.Categories, scaleCat) {
435+
meta.Categories = append(meta.Categories, scaleCat)
436+
}
437+
449438
meta.Version = v.Name
450439
break
451440
}
452441
}
453442

454-
// meta, errs := extractMeta(o)
455-
// if len(errs) > 0 {
456-
// log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs))
457-
// continue
458-
// }
459-
meta.Categories = append(meta.Categories, crdCat)
460-
gvr := client.NewGVRFromMeta(meta)
461-
m[gvr] = meta
443+
if !slices.Contains(meta.Categories, crdCat) {
444+
meta.Categories = append(meta.Categories, crdCat)
445+
}
446+
447+
m.mx.Lock()
448+
m.resMetas[client.NewGVRFromMeta(meta)] = meta
449+
m.mx.Unlock()
462450
}
463451
}
464452

internal/dao/scalable.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright Authors of K9s
3+
14
package dao
25

36
import (
47
"context"
58

6-
"github.com/derailed/k9s/internal/client"
79
"github.com/rs/zerolog/log"
810
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
911
"k8s.io/client-go/dynamic"
1012
"k8s.io/client-go/restmapper"
1113
"k8s.io/client-go/scale"
14+
15+
"github.com/derailed/k9s/internal/client"
1216
)
1317

1418
var _ Scalable = (*Scaler)(nil)

internal/dao/types.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import (
88
"io"
99
"time"
1010

11-
"github.com/derailed/k9s/internal/client"
12-
"github.com/derailed/k9s/internal/watch"
1311
v1 "k8s.io/api/core/v1"
1412
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1513
"k8s.io/apimachinery/pkg/labels"
1614
"k8s.io/apimachinery/pkg/runtime"
1715
"k8s.io/client-go/informers"
1816
restclient "k8s.io/client-go/rest"
17+
18+
"github.com/derailed/k9s/internal/client"
19+
"github.com/derailed/k9s/internal/watch"
1920
)
2021

2122
// ResourceMetas represents a collection of resource metadata.

internal/view/command.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import (
1111
"strings"
1212
"sync"
1313

14+
"github.com/rs/zerolog/log"
15+
1416
"github.com/derailed/k9s/internal/client"
1517
"github.com/derailed/k9s/internal/dao"
1618
"github.com/derailed/k9s/internal/model"
1719
"github.com/derailed/k9s/internal/view/cmd"
18-
"github.com/rs/zerolog/log"
1920
)
2021

2122
var (
@@ -288,12 +289,7 @@ func (c *Command) viewMetaFor(p *cmd.Interpreter) (client.GVR, *MetaViewer, erro
288289

289290
v := MetaViewer{
290291
viewerFn: func(gvr client.GVR) ResourceViewer {
291-
viewer := NewOwnerExtender(NewBrowser(gvr))
292-
if dao.MetaAccess.IsScalable(gvr) {
293-
viewer = NewScaleExtender(viewer)
294-
}
295-
296-
return viewer
292+
return NewScaleExtender(NewOwnerExtender(NewBrowser(gvr)))
297293
},
298294
}
299295
if mv, ok := customViewers[gvr]; ok {

internal/view/dp_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ package view_test
66
import (
77
"testing"
88

9+
"github.com/stretchr/testify/assert"
10+
911
"github.com/derailed/k9s/internal/client"
1012
"github.com/derailed/k9s/internal/view"
11-
"github.com/stretchr/testify/assert"
1213
)
1314

1415
func TestDeploy(t *testing.T) {

internal/view/scale_extender.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import (
99
"strconv"
1010
"strings"
1111

12-
"github.com/derailed/k9s/internal/config"
13-
14-
"github.com/derailed/k9s/internal/dao"
15-
"github.com/derailed/k9s/internal/ui"
1612
"github.com/derailed/tcell/v2"
1713
"github.com/derailed/tview"
1814
"github.com/rs/zerolog/log"
15+
16+
"github.com/derailed/k9s/internal/config"
17+
"github.com/derailed/k9s/internal/dao"
18+
"github.com/derailed/k9s/internal/ui"
1919
)
2020

2121
// ScaleExtender adds scaling extensions.
@@ -35,12 +35,21 @@ func (s *ScaleExtender) bindKeys(aa *ui.KeyActions) {
3535
if s.App().Config.K9s.IsReadOnly() {
3636
return
3737
}
38-
aa.Add(ui.KeyS, ui.NewKeyActionWithOpts("Scale", s.scaleCmd,
39-
ui.ActionOpts{
40-
Visible: true,
41-
Dangerous: true,
42-
},
43-
))
38+
39+
meta, err := dao.MetaAccess.MetaFor(s.GVR())
40+
if err != nil {
41+
log.Error().Err(err).Msgf("Unable to retrieve meta information for %s", s.GVR())
42+
return
43+
}
44+
45+
if !dao.IsCRD(meta) || dao.IsScalable(meta) {
46+
aa.Add(ui.KeyS, ui.NewKeyActionWithOpts("Scale", s.scaleCmd,
47+
ui.ActionOpts{
48+
Visible: true,
49+
Dangerous: true,
50+
},
51+
))
52+
}
4453
}
4554

4655
func (s *ScaleExtender) scaleCmd(evt *tcell.EventKey) *tcell.EventKey {
@@ -127,7 +136,7 @@ func (s *ScaleExtender) makeScaleForm(sels []string) (*tview.Form, error) {
127136
if len(sels) == 1 {
128137
// If the CRD resource supports scaling, then first try to
129138
// read the replicas directly from the CRD.
130-
if dao.MetaAccess.IsScalable(s.GVR()) {
139+
if meta, _ := dao.MetaAccess.MetaFor(s.GVR()); dao.IsScalable(meta) {
131140
replicas, err := s.replicasFromScaleSubresource(sels[0])
132141
if err == nil && len(replicas) != 0 {
133142
factor = replicas
@@ -169,7 +178,7 @@ func (s *ScaleExtender) makeScaleForm(sels []string) (*tview.Form, error) {
169178
return
170179
}
171180
}
172-
if len(sels) == 1 {
181+
if len(sels) != 1 {
173182
s.App().Flash().Infof("[%d] %s scaled successfully", len(sels), singularize(s.GVR().R()))
174183
} else {
175184
s.App().Flash().Infof("%s %s scaled successfully", s.GVR().R(), sels[0])

internal/view/sts_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ package view_test
66
import (
77
"testing"
88

9+
"github.com/stretchr/testify/assert"
10+
911
"github.com/derailed/k9s/internal/client"
1012
"github.com/derailed/k9s/internal/view"
11-
"github.com/stretchr/testify/assert"
1213
)
1314

1415
func TestStatefulSetNew(t *testing.T) {

internal/watch/factory.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@ import (
99
"sync"
1010
"time"
1111

12-
"github.com/derailed/k9s/internal/client"
1312
"github.com/rs/zerolog/log"
1413
v1 "k8s.io/api/core/v1"
1514
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1615
"k8s.io/apimachinery/pkg/labels"
1716
"k8s.io/apimachinery/pkg/runtime"
1817
di "k8s.io/client-go/dynamic/dynamicinformer"
1918
"k8s.io/client-go/informers"
19+
20+
"github.com/derailed/k9s/internal/client"
2021
)
2122

2223
const (
23-
defaultResync = 10 * time.Minute
24+
defaultResync = 10 * time.Minute
25+
defaultWaitTime = 250 * time.Millisecond
2426
)
2527

2628
// Factory tracks various resource informers.
@@ -142,8 +144,13 @@ func (f *Factory) waitForCacheSync(ns string) {
142144
return
143145
}
144146

145-
// we must block until all started informers' caches were synced
146-
_ = fac.WaitForCacheSync(f.stopChan)
147+
// Hang for a sec for the cache to refresh if still not done bail out!
148+
c := make(chan struct{})
149+
go func(c chan struct{}) {
150+
<-time.After(defaultWaitTime)
151+
close(c)
152+
}(c)
153+
_ = fac.WaitForCacheSync(c)
147154
}
148155

149156
// WaitForCacheSync waits for all factories to update their cache.

0 commit comments

Comments
 (0)