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  }