1
2
3
4
5
6
7
8 package k8s
9
10 import (
11 "context"
12 "fmt"
13 "strings"
14
15 "github.com/emissary-ingress/emissary/v3/pkg/kates"
16
17 "k8s.io/apimachinery/pkg/api/meta"
18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19 "k8s.io/apimachinery/pkg/runtime/schema"
20
21 "k8s.io/cli-runtime/pkg/genericclioptions"
22
23 "k8s.io/client-go/dynamic"
24 "k8s.io/client-go/rest"
25
26 "github.com/kballard/go-shellquote"
27 "github.com/pkg/errors"
28 "github.com/spf13/pflag"
29
30 _ "k8s.io/client-go/plugin/pkg/client/auth"
31 )
32
33 const (
34
35
36 NamespaceAll = metav1.NamespaceAll
37
38 NamespaceNone = metav1.NamespaceNone
39 )
40
41
42 type KubeInfo struct {
43 flags *pflag.FlagSet
44 configFlags *genericclioptions.ConfigFlags
45 config *rest.Config
46 namespace string
47 }
48
49
50
51 func NewKubeInfo(configfile, context, namespace string) *KubeInfo {
52 flags := pflag.NewFlagSet("KubeInfo", pflag.ContinueOnError)
53 result := NewKubeInfoFromFlags(flags)
54
55 var args []string
56 if configfile != "" {
57 args = append(args, "--kubeconfig", configfile)
58 }
59 if context != "" {
60 args = append(args, "--context", context)
61 }
62 if namespace != "" {
63 args = append(args, "--namespace", namespace)
64 }
65
66 if err := flags.Parse(args); err != nil {
67
68
69 panic(err)
70 }
71 return result
72 }
73
74
75
76
77 func NewKubeInfoFromFlags(flags *pflag.FlagSet) *KubeInfo {
78 configFlags := genericclioptions.NewConfigFlags(false)
79
80
81
82
83
84
85
86 configFlags.AddFlags(flags)
87 return &KubeInfo{flags, configFlags, nil, ""}
88 }
89
90 func (info *KubeInfo) load() error {
91 if info.config == nil {
92 configLoader := info.configFlags.ToRawKubeConfigLoader()
93
94 config, err := configLoader.ClientConfig()
95 if err != nil {
96 return errors.Errorf("Failed to get REST config: %v", err)
97 }
98
99 namespace, _, err := configLoader.Namespace()
100 if err != nil {
101 return errors.Errorf("Failed to get namespace: %v", err)
102 }
103
104 info.config = config
105 info.namespace = namespace
106 }
107
108 return nil
109 }
110
111
112 func (info *KubeInfo) GetConfigFlags() *genericclioptions.ConfigFlags {
113 return info.configFlags
114 }
115
116
117 func (info *KubeInfo) Namespace() (string, error) {
118 err := info.load()
119 if err != nil {
120 return "", err
121 }
122 return info.namespace, nil
123 }
124
125
126 func (info *KubeInfo) GetRestConfig() (*rest.Config, error) {
127 err := info.load()
128 if err != nil {
129 return nil, err
130 }
131 return info.config, nil
132 }
133
134
135
136 func (info *KubeInfo) GetKubectl(args string) (string, error) {
137 parts, err := shellquote.Split(args)
138 if err != nil {
139 return "", err
140 }
141 kargs, err := info.GetKubectlArray(parts...)
142 if err != nil {
143 return "", err
144 }
145 return strings.Join(kargs, " "), nil
146 }
147
148
149 func (info *KubeInfo) GetKubectlArray(args ...string) ([]string, error) {
150 res := []string{}
151
152 info.flags.Visit(func(f *pflag.Flag) {
153 res = append(res, fmt.Sprintf("--%s", f.Name), f.Value.String())
154 })
155
156 res = append(res, args...)
157
158 return res, nil
159 }
160
161
162 type Client struct {
163 config *rest.Config
164 Namespace string
165 restMapper meta.RESTMapper
166 }
167
168
169
170 func NewClient(info *KubeInfo) (*Client, error) {
171 if info == nil {
172 info = NewKubeInfo("", "", "")
173 }
174
175 config, err := info.GetRestConfig()
176 if err != nil {
177 return nil, err
178 }
179 namespace, err := info.Namespace()
180 if err != nil {
181 return nil, err
182 }
183
184 mapper, _, err := kates.NewRESTMapper(info.configFlags)
185 if err != nil {
186 return nil, err
187 }
188
189 return &Client{
190 config: config,
191 Namespace: namespace,
192 restMapper: mapper,
193 }, nil
194 }
195
196
197
198
199
200 type ResourceType struct {
201 Group string
202 Version string
203 Name string
204 Kind string
205 Namespaced bool
206 }
207
208 func (r ResourceType) String() string {
209 return r.Name + "." + r.Version + "." + r.Group
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 func (c *Client) ResolveResourceType(resource string) (ResourceType, error) {
227 restmapping, err := mappingFor(resource, c.restMapper)
228 if err != nil {
229 return ResourceType{}, err
230 }
231 return ResourceType{
232 Group: restmapping.GroupVersionKind.Group,
233 Version: restmapping.GroupVersionKind.Version,
234 Name: restmapping.Resource.Resource,
235 Kind: restmapping.GroupVersionKind.Kind,
236 Namespaced: restmapping.Scope.Name() == meta.RESTScopeNameNamespace,
237 }, nil
238 }
239
240
241
242
243
244
245
246 func mappingFor(resourceOrKindArg string, restMapper meta.RESTMapper) (*meta.RESTMapping, error) {
247 fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceOrKindArg)
248 gvk := schema.GroupVersionKind{}
249
250
251 if fullySpecifiedGVR != nil {
252 gvk, _ = restMapper.KindFor(*fullySpecifiedGVR)
253 }
254 if gvk.Empty() {
255 gvk, _ = restMapper.KindFor(groupResource.WithVersion(""))
256 }
257 if !gvk.Empty() {
258 return restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
259 }
260
261 fullySpecifiedGVK, groupKind := schema.ParseKindArg(resourceOrKindArg)
262 if fullySpecifiedGVK == nil {
263 gvk := groupKind.WithVersion("")
264 fullySpecifiedGVK = &gvk
265 }
266
267 if !fullySpecifiedGVK.Empty() {
268 if mapping, err := restMapper.RESTMapping(fullySpecifiedGVK.GroupKind(), fullySpecifiedGVK.Version); err == nil {
269 return mapping, nil
270 }
271 }
272
273 mapping, err := restMapper.RESTMapping(groupKind, gvk.Version)
274 if err != nil {
275
276
277
278
279
280 if meta.IsNoMatchError(err) {
281 return nil, fmt.Errorf("the server doesn't have a resource type %q", groupResource.Resource)
282 }
283 return nil, err
284 }
285
286 return mapping, nil
287 }
288
289
290 func (c *Client) List(ctx context.Context, resource string) ([]Resource, error) {
291 return c.ListNamespace(ctx, NamespaceAll, resource)
292 }
293
294
295
296
297 func (c *Client) ListNamespace(ctx context.Context, namespace, resource string) ([]Resource, error) {
298 return c.SelectiveList(ctx, namespace, resource, "", "")
299 }
300
301 func (c *Client) SelectiveList(ctx context.Context, namespace, resource, fieldSelector, labelSelector string) ([]Resource, error) {
302 return c.ListQuery(ctx, Query{
303 Kind: resource,
304 Namespace: namespace,
305 FieldSelector: fieldSelector,
306 LabelSelector: labelSelector,
307 })
308 }
309
310
311 type Query struct {
312
313 Kind string
314
315
316
317 Namespace string
318
319
320
321 FieldSelector string
322 LabelSelector string
323
324 resourceType ResourceType
325 }
326
327 func (q *Query) resolve(c *Client) error {
328 if q.resourceType != (ResourceType{}) {
329 return nil
330 }
331
332 rt, err := c.ResolveResourceType(q.Kind)
333 if err != nil {
334 return err
335 }
336 q.resourceType = rt
337 return nil
338 }
339
340
341
342 func (c *Client) ListQuery(ctx context.Context, query Query) ([]Resource, error) {
343 err := query.resolve(c)
344 if err != nil {
345 return nil, err
346 }
347
348 ri := query.resourceType
349
350 dyn, err := dynamic.NewForConfig(c.config)
351 if err != nil {
352 return nil, errors.Wrap(err, "failed to create dynamic context")
353 }
354
355 cli := dyn.Resource(schema.GroupVersionResource{
356 Group: ri.Group,
357 Version: ri.Version,
358 Resource: ri.Name,
359 })
360
361 var filtered dynamic.ResourceInterface
362 if ri.Namespaced && query.Namespace != "" {
363 filtered = cli.Namespace(query.Namespace)
364 } else {
365 filtered = cli
366 }
367
368 uns, err := filtered.List(ctx, metav1.ListOptions{
369 FieldSelector: query.FieldSelector,
370 LabelSelector: query.LabelSelector,
371 })
372 if err != nil {
373 return nil, err
374 }
375
376 result := make([]Resource, len(uns.Items))
377 for idx, un := range uns.Items {
378 result[idx] = un.UnstructuredContent()
379 }
380 return result, nil
381 }
382
View as plain text