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