Skip to content

Commit 28af300

Browse files
author
Cornelius Weig
committed
Fall back to fetch resources individually when bulk fetching fails
1 parent 0d9bf01 commit 28af300

4 files changed

Lines changed: 93 additions & 44 deletions

File tree

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ require (
66
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
77
github.com/gogo/protobuf v1.2.0 // indirect
88
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect
9+
github.com/google/go-github v17.0.0+incompatible
10+
github.com/google/go-querystring v1.0.0 // indirect
911
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
1012
github.com/googleapis/gnostic v0.2.0 // indirect
1113
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
@@ -20,11 +22,11 @@ require (
2022
github.com/spf13/cobra v0.0.3
2123
github.com/spf13/viper v1.3.1
2224
github.com/stretchr/testify v1.2.2
23-
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd // indirect
24-
golang.org/x/oauth2 v0.0.0-20190212230446-3e8b2be13635
25+
golang.org/x/oauth2 v0.0.0-20190212230446-3e8b2be13635
2526
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
27+
golang.org/x/tools v0.0.0-20190221180947-9c8c5aeafa05 // indirect
2628
gopkg.in/inf.v0 v0.9.1 // indirect
27-
k8s.io/api v0.0.0-20181121191454-a61488babbd6
29+
k8s.io/api v0.0.0-20181121191454-a61488babbd6
2830
k8s.io/apimachinery v0.0.0-20190211022232-e355a776c090
2931
k8s.io/cli-runtime v0.0.0-20190202014047-491c94071cfa
3032
k8s.io/client-go v10.0.0+incompatible

go.sum

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
1919
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
2020
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
2121
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
22+
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
23+
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
24+
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
25+
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
2226
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
2327
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
2428
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
@@ -79,6 +83,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jY
7983
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
8084
golang.org/x/oauth2 v0.0.0-20190212230446-3e8b2be13635 h1:dOJmQysgY8iOBECuNp0vlKHWEtfiTnyjisEizRV3/4o=
8185
golang.org/x/oauth2 v0.0.0-20190212230446-3e8b2be13635/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
86+
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
8287
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
8388
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
8489
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -88,6 +93,8 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
8893
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
8994
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
9095
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
96+
golang.org/x/tools v0.0.0-20190221180947-9c8c5aeafa05 h1:MM2xcv2CSTP6dY4aMDyU7S9F99BKMwzVAA/l5bF39J8=
97+
golang.org/x/tools v0.0.0-20190221180947-9c8c5aeafa05/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
9198
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
9299
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
93100
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

pkg/client/client.go

Lines changed: 79 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@ import (
2222
"github.com/pkg/errors"
2323
"github.com/sirupsen/logrus"
2424
"github.com/spf13/viper"
25+
"k8s.io/api/core/v1"
2526
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627
"k8s.io/apimachinery/pkg/runtime"
2728
"k8s.io/apimachinery/pkg/runtime/schema"
2829
"k8s.io/apimachinery/pkg/util/sets"
2930
"k8s.io/cli-runtime/pkg/genericclioptions"
3031
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
31-
authv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
3232
"sort"
3333
"strings"
34+
"sync"
3435
)
3536

3637
// groupResource contains the APIGroup and APIResource
@@ -39,53 +40,26 @@ type groupResource struct {
3940
APIResource metav1.APIResource
4041
}
4142

42-
// TODO rework client, so that it does not fail without cluster admin rights
4343
func GetAllServerResources(flags *genericclioptions.ConfigFlags) (runtime.Object, error) {
4444
useCache := viper.GetBool(constants.FlagUseCache)
4545
scope := viper.GetString(constants.FlagScope)
4646

47-
grs, err := FetchAvailableGroupResources(useCache, scope, flags)
47+
grs, err := fetchAvailableGroupResources(useCache, scope, flags)
4848
if err != nil {
4949
return nil, errors.Wrap(err, "fetch available group resources")
5050
}
5151

52-
resources := extractRelevantResourceNames(grs, viper.GetStringSlice(constants.FlagExclude))
52+
resources := extractRelevantResources(grs, viper.GetStringSlice(constants.FlagExclude))
5353

54-
restConfig, _ := flags.ToRESTConfig()
55-
authClient, _ := authv1.NewForConfig(restConfig)
56-
allowed, err := CheckResourceAccessPar(authClient, resources)
57-
if err != nil {
58-
return nil, errors.Wrap(err, "check resource access")
54+
response, err := fetchResourcesBulk(flags, resources...)
55+
if err == nil {
56+
return response, nil
5957
}
6058

61-
allowedResourceTypes := ToResourceTypes(allowed)
62-
logrus.Debugf("Resources to fetch: %s", allowedResourceTypes)
63-
64-
request := resource.NewBuilder(flags).
65-
Unstructured().
66-
SelectAllParam(true).
67-
ResourceTypes(allowedResourceTypes...).
68-
Latest()
69-
70-
if ns := viper.GetString(constants.FlagNamespace); ns != "" {
71-
logrus.Debugf("Restricting to namespace %s", ns)
72-
request.NamespaceParam(ns)
73-
} else {
74-
request.AllNamespaces(true)
75-
}
76-
77-
response := request.Do()
78-
79-
if infos, err := response.Infos(); err != nil {
80-
return nil, errors.Wrap(err, "request resources")
81-
} else if len(infos) == 0 {
82-
return nil, fmt.Errorf("No resources found")
83-
}
84-
85-
return response.Object()
59+
return fetchResourcesIncremental(flags, resources...)
8660
}
8761

88-
func FetchAvailableGroupResources(cache bool, scope string, flags *genericclioptions.ConfigFlags) ([]groupResource, error) {
62+
func fetchAvailableGroupResources(cache bool, scope string, flags *genericclioptions.ConfigFlags) ([]groupResource, error) {
8963
client, err := flags.ToDiscoveryClient()
9064
if err != nil {
9165
return nil, errors.Wrap(err, "discovery client")
@@ -105,7 +79,7 @@ func FetchAvailableGroupResources(cache bool, scope string, flags *genericcliopt
10579
return nil, errors.Wrap(err, "get preferred resources")
10680
}
10781

108-
grs := []groupResource{}
82+
var grs []groupResource
10983
for _, list := range resources {
11084
if len(list.APIResources) == 0 {
11185
continue
@@ -138,11 +112,11 @@ func FetchAvailableGroupResources(cache bool, scope string, flags *genericcliopt
138112
return grs, nil
139113
}
140114

141-
func extractRelevantResourceNames(grs []groupResource, exclusions []string) []groupResource {
115+
func extractRelevantResources(grs []groupResource, exclusions []string) []groupResource {
142116
sort.Stable(sortableGroupResource(grs))
143117
forbidden := sets.NewString(exclusions...)
144118

145-
result := []groupResource{}
119+
var result []groupResource
146120
for _, r := range grs {
147121
name := r.fullName()
148122
resourceIds := r.APIResource.ShortNames
@@ -157,6 +131,71 @@ func extractRelevantResourceNames(grs []groupResource, exclusions []string) []gr
157131
return result
158132
}
159133

134+
// Fetches all objects in bulk. This is much faster than incrementally but may fail due to missing rights
135+
func fetchResourcesBulk(flags *genericclioptions.ConfigFlags, resourceTypes ...groupResource) (runtime.Object, error) {
136+
resourceNames := ToResourceTypes(resourceTypes)
137+
logrus.Debugf("Resources to fetch: %s", resourceNames)
138+
139+
request := resource.NewBuilder(flags).
140+
Unstructured().
141+
SelectAllParam(true).
142+
ResourceTypes(resourceNames...).
143+
Latest()
144+
if ns := viper.GetString(constants.FlagNamespace); ns != "" {
145+
request.NamespaceParam(ns)
146+
} else {
147+
request.AllNamespaces(true)
148+
}
149+
150+
return request.Do().Object()
151+
}
152+
153+
// Fetches all objects of the given resources one-by-one. This can be used as a fallback when fetchResourcesBulk fails.
154+
func fetchResourcesIncremental(flags *genericclioptions.ConfigFlags, rs ...groupResource) (runtime.Object, error) {
155+
logrus.Debug("Fetch resources incrementally")
156+
group := sync.WaitGroup{}
157+
158+
objsChan := make(chan runtime.Object)
159+
for _, r := range rs {
160+
r := r
161+
group.Add(1)
162+
go func(sendObj chan<- runtime.Object) {
163+
defer group.Done()
164+
if o, e := fetchResourcesBulk(flags, r); e != nil {
165+
logrus.Warnf("Cannot fetch: %s", e)
166+
} else {
167+
sendObj <- o
168+
}
169+
}(objsChan)
170+
}
171+
172+
var objs []runtime.Object
173+
go func(recvObj <-chan runtime.Object) {
174+
for o := range recvObj {
175+
objs = append(objs, o)
176+
}
177+
}(objsChan)
178+
179+
logrus.Debug("Wait for resource requests")
180+
group.Wait()
181+
logrus.Debug("Resource requests all done")
182+
close(objsChan)
183+
184+
if len(objs) == 0 {
185+
return nil, fmt.Errorf("Not authorized to list any resources. Try to narrow the scope with --namespace.")
186+
}
187+
188+
return toV1List(objs), nil
189+
}
190+
191+
func toV1List(objects []runtime.Object) runtime.Object {
192+
var raw []runtime.RawExtension
193+
for _, o := range objects {
194+
raw = append(raw, runtime.RawExtension{Object: o})
195+
}
196+
return &v1.List{Items: raw}
197+
}
198+
160199
func getResourceScope(scope string) (skipCluster, skipNamespace bool, err error) {
161200
switch scope {
162201
case "":
@@ -174,6 +213,7 @@ func getResourceScope(scope string) (skipCluster, skipNamespace bool, err error)
174213
return
175214
}
176215

216+
// Extracts the full name including APIGroup, e.g. 'deployment.apps'
177217
func (g groupResource) fullName() string {
178218
if g.APIGroup == "" {
179219
return g.APIResource.Name
@@ -184,7 +224,7 @@ func (g groupResource) fullName() string {
184224
type sortableGroupResource []groupResource
185225

186226
func ToResourceTypes(in []groupResource) []string {
187-
result := []string{}
227+
var result []string
188228
for _, r := range in {
189229
result = append(result, r.fullName())
190230
}

pkg/client/client_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ func TestExtractRelevantResourceNames(t *testing.T) {
7474
})
7575
}
7676

77-
names := extractRelevantResourceNames(grs, test.exclude)
78-
assert.Equal(t, test.expected, names)
77+
names := extractRelevantResources(grs, test.exclude)
78+
assert.Equal(t, test.expected, ToResourceTypes(names))
7979
})
8080
}
8181
}

0 commit comments

Comments
 (0)