1 package k8s
2
3 import (
4 "context"
5 "fmt"
6 "strings"
7 "time"
8
9 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10 "k8s.io/apimachinery/pkg/runtime/schema"
11 )
12
13
14
15 type CommandCompletion struct {
16 k8sAPI *KubernetesAPI
17 namespace string
18 }
19
20
21 func NewCommandCompletion(
22 k8sAPI *KubernetesAPI,
23 namespace string,
24 ) *CommandCompletion {
25 return &CommandCompletion{
26 k8sAPI: k8sAPI,
27 namespace: namespace,
28 }
29 }
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 func (c *CommandCompletion) Complete(args []string, toComplete string) ([]string, error) {
46 ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
47 defer cancelFn()
48
49 suggestions := []string{}
50 if len(args) == 0 && toComplete == "" {
51 return CompletionResourceTypes, nil
52 }
53
54 if len(args) == 0 && toComplete != "" {
55 for _, t := range CompletionResourceTypes {
56 if strings.HasPrefix(t, toComplete) {
57 suggestions = append(suggestions, t)
58 }
59 }
60 return suggestions, nil
61 }
62
63
64
65 if strings.Contains(args[0], "/") {
66 return []string{}, nil
67 }
68
69 resType, err := CanonicalResourceNameFromFriendlyName(args[0])
70 if err != nil {
71 return nil, fmt.Errorf("%s is not a valid resource name", args)
72 }
73
74
75 if resType == "namespace" {
76 c.namespace = ""
77 }
78
79 gvr, err := c.getGroupVersionKindForResource(resType)
80 if err != nil {
81 return nil, err
82 }
83
84 uList, err := c.k8sAPI.DynamicClient.
85 Resource(*gvr).
86 Namespace(c.namespace).
87 List(ctx, metav1.ListOptions{})
88 if err != nil {
89 return nil, err
90 }
91
92 otherResources := args[1:]
93 for _, u := range uList.Items {
94 name := u.GetName()
95
96
97
98
99
100
101
102
103 if strings.HasPrefix(name, toComplete) &&
104 !containsResource(name, otherResources) {
105 suggestions = append(suggestions, name)
106 }
107 }
108
109 return suggestions, nil
110 }
111
112 func (c *CommandCompletion) getGroupVersionKindForResource(resourceName string) (*schema.GroupVersionResource, error) {
113 _, apiResourceList, err := c.k8sAPI.Discovery().ServerGroupsAndResources()
114 if err != nil {
115 return nil, err
116 }
117
118
119
120 pluralResourceName, err := PluralResourceNameFromFriendlyName(resourceName)
121 if err != nil {
122 return nil, fmt.Errorf("%s not a valid resource name", resourceName)
123 }
124
125 gvr, err := findGroupVersionResource(resourceName, pluralResourceName, apiResourceList)
126 if err != nil {
127 return nil, fmt.Errorf("could not find GroupVersionResource for %s", resourceName)
128 }
129
130 return gvr, nil
131 }
132
133 func findGroupVersionResource(singularName string, pluralName string, apiResourceList []*metav1.APIResourceList) (*schema.GroupVersionResource, error) {
134 err := fmt.Errorf("could not find the requested resource")
135 for _, res := range apiResourceList {
136 for _, r := range res.APIResources {
137
138
139
140
141
142
143 if strings.ToLower(r.Kind) != singularName || r.Name != pluralName {
144 continue
145 }
146
147 gv := strings.Split(res.GroupVersion, "/")
148
149 if len(gv) == 1 && gv[0] == "v1" {
150 return &schema.GroupVersionResource{
151 Version: gv[0],
152 Resource: r.Name,
153 }, nil
154 }
155
156 if len(gv) != 2 {
157 return nil, err
158 }
159
160 return &schema.GroupVersionResource{
161 Group: gv[0],
162 Version: gv[1],
163 Resource: r.Name,
164 }, nil
165 }
166 }
167
168 return nil, err
169 }
170
171 func containsResource(resource string, otherResources []string) bool {
172 for _, r := range otherResources {
173 if r == resource {
174 return true
175 }
176 }
177
178 return false
179 }
180
View as plain text