github.com/migueleliasweb/helm@v2.6.1+incompatible/pkg/kube/client.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package kube // import "k8s.io/helm/pkg/kube"
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	goerrors "errors"
    23  	"fmt"
    24  	"io"
    25  	"log"
    26  	"strings"
    27  	"time"
    28  
    29  	jsonpatch "github.com/evanphx/json-patch"
    30  	"k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/meta"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/fields"
    35  	"k8s.io/apimachinery/pkg/labels"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	"k8s.io/apimachinery/pkg/types"
    38  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    39  	"k8s.io/apimachinery/pkg/watch"
    40  	"k8s.io/client-go/tools/clientcmd"
    41  	"k8s.io/kubernetes/pkg/api"
    42  	"k8s.io/kubernetes/pkg/api/helper"
    43  	"k8s.io/kubernetes/pkg/api/v1"
    44  	apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
    45  	batchinternal "k8s.io/kubernetes/pkg/apis/batch"
    46  	batch "k8s.io/kubernetes/pkg/apis/batch/v1"
    47  	"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
    48  	conditions "k8s.io/kubernetes/pkg/client/unversioned"
    49  	"k8s.io/kubernetes/pkg/kubectl"
    50  	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
    51  	"k8s.io/kubernetes/pkg/kubectl/resource"
    52  	"k8s.io/kubernetes/pkg/printers"
    53  )
    54  
    55  // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found.
    56  var ErrNoObjectsVisited = goerrors.New("no objects visited")
    57  
    58  // Client represents a client capable of communicating with the Kubernetes API.
    59  type Client struct {
    60  	cmdutil.Factory
    61  	// SchemaCacheDir is the path for loading cached schema.
    62  	SchemaCacheDir string
    63  
    64  	Log func(string, ...interface{})
    65  }
    66  
    67  // New creates a new Client.
    68  func New(config clientcmd.ClientConfig) *Client {
    69  	return &Client{
    70  		Factory:        cmdutil.NewFactory(config),
    71  		SchemaCacheDir: clientcmd.RecommendedSchemaFile,
    72  		Log:            func(_ string, _ ...interface{}) {},
    73  	}
    74  }
    75  
    76  // ResourceActorFunc performs an action on a single resource.
    77  type ResourceActorFunc func(*resource.Info) error
    78  
    79  // Create creates Kubernetes resources from an io.reader.
    80  //
    81  // Namespace will set the namespace.
    82  func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error {
    83  	client, err := c.ClientSet()
    84  	if err != nil {
    85  		return err
    86  	}
    87  	if err := ensureNamespace(client, namespace); err != nil {
    88  		return err
    89  	}
    90  	c.Log("building resources from manifest")
    91  	infos, buildErr := c.BuildUnstructured(namespace, reader)
    92  	if buildErr != nil {
    93  		return buildErr
    94  	}
    95  	c.Log("creating %d resource(s)", len(infos))
    96  	if err := perform(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  		c.Log("warning: failed to load schema: %s", err)
   109  	}
   110  	return c.NewBuilder(true).
   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  		c.Log("warning: failed to load schema: %s", err)
   125  	}
   126  
   127  	var result Result
   128  	b, err := c.NewUnstructuredBuilder(true)
   129  	if err != nil {
   130  		return result, err
   131  	}
   132  	result, err = b.ContinueOnError().
   133  		Schema(schema).
   134  		NamespaceParam(namespace).
   135  		DefaultNamespace().
   136  		Stream(reader, "").
   137  		Flatten().
   138  		Do().Infos()
   139  	return result, scrubValidationError(err)
   140  }
   141  
   142  // Build validates for Kubernetes objects and returns resource Infos from a io.Reader.
   143  func (c *Client) Build(namespace string, reader io.Reader) (Result, error) {
   144  	var result Result
   145  	result, err := c.newBuilder(namespace, reader).Infos()
   146  	return result, scrubValidationError(err)
   147  }
   148  
   149  // Get gets Kubernetes resources as pretty-printed string.
   150  //
   151  // Namespace will set the namespace.
   152  func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
   153  	// Since we don't know what order the objects come in, let's group them by the types, so
   154  	// that when we print them, they come out looking good (headers apply to subgroups, etc.).
   155  	objs := make(map[string][]runtime.Object)
   156  	infos, err := c.BuildUnstructured(namespace, reader)
   157  	if err != nil {
   158  		return "", err
   159  	}
   160  	missing := []string{}
   161  	err = perform(infos, func(info *resource.Info) error {
   162  		c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name)
   163  		if err := info.Get(); err != nil {
   164  			c.Log("WARNING: Failed Get for resource %q: %s", info.Name, err)
   165  			missing = append(missing, fmt.Sprintf("%v\t\t%s", info.Mapping.Resource, info.Name))
   166  			return nil
   167  		}
   168  
   169  		// Use APIVersion/Kind as grouping mechanism. I'm not sure if you can have multiple
   170  		// versions per cluster, but this certainly won't hurt anything, so let's be safe.
   171  		gvk := info.ResourceMapping().GroupVersionKind
   172  		vk := gvk.Version + "/" + gvk.Kind
   173  		objs[vk] = append(objs[vk], info.Object)
   174  		return nil
   175  	})
   176  	if err != nil {
   177  		return "", err
   178  	}
   179  
   180  	// Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so
   181  	// spin through them and print them. Printer is cool since it prints the header only when
   182  	// an object type changes, so we can just rely on that. Problem is it doesn't seem to keep
   183  	// track of tab widths.
   184  	buf := new(bytes.Buffer)
   185  	p, _ := c.Printer(nil, printers.PrintOptions{})
   186  	for t, ot := range objs {
   187  		if _, err = buf.WriteString("==> " + t + "\n"); err != nil {
   188  			return "", err
   189  		}
   190  		for _, o := range ot {
   191  			if err := p.PrintObj(o, buf); err != nil {
   192  				c.Log("failed to print object type %s, object: %q :\n %v", t, o, err)
   193  				return "", err
   194  			}
   195  		}
   196  		if _, err := buf.WriteString("\n"); err != nil {
   197  			return "", err
   198  		}
   199  	}
   200  	if len(missing) > 0 {
   201  		buf.WriteString("==> MISSING\nKIND\t\tNAME\n")
   202  		for _, s := range missing {
   203  			fmt.Fprintln(buf, s)
   204  		}
   205  	}
   206  	return buf.String(), nil
   207  }
   208  
   209  // Update reads in the current configuration and a target configuration from io.reader
   210  // and creates resources that don't already exists, updates resources that have been modified
   211  // in the target configuration and deletes resources from the current configuration that are
   212  // not present in the target configuration.
   213  //
   214  // Namespace will set the namespaces.
   215  func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error {
   216  	original, err := c.BuildUnstructured(namespace, originalReader)
   217  	if err != nil {
   218  		return fmt.Errorf("failed decoding reader into objects: %s", err)
   219  	}
   220  
   221  	c.Log("building resources from updated manifest")
   222  	target, err := c.BuildUnstructured(namespace, targetReader)
   223  	if err != nil {
   224  		return fmt.Errorf("failed decoding reader into objects: %s", err)
   225  	}
   226  
   227  	updateErrors := []string{}
   228  
   229  	c.Log("checking %d resources for changes", len(target))
   230  	err = target.Visit(func(info *resource.Info, err error) error {
   231  		if err != nil {
   232  			return err
   233  		}
   234  
   235  		helper := resource.NewHelper(info.Client, info.Mapping)
   236  		if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil {
   237  			if !errors.IsNotFound(err) {
   238  				return fmt.Errorf("Could not get information about the resource: %s", err)
   239  			}
   240  
   241  			// Since the resource does not exist, create it.
   242  			if err := createResource(info); err != nil {
   243  				return fmt.Errorf("failed to create resource: %s", err)
   244  			}
   245  
   246  			kind := info.Mapping.GroupVersionKind.Kind
   247  			c.Log("Created a new %s called %q\n", kind, info.Name)
   248  			return nil
   249  		}
   250  
   251  		originalInfo := original.Get(info)
   252  		if originalInfo == nil {
   253  			return fmt.Errorf("no resource with the name %q found", info.Name)
   254  		}
   255  
   256  		if err := updateResource(c, info, originalInfo.Object, force, recreate); err != nil {
   257  			c.Log("error updating the resource %q:\n\t %v", info.Name, err)
   258  			updateErrors = append(updateErrors, err.Error())
   259  		}
   260  
   261  		return nil
   262  	})
   263  
   264  	switch {
   265  	case err != nil:
   266  		return err
   267  	case len(updateErrors) != 0:
   268  		return fmt.Errorf(strings.Join(updateErrors, " && "))
   269  	}
   270  
   271  	for _, info := range original.Difference(target) {
   272  		c.Log("Deleting %q in %s...", info.Name, info.Namespace)
   273  		if err := deleteResource(c, info); err != nil {
   274  			c.Log("Failed to delete %q, err: %s", info.Name, err)
   275  		}
   276  	}
   277  	if shouldWait {
   278  		return c.waitForResources(time.Duration(timeout)*time.Second, target)
   279  	}
   280  	return nil
   281  }
   282  
   283  // Delete deletes Kubernetes resources from an io.reader.
   284  //
   285  // Namespace will set the namespace.
   286  func (c *Client) Delete(namespace string, reader io.Reader) error {
   287  	infos, err := c.BuildUnstructured(namespace, reader)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	return perform(infos, func(info *resource.Info) error {
   292  		c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind)
   293  		err := deleteResource(c, info)
   294  		return c.skipIfNotFound(err)
   295  	})
   296  }
   297  
   298  func (c *Client) skipIfNotFound(err error) error {
   299  	if errors.IsNotFound(err) {
   300  		c.Log("%v", err)
   301  		return nil
   302  	}
   303  	return err
   304  }
   305  
   306  func (c *Client) watchTimeout(t time.Duration) ResourceActorFunc {
   307  	return func(info *resource.Info) error {
   308  		return c.watchUntilReady(t, info)
   309  	}
   310  }
   311  
   312  // WatchUntilReady watches the resource given in the reader, and waits until it is ready.
   313  //
   314  // This function is mainly for hook implementations. It watches for a resource to
   315  // hit a particular milestone. The milestone depends on the Kind.
   316  //
   317  // For most kinds, it checks to see if the resource is marked as Added or Modified
   318  // by the Kubernetes event stream. For some kinds, it does more:
   319  //
   320  // - Jobs: A job is marked "Ready" when it has successfully completed. This is
   321  //   ascertained by watching the Status fields in a job's output.
   322  //
   323  // Handling for other kinds will be added as necessary.
   324  func (c *Client) WatchUntilReady(namespace string, reader io.Reader, timeout int64, shouldWait bool) error {
   325  	infos, err := c.Build(namespace, reader)
   326  	if err != nil {
   327  		return err
   328  	}
   329  	// For jobs, there's also the option to do poll c.Jobs(namespace).Get():
   330  	// https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300
   331  	return perform(infos, c.watchTimeout(time.Duration(timeout)*time.Second))
   332  }
   333  
   334  func perform(infos Result, fn ResourceActorFunc) error {
   335  	if len(infos) == 0 {
   336  		return ErrNoObjectsVisited
   337  	}
   338  
   339  	for _, info := range infos {
   340  		if err := fn(info); err != nil {
   341  			return err
   342  		}
   343  	}
   344  	return nil
   345  }
   346  
   347  func createResource(info *resource.Info) error {
   348  	obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
   349  	if err != nil {
   350  		return err
   351  	}
   352  	return info.Refresh(obj, true)
   353  }
   354  
   355  func deleteResource(c *Client, info *resource.Info) error {
   356  	reaper, err := c.Reaper(info.Mapping)
   357  	if err != nil {
   358  		// If there is no reaper for this resources, delete it.
   359  		if kubectl.IsNoSuchReaperError(err) {
   360  			return resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name)
   361  		}
   362  		return err
   363  	}
   364  	c.Log("Using reaper for deleting %q", info.Name)
   365  	return reaper.Stop(info.Namespace, info.Name, 0, nil)
   366  }
   367  
   368  func createPatch(mapping *meta.RESTMapping, target, current runtime.Object) ([]byte, types.PatchType, error) {
   369  	oldData, err := json.Marshal(current)
   370  	if err != nil {
   371  		return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %s", err)
   372  	}
   373  	newData, err := json.Marshal(target)
   374  	if err != nil {
   375  		return nil, types.StrategicMergePatchType, fmt.Errorf("serializing target configuration: %s", err)
   376  	}
   377  
   378  	if helper.Semantic.DeepEqual(oldData, newData) {
   379  		return nil, types.StrategicMergePatchType, nil
   380  	}
   381  
   382  	// Get a versioned object
   383  	versionedObject, err := api.Scheme.New(mapping.GroupVersionKind)
   384  	switch {
   385  	case runtime.IsNotRegisteredError(err):
   386  		// fall back to generic JSON merge patch
   387  		patch, err := jsonpatch.CreateMergePatch(oldData, newData)
   388  		return patch, types.MergePatchType, err
   389  	case err != nil:
   390  		return nil, types.StrategicMergePatchType, fmt.Errorf("failed to get versionedObject: %s", err)
   391  	default:
   392  		patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject)
   393  		return patch, types.StrategicMergePatchType, err
   394  	}
   395  }
   396  
   397  func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error {
   398  	patch, patchType, err := createPatch(target.Mapping, target.Object, currentObj)
   399  	if err != nil {
   400  		return fmt.Errorf("failed to create patch: %s", err)
   401  	}
   402  	if patch == nil {
   403  		c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name)
   404  		// This needs to happen to make sure that tiller has the latest info from the API
   405  		// Otherwise there will be no labels and other functions that use labels will panic
   406  		if err := target.Get(); err != nil {
   407  			return fmt.Errorf("error trying to refresh resource information: %v", err)
   408  		}
   409  		return nil
   410  	}
   411  
   412  	// send patch to server
   413  	helper := resource.NewHelper(target.Client, target.Mapping)
   414  
   415  	obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch)
   416  	if err != nil {
   417  		kind := target.Mapping.GroupVersionKind.Kind
   418  		log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err)
   419  
   420  		if force {
   421  			// Attempt to delete...
   422  			if err := deleteResource(c, target); err != nil {
   423  				return err
   424  			}
   425  			log.Printf("Deleted %s: %q", kind, target.Name)
   426  
   427  			// ... and recreate
   428  			if err := createResource(target); err != nil {
   429  				return fmt.Errorf("Failed to recreate resource: %s", err)
   430  			}
   431  			log.Printf("Created a new %s called %q\n", kind, target.Name)
   432  
   433  			// No need to refresh the target, as we recreated the resource based
   434  			// on it. In addition, it might not exist yet and a call to `Refresh`
   435  			// may fail.
   436  		} else {
   437  			log.Print("Use --force to force recreation of the resource")
   438  			return err
   439  		}
   440  	} else {
   441  		// When patch succeeds without needing to recreate, refresh target.
   442  		target.Refresh(obj, true)
   443  	}
   444  
   445  	if !recreate {
   446  		return nil
   447  	}
   448  
   449  	versioned, err := c.AsVersionedObject(target.Object)
   450  	if runtime.IsNotRegisteredError(err) {
   451  		return nil
   452  	}
   453  	if err != nil {
   454  		return err
   455  	}
   456  
   457  	selector, err := getSelectorFromObject(versioned)
   458  	if err != nil {
   459  		return nil
   460  	}
   461  
   462  	client, err := c.ClientSet()
   463  	if err != nil {
   464  		return err
   465  	}
   466  
   467  	pods, err := client.Core().Pods(target.Namespace).List(metav1.ListOptions{
   468  		FieldSelector: fields.Everything().String(),
   469  		LabelSelector: labels.Set(selector).AsSelector().String(),
   470  	})
   471  	if err != nil {
   472  		return err
   473  	}
   474  
   475  	// Restart pods
   476  	for _, pod := range pods.Items {
   477  		c.Log("Restarting pod: %v/%v", pod.Namespace, pod.Name)
   478  
   479  		// Delete each pod for get them restarted with changed spec.
   480  		if err := client.Core().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil {
   481  			return err
   482  		}
   483  	}
   484  	return nil
   485  }
   486  
   487  func getSelectorFromObject(obj runtime.Object) (map[string]string, error) {
   488  	switch typed := obj.(type) {
   489  	case *v1.ReplicationController:
   490  		return typed.Spec.Selector, nil
   491  	case *v1beta1.ReplicaSet:
   492  		return typed.Spec.Selector.MatchLabels, nil
   493  	case *v1beta1.Deployment:
   494  		return typed.Spec.Selector.MatchLabels, nil
   495  	case *v1beta1.DaemonSet:
   496  		return typed.Spec.Selector.MatchLabels, nil
   497  	case *batch.Job:
   498  		return typed.Spec.Selector.MatchLabels, nil
   499  	case *apps.StatefulSet:
   500  		return typed.Spec.Selector.MatchLabels, nil
   501  	default:
   502  		return nil, fmt.Errorf("Unsupported kind when getting selector: %v", obj)
   503  	}
   504  }
   505  
   506  func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error {
   507  	w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
   508  	if err != nil {
   509  		return err
   510  	}
   511  
   512  	kind := info.Mapping.GroupVersionKind.Kind
   513  	c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout)
   514  
   515  	// What we watch for depends on the Kind.
   516  	// - For a Job, we watch for completion.
   517  	// - For all else, we watch until Ready.
   518  	// In the future, we might want to add some special logic for types
   519  	// like Ingress, Volume, etc.
   520  
   521  	_, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) {
   522  		switch e.Type {
   523  		case watch.Added, watch.Modified:
   524  			// For things like a secret or a config map, this is the best indicator
   525  			// we get. We care mostly about jobs, where what we want to see is
   526  			// the status go into a good state. For other types, like ReplicaSet
   527  			// we don't really do anything to support these as hooks.
   528  			c.Log("Add/Modify event for %s: %v", info.Name, e.Type)
   529  			if kind == "Job" {
   530  				return c.waitForJob(e, info.Name)
   531  			}
   532  			return true, nil
   533  		case watch.Deleted:
   534  			c.Log("Deleted event for %s", info.Name)
   535  			return true, nil
   536  		case watch.Error:
   537  			// Handle error and return with an error.
   538  			c.Log("Error event for %s", info.Name)
   539  			return true, fmt.Errorf("Failed to deploy %s", info.Name)
   540  		default:
   541  			return false, nil
   542  		}
   543  	})
   544  	return err
   545  }
   546  
   547  // AsVersionedObject converts a runtime.object to a versioned object.
   548  func (c *Client) AsVersionedObject(obj runtime.Object) (runtime.Object, error) {
   549  	json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	versions := &runtime.VersionedObjects{}
   554  	err = runtime.DecodeInto(c.Decoder(true), json, versions)
   555  	return versions.First(), err
   556  }
   557  
   558  // waitForJob is a helper that waits for a job to complete.
   559  //
   560  // This operates on an event returned from a watcher.
   561  func (c *Client) waitForJob(e watch.Event, name string) (bool, error) {
   562  	o, ok := e.Object.(*batchinternal.Job)
   563  	if !ok {
   564  		return true, fmt.Errorf("Expected %s to be a *batch.Job, got %T", name, e.Object)
   565  	}
   566  
   567  	for _, c := range o.Status.Conditions {
   568  		if c.Type == batchinternal.JobComplete && c.Status == api.ConditionTrue {
   569  			return true, nil
   570  		} else if c.Type == batchinternal.JobFailed && c.Status == api.ConditionTrue {
   571  			return true, fmt.Errorf("Job failed: %s", c.Reason)
   572  		}
   573  	}
   574  
   575  	c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded)
   576  	return false, nil
   577  }
   578  
   579  // scrubValidationError removes kubectl info from the message.
   580  func scrubValidationError(err error) error {
   581  	if err == nil {
   582  		return nil
   583  	}
   584  	const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false"
   585  
   586  	if strings.Contains(err.Error(), stopValidateMessage) {
   587  		return goerrors.New(strings.Replace(err.Error(), "; "+stopValidateMessage, "", -1))
   588  	}
   589  	return err
   590  }
   591  
   592  // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
   593  // and returns said phase (PodSucceeded or PodFailed qualify).
   594  func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) {
   595  	infos, err := c.Build(namespace, reader)
   596  	if err != nil {
   597  		return api.PodUnknown, err
   598  	}
   599  	info := infos[0]
   600  
   601  	kind := info.Mapping.GroupVersionKind.Kind
   602  	if kind != "Pod" {
   603  		return api.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name)
   604  	}
   605  
   606  	if err := c.watchPodUntilComplete(timeout, info); err != nil {
   607  		return api.PodUnknown, err
   608  	}
   609  
   610  	if err := info.Get(); err != nil {
   611  		return api.PodUnknown, err
   612  	}
   613  	status := info.Object.(*api.Pod).Status.Phase
   614  
   615  	return status, nil
   616  }
   617  
   618  func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error {
   619  	w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
   620  	if err != nil {
   621  		return err
   622  	}
   623  
   624  	c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout)
   625  	_, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) {
   626  		return conditions.PodCompleted(e)
   627  	})
   628  
   629  	return err
   630  }