Skip to content

Commit 1b7829c

Browse files
committed
it works!
Signed-off-by: Ahmet Alp Balkan <[email protected]>
1 parent 2d35f63 commit 1b7829c

File tree

6 files changed

+178
-20
lines changed

6 files changed

+178
-20
lines changed

cmd/kubectl-tree/apis.go

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,77 @@ import (
88
"strings"
99
)
1010

11-
type resourceMap map[string][]schema.GroupVersionResource // names to apis binding
11+
type apiResource struct {
12+
r metav1.APIResource
13+
gv schema.GroupVersion
14+
}
1215

13-
func (rm resourceMap) lookup(s string) []schema.GroupVersionResource {
14-
return rm[strings.ToLower(s)]
16+
func (a apiResource) GroupVersionResource() schema.GroupVersionResource {
17+
return schema.GroupVersionResource{
18+
Group: a.gv.Group,
19+
Version: a.gv.Version,
20+
Resource: a.r.Name,
21+
}
1522
}
1623

17-
func fullAPIName(a schema.GroupVersionResource) string {
18-
return strings.Join([]string{a.Resource, a.Version, a.Group}, ".")
24+
type resourceNameLookup map[string][]apiResource
25+
26+
type resourceMap struct {
27+
list []apiResource
28+
m resourceNameLookup
29+
} // names to apis binding
30+
31+
func (rm *resourceMap) lookup(s string) []apiResource {
32+
return rm.m[strings.ToLower(s)]
33+
}
34+
35+
func (rm *resourceMap) resources() []apiResource { return rm.list }
36+
37+
func fullAPIName(a apiResource) string {
38+
sgv := a.GroupVersionResource()
39+
return strings.Join([]string{sgv.Resource, sgv.Version, sgv.Group}, ".")
1940
}
2041

21-
func buildAPILookup(client discovery.DiscoveryInterface) (resourceMap, error) {
42+
func buildAPILookup(client discovery.DiscoveryInterface) (*resourceMap, error) {
2243
resList, err := client.ServerPreferredResources()
2344
if err != nil {
2445
return nil, fmt.Errorf("failed to fetch api groups from kubernetes: %w", err)
2546
}
2647

27-
nameMap := make(resourceMap)
28-
48+
rm := &resourceMap{
49+
m: make(resourceNameLookup),
50+
}
2951
for _, group := range resList {
3052
gv, err := schema.ParseGroupVersion(group.GroupVersion)
3153
if err != nil {
3254
return nil, fmt.Errorf("%q cannot be parsed into groupversion: %w", group.GroupVersion, err)
3355
}
3456

3557
for _, apiRes := range group.APIResources {
36-
if len(apiRes.Verbs) == 0 {
58+
if !contains(apiRes.Verbs, "list") {
3759
continue
3860
}
3961

62+
v := apiResource{
63+
gv: gv,
64+
r: apiRes,
65+
}
4066
for _, name := range apiNames(apiRes, gv) {
41-
nameMap[name] = append(nameMap[name], schema.GroupVersionResource{
42-
Group: gv.Group,
43-
Version: gv.Version,
44-
Resource: apiRes.Name,
45-
})
67+
rm.m[name] = append(rm.m[name], v)
4668
}
69+
rm.list = append(rm.list, v)
70+
}
71+
}
72+
return rm, nil
73+
}
74+
75+
func contains(v []string, s string) bool {
76+
for _, vv := range v {
77+
if vv == s {
78+
return true
4779
}
4880
}
49-
return nameMap, nil
81+
return false
5082
}
5183

5284
// return all names that could refer to this APIResource

cmd/kubectl-tree/query.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
6+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
7+
"k8s.io/client-go/dynamic"
8+
"sync"
9+
)
10+
11+
func QueryResources(client dynamic.Interface, apis []apiResource) ([]unstructured.Unstructured, error) {
12+
var mu sync.Mutex
13+
var wg sync.WaitGroup
14+
var out []unstructured.Unstructured
15+
16+
var errResult error
17+
for _, api := range apis {
18+
wg.Add(1)
19+
go func(a apiResource) {
20+
defer wg.Done()
21+
v, err := queryAPI(client, a)
22+
if err != nil {
23+
errResult = err
24+
return
25+
}
26+
mu.Lock()
27+
out = append(out, v...)
28+
mu.Unlock()
29+
}(api)
30+
}
31+
wg.Wait()
32+
return out, errResult
33+
}
34+
35+
func queryAPI(client dynamic.Interface, api apiResource) ([]unstructured.Unstructured, error) {
36+
var out []unstructured.Unstructured
37+
38+
var next string
39+
for {
40+
resp, err := client.Resource(api.GroupVersionResource()).List(metav1.ListOptions{
41+
Limit: 250,
42+
Continue: next,
43+
})
44+
if err != nil {
45+
return nil, fmt.Errorf("listing resources failed (%s): %w", api.GroupVersionResource(), err)
46+
}
47+
out = append(out, resp.Items...)
48+
49+
fmt.Printf("found %d objects in %s (next=%s)\n", len(resp.Items), api.GroupVersionResource(), resp.GetContinue())
50+
next = resp.GetContinue()
51+
if next == "" {
52+
break
53+
}
54+
}
55+
return out, nil
56+
}

cmd/kubectl-tree/relationship.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
5+
"k8s.io/apimachinery/pkg/types"
6+
)
7+
8+
type objectDirectory struct {
9+
items map[types.UID]unstructured.Unstructured
10+
ownership map[types.UID]map[types.UID]bool
11+
}
12+
13+
func newObjectDirectory(objs []unstructured.Unstructured) objectDirectory {
14+
v := objectDirectory{
15+
items: make(map[types.UID]unstructured.Unstructured),
16+
ownership: make(map[types.UID]map[types.UID]bool),
17+
}
18+
for _, obj := range objs {
19+
v.items[obj.GetUID()] = obj
20+
for _, ownerRef := range obj.GetOwnerReferences() {
21+
if v.ownership[ownerRef.UID] == nil {
22+
v.ownership[ownerRef.UID] = make(map[types.UID]bool)
23+
}
24+
v.ownership[ownerRef.UID][obj.GetUID()] = true
25+
}
26+
}
27+
return v
28+
}
29+
30+
func (od objectDirectory) getObject(id types.UID) unstructured.Unstructured { return od.items[id] }
31+
32+
func (od objectDirectory) ownedBy(ownerID types.UID) []types.UID {
33+
var out []types.UID
34+
for k := range od.ownership[ownerID] {
35+
out = append(out, k)
36+
}
37+
return out
38+
}

cmd/kubectl-tree/rootcmd.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/spf13/cobra"
2121
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2222
"k8s.io/cli-runtime/pkg/genericclioptions"
23+
"os"
2324
"strings"
2425
)
2526

@@ -39,6 +40,8 @@ func run(cmd *cobra.Command, args []string) error {
3940
if err != nil {
4041
return err
4142
}
43+
restConfig.QPS = 1000
44+
restConfig.Burst = 1000
4245
dyn, err := dynamicClient(restConfig)
4346
if err != nil {
4447
return err
@@ -65,13 +68,26 @@ func run(cmd *cobra.Command, args []string) error {
6568
strings.Join(names, ", "))
6669
}
6770

68-
//namespace := "default" // TODO figure out how to use genericclioptions to read kubeconfig + cli opt
6971
fmt.Printf("kind=%#v name=%s\n", apiRes[0], name)
70-
result, err := dyn.Resource(apiRes[0]).Get(name, metav1.GetOptions{})
72+
if *cf.Namespace == "" {
73+
*cf.Namespace = "default" // TODO(ahmetb) figure out how to have this auto-set by kubeconfig w/ cli override
74+
}
75+
76+
obj, err := dyn.Resource(apiRes[0].GroupVersionResource()).Namespace(*cf.Namespace).Get(name, metav1.GetOptions{})
7177
if err != nil {
7278
return fmt.Errorf("failed to get: %w", err)
7379
}
7480

75-
fmt.Printf("%#v", result)
81+
apiObjects, err := QueryResources(dyn, apis.resources())
82+
if err != nil {
83+
return fmt.Errorf("error while querying api objects: %w", err)
84+
}
85+
fmt.Printf("%d api objects found\n", len(apiObjects))
86+
87+
objs := newObjectDirectory(apiObjects)
88+
if len(objs.ownership[obj.GetUID()]) == 0 {
89+
return fmt.Errorf("no resources are owned by the specified object")
90+
}
91+
treeView(os.Stderr, objs, *obj)
7692
return nil
7793
}

cmd/kubectl-tree/search.go

Lines changed: 0 additions & 2 deletions
This file was deleted.

cmd/kubectl-tree/tree.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
7+
)
8+
9+
func treeView(out io.Writer, objs objectDirectory, obj unstructured.Unstructured) {
10+
treeViewInner("", out, objs, obj)
11+
}
12+
13+
func treeViewInner(prefix string, out io.Writer, objs objectDirectory, obj unstructured.Unstructured) {
14+
fmt.Fprintf(out, prefix+"%s/%s (#%s#)\n", obj.GetKind(), obj.GetName(),obj.GetUID())
15+
for _, child := range objs.ownedBy(obj.GetUID()) {
16+
treeViewInner(prefix+" ", out, objs, objs.getObject(child))
17+
}
18+
}

0 commit comments

Comments
 (0)