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