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