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