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

     1  /*
     2  Copyright The Helm Authors.
     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  	"context"
    22  	"encoding/json"
    23  	goerrors "errors"
    24  	"fmt"
    25  	"io"
    26  	"log"
    27  	"strings"
    28  	"time"
    29  
    30  	jsonpatch "github.com/evanphx/json-patch"
    31  	appsv1 "k8s.io/api/apps/v1"
    32  	appsv1beta1 "k8s.io/api/apps/v1beta1"
    33  	appsv1beta2 "k8s.io/api/apps/v1beta2"
    34  	batch "k8s.io/api/batch/v1"
    35  	"k8s.io/api/core/v1"
    36  	extv1beta1 "k8s.io/api/extensions/v1beta1"
    37  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    38  	"k8s.io/apimachinery/pkg/api/errors"
    39  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    40  	"k8s.io/apimachinery/pkg/labels"
    41  	"k8s.io/apimachinery/pkg/runtime"
    42  	"k8s.io/apimachinery/pkg/runtime/schema"
    43  	"k8s.io/apimachinery/pkg/types"
    44  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    45  	"k8s.io/apimachinery/pkg/watch"
    46  	"k8s.io/cli-runtime/pkg/genericclioptions"
    47  	"k8s.io/cli-runtime/pkg/genericclioptions/resource"
    48  	"k8s.io/client-go/kubernetes/scheme"
    49  	watchtools "k8s.io/client-go/tools/watch"
    50  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    51  	"k8s.io/kubernetes/pkg/apis/core"
    52  	"k8s.io/kubernetes/pkg/kubectl/cmd/get"
    53  	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
    54  	"k8s.io/kubernetes/pkg/kubectl/validation"
    55  )
    56  
    57  // MissingGetHeader is added to Get's output when a resource is not found.
    58  const MissingGetHeader = "==> MISSING\nKIND\t\tNAME\n"
    59  
    60  // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found.
    61  var ErrNoObjectsVisited = goerrors.New("no objects visited")
    62  
    63  // Client represents a client capable of communicating with the Kubernetes API.
    64  type Client struct {
    65  	cmdutil.Factory
    66  	Log func(string, ...interface{})
    67  }
    68  
    69  // New creates a new Client.
    70  func New(getter genericclioptions.RESTClientGetter) *Client {
    71  	if getter == nil {
    72  		getter = genericclioptions.NewConfigFlags()
    73  	}
    74  	return &Client{
    75  		Factory: cmdutil.NewFactory(getter),
    76  		Log:     nopLogger,
    77  	}
    78  }
    79  
    80  var nopLogger = func(_ string, _ ...interface{}) {}
    81  
    82  // ResourceActorFunc performs an action on a single resource.
    83  type ResourceActorFunc func(*resource.Info) error
    84  
    85  // Create creates Kubernetes resources from an io.reader.
    86  //
    87  // Namespace will set the namespace.
    88  func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error {
    89  	client, err := c.KubernetesClientSet()
    90  	if err != nil {
    91  		return err
    92  	}
    93  	if err := ensureNamespace(client, namespace); err != nil {
    94  		return err
    95  	}
    96  	c.Log("building resources from manifest")
    97  	infos, buildErr := c.BuildUnstructured(namespace, reader)
    98  	if buildErr != nil {
    99  		return buildErr
   100  	}
   101  	c.Log("creating %d resource(s)", len(infos))
   102  	if err := perform(infos, createResource); err != nil {
   103  		return err
   104  	}
   105  	if shouldWait {
   106  		return c.waitForResources(time.Duration(timeout)*time.Second, infos)
   107  	}
   108  	return nil
   109  }
   110  
   111  func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result {
   112  	return c.NewBuilder().
   113  		ContinueOnError().
   114  		WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
   115  		Schema(c.validator()).
   116  		NamespaceParam(namespace).
   117  		DefaultNamespace().
   118  		Stream(reader, "").
   119  		Flatten().
   120  		Do()
   121  }
   122  
   123  func (c *Client) validator() validation.Schema {
   124  	schema, err := c.Validator(true)
   125  	if err != nil {
   126  		c.Log("warning: failed to load schema: %s", err)
   127  	}
   128  	return schema
   129  }
   130  
   131  // BuildUnstructured validates for Kubernetes objects and returns unstructured infos.
   132  func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) {
   133  	var result Result
   134  
   135  	result, err := c.NewBuilder().
   136  		Unstructured().
   137  		ContinueOnError().
   138  		NamespaceParam(namespace).
   139  		DefaultNamespace().
   140  		Stream(reader, "").
   141  		Schema(c.validator()).
   142  		Flatten().
   143  		Do().Infos()
   144  	return result, scrubValidationError(err)
   145  }
   146  
   147  // Build validates for Kubernetes objects and returns resource Infos from a io.Reader.
   148  func (c *Client) Build(namespace string, reader io.Reader) (Result, error) {
   149  	var result Result
   150  	result, err := c.newBuilder(namespace, reader).Infos()
   151  	return result, scrubValidationError(err)
   152  }
   153  
   154  // Get gets Kubernetes resources as pretty-printed string.
   155  //
   156  // Namespace will set the namespace.
   157  func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
   158  	// Since we don't know what order the objects come in, let's group them by the types, so
   159  	// that when we print them, they come out looking good (headers apply to subgroups, etc.).
   160  	objs := make(map[string][]runtime.Object)
   161  	infos, err := c.BuildUnstructured(namespace, reader)
   162  	if err != nil {
   163  		return "", err
   164  	}
   165  
   166  	var objPods = make(map[string][]v1.Pod)
   167  
   168  	missing := []string{}
   169  	err = perform(infos, func(info *resource.Info) error {
   170  		c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name)
   171  		if err := info.Get(); err != nil {
   172  			c.Log("WARNING: Failed Get for resource %q: %s", info.Name, err)
   173  			missing = append(missing, fmt.Sprintf("%v\t\t%s", info.Mapping.Resource, info.Name))
   174  			return nil
   175  		}
   176  
   177  		// Use APIVersion/Kind as grouping mechanism. I'm not sure if you can have multiple
   178  		// versions per cluster, but this certainly won't hurt anything, so let's be safe.
   179  		gvk := info.ResourceMapping().GroupVersionKind
   180  		vk := gvk.Version + "/" + gvk.Kind
   181  		internalObj, err := asInternal(info)
   182  		if err != nil {
   183  			// If the problem is just that the resource is not registered, don't print any
   184  			// error. This is normal for custom resources.
   185  			if !runtime.IsNotRegisteredError(err) {
   186  				c.Log("Warning: conversion to internal type failed: %v", err)
   187  			}
   188  			// Add the unstructured object in this situation. It will still get listed, just
   189  			// with less information.
   190  			objs[vk] = append(objs[vk], info.Object)
   191  		} else {
   192  			objs[vk] = append(objs[vk], internalObj)
   193  		}
   194  
   195  		//Get the relation pods
   196  		objPods, err = c.getSelectRelationPod(info, objPods)
   197  		if err != nil {
   198  			c.Log("Warning: get the relation pod is failed, err:%s", err.Error())
   199  		}
   200  
   201  		return nil
   202  	})
   203  	if err != nil {
   204  		return "", err
   205  	}
   206  
   207  	//here, we will add the objPods to the objs
   208  	for key, podItems := range objPods {
   209  		for i := range podItems {
   210  			pod := &core.Pod{}
   211  			legacyscheme.Scheme.Convert(&podItems[i], pod, nil)
   212  			objs[key+"(related)"] = append(objs[key+"(related)"], pod)
   213  		}
   214  	}
   215  
   216  	// Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so
   217  	// spin through them and print them. Printer is cool since it prints the header only when
   218  	// an object type changes, so we can just rely on that. Problem is it doesn't seem to keep
   219  	// track of tab widths.
   220  	buf := new(bytes.Buffer)
   221  	printFlags := get.NewHumanPrintFlags()
   222  	for t, ot := range objs {
   223  		if _, err = fmt.Fprintf(buf, "==> %s\n", t); err != nil {
   224  			return "", err
   225  		}
   226  		typePrinter, _ := printFlags.ToPrinter("")
   227  		for _, o := range ot {
   228  			if err := typePrinter.PrintObj(o, buf); err != nil {
   229  				c.Log("failed to print object type %s, object: %q :\n %v", t, o, err)
   230  				return "", err
   231  			}
   232  		}
   233  		if _, err := buf.WriteString("\n"); err != nil {
   234  			return "", err
   235  		}
   236  	}
   237  	if len(missing) > 0 {
   238  		buf.WriteString(MissingGetHeader)
   239  		for _, s := range missing {
   240  			fmt.Fprintln(buf, s)
   241  		}
   242  	}
   243  	return buf.String(), nil
   244  }
   245  
   246  // Update reads in the current configuration and a target configuration from io.reader
   247  // and creates resources that don't already exists, updates resources that have been modified
   248  // in the target configuration and deletes resources from the current configuration that are
   249  // not present in the target configuration.
   250  //
   251  // Namespace will set the namespaces.
   252  func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error {
   253  	original, err := c.BuildUnstructured(namespace, originalReader)
   254  	if err != nil {
   255  		return fmt.Errorf("failed decoding reader into objects: %s", err)
   256  	}
   257  
   258  	c.Log("building resources from updated manifest")
   259  	target, err := c.BuildUnstructured(namespace, targetReader)
   260  	if err != nil {
   261  		return fmt.Errorf("failed decoding reader into objects: %s", err)
   262  	}
   263  
   264  	updateErrors := []string{}
   265  
   266  	c.Log("checking %d resources for changes", len(target))
   267  	err = target.Visit(func(info *resource.Info, err error) error {
   268  		if err != nil {
   269  			return err
   270  		}
   271  
   272  		helper := resource.NewHelper(info.Client, info.Mapping)
   273  		if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil {
   274  			if !errors.IsNotFound(err) {
   275  				return fmt.Errorf("Could not get information about the resource: %s", err)
   276  			}
   277  
   278  			// Since the resource does not exist, create it.
   279  			if err := createResource(info); err != nil {
   280  				return fmt.Errorf("failed to create resource: %s", err)
   281  			}
   282  
   283  			kind := info.Mapping.GroupVersionKind.Kind
   284  			c.Log("Created a new %s called %q\n", kind, info.Name)
   285  			return nil
   286  		}
   287  
   288  		originalInfo := original.Get(info)
   289  		if originalInfo == nil {
   290  			kind := info.Mapping.GroupVersionKind.Kind
   291  			return fmt.Errorf("no %s with the name %q found", kind, info.Name)
   292  		}
   293  
   294  		if err := updateResource(c, info, originalInfo.Object, force, recreate); err != nil {
   295  			c.Log("error updating the resource %q:\n\t %v", info.Name, err)
   296  			updateErrors = append(updateErrors, err.Error())
   297  		}
   298  
   299  		return nil
   300  	})
   301  
   302  	switch {
   303  	case err != nil:
   304  		return err
   305  	case len(updateErrors) != 0:
   306  		return fmt.Errorf(strings.Join(updateErrors, " && "))
   307  	}
   308  
   309  	for _, info := range original.Difference(target) {
   310  		c.Log("Deleting %q in %s...", info.Name, info.Namespace)
   311  		if err := deleteResource(info); err != nil {
   312  			c.Log("Failed to delete %q, err: %s", info.Name, err)
   313  		}
   314  	}
   315  	if shouldWait {
   316  		return c.waitForResources(time.Duration(timeout)*time.Second, target)
   317  	}
   318  	return nil
   319  }
   320  
   321  // Delete deletes Kubernetes resources from an io.reader.
   322  //
   323  // Namespace will set the namespace.
   324  func (c *Client) Delete(namespace string, reader io.Reader) error {
   325  	infos, err := c.BuildUnstructured(namespace, reader)
   326  	if err != nil {
   327  		return err
   328  	}
   329  	return perform(infos, func(info *resource.Info) error {
   330  		c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind)
   331  		err := deleteResource(info)
   332  		return c.skipIfNotFound(err)
   333  	})
   334  }
   335  
   336  func (c *Client) skipIfNotFound(err error) error {
   337  	if errors.IsNotFound(err) {
   338  		c.Log("%v", err)
   339  		return nil
   340  	}
   341  	return err
   342  }
   343  
   344  func (c *Client) watchTimeout(t time.Duration) ResourceActorFunc {
   345  	return func(info *resource.Info) error {
   346  		return c.watchUntilReady(t, info)
   347  	}
   348  }
   349  
   350  // WatchUntilReady watches the resource given in the reader, and waits until it is ready.
   351  //
   352  // This function is mainly for hook implementations. It watches for a resource to
   353  // hit a particular milestone. The milestone depends on the Kind.
   354  //
   355  // For most kinds, it checks to see if the resource is marked as Added or Modified
   356  // by the Kubernetes event stream. For some kinds, it does more:
   357  //
   358  // - Jobs: A job is marked "Ready" when it has successfully completed. This is
   359  //   ascertained by watching the Status fields in a job's output.
   360  //
   361  // Handling for other kinds will be added as necessary.
   362  func (c *Client) WatchUntilReady(namespace string, reader io.Reader, timeout int64, shouldWait bool) error {
   363  	infos, err := c.BuildUnstructured(namespace, reader)
   364  	if err != nil {
   365  		return err
   366  	}
   367  	// For jobs, there's also the option to do poll c.Jobs(namespace).Get():
   368  	// https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300
   369  	return perform(infos, c.watchTimeout(time.Duration(timeout)*time.Second))
   370  }
   371  
   372  func perform(infos Result, fn ResourceActorFunc) error {
   373  	if len(infos) == 0 {
   374  		return ErrNoObjectsVisited
   375  	}
   376  
   377  	for _, info := range infos {
   378  		if err := fn(info); err != nil {
   379  			return err
   380  		}
   381  	}
   382  	return nil
   383  }
   384  
   385  func createResource(info *resource.Info) error {
   386  	obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil)
   387  	if err != nil {
   388  		return err
   389  	}
   390  	return info.Refresh(obj, true)
   391  }
   392  
   393  func deleteResource(info *resource.Info) error {
   394  	policy := metav1.DeletePropagationBackground
   395  	opts := &metav1.DeleteOptions{PropagationPolicy: &policy}
   396  	_, err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, opts)
   397  	return err
   398  }
   399  
   400  func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.PatchType, error) {
   401  	oldData, err := json.Marshal(current)
   402  	if err != nil {
   403  		return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %s", err)
   404  	}
   405  	newData, err := json.Marshal(target.Object)
   406  	if err != nil {
   407  		return nil, types.StrategicMergePatchType, fmt.Errorf("serializing target configuration: %s", err)
   408  	}
   409  
   410  	// While different objects need different merge types, the parent function
   411  	// that calls this does not try to create a patch when the data (first
   412  	// returned object) is nil. We can skip calculating the merge type as
   413  	// the returned merge type is ignored.
   414  	if apiequality.Semantic.DeepEqual(oldData, newData) {
   415  		return nil, types.StrategicMergePatchType, nil
   416  	}
   417  
   418  	// Get a versioned object
   419  	versionedObject := asVersioned(target)
   420  
   421  	// Unstructured objects, such as CRDs, may not have an not registered error
   422  	// returned from ConvertToVersion. Anything that's unstructured should
   423  	// use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported
   424  	// on objects like CRDs.
   425  	_, isUnstructured := versionedObject.(runtime.Unstructured)
   426  
   427  	switch {
   428  	case runtime.IsNotRegisteredError(err), isUnstructured:
   429  		// fall back to generic JSON merge patch
   430  		patch, err := jsonpatch.CreateMergePatch(oldData, newData)
   431  		return patch, types.MergePatchType, err
   432  	case err != nil:
   433  		return nil, types.StrategicMergePatchType, fmt.Errorf("failed to get versionedObject: %s", err)
   434  	default:
   435  		patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject)
   436  		return patch, types.StrategicMergePatchType, err
   437  	}
   438  }
   439  
   440  func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error {
   441  	patch, patchType, err := createPatch(target, currentObj)
   442  	if err != nil {
   443  		return fmt.Errorf("failed to create patch: %s", err)
   444  	}
   445  	if patch == nil {
   446  		c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name)
   447  		// This needs to happen to make sure that tiller has the latest info from the API
   448  		// Otherwise there will be no labels and other functions that use labels will panic
   449  		if err := target.Get(); err != nil {
   450  			return fmt.Errorf("error trying to refresh resource information: %v", err)
   451  		}
   452  	} else {
   453  		// send patch to server
   454  		helper := resource.NewHelper(target.Client, target.Mapping)
   455  
   456  		obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch, nil)
   457  		if err != nil {
   458  			kind := target.Mapping.GroupVersionKind.Kind
   459  			log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err)
   460  
   461  			if force {
   462  				// Attempt to delete...
   463  				if err := deleteResource(target); err != nil {
   464  					return err
   465  				}
   466  				log.Printf("Deleted %s: %q", kind, target.Name)
   467  
   468  				// ... and recreate
   469  				if err := createResource(target); err != nil {
   470  					return fmt.Errorf("Failed to recreate resource: %s", err)
   471  				}
   472  				log.Printf("Created a new %s called %q\n", kind, target.Name)
   473  
   474  				// No need to refresh the target, as we recreated the resource based
   475  				// on it. In addition, it might not exist yet and a call to `Refresh`
   476  				// may fail.
   477  			} else {
   478  				log.Print("Use --force to force recreation of the resource")
   479  				return err
   480  			}
   481  		} else {
   482  			// When patch succeeds without needing to recreate, refresh target.
   483  			target.Refresh(obj, true)
   484  		}
   485  	}
   486  
   487  	if !recreate {
   488  		return nil
   489  	}
   490  
   491  	versioned := asVersioned(target)
   492  	selector, ok := getSelectorFromObject(versioned)
   493  	if !ok {
   494  		return nil
   495  	}
   496  
   497  	client, err := c.KubernetesClientSet()
   498  	if err != nil {
   499  		return err
   500  	}
   501  
   502  	pods, err := client.CoreV1().Pods(target.Namespace).List(metav1.ListOptions{
   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.CoreV1().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, bool) {
   522  	switch typed := obj.(type) {
   523  
   524  	case *v1.ReplicationController:
   525  		return typed.Spec.Selector, true
   526  
   527  	case *extv1beta1.ReplicaSet:
   528  		return typed.Spec.Selector.MatchLabels, true
   529  	case *appsv1.ReplicaSet:
   530  		return typed.Spec.Selector.MatchLabels, true
   531  
   532  	case *extv1beta1.Deployment:
   533  		return typed.Spec.Selector.MatchLabels, true
   534  	case *appsv1beta1.Deployment:
   535  		return typed.Spec.Selector.MatchLabels, true
   536  	case *appsv1beta2.Deployment:
   537  		return typed.Spec.Selector.MatchLabels, true
   538  	case *appsv1.Deployment:
   539  		return typed.Spec.Selector.MatchLabels, true
   540  
   541  	case *extv1beta1.DaemonSet:
   542  		return typed.Spec.Selector.MatchLabels, true
   543  	case *appsv1beta2.DaemonSet:
   544  		return typed.Spec.Selector.MatchLabels, true
   545  	case *appsv1.DaemonSet:
   546  		return typed.Spec.Selector.MatchLabels, true
   547  
   548  	case *batch.Job:
   549  		return typed.Spec.Selector.MatchLabels, true
   550  
   551  	case *appsv1beta1.StatefulSet:
   552  		return typed.Spec.Selector.MatchLabels, true
   553  	case *appsv1beta2.StatefulSet:
   554  		return typed.Spec.Selector.MatchLabels, true
   555  	case *appsv1.StatefulSet:
   556  		return typed.Spec.Selector.MatchLabels, true
   557  
   558  	default:
   559  		return nil, false
   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  	ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
   579  	defer cancel()
   580  	_, err = watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) {
   581  		switch e.Type {
   582  		case watch.Added, watch.Modified:
   583  			// For things like a secret or a config map, this is the best indicator
   584  			// we get. We care mostly about jobs, where what we want to see is
   585  			// the status go into a good state. For other types, like ReplicaSet
   586  			// we don't really do anything to support these as hooks.
   587  			c.Log("Add/Modify event for %s: %v", info.Name, e.Type)
   588  			if kind == "Job" {
   589  				return c.waitForJob(e, info.Name)
   590  			}
   591  			return true, nil
   592  		case watch.Deleted:
   593  			c.Log("Deleted event for %s", info.Name)
   594  			return true, nil
   595  		case watch.Error:
   596  			// Handle error and return with an error.
   597  			c.Log("Error event for %s", info.Name)
   598  			return true, fmt.Errorf("Failed to deploy %s", info.Name)
   599  		default:
   600  			return false, nil
   601  		}
   602  	})
   603  	return err
   604  }
   605  
   606  // waitForJob is a helper that waits for a job to complete.
   607  //
   608  // This operates on an event returned from a watcher.
   609  func (c *Client) waitForJob(e watch.Event, name string) (bool, error) {
   610  	job := &batch.Job{}
   611  	err := legacyscheme.Scheme.Convert(e.Object, job, nil)
   612  	if err != nil {
   613  		return true, err
   614  	}
   615  
   616  	for _, c := range job.Status.Conditions {
   617  		if c.Type == batch.JobComplete && c.Status == v1.ConditionTrue {
   618  			return true, nil
   619  		} else if c.Type == batch.JobFailed && c.Status == v1.ConditionTrue {
   620  			return true, fmt.Errorf("Job failed: %s", c.Reason)
   621  		}
   622  	}
   623  
   624  	c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, job.Status.Active, job.Status.Failed, job.Status.Succeeded)
   625  	return false, nil
   626  }
   627  
   628  // scrubValidationError removes kubectl info from the message.
   629  func scrubValidationError(err error) error {
   630  	if err == nil {
   631  		return nil
   632  	}
   633  	const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false"
   634  
   635  	if strings.Contains(err.Error(), stopValidateMessage) {
   636  		return goerrors.New(strings.Replace(err.Error(), "; "+stopValidateMessage, "", -1))
   637  	}
   638  	return err
   639  }
   640  
   641  // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
   642  // and returns said phase (PodSucceeded or PodFailed qualify).
   643  func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (v1.PodPhase, error) {
   644  	infos, err := c.Build(namespace, reader)
   645  	if err != nil {
   646  		return v1.PodUnknown, err
   647  	}
   648  	info := infos[0]
   649  
   650  	kind := info.Mapping.GroupVersionKind.Kind
   651  	if kind != "Pod" {
   652  		return v1.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name)
   653  	}
   654  
   655  	if err := c.watchPodUntilComplete(timeout, info); err != nil {
   656  		return v1.PodUnknown, err
   657  	}
   658  
   659  	if err := info.Get(); err != nil {
   660  		return v1.PodUnknown, err
   661  	}
   662  	status := info.Object.(*v1.Pod).Status.Phase
   663  
   664  	return status, nil
   665  }
   666  
   667  func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error {
   668  	w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
   669  	if err != nil {
   670  		return err
   671  	}
   672  
   673  	c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout)
   674  	ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
   675  	defer cancel()
   676  	_, err = watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) {
   677  		return isPodComplete(e)
   678  	})
   679  
   680  	return err
   681  }
   682  
   683  func isPodComplete(event watch.Event) (bool, error) {
   684  	o, ok := event.Object.(*v1.Pod)
   685  	if !ok {
   686  		return true, fmt.Errorf("expected a *v1.Pod, got %T", event.Object)
   687  	}
   688  	if event.Type == watch.Deleted {
   689  		return false, fmt.Errorf("pod not found")
   690  	}
   691  	switch o.Status.Phase {
   692  	case v1.PodFailed, v1.PodSucceeded:
   693  		return true, nil
   694  	}
   695  	return false, nil
   696  }
   697  
   698  //get a kubernetes resources' relation pods
   699  // kubernetes resource used select labels to relate pods
   700  func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]v1.Pod) (map[string][]v1.Pod, error) {
   701  	if info == nil {
   702  		return objPods, nil
   703  	}
   704  
   705  	c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name)
   706  
   707  	versioned := asVersioned(info)
   708  	selector, ok := getSelectorFromObject(versioned)
   709  	if !ok {
   710  		return objPods, nil
   711  	}
   712  
   713  	client, _ := c.KubernetesClientSet()
   714  
   715  	pods, err := client.CoreV1().Pods(info.Namespace).List(metav1.ListOptions{
   716  		LabelSelector: labels.Set(selector).AsSelector().String(),
   717  	})
   718  	if err != nil {
   719  		return objPods, err
   720  	}
   721  
   722  	for _, pod := range pods.Items {
   723  		vk := "v1/Pod"
   724  		if !isFoundPod(objPods[vk], pod) {
   725  			objPods[vk] = append(objPods[vk], pod)
   726  		}
   727  	}
   728  	return objPods, nil
   729  }
   730  
   731  func isFoundPod(podItem []v1.Pod, pod v1.Pod) bool {
   732  	for _, value := range podItem {
   733  		if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) {
   734  			return true
   735  		}
   736  	}
   737  	return false
   738  }
   739  
   740  func asVersioned(info *resource.Info) runtime.Object {
   741  	converter := runtime.ObjectConvertor(scheme.Scheme)
   742  	groupVersioner := runtime.GroupVersioner(schema.GroupVersions(scheme.Scheme.PrioritizedVersionsAllGroups()))
   743  	if info.Mapping != nil {
   744  		groupVersioner = info.Mapping.GroupVersionKind.GroupVersion()
   745  	}
   746  
   747  	if obj, err := converter.ConvertToVersion(info.Object, groupVersioner); err == nil {
   748  		return obj
   749  	}
   750  	return info.Object
   751  }
   752  
   753  func asInternal(info *resource.Info) (runtime.Object, error) {
   754  	groupVersioner := info.Mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
   755  	return legacyscheme.Scheme.ConvertToVersion(info.Object, groupVersioner)
   756  }