github.com/darkowlzz/helm@v2.5.1-0.20171213183701-6707fe0468d4+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  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(true).
   113  		ContinueOnError().
   114  		Schema(c.validator()).
   115  		NamespaceParam(namespace).
   116  		DefaultNamespace().
   117  		Stream(reader, "").
   118  		Flatten().
   119  		Do()
   120  }
   121  
   122  func (c *Client) validator() validation.Schema {
   123  	const openapi = false // only works on v1.8 clusters
   124  	schema, err := c.Validator(true, openapi, c.SchemaCacheDir)
   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  	b, err := c.NewUnstructuredBuilder(true)
   136  	if err != nil {
   137  		return result, err
   138  	}
   139  	result, err = b.ContinueOnError().
   140  		Schema(c.validator()).
   141  		NamespaceParam(namespace).
   142  		DefaultNamespace().
   143  		Stream(reader, "").
   144  		Flatten().
   145  		Do().Infos()
   146  	return result, scrubValidationError(err)
   147  }
   148  
   149  // Build validates for Kubernetes objects and returns resource Infos from a io.Reader.
   150  func (c *Client) Build(namespace string, reader io.Reader) (Result, error) {
   151  	var result Result
   152  	result, err := c.newBuilder(namespace, reader).Infos()
   153  	return result, scrubValidationError(err)
   154  }
   155  
   156  // Get gets Kubernetes resources as pretty-printed string.
   157  //
   158  // Namespace will set the namespace.
   159  func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
   160  	// Since we don't know what order the objects come in, let's group them by the types, so
   161  	// that when we print them, they come out looking good (headers apply to subgroups, etc.).
   162  	objs := make(map[string][]runtime.Object)
   163  	infos, err := c.BuildUnstructured(namespace, reader)
   164  	if err != nil {
   165  		return "", err
   166  	}
   167  
   168  	var objPods = make(map[string][]api.Pod)
   169  
   170  	missing := []string{}
   171  	err = perform(infos, func(info *resource.Info) error {
   172  		c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name)
   173  		if err := info.Get(); err != nil {
   174  			c.Log("WARNING: Failed Get for resource %q: %s", info.Name, err)
   175  			missing = append(missing, fmt.Sprintf("%v\t\t%s", info.Mapping.Resource, info.Name))
   176  			return nil
   177  		}
   178  
   179  		// Use APIVersion/Kind as grouping mechanism. I'm not sure if you can have multiple
   180  		// versions per cluster, but this certainly won't hurt anything, so let's be safe.
   181  		gvk := info.ResourceMapping().GroupVersionKind
   182  		vk := gvk.Version + "/" + gvk.Kind
   183  		objs[vk] = append(objs[vk], info.Object)
   184  
   185  		//Get the relation pods
   186  		objPods, err = c.getSelectRelationPod(info, objPods)
   187  		if err != nil {
   188  			c.Log("Warning: get the relation pod is failed, err:%s", err.Error())
   189  		}
   190  
   191  		return nil
   192  	})
   193  	if err != nil {
   194  		return "", err
   195  	}
   196  
   197  	//here, we will add the objPods to the objs
   198  	for key, podItems := range objPods {
   199  		for i := range podItems {
   200  			objs[key+"(related)"] = append(objs[key+"(related)"], &podItems[i])
   201  		}
   202  	}
   203  
   204  	// Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so
   205  	// spin through them and print them. Printer is cool since it prints the header only when
   206  	// an object type changes, so we can just rely on that. Problem is it doesn't seem to keep
   207  	// track of tab widths.
   208  	buf := new(bytes.Buffer)
   209  	p, _ := c.Printer(nil, printers.PrintOptions{})
   210  	for t, ot := range objs {
   211  		if _, err = buf.WriteString("==> " + t + "\n"); err != nil {
   212  			return "", err
   213  		}
   214  		for _, o := range ot {
   215  			if err := p.PrintObj(o, buf); err != nil {
   216  				c.Log("failed to print object type %s, object: %q :\n %v", t, o, err)
   217  				return "", err
   218  			}
   219  		}
   220  		if _, err := buf.WriteString("\n"); err != nil {
   221  			return "", err
   222  		}
   223  	}
   224  	if len(missing) > 0 {
   225  		buf.WriteString(MissingGetHeader)
   226  		for _, s := range missing {
   227  			fmt.Fprintln(buf, s)
   228  		}
   229  	}
   230  	return buf.String(), nil
   231  }
   232  
   233  // Update reads in the current configuration and a target configuration from io.reader
   234  // and creates resources that don't already exists, updates resources that have been modified
   235  // in the target configuration and deletes resources from the current configuration that are
   236  // not present in the target configuration.
   237  //
   238  // Namespace will set the namespaces.
   239  func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error {
   240  	original, err := c.BuildUnstructured(namespace, originalReader)
   241  	if err != nil {
   242  		return fmt.Errorf("failed decoding reader into objects: %s", err)
   243  	}
   244  
   245  	c.Log("building resources from updated manifest")
   246  	target, err := c.BuildUnstructured(namespace, targetReader)
   247  	if err != nil {
   248  		return fmt.Errorf("failed decoding reader into objects: %s", err)
   249  	}
   250  
   251  	updateErrors := []string{}
   252  
   253  	c.Log("checking %d resources for changes", len(target))
   254  	err = target.Visit(func(info *resource.Info, err error) error {
   255  		if err != nil {
   256  			return err
   257  		}
   258  
   259  		helper := resource.NewHelper(info.Client, info.Mapping)
   260  		if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil {
   261  			if !errors.IsNotFound(err) {
   262  				return fmt.Errorf("Could not get information about the resource: %s", err)
   263  			}
   264  
   265  			// Since the resource does not exist, create it.
   266  			if err := createResource(info); err != nil {
   267  				return fmt.Errorf("failed to create resource: %s", err)
   268  			}
   269  
   270  			kind := info.Mapping.GroupVersionKind.Kind
   271  			c.Log("Created a new %s called %q\n", kind, info.Name)
   272  			return nil
   273  		}
   274  
   275  		originalInfo := original.Get(info)
   276  		if originalInfo == nil {
   277  			return fmt.Errorf("no resource with the name %q found", 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  	if helper.Semantic.DeepEqual(oldData, newData) {
   403  		return nil, types.StrategicMergePatchType, nil
   404  	}
   405  
   406  	// Get a versioned object
   407  	versionedObject, err := api.Scheme.New(mapping.GroupVersionKind)
   408  	switch {
   409  	case runtime.IsNotRegisteredError(err):
   410  		// fall back to generic JSON merge patch
   411  		patch, err := jsonpatch.CreateMergePatch(oldData, newData)
   412  		return patch, types.MergePatchType, err
   413  	case err != nil:
   414  		return nil, types.StrategicMergePatchType, fmt.Errorf("failed to get versionedObject: %s", err)
   415  	default:
   416  		patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject)
   417  		return patch, types.StrategicMergePatchType, err
   418  	}
   419  }
   420  
   421  func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error {
   422  	patch, patchType, err := createPatch(target.Mapping, target.Object, currentObj)
   423  	if err != nil {
   424  		return fmt.Errorf("failed to create patch: %s", err)
   425  	}
   426  	if patch == nil {
   427  		c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name)
   428  		// This needs to happen to make sure that tiller has the latest info from the API
   429  		// Otherwise there will be no labels and other functions that use labels will panic
   430  		if err := target.Get(); err != nil {
   431  			return fmt.Errorf("error trying to refresh resource information: %v", err)
   432  		}
   433  		return nil
   434  	}
   435  
   436  	// send patch to server
   437  	helper := resource.NewHelper(target.Client, target.Mapping)
   438  
   439  	obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch)
   440  	if err != nil {
   441  		kind := target.Mapping.GroupVersionKind.Kind
   442  		log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err)
   443  
   444  		if force {
   445  			// Attempt to delete...
   446  			if err := deleteResource(c, target); err != nil {
   447  				return err
   448  			}
   449  			log.Printf("Deleted %s: %q", kind, target.Name)
   450  
   451  			// ... and recreate
   452  			if err := createResource(target); err != nil {
   453  				return fmt.Errorf("Failed to recreate resource: %s", err)
   454  			}
   455  			log.Printf("Created a new %s called %q\n", kind, target.Name)
   456  
   457  			// No need to refresh the target, as we recreated the resource based
   458  			// on it. In addition, it might not exist yet and a call to `Refresh`
   459  			// may fail.
   460  		} else {
   461  			log.Print("Use --force to force recreation of the resource")
   462  			return err
   463  		}
   464  	} else {
   465  		// When patch succeeds without needing to recreate, refresh target.
   466  		target.Refresh(obj, true)
   467  	}
   468  
   469  	if !recreate {
   470  		return nil
   471  	}
   472  
   473  	versioned, err := c.AsVersionedObject(target.Object)
   474  	if runtime.IsNotRegisteredError(err) {
   475  		return nil
   476  	}
   477  	if err != nil {
   478  		return err
   479  	}
   480  
   481  	selector, err := getSelectorFromObject(versioned)
   482  	if err != nil {
   483  		return nil
   484  	}
   485  
   486  	client, err := c.ClientSet()
   487  	if err != nil {
   488  		return err
   489  	}
   490  
   491  	pods, err := client.Core().Pods(target.Namespace).List(metav1.ListOptions{
   492  		FieldSelector: fields.Everything().String(),
   493  		LabelSelector: labels.Set(selector).AsSelector().String(),
   494  	})
   495  	if err != nil {
   496  		return err
   497  	}
   498  
   499  	// Restart pods
   500  	for _, pod := range pods.Items {
   501  		c.Log("Restarting pod: %v/%v", pod.Namespace, pod.Name)
   502  
   503  		// Delete each pod for get them restarted with changed spec.
   504  		if err := client.Core().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil {
   505  			return err
   506  		}
   507  	}
   508  	return nil
   509  }
   510  
   511  func getSelectorFromObject(obj runtime.Object) (map[string]string, error) {
   512  	switch typed := obj.(type) {
   513  	case *v1.ReplicationController:
   514  		return typed.Spec.Selector, nil
   515  	case *v1beta1.ReplicaSet:
   516  		return typed.Spec.Selector.MatchLabels, nil
   517  	case *v1beta1.Deployment:
   518  		return typed.Spec.Selector.MatchLabels, nil
   519  	case *v1beta1.DaemonSet:
   520  		return typed.Spec.Selector.MatchLabels, nil
   521  	case *batch.Job:
   522  		return typed.Spec.Selector.MatchLabels, nil
   523  	case *apps.StatefulSet:
   524  		return typed.Spec.Selector.MatchLabels, nil
   525  	default:
   526  		return nil, fmt.Errorf("Unsupported kind when getting selector: %v", obj)
   527  	}
   528  }
   529  
   530  func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error {
   531  	w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
   532  	if err != nil {
   533  		return err
   534  	}
   535  
   536  	kind := info.Mapping.GroupVersionKind.Kind
   537  	c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout)
   538  
   539  	// What we watch for depends on the Kind.
   540  	// - For a Job, we watch for completion.
   541  	// - For all else, we watch until Ready.
   542  	// In the future, we might want to add some special logic for types
   543  	// like Ingress, Volume, etc.
   544  
   545  	_, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) {
   546  		switch e.Type {
   547  		case watch.Added, watch.Modified:
   548  			// For things like a secret or a config map, this is the best indicator
   549  			// we get. We care mostly about jobs, where what we want to see is
   550  			// the status go into a good state. For other types, like ReplicaSet
   551  			// we don't really do anything to support these as hooks.
   552  			c.Log("Add/Modify event for %s: %v", info.Name, e.Type)
   553  			if kind == "Job" {
   554  				return c.waitForJob(e, info.Name)
   555  			}
   556  			return true, nil
   557  		case watch.Deleted:
   558  			c.Log("Deleted event for %s", info.Name)
   559  			return true, nil
   560  		case watch.Error:
   561  			// Handle error and return with an error.
   562  			c.Log("Error event for %s", info.Name)
   563  			return true, fmt.Errorf("Failed to deploy %s", info.Name)
   564  		default:
   565  			return false, nil
   566  		}
   567  	})
   568  	return err
   569  }
   570  
   571  // AsVersionedObject converts a runtime.object to a versioned object.
   572  func (c *Client) AsVersionedObject(obj runtime.Object) (runtime.Object, error) {
   573  	json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
   574  	if err != nil {
   575  		return nil, err
   576  	}
   577  	versions := &runtime.VersionedObjects{}
   578  	err = runtime.DecodeInto(c.Decoder(true), json, versions)
   579  	return versions.First(), err
   580  }
   581  
   582  // waitForJob is a helper that waits for a job to complete.
   583  //
   584  // This operates on an event returned from a watcher.
   585  func (c *Client) waitForJob(e watch.Event, name string) (bool, error) {
   586  	o, ok := e.Object.(*batchinternal.Job)
   587  	if !ok {
   588  		return true, fmt.Errorf("Expected %s to be a *batch.Job, got %T", name, e.Object)
   589  	}
   590  
   591  	for _, c := range o.Status.Conditions {
   592  		if c.Type == batchinternal.JobComplete && c.Status == api.ConditionTrue {
   593  			return true, nil
   594  		} else if c.Type == batchinternal.JobFailed && c.Status == api.ConditionTrue {
   595  			return true, fmt.Errorf("Job failed: %s", c.Reason)
   596  		}
   597  	}
   598  
   599  	c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded)
   600  	return false, nil
   601  }
   602  
   603  // scrubValidationError removes kubectl info from the message.
   604  func scrubValidationError(err error) error {
   605  	if err == nil {
   606  		return nil
   607  	}
   608  	const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false"
   609  
   610  	if strings.Contains(err.Error(), stopValidateMessage) {
   611  		return goerrors.New(strings.Replace(err.Error(), "; "+stopValidateMessage, "", -1))
   612  	}
   613  	return err
   614  }
   615  
   616  // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
   617  // and returns said phase (PodSucceeded or PodFailed qualify).
   618  func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) {
   619  	infos, err := c.Build(namespace, reader)
   620  	if err != nil {
   621  		return api.PodUnknown, err
   622  	}
   623  	info := infos[0]
   624  
   625  	kind := info.Mapping.GroupVersionKind.Kind
   626  	if kind != "Pod" {
   627  		return api.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name)
   628  	}
   629  
   630  	if err := c.watchPodUntilComplete(timeout, info); err != nil {
   631  		return api.PodUnknown, err
   632  	}
   633  
   634  	if err := info.Get(); err != nil {
   635  		return api.PodUnknown, err
   636  	}
   637  	status := info.Object.(*api.Pod).Status.Phase
   638  
   639  	return status, nil
   640  }
   641  
   642  func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error {
   643  	w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
   644  	if err != nil {
   645  		return err
   646  	}
   647  
   648  	c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout)
   649  	_, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) {
   650  		return conditions.PodCompleted(e)
   651  	})
   652  
   653  	return err
   654  }
   655  
   656  //get an kubernetes resources's relation pods
   657  // kubernetes resource used select labels to relate pods
   658  func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]api.Pod) (map[string][]api.Pod, error) {
   659  	if info == nil {
   660  		return objPods, nil
   661  	}
   662  
   663  	c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name)
   664  
   665  	versioned, err := c.AsVersionedObject(info.Object)
   666  	if runtime.IsNotRegisteredError(err) {
   667  		return objPods, nil
   668  	}
   669  	if err != nil {
   670  		return objPods, err
   671  	}
   672  
   673  	// We can ignore this error because it will only error if it isn't a type that doesn't
   674  	// have pods. In that case, we don't care
   675  	selector, _ := getSelectorFromObject(versioned)
   676  
   677  	selectorString := labels.Set(selector).AsSelector().String()
   678  
   679  	// If we have an empty selector, this likely is a service or config map, so bail out now
   680  	if selectorString == "" {
   681  		return objPods, nil
   682  	}
   683  
   684  	client, _ := c.ClientSet()
   685  
   686  	pods, err := client.Core().Pods(info.Namespace).List(metav1.ListOptions{
   687  		FieldSelector: fields.Everything().String(),
   688  		LabelSelector: labels.Set(selector).AsSelector().String(),
   689  	})
   690  	if err != nil {
   691  		return objPods, err
   692  	}
   693  
   694  	for _, pod := range pods.Items {
   695  		if pod.APIVersion == "" {
   696  			pod.APIVersion = "v1"
   697  		}
   698  
   699  		if pod.Kind == "" {
   700  			pod.Kind = "Pod"
   701  		}
   702  		vk := pod.GroupVersionKind().Version + "/" + pod.GroupVersionKind().Kind
   703  
   704  		if !isFoundPod(objPods[vk], pod) {
   705  			objPods[vk] = append(objPods[vk], pod)
   706  		}
   707  	}
   708  	return objPods, nil
   709  }
   710  
   711  func isFoundPod(podItem []api.Pod, pod api.Pod) bool {
   712  	for _, value := range podItem {
   713  		if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) {
   714  			return true
   715  		}
   716  	}
   717  	return false
   718  }