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