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