github.com/vtuson/helm@v2.8.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 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 "k8s.io/apimachinery/pkg/api/meta" 39 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 40 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 41 "k8s.io/apimachinery/pkg/fields" 42 "k8s.io/apimachinery/pkg/labels" 43 "k8s.io/apimachinery/pkg/runtime" 44 "k8s.io/apimachinery/pkg/types" 45 "k8s.io/apimachinery/pkg/util/strategicpatch" 46 "k8s.io/apimachinery/pkg/watch" 47 "k8s.io/client-go/tools/clientcmd" 48 batchinternal "k8s.io/kubernetes/pkg/apis/batch" 49 "k8s.io/kubernetes/pkg/apis/core" 50 conditions "k8s.io/kubernetes/pkg/client/unversioned" 51 "k8s.io/kubernetes/pkg/kubectl" 52 cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 53 "k8s.io/kubernetes/pkg/kubectl/resource" 54 "k8s.io/kubernetes/pkg/kubectl/validation" 55 "k8s.io/kubernetes/pkg/printers" 56 ) 57 58 const ( 59 // MissingGetHeader is added to Get's outout when a resource is not found. 60 MissingGetHeader = "==> MISSING\nKIND\t\tNAME\n" 61 ) 62 63 // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. 64 var ErrNoObjectsVisited = goerrors.New("no objects visited") 65 66 // Client represents a client capable of communicating with the Kubernetes API. 67 type Client struct { 68 cmdutil.Factory 69 // SchemaCacheDir is the path for loading cached schema. 70 SchemaCacheDir string 71 72 Log func(string, ...interface{}) 73 } 74 75 // New creates a new Client. 76 func New(config clientcmd.ClientConfig) *Client { 77 return &Client{ 78 Factory: cmdutil.NewFactory(config), 79 SchemaCacheDir: clientcmd.RecommendedSchemaFile, 80 Log: func(_ string, _ ...interface{}) {}, 81 } 82 } 83 84 // ResourceActorFunc performs an action on a single resource. 85 type ResourceActorFunc func(*resource.Info) error 86 87 // Create creates Kubernetes resources from an io.reader. 88 // 89 // Namespace will set the namespace. 90 func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error { 91 client, err := c.ClientSet() 92 if err != nil { 93 return err 94 } 95 if err := ensureNamespace(client, namespace); err != nil { 96 return err 97 } 98 c.Log("building resources from manifest") 99 infos, buildErr := c.BuildUnstructured(namespace, reader) 100 if buildErr != nil { 101 return buildErr 102 } 103 c.Log("creating %d resource(s)", len(infos)) 104 if err := perform(infos, createResource); err != nil { 105 return err 106 } 107 if shouldWait { 108 return c.waitForResources(time.Duration(timeout)*time.Second, infos) 109 } 110 return nil 111 } 112 113 func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result { 114 return c.NewBuilder(). 115 Internal(). 116 ContinueOnError(). 117 Schema(c.validator()). 118 NamespaceParam(namespace). 119 DefaultNamespace(). 120 Stream(reader, ""). 121 Flatten(). 122 Do() 123 } 124 125 func (c *Client) validator() validation.Schema { 126 schema, err := c.Validator(true) 127 if err != nil { 128 c.Log("warning: failed to load schema: %s", err) 129 } 130 return schema 131 } 132 133 // BuildUnstructured validates for Kubernetes objects and returns unstructured infos. 134 func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) { 135 var result Result 136 137 result, err := c.NewBuilder(). 138 Unstructured(). 139 ContinueOnError(). 140 NamespaceParam(namespace). 141 DefaultNamespace(). 142 Stream(reader, ""). 143 Flatten(). 144 Do().Infos() 145 return result, scrubValidationError(err) 146 } 147 148 // Build validates for Kubernetes objects and returns resource Infos from a io.Reader. 149 func (c *Client) Build(namespace string, reader io.Reader) (Result, error) { 150 var result Result 151 result, err := c.newBuilder(namespace, reader).Infos() 152 return result, scrubValidationError(err) 153 } 154 155 // Get gets Kubernetes resources as pretty-printed string. 156 // 157 // Namespace will set the namespace. 158 func (c *Client) Get(namespace string, reader io.Reader) (string, error) { 159 // Since we don't know what order the objects come in, let's group them by the types, so 160 // that when we print them, they come out looking good (headers apply to subgroups, etc.). 161 objs := make(map[string][]runtime.Object) 162 infos, err := c.BuildUnstructured(namespace, reader) 163 if err != nil { 164 return "", err 165 } 166 167 var objPods = make(map[string][]core.Pod) 168 169 missing := []string{} 170 err = perform(infos, func(info *resource.Info) error { 171 c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name) 172 if err := info.Get(); err != nil { 173 c.Log("WARNING: Failed Get for resource %q: %s", info.Name, err) 174 missing = append(missing, fmt.Sprintf("%v\t\t%s", info.Mapping.Resource, info.Name)) 175 return nil 176 } 177 178 // Use APIVersion/Kind as grouping mechanism. I'm not sure if you can have multiple 179 // versions per cluster, but this certainly won't hurt anything, so let's be safe. 180 gvk := info.ResourceMapping().GroupVersionKind 181 vk := gvk.Version + "/" + gvk.Kind 182 objs[vk] = append(objs[vk], info.Object) 183 184 //Get the relation pods 185 objPods, err = c.getSelectRelationPod(info, objPods) 186 if err != nil { 187 c.Log("Warning: get the relation pod is failed, err:%s", err.Error()) 188 } 189 190 return nil 191 }) 192 if err != nil { 193 return "", err 194 } 195 196 //here, we will add the objPods to the objs 197 for key, podItems := range objPods { 198 for i := range podItems { 199 objs[key+"(related)"] = append(objs[key+"(related)"], &podItems[i]) 200 } 201 } 202 203 // Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so 204 // spin through them and print them. Printer is cool since it prints the header only when 205 // an object type changes, so we can just rely on that. Problem is it doesn't seem to keep 206 // track of tab widths. 207 buf := new(bytes.Buffer) 208 p, _ := c.Printer(nil, printers.PrintOptions{}) 209 for t, ot := range objs { 210 if _, err = buf.WriteString("==> " + t + "\n"); err != nil { 211 return "", err 212 } 213 for _, o := range ot { 214 if err := p.PrintObj(o, buf); err != nil { 215 c.Log("failed to print object type %s, object: %q :\n %v", t, o, err) 216 return "", err 217 } 218 } 219 if _, err := buf.WriteString("\n"); err != nil { 220 return "", err 221 } 222 } 223 if len(missing) > 0 { 224 buf.WriteString(MissingGetHeader) 225 for _, s := range missing { 226 fmt.Fprintln(buf, s) 227 } 228 } 229 return buf.String(), nil 230 } 231 232 // Update reads in the current configuration and a target configuration from io.reader 233 // and creates resources that don't already exists, updates resources that have been modified 234 // in the target configuration and deletes resources from the current configuration that are 235 // not present in the target configuration. 236 // 237 // Namespace will set the namespaces. 238 func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { 239 original, err := c.BuildUnstructured(namespace, originalReader) 240 if err != nil { 241 return fmt.Errorf("failed decoding reader into objects: %s", err) 242 } 243 244 c.Log("building resources from updated manifest") 245 target, err := c.BuildUnstructured(namespace, targetReader) 246 if err != nil { 247 return fmt.Errorf("failed decoding reader into objects: %s", err) 248 } 249 250 updateErrors := []string{} 251 252 c.Log("checking %d resources for changes", len(target)) 253 err = target.Visit(func(info *resource.Info, err error) error { 254 if err != nil { 255 return err 256 } 257 258 helper := resource.NewHelper(info.Client, info.Mapping) 259 if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil { 260 if !errors.IsNotFound(err) { 261 return fmt.Errorf("Could not get information about the resource: %s", err) 262 } 263 264 // Since the resource does not exist, create it. 265 if err := createResource(info); err != nil { 266 return fmt.Errorf("failed to create resource: %s", err) 267 } 268 269 kind := info.Mapping.GroupVersionKind.Kind 270 c.Log("Created a new %s called %q\n", kind, info.Name) 271 return nil 272 } 273 274 originalInfo := original.Get(info) 275 if originalInfo == nil { 276 kind := info.Mapping.GroupVersionKind.Kind 277 return fmt.Errorf("no %s with the name %q found", kind, 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 // While different objects need different merge types, the parent function 403 // that calls this does not try to create a patch when the data (first 404 // returned object) is nil. We can skip calculating the the merge type as 405 // the returned merge type is ignored. 406 if apiequality.Semantic.DeepEqual(oldData, newData) { 407 return nil, types.StrategicMergePatchType, nil 408 } 409 410 // Get a versioned object 411 versionedObject, err := mapping.ConvertToVersion(target, mapping.GroupVersionKind.GroupVersion()) 412 413 // Unstructured objects, such as CRDs, may not have an not registered error 414 // returned from ConvertToVersion. Anything that's unstructured should 415 // use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported 416 // on objects like CRDs. 417 _, isUnstructured := versionedObject.(runtime.Unstructured) 418 419 switch { 420 case runtime.IsNotRegisteredError(err), isUnstructured: 421 // fall back to generic JSON merge patch 422 patch, err := jsonpatch.CreateMergePatch(oldData, newData) 423 return patch, types.MergePatchType, err 424 case err != nil: 425 return nil, types.StrategicMergePatchType, fmt.Errorf("failed to get versionedObject: %s", err) 426 default: 427 patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject) 428 return patch, types.StrategicMergePatchType, err 429 } 430 } 431 432 func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error { 433 patch, patchType, err := createPatch(target.Mapping, target.Object, currentObj) 434 if err != nil { 435 return fmt.Errorf("failed to create patch: %s", err) 436 } 437 if patch == nil { 438 c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name) 439 // This needs to happen to make sure that tiller has the latest info from the API 440 // Otherwise there will be no labels and other functions that use labels will panic 441 if err := target.Get(); err != nil { 442 return fmt.Errorf("error trying to refresh resource information: %v", err) 443 } 444 } else { 445 // send patch to server 446 helper := resource.NewHelper(target.Client, target.Mapping) 447 448 obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch) 449 if err != nil { 450 kind := target.Mapping.GroupVersionKind.Kind 451 log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err) 452 453 if force { 454 // Attempt to delete... 455 if err := deleteResource(c, target); err != nil { 456 return err 457 } 458 log.Printf("Deleted %s: %q", kind, target.Name) 459 460 // ... and recreate 461 if err := createResource(target); err != nil { 462 return fmt.Errorf("Failed to recreate resource: %s", err) 463 } 464 log.Printf("Created a new %s called %q\n", kind, target.Name) 465 466 // No need to refresh the target, as we recreated the resource based 467 // on it. In addition, it might not exist yet and a call to `Refresh` 468 // may fail. 469 } else { 470 log.Print("Use --force to force recreation of the resource") 471 return err 472 } 473 } else { 474 // When patch succeeds without needing to recreate, refresh target. 475 target.Refresh(obj, true) 476 } 477 } 478 479 if !recreate { 480 return nil 481 } 482 483 versioned, err := c.AsVersionedObject(target.Object) 484 if runtime.IsNotRegisteredError(err) { 485 return nil 486 } 487 if err != nil { 488 return err 489 } 490 491 selector, err := getSelectorFromObject(versioned) 492 if err != nil { 493 return nil 494 } 495 496 client, err := c.ClientSet() 497 if err != nil { 498 return err 499 } 500 501 pods, err := client.Core().Pods(target.Namespace).List(metav1.ListOptions{ 502 FieldSelector: fields.Everything().String(), 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.Core().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, error) { 522 switch typed := obj.(type) { 523 524 case *v1.ReplicationController: 525 return typed.Spec.Selector, nil 526 527 case *extv1beta1.ReplicaSet: 528 return typed.Spec.Selector.MatchLabels, nil 529 case *appsv1.ReplicaSet: 530 return typed.Spec.Selector.MatchLabels, nil 531 532 case *extv1beta1.Deployment: 533 return typed.Spec.Selector.MatchLabels, nil 534 case *appsv1beta1.Deployment: 535 return typed.Spec.Selector.MatchLabels, nil 536 case *appsv1beta2.Deployment: 537 return typed.Spec.Selector.MatchLabels, nil 538 case *appsv1.Deployment: 539 return typed.Spec.Selector.MatchLabels, nil 540 541 case *extv1beta1.DaemonSet: 542 return typed.Spec.Selector.MatchLabels, nil 543 case *appsv1beta2.DaemonSet: 544 return typed.Spec.Selector.MatchLabels, nil 545 case *appsv1.DaemonSet: 546 return typed.Spec.Selector.MatchLabels, nil 547 548 case *batch.Job: 549 return typed.Spec.Selector.MatchLabels, nil 550 551 case *appsv1beta1.StatefulSet: 552 return typed.Spec.Selector.MatchLabels, nil 553 case *appsv1beta2.StatefulSet: 554 return typed.Spec.Selector.MatchLabels, nil 555 case *appsv1.StatefulSet: 556 return typed.Spec.Selector.MatchLabels, nil 557 558 default: 559 return nil, fmt.Errorf("Unsupported kind when getting selector: %v", obj) 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 _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { 579 switch e.Type { 580 case watch.Added, watch.Modified: 581 // For things like a secret or a config map, this is the best indicator 582 // we get. We care mostly about jobs, where what we want to see is 583 // the status go into a good state. For other types, like ReplicaSet 584 // we don't really do anything to support these as hooks. 585 c.Log("Add/Modify event for %s: %v", info.Name, e.Type) 586 if kind == "Job" { 587 return c.waitForJob(e, info.Name) 588 } 589 return true, nil 590 case watch.Deleted: 591 c.Log("Deleted event for %s", info.Name) 592 return true, nil 593 case watch.Error: 594 // Handle error and return with an error. 595 c.Log("Error event for %s", info.Name) 596 return true, fmt.Errorf("Failed to deploy %s", info.Name) 597 default: 598 return false, nil 599 } 600 }) 601 return err 602 } 603 604 // AsVersionedObject converts a runtime.object to a versioned object. 605 func (c *Client) AsVersionedObject(obj runtime.Object) (runtime.Object, error) { 606 json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 607 if err != nil { 608 return nil, err 609 } 610 versions := &runtime.VersionedObjects{} 611 err = runtime.DecodeInto(c.Decoder(true), json, versions) 612 return versions.First(), err 613 } 614 615 // waitForJob is a helper that waits for a job to complete. 616 // 617 // This operates on an event returned from a watcher. 618 func (c *Client) waitForJob(e watch.Event, name string) (bool, error) { 619 o, ok := e.Object.(*batchinternal.Job) 620 if !ok { 621 return true, fmt.Errorf("Expected %s to be a *batch.Job, got %T", name, e.Object) 622 } 623 624 for _, c := range o.Status.Conditions { 625 if c.Type == batchinternal.JobComplete && c.Status == core.ConditionTrue { 626 return true, nil 627 } else if c.Type == batchinternal.JobFailed && c.Status == core.ConditionTrue { 628 return true, fmt.Errorf("Job failed: %s", c.Reason) 629 } 630 } 631 632 c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) 633 return false, nil 634 } 635 636 // scrubValidationError removes kubectl info from the message. 637 func scrubValidationError(err error) error { 638 if err == nil { 639 return nil 640 } 641 const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false" 642 643 if strings.Contains(err.Error(), stopValidateMessage) { 644 return goerrors.New(strings.Replace(err.Error(), "; "+stopValidateMessage, "", -1)) 645 } 646 return err 647 } 648 649 // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase 650 // and returns said phase (PodSucceeded or PodFailed qualify). 651 func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { 652 infos, err := c.Build(namespace, reader) 653 if err != nil { 654 return core.PodUnknown, err 655 } 656 info := infos[0] 657 658 kind := info.Mapping.GroupVersionKind.Kind 659 if kind != "Pod" { 660 return core.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name) 661 } 662 663 if err := c.watchPodUntilComplete(timeout, info); err != nil { 664 return core.PodUnknown, err 665 } 666 667 if err := info.Get(); err != nil { 668 return core.PodUnknown, err 669 } 670 status := info.Object.(*core.Pod).Status.Phase 671 672 return status, nil 673 } 674 675 func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error { 676 w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) 677 if err != nil { 678 return err 679 } 680 681 c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout) 682 _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { 683 return conditions.PodCompleted(e) 684 }) 685 686 return err 687 } 688 689 //get an kubernetes resources's relation pods 690 // kubernetes resource used select labels to relate pods 691 func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]core.Pod) (map[string][]core.Pod, error) { 692 if info == nil { 693 return objPods, nil 694 } 695 696 c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name) 697 698 versioned, err := c.AsVersionedObject(info.Object) 699 if runtime.IsNotRegisteredError(err) { 700 return objPods, nil 701 } 702 if err != nil { 703 return objPods, err 704 } 705 706 // We can ignore this error because it will only error if it isn't a type that doesn't 707 // have pods. In that case, we don't care 708 selector, _ := getSelectorFromObject(versioned) 709 710 selectorString := labels.Set(selector).AsSelector().String() 711 712 // If we have an empty selector, this likely is a service or config map, so bail out now 713 if selectorString == "" { 714 return objPods, nil 715 } 716 717 client, _ := c.ClientSet() 718 719 pods, err := client.Core().Pods(info.Namespace).List(metav1.ListOptions{ 720 FieldSelector: fields.Everything().String(), 721 LabelSelector: labels.Set(selector).AsSelector().String(), 722 }) 723 if err != nil { 724 return objPods, err 725 } 726 727 for _, pod := range pods.Items { 728 if pod.APIVersion == "" { 729 pod.APIVersion = "v1" 730 } 731 732 if pod.Kind == "" { 733 pod.Kind = "Pod" 734 } 735 vk := pod.GroupVersionKind().Version + "/" + pod.GroupVersionKind().Kind 736 737 if !isFoundPod(objPods[vk], pod) { 738 objPods[vk] = append(objPods[vk], pod) 739 } 740 } 741 return objPods, nil 742 } 743 744 func isFoundPod(podItem []core.Pod, pod core.Pod) bool { 745 for _, value := range podItem { 746 if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) { 747 return true 748 } 749 } 750 return false 751 }