1
16
17 package completion
18
19 import (
20 "bytes"
21 "fmt"
22 "io"
23 "os"
24 "strings"
25 "time"
26
27 "github.com/spf13/cobra"
28
29 "k8s.io/apimachinery/pkg/api/meta"
30 "k8s.io/cli-runtime/pkg/genericclioptions"
31 "k8s.io/cli-runtime/pkg/genericiooptions"
32 "k8s.io/cli-runtime/pkg/printers"
33 "k8s.io/kubectl/pkg/cmd/apiresources"
34 "k8s.io/kubectl/pkg/cmd/get"
35 cmdutil "k8s.io/kubectl/pkg/cmd/util"
36 "k8s.io/kubectl/pkg/polymorphichelpers"
37 "k8s.io/kubectl/pkg/scheme"
38 )
39
40 var factory cmdutil.Factory
41
42
43
44 func SetFactoryForCompletion(f cmdutil.Factory) {
45 factory = f
46 }
47
48
49
50 func ResourceTypeAndNameCompletionFunc(f cmdutil.Factory) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
51 return resourceTypeAndNameCompletionFunc(f, nil, true)
52 }
53
54
55
56
57 func SpecifiedResourceTypeAndNameCompletionFunc(f cmdutil.Factory, allowedTypes []string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
58 return resourceTypeAndNameCompletionFunc(f, allowedTypes, true)
59 }
60
61
62
63
64 func SpecifiedResourceTypeAndNameNoRepeatCompletionFunc(f cmdutil.Factory, allowedTypes []string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
65 return resourceTypeAndNameCompletionFunc(f, allowedTypes, false)
66 }
67
68
69
70
71
72
73 func ResourceNameCompletionFunc(f cmdutil.Factory, resourceType string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
74 return func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
75 var comps []string
76 if len(args) == 0 {
77 comps = CompGetResource(f, resourceType, toComplete)
78 }
79 return comps, cobra.ShellCompDirectiveNoFileComp
80 }
81 }
82
83
84
85
86 func PodResourceNameCompletionFunc(f cmdutil.Factory) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
87 return func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
88 var comps []string
89 directive := cobra.ShellCompDirectiveNoFileComp
90 if len(args) == 0 {
91 comps, directive = doPodResourceCompletion(f, toComplete)
92 }
93 return comps, directive
94 }
95 }
96
97
98
99
100
101 func PodResourceNameAndContainerCompletionFunc(f cmdutil.Factory) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
102 return func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
103 var comps []string
104 directive := cobra.ShellCompDirectiveNoFileComp
105 if len(args) == 0 {
106 comps, directive = doPodResourceCompletion(f, toComplete)
107 } else if len(args) == 1 {
108 podName := convertResourceNameToPodName(f, args[0])
109 comps = CompGetContainers(f, podName, toComplete)
110 }
111 return comps, directive
112 }
113 }
114
115
116
117
118 func ContainerCompletionFunc(f cmdutil.Factory) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
119 return func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
120 var comps []string
121
122
123 if len(args) > 0 {
124 podName := convertResourceNameToPodName(f, args[0])
125 comps = CompGetContainers(f, podName, toComplete)
126 }
127 return comps, cobra.ShellCompDirectiveNoFileComp
128 }
129 }
130
131
132
133 func ContextCompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
134 if len(args) == 0 {
135 return ListContextsInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp
136 }
137 return nil, cobra.ShellCompDirectiveNoFileComp
138 }
139
140
141
142 func ClusterCompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
143 if len(args) == 0 {
144 return ListClustersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp
145 }
146 return nil, cobra.ShellCompDirectiveNoFileComp
147 }
148
149
150
151 func UserCompletionFunc(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
152 if len(args) == 0 {
153 return ListUsersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp
154 }
155 return nil, cobra.ShellCompDirectiveNoFileComp
156 }
157
158
159 func CompGetResource(f cmdutil.Factory, resourceName string, toComplete string) []string {
160 template := "{{ range .items }}{{ .metadata.name }} {{ end }}"
161 return CompGetFromTemplate(&template, f, "", []string{resourceName}, toComplete)
162 }
163
164
165 func CompGetContainers(f cmdutil.Factory, podName string, toComplete string) []string {
166 template := "{{ range .spec.initContainers }}{{ .name }} {{end}}{{ range .spec.containers }}{{ .name }} {{ end }}"
167 return CompGetFromTemplate(&template, f, "", []string{"pod", podName}, toComplete)
168 }
169
170
171
172 func CompGetFromTemplate(template *string, f cmdutil.Factory, namespace string, args []string, toComplete string) []string {
173 buf := new(bytes.Buffer)
174 streams := genericiooptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: io.Discard}
175 o := get.NewGetOptions("kubectl", streams)
176
177
178 o.PrintFlags.TemplateFlags.GoTemplatePrintFlags.TemplateArgument = template
179 format := "go-template"
180 o.PrintFlags.OutputFormat = &format
181
182
183
184
185 if namespace != "" {
186 o.Namespace = namespace
187 o.ExplicitNamespace = true
188 } else {
189 var err error
190 o.Namespace, o.ExplicitNamespace, err = f.ToRawKubeConfigLoader().Namespace()
191 if err != nil {
192 return nil
193 }
194 }
195
196 o.ToPrinter = func(mapping *meta.RESTMapping, outputObjects *bool, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) {
197 printer, err := o.PrintFlags.ToPrinter()
198 if err != nil {
199 return nil, err
200 }
201 return printer.PrintObj, nil
202 }
203
204 o.Run(f, args)
205
206 var comps []string
207 resources := strings.Split(buf.String(), " ")
208 for _, res := range resources {
209 if res != "" && strings.HasPrefix(res, toComplete) {
210 comps = append(comps, res)
211 }
212 }
213 return comps
214 }
215
216
217 func ListContextsInConfig(toComplete string) []string {
218 config, err := factory.ToRawKubeConfigLoader().RawConfig()
219 if err != nil {
220 return nil
221 }
222 var ret []string
223 for name := range config.Contexts {
224 if strings.HasPrefix(name, toComplete) {
225 ret = append(ret, name)
226 }
227 }
228 return ret
229 }
230
231
232 func ListClustersInConfig(toComplete string) []string {
233 config, err := factory.ToRawKubeConfigLoader().RawConfig()
234 if err != nil {
235 return nil
236 }
237 var ret []string
238 for name := range config.Clusters {
239 if strings.HasPrefix(name, toComplete) {
240 ret = append(ret, name)
241 }
242 }
243 return ret
244 }
245
246
247 func ListUsersInConfig(toComplete string) []string {
248 config, err := factory.ToRawKubeConfigLoader().RawConfig()
249 if err != nil {
250 return nil
251 }
252 var ret []string
253 for name := range config.AuthInfos {
254 if strings.HasPrefix(name, toComplete) {
255 ret = append(ret, name)
256 }
257 }
258 return ret
259 }
260
261
262 func compGetResourceList(restClientGetter genericclioptions.RESTClientGetter, cmd *cobra.Command, toComplete string) []string {
263 buf := new(bytes.Buffer)
264 streams := genericiooptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: io.Discard}
265 o := apiresources.NewAPIResourceOptions(streams)
266
267 o.Complete(restClientGetter, cmd, nil)
268
269
270 o.Output = "name"
271 o.Cached = true
272 o.Verbs = []string{"get"}
273
274
275
276 o.RunAPIResources()
277
278
279
280
281 prefix := ""
282 suffix := toComplete
283 lastIdx := strings.LastIndex(toComplete, ",")
284 if lastIdx != -1 {
285 prefix = toComplete[0 : lastIdx+1]
286 suffix = toComplete[lastIdx+1:]
287 }
288 var comps []string
289 resources := strings.Split(buf.String(), "\n")
290 for _, res := range resources {
291 if res != "" && strings.HasPrefix(res, suffix) {
292 comps = append(comps, fmt.Sprintf("%s%s", prefix, res))
293 }
294 }
295 return comps
296 }
297
298
299
300 func resourceTypeAndNameCompletionFunc(f cmdutil.Factory, allowedTypes []string, allowRepeat bool) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
301 return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
302 var comps []string
303 directive := cobra.ShellCompDirectiveNoFileComp
304
305 if len(args) > 0 && !strings.Contains(args[0], "/") {
306
307
308 if allowRepeat || len(args) == 1 {
309 comps = CompGetResource(f, args[0], toComplete)
310
311
312 if len(args) > 1 {
313 comps = cmdutil.Difference(comps, args[1:])
314 }
315 }
316 } else {
317 slashIdx := strings.Index(toComplete, "/")
318 if slashIdx == -1 {
319 if len(args) == 0 {
320
321
322
323
324 if len(allowedTypes) == 0 {
325 comps = compGetResourceList(f, cmd, toComplete)
326 } else {
327 for _, c := range allowedTypes {
328 if strings.HasPrefix(c, toComplete) {
329 comps = append(comps, c)
330 }
331 }
332 }
333 } else {
334
335
336 if allowRepeat {
337
338
339 directive |= cobra.ShellCompDirectiveNoSpace
340
341 if len(allowedTypes) == 0 {
342 typeComps := compGetResourceList(f, cmd, toComplete)
343 for _, c := range typeComps {
344 comps = append(comps, fmt.Sprintf("%s/", c))
345 }
346 } else {
347 for _, c := range allowedTypes {
348 if strings.HasPrefix(c, toComplete) {
349 comps = append(comps, fmt.Sprintf("%s/", c))
350 }
351 }
352 }
353 }
354 }
355 } else {
356
357
358 if allowRepeat || len(args) == 0 {
359 resourceType := toComplete[:slashIdx]
360 toComplete = toComplete[slashIdx+1:]
361 nameComps := CompGetResource(f, resourceType, toComplete)
362 for _, c := range nameComps {
363 comps = append(comps, fmt.Sprintf("%s/%s", resourceType, c))
364 }
365
366
367 if len(args) > 0 {
368 comps = cmdutil.Difference(comps, args[0:])
369 }
370 }
371 }
372 }
373 return comps, directive
374 }
375 }
376
377
378
379
380 func doPodResourceCompletion(f cmdutil.Factory, toComplete string) ([]string, cobra.ShellCompDirective) {
381 var comps []string
382 directive := cobra.ShellCompDirectiveNoFileComp
383 slashIdx := strings.Index(toComplete, "/")
384 if slashIdx == -1 {
385
386 comps = CompGetResource(f, "pod", toComplete)
387
388
389
390 resourcesWithPods := []string{
391 "daemonsets",
392 "deployments",
393 "pods",
394 "jobs",
395 "replicasets",
396 "replicationcontrollers",
397 "services",
398 "statefulsets"}
399
400 if len(comps) == 0 {
401
402
403 directive |= cobra.ShellCompDirectiveNoSpace
404 }
405
406 for _, resource := range resourcesWithPods {
407 if strings.HasPrefix(resource, toComplete) {
408 comps = append(comps, fmt.Sprintf("%s/", resource))
409 }
410 }
411 } else {
412
413 resourceType := toComplete[:slashIdx]
414 toComplete = toComplete[slashIdx+1:]
415 nameComps := CompGetResource(f, resourceType, toComplete)
416 for _, c := range nameComps {
417 comps = append(comps, fmt.Sprintf("%s/%s", resourceType, c))
418 }
419 }
420 return comps, directive
421 }
422
423
424
425
426
427 func convertResourceNameToPodName(f cmdutil.Factory, resourceName string) string {
428 var podName string
429 if !strings.Contains(resourceName, "/") {
430
431 podName = resourceName
432 } else {
433
434 ns, _, err := f.ToRawKubeConfigLoader().Namespace()
435 if err != nil {
436 return ""
437 }
438
439 resourceWithPod, err := f.NewBuilder().
440 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
441 ContinueOnError().
442 NamespaceParam(ns).DefaultNamespace().
443 ResourceNames("pods", resourceName).
444 Do().Object()
445 if err != nil {
446 return ""
447 }
448
449
450 forwardablePod, err := polymorphichelpers.AttachablePodForObjectFn(f, resourceWithPod, 100*time.Millisecond)
451 if err != nil {
452 return ""
453 }
454 podName = forwardablePod.Name
455 }
456 return podName
457 }
458
View as plain text