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