github.com/strongmonkey/helm@v2.7.2+incompatible/pkg/kube/client.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package kube // import "k8s.io/helm/pkg/kube" 18 19 import ( 20 "bytes" 21 "encoding/json" 22 goerrors "errors" 23 "fmt" 24 "io" 25 "log" 26 "strings" 27 "time" 28 29 jsonpatch "github.com/evanphx/json-patch" 30 apps "k8s.io/api/apps/v1beta2" 31 batch "k8s.io/api/batch/v1" 32 "k8s.io/api/core/v1" 33 "k8s.io/api/extensions/v1beta1" 34 "k8s.io/apimachinery/pkg/api/errors" 35 "k8s.io/apimachinery/pkg/api/meta" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 38 "k8s.io/apimachinery/pkg/fields" 39 "k8s.io/apimachinery/pkg/labels" 40 "k8s.io/apimachinery/pkg/runtime" 41 "k8s.io/apimachinery/pkg/types" 42 "k8s.io/apimachinery/pkg/util/strategicpatch" 43 "k8s.io/apimachinery/pkg/watch" 44 "k8s.io/client-go/tools/clientcmd" 45 "k8s.io/kubernetes/pkg/api" 46 "k8s.io/kubernetes/pkg/api/helper" 47 batchinternal "k8s.io/kubernetes/pkg/apis/batch" 48 conditions "k8s.io/kubernetes/pkg/client/unversioned" 49 "k8s.io/kubernetes/pkg/kubectl" 50 cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 51 "k8s.io/kubernetes/pkg/kubectl/resource" 52 "k8s.io/kubernetes/pkg/kubectl/validation" 53 "k8s.io/kubernetes/pkg/printers" 54 ) 55 56 // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. 57 var ErrNoObjectsVisited = goerrors.New("no objects visited") 58 59 // Client represents a client capable of communicating with the Kubernetes API. 60 type Client struct { 61 cmdutil.Factory 62 // SchemaCacheDir is the path for loading cached schema. 63 SchemaCacheDir string 64 65 Log func(string, ...interface{}) 66 } 67 68 // New creates a new Client. 69 func New(config clientcmd.ClientConfig) *Client { 70 return &Client{ 71 Factory: cmdutil.NewFactory(config), 72 SchemaCacheDir: clientcmd.RecommendedSchemaFile, 73 Log: func(_ string, _ ...interface{}) {}, 74 } 75 } 76 77 // ResourceActorFunc performs an action on a single resource. 78 type ResourceActorFunc func(*resource.Info) error 79 80 // Create creates Kubernetes resources from an io.reader. 81 // 82 // Namespace will set the namespace. 83 func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error { 84 client, err := c.ClientSet() 85 if err != nil { 86 return err 87 } 88 if err := ensureNamespace(client, namespace); err != nil { 89 return err 90 } 91 c.Log("building resources from manifest") 92 infos, buildErr := c.BuildUnstructured(namespace, reader) 93 if buildErr != nil { 94 return buildErr 95 } 96 c.Log("creating %d resource(s)", len(infos)) 97 if err := perform(infos, createResource); err != nil { 98 return err 99 } 100 if shouldWait { 101 return c.waitForResources(time.Duration(timeout)*time.Second, infos) 102 } 103 return nil 104 } 105 106 func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result { 107 return c.NewBuilder(true). 108 ContinueOnError(). 109 Schema(c.validator()). 110 NamespaceParam(namespace). 111 DefaultNamespace(). 112 Stream(reader, ""). 113 Flatten(). 114 Do() 115 } 116 117 func (c *Client) validator() validation.Schema { 118 const openapi = false // only works on v1.8 clusters 119 schema, err := c.Validator(true, openapi, c.SchemaCacheDir) 120 if err != nil { 121 c.Log("warning: failed to load schema: %s", err) 122 } 123 return schema 124 } 125 126 // BuildUnstructured validates for Kubernetes objects and returns unstructured infos. 127 func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) { 128 var result Result 129 130 b, err := c.NewUnstructuredBuilder(true) 131 if err != nil { 132 return result, err 133 } 134 result, err = b.ContinueOnError(). 135 Schema(c.validator()). 136 NamespaceParam(namespace). 137 DefaultNamespace(). 138 Stream(reader, ""). 139 Flatten(). 140 Do().Infos() 141 return result, scrubValidationError(err) 142 } 143 144 // Build validates for Kubernetes objects and returns resource Infos from a io.Reader. 145 func (c *Client) Build(namespace string, reader io.Reader) (Result, error) { 146 var result Result 147 result, err := c.newBuilder(namespace, reader).Infos() 148 return result, scrubValidationError(err) 149 } 150 151 // Get gets Kubernetes resources as pretty-printed string. 152 // 153 // Namespace will set the namespace. 154 func (c *Client) Get(namespace string, reader io.Reader) (string, error) { 155 // Since we don't know what order the objects come in, let's group them by the types, so 156 // that when we print them, they come out looking good (headers apply to subgroups, etc.). 157 objs := make(map[string][]runtime.Object) 158 infos, err := c.BuildUnstructured(namespace, reader) 159 if err != nil { 160 return "", err 161 } 162 163 var objPods = make(map[string][]api.Pod) 164 165 missing := []string{} 166 err = perform(infos, func(info *resource.Info) error { 167 c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name) 168 if err := info.Get(); err != nil { 169 c.Log("WARNING: Failed Get for resource %q: %s", info.Name, err) 170 missing = append(missing, fmt.Sprintf("%v\t\t%s", info.Mapping.Resource, info.Name)) 171 return nil 172 } 173 174 // Use APIVersion/Kind as grouping mechanism. I'm not sure if you can have multiple 175 // versions per cluster, but this certainly won't hurt anything, so let's be safe. 176 gvk := info.ResourceMapping().GroupVersionKind 177 vk := gvk.Version + "/" + gvk.Kind 178 objs[vk] = append(objs[vk], info.Object) 179 180 //Get the relation pods 181 objPods, err = c.getSelectRelationPod(info, objPods) 182 if err != nil { 183 c.Log("Warning: get the relation pod is failed, err:%s", err.Error()) 184 } 185 186 return nil 187 }) 188 if err != nil { 189 return "", err 190 } 191 192 //here, we will add the objPods to the objs 193 for key, podItems := range objPods { 194 for i := range podItems { 195 objs[key+"(related)"] = append(objs[key+"(related)"], &podItems[i]) 196 } 197 } 198 199 // Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so 200 // spin through them and print them. Printer is cool since it prints the header only when 201 // an object type changes, so we can just rely on that. Problem is it doesn't seem to keep 202 // track of tab widths. 203 buf := new(bytes.Buffer) 204 p, _ := c.Printer(nil, printers.PrintOptions{}) 205 for t, ot := range objs { 206 if _, err = buf.WriteString("==> " + t + "\n"); err != nil { 207 return "", err 208 } 209 for _, o := range ot { 210 if err := p.PrintObj(o, buf); err != nil { 211 c.Log("failed to print object type %s, object: %q :\n %v", t, o, err) 212 return "", err 213 } 214 } 215 if _, err := buf.WriteString("\n"); err != nil { 216 return "", err 217 } 218 } 219 if len(missing) > 0 { 220 buf.WriteString("==> MISSING\nKIND\t\tNAME\n") 221 for _, s := range missing { 222 fmt.Fprintln(buf, s) 223 } 224 } 225 return buf.String(), nil 226 } 227 228 // Update reads in the current configuration and a target configuration from io.reader 229 // and creates resources that don't already exists, updates resources that have been modified 230 // in the target configuration and deletes resources from the current configuration that are 231 // not present in the target configuration. 232 // 233 // Namespace will set the namespaces. 234 func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { 235 original, err := c.BuildUnstructured(namespace, originalReader) 236 if err != nil { 237 return fmt.Errorf("failed decoding reader into objects: %s", err) 238 } 239 240 c.Log("building resources from updated manifest") 241 target, err := c.BuildUnstructured(namespace, targetReader) 242 if err != nil { 243 return fmt.Errorf("failed decoding reader into objects: %s", err) 244 } 245 246 updateErrors := []string{} 247 248 c.Log("checking %d resources for changes", len(target)) 249 err = target.Visit(func(info *resource.Info, err error) error { 250 if err != nil { 251 return err 252 } 253 254 helper := resource.NewHelper(info.Client, info.Mapping) 255 if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil { 256 if !errors.IsNotFound(err) { 257 return fmt.Errorf("Could not get information about the resource: %s", err) 258 } 259 260 // Since the resource does not exist, create it. 261 if err := createResource(info); err != nil { 262 return fmt.Errorf("failed to create resource: %s", err) 263 } 264 265 kind := info.Mapping.GroupVersionKind.Kind 266 c.Log("Created a new %s called %q\n", kind, info.Name) 267 return nil 268 } 269 270 originalInfo := original.Get(info) 271 if originalInfo == nil { 272 return fmt.Errorf("no resource with the name %q found", info.Name) 273 } 274 275 if err := updateResource(c, info, originalInfo.Object, force, recreate); err != nil { 276 c.Log("error updating the resource %q:\n\t %v", info.Name, err) 277 updateErrors = append(updateErrors, err.Error()) 278 } 279 280 return nil 281 }) 282 283 switch { 284 case err != nil: 285 return err 286 case len(updateErrors) != 0: 287 return fmt.Errorf(strings.Join(updateErrors, " && ")) 288 } 289 290 for _, info := range original.Difference(target) { 291 c.Log("Deleting %q in %s...", info.Name, info.Namespace) 292 if err := deleteResource(c, info); err != nil { 293 c.Log("Failed to delete %q, err: %s", info.Name, err) 294 } 295 } 296 if shouldWait { 297 return c.waitForResources(time.Duration(timeout)*time.Second, target) 298 } 299 return nil 300 } 301 302 // Delete deletes Kubernetes resources from an io.reader. 303 // 304 // Namespace will set the namespace. 305 func (c *Client) Delete(namespace string, reader io.Reader) error { 306 infos, err := c.BuildUnstructured(namespace, reader) 307 if err != nil { 308 return err 309 } 310 return perform(infos, func(info *resource.Info) error { 311 c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) 312 err := deleteResource(c, info) 313 return c.skipIfNotFound(err) 314 }) 315 } 316 317 func (c *Client) skipIfNotFound(err error) error { 318 if errors.IsNotFound(err) { 319 c.Log("%v", err) 320 return nil 321 } 322 return err 323 } 324 325 func (c *Client) watchTimeout(t time.Duration) ResourceActorFunc { 326 return func(info *resource.Info) error { 327 return c.watchUntilReady(t, info) 328 } 329 } 330 331 // WatchUntilReady watches the resource given in the reader, and waits until it is ready. 332 // 333 // This function is mainly for hook implementations. It watches for a resource to 334 // hit a particular milestone. The milestone depends on the Kind. 335 // 336 // For most kinds, it checks to see if the resource is marked as Added or Modified 337 // by the Kubernetes event stream. For some kinds, it does more: 338 // 339 // - Jobs: A job is marked "Ready" when it has successfully completed. This is 340 // ascertained by watching the Status fields in a job's output. 341 // 342 // Handling for other kinds will be added as necessary. 343 func (c *Client) WatchUntilReady(namespace string, reader io.Reader, timeout int64, shouldWait bool) error { 344 infos, err := c.Build(namespace, reader) 345 if err != nil { 346 return err 347 } 348 // For jobs, there's also the option to do poll c.Jobs(namespace).Get(): 349 // https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300 350 return perform(infos, c.watchTimeout(time.Duration(timeout)*time.Second)) 351 } 352 353 func perform(infos Result, fn ResourceActorFunc) error { 354 if len(infos) == 0 { 355 return ErrNoObjectsVisited 356 } 357 358 for _, info := range infos { 359 if err := fn(info); err != nil { 360 return err 361 } 362 } 363 return nil 364 } 365 366 func createResource(info *resource.Info) error { 367 obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) 368 if err != nil { 369 return err 370 } 371 return info.Refresh(obj, true) 372 } 373 374 func deleteResource(c *Client, info *resource.Info) error { 375 reaper, err := c.Reaper(info.Mapping) 376 if err != nil { 377 // If there is no reaper for this resources, delete it. 378 if kubectl.IsNoSuchReaperError(err) { 379 return resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name) 380 } 381 return err 382 } 383 c.Log("Using reaper for deleting %q", info.Name) 384 return reaper.Stop(info.Namespace, info.Name, 0, nil) 385 } 386 387 func createPatch(mapping *meta.RESTMapping, target, current runtime.Object) ([]byte, types.PatchType, error) { 388 oldData, err := json.Marshal(current) 389 if err != nil { 390 return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %s", err) 391 } 392 newData, err := json.Marshal(target) 393 if err != nil { 394 return nil, types.StrategicMergePatchType, fmt.Errorf("serializing target configuration: %s", err) 395 } 396 397 if helper.Semantic.DeepEqual(oldData, newData) { 398 return nil, types.StrategicMergePatchType, nil 399 } 400 401 // Get a versioned object 402 versionedObject, err := api.Scheme.New(mapping.GroupVersionKind) 403 switch { 404 case runtime.IsNotRegisteredError(err): 405 // fall back to generic JSON merge patch 406 patch, err := jsonpatch.CreateMergePatch(oldData, newData) 407 return patch, types.MergePatchType, err 408 case err != nil: 409 return nil, types.StrategicMergePatchType, fmt.Errorf("failed to get versionedObject: %s", err) 410 default: 411 patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject) 412 return patch, types.StrategicMergePatchType, err 413 } 414 } 415 416 func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error { 417 patch, patchType, err := createPatch(target.Mapping, target.Object, currentObj) 418 if err != nil { 419 return fmt.Errorf("failed to create patch: %s", err) 420 } 421 if patch == nil { 422 c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name) 423 // This needs to happen to make sure that tiller has the latest info from the API 424 // Otherwise there will be no labels and other functions that use labels will panic 425 if err := target.Get(); err != nil { 426 return fmt.Errorf("error trying to refresh resource information: %v", err) 427 } 428 return nil 429 } 430 431 // send patch to server 432 helper := resource.NewHelper(target.Client, target.Mapping) 433 434 obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch) 435 if err != nil { 436 kind := target.Mapping.GroupVersionKind.Kind 437 log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err) 438 439 if force { 440 // Attempt to delete... 441 if err := deleteResource(c, target); err != nil { 442 return err 443 } 444 log.Printf("Deleted %s: %q", kind, target.Name) 445 446 // ... and recreate 447 if err := createResource(target); err != nil { 448 return fmt.Errorf("Failed to recreate resource: %s", err) 449 } 450 log.Printf("Created a new %s called %q\n", kind, target.Name) 451 452 // No need to refresh the target, as we recreated the resource based 453 // on it. In addition, it might not exist yet and a call to `Refresh` 454 // may fail. 455 } else { 456 log.Print("Use --force to force recreation of the resource") 457 return err 458 } 459 } else { 460 // When patch succeeds without needing to recreate, refresh target. 461 target.Refresh(obj, true) 462 } 463 464 if !recreate { 465 return nil 466 } 467 468 versioned, err := c.AsVersionedObject(target.Object) 469 if runtime.IsNotRegisteredError(err) { 470 return nil 471 } 472 if err != nil { 473 return err 474 } 475 476 selector, err := getSelectorFromObject(versioned) 477 if err != nil { 478 return nil 479 } 480 481 client, err := c.ClientSet() 482 if err != nil { 483 return err 484 } 485 486 pods, err := client.Core().Pods(target.Namespace).List(metav1.ListOptions{ 487 FieldSelector: fields.Everything().String(), 488 LabelSelector: labels.Set(selector).AsSelector().String(), 489 }) 490 if err != nil { 491 return err 492 } 493 494 // Restart pods 495 for _, pod := range pods.Items { 496 c.Log("Restarting pod: %v/%v", pod.Namespace, pod.Name) 497 498 // Delete each pod for get them restarted with changed spec. 499 if err := client.Core().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil { 500 return err 501 } 502 } 503 return nil 504 } 505 506 func getSelectorFromObject(obj runtime.Object) (map[string]string, error) { 507 switch typed := obj.(type) { 508 case *v1.ReplicationController: 509 return typed.Spec.Selector, nil 510 case *v1beta1.ReplicaSet: 511 return typed.Spec.Selector.MatchLabels, nil 512 case *v1beta1.Deployment: 513 return typed.Spec.Selector.MatchLabels, nil 514 case *v1beta1.DaemonSet: 515 return typed.Spec.Selector.MatchLabels, nil 516 case *batch.Job: 517 return typed.Spec.Selector.MatchLabels, nil 518 case *apps.StatefulSet: 519 return typed.Spec.Selector.MatchLabels, nil 520 default: 521 return nil, fmt.Errorf("Unsupported kind when getting selector: %v", obj) 522 } 523 } 524 525 func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error { 526 w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) 527 if err != nil { 528 return err 529 } 530 531 kind := info.Mapping.GroupVersionKind.Kind 532 c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) 533 534 // What we watch for depends on the Kind. 535 // - For a Job, we watch for completion. 536 // - For all else, we watch until Ready. 537 // In the future, we might want to add some special logic for types 538 // like Ingress, Volume, etc. 539 540 _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { 541 switch e.Type { 542 case watch.Added, watch.Modified: 543 // For things like a secret or a config map, this is the best indicator 544 // we get. We care mostly about jobs, where what we want to see is 545 // the status go into a good state. For other types, like ReplicaSet 546 // we don't really do anything to support these as hooks. 547 c.Log("Add/Modify event for %s: %v", info.Name, e.Type) 548 if kind == "Job" { 549 return c.waitForJob(e, info.Name) 550 } 551 return true, nil 552 case watch.Deleted: 553 c.Log("Deleted event for %s", info.Name) 554 return true, nil 555 case watch.Error: 556 // Handle error and return with an error. 557 c.Log("Error event for %s", info.Name) 558 return true, fmt.Errorf("Failed to deploy %s", info.Name) 559 default: 560 return false, nil 561 } 562 }) 563 return err 564 } 565 566 // AsVersionedObject converts a runtime.object to a versioned object. 567 func (c *Client) AsVersionedObject(obj runtime.Object) (runtime.Object, error) { 568 json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 569 if err != nil { 570 return nil, err 571 } 572 versions := &runtime.VersionedObjects{} 573 err = runtime.DecodeInto(c.Decoder(true), json, versions) 574 return versions.First(), err 575 } 576 577 // waitForJob is a helper that waits for a job to complete. 578 // 579 // This operates on an event returned from a watcher. 580 func (c *Client) waitForJob(e watch.Event, name string) (bool, error) { 581 o, ok := e.Object.(*batchinternal.Job) 582 if !ok { 583 return true, fmt.Errorf("Expected %s to be a *batch.Job, got %T", name, e.Object) 584 } 585 586 for _, c := range o.Status.Conditions { 587 if c.Type == batchinternal.JobComplete && c.Status == api.ConditionTrue { 588 return true, nil 589 } else if c.Type == batchinternal.JobFailed && c.Status == api.ConditionTrue { 590 return true, fmt.Errorf("Job failed: %s", c.Reason) 591 } 592 } 593 594 c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) 595 return false, nil 596 } 597 598 // scrubValidationError removes kubectl info from the message. 599 func scrubValidationError(err error) error { 600 if err == nil { 601 return nil 602 } 603 const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false" 604 605 if strings.Contains(err.Error(), stopValidateMessage) { 606 return goerrors.New(strings.Replace(err.Error(), "; "+stopValidateMessage, "", -1)) 607 } 608 return err 609 } 610 611 // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase 612 // and returns said phase (PodSucceeded or PodFailed qualify). 613 func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { 614 infos, err := c.Build(namespace, reader) 615 if err != nil { 616 return api.PodUnknown, err 617 } 618 info := infos[0] 619 620 kind := info.Mapping.GroupVersionKind.Kind 621 if kind != "Pod" { 622 return api.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name) 623 } 624 625 if err := c.watchPodUntilComplete(timeout, info); err != nil { 626 return api.PodUnknown, err 627 } 628 629 if err := info.Get(); err != nil { 630 return api.PodUnknown, err 631 } 632 status := info.Object.(*api.Pod).Status.Phase 633 634 return status, nil 635 } 636 637 func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error { 638 w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) 639 if err != nil { 640 return err 641 } 642 643 c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout) 644 _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { 645 return conditions.PodCompleted(e) 646 }) 647 648 return err 649 } 650 651 //get an kubernetes resources's relation pods 652 // kubernetes resource used select labels to relate pods 653 func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]api.Pod) (map[string][]api.Pod, error) { 654 if info == nil { 655 return objPods, nil 656 } 657 658 c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name) 659 660 versioned, err := c.AsVersionedObject(info.Object) 661 if runtime.IsNotRegisteredError(err) { 662 return objPods, nil 663 } 664 if err != nil { 665 return objPods, err 666 } 667 668 // We can ignore this error because it will only error if it isn't a type that doesn't 669 // have pods. In that case, we don't care 670 selector, _ := getSelectorFromObject(versioned) 671 672 selectorString := labels.Set(selector).AsSelector().String() 673 674 // If we have an empty selector, this likely is a service or config map, so bail out now 675 if selectorString == "" { 676 return objPods, nil 677 } 678 679 client, _ := c.ClientSet() 680 681 pods, err := client.Core().Pods(info.Namespace).List(metav1.ListOptions{ 682 FieldSelector: fields.Everything().String(), 683 LabelSelector: labels.Set(selector).AsSelector().String(), 684 }) 685 if err != nil { 686 return objPods, err 687 } 688 689 for _, pod := range pods.Items { 690 if pod.APIVersion == "" { 691 pod.APIVersion = "v1" 692 } 693 694 if pod.Kind == "" { 695 pod.Kind = "Pod" 696 } 697 vk := pod.GroupVersionKind().Version + "/" + pod.GroupVersionKind().Kind 698 699 if !isFoundPod(objPods[vk], pod) { 700 objPods[vk] = append(objPods[vk], pod) 701 } 702 } 703 return objPods, nil 704 } 705 706 func isFoundPod(podItem []api.Pod, pod api.Pod) bool { 707 for _, value := range podItem { 708 if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) { 709 return true 710 } 711 } 712 return false 713 }