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

     1  /*
     2  Copyright 2016 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package kube // import "k8s.io/helm/pkg/kube"
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	goerrors "errors"
    23  	"fmt"
    24  	"io"
    25  	"log"
    26  	"strings"
    27  	"time"
    28  
    29  	jsonpatch "github.com/evanphx/json-patch"
    30  	appsv1 "k8s.io/api/apps/v1"
    31  	appsv1beta1 "k8s.io/api/apps/v1beta1"
    32  	appsv1beta2 "k8s.io/api/apps/v1beta2"
    33  	batch "k8s.io/api/batch/v1"
    34  	"k8s.io/api/core/v1"
    35  	extv1beta1 "k8s.io/api/extensions/v1beta1"
    36  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    37  	"k8s.io/apimachinery/pkg/api/errors"
    38  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    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  	"k8s.io/kubernetes/pkg/kubectl"
    49  	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
    50  	"k8s.io/kubernetes/pkg/kubectl/resource"
    51  	"k8s.io/kubernetes/pkg/kubectl/validation"
    52  	"k8s.io/kubernetes/pkg/printers"
    53  )
    54  
    55  const (
    56  	// MissingGetHeader is added to Get's outout when a resource is not found.
    57  	MissingGetHeader = "==> MISSING\nKIND\t\tNAME\n"
    58  )
    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  	// SchemaCacheDir is the path for loading cached schema.
    67  	SchemaCacheDir string
    68  
    69  	Log func(string, ...interface{})
    70  }
    71  
    72  // New creates a new Client.
    73  func New(config clientcmd.ClientConfig) *Client {
    74  	return &Client{
    75  		Factory:        cmdutil.NewFactory(config),
    76  		SchemaCacheDir: clientcmd.RecommendedSchemaFile,
    77  		Log:            nopLogger,
    78  	}
    79  }
    80  
    81  var nopLogger = func(_ string, _ ...interface{}) {}
    82  
    83  // ResourceActorFunc performs an action on a single resource.
    84  type ResourceActorFunc func(*resource.Info) error
    85  
    86  // Create creates Kubernetes resources from an io.reader.
    87  //
    88  // Namespace will set the namespace.
    89  func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error {
    90  	client, err := c.ClientSet()
    91  	if err != nil {
    92  		return err
    93  	}
    94  	if err := ensureNamespace(client, namespace); err != nil {
    95  		return err
    96  	}
    97  	c.Log("building resources from manifest")
    98  	infos, buildErr := c.BuildUnstructured(namespace, reader)
    99  	if buildErr != nil {
   100  		return buildErr
   101  	}
   102  	c.Log("creating %d resource(s)", len(infos))
   103  	if err := perform(infos, createResource); err != nil {
   104  		return err
   105  	}
   106  	if shouldWait {
   107  		return c.waitForResources(time.Duration(timeout)*time.Second, infos)
   108  	}
   109  	return nil
   110  }
   111  
   112  func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result {
   113  	return c.NewBuilder().
   114  		Internal().
   115  		ContinueOnError().
   116  		Schema(c.validator()).
   117  		NamespaceParam(namespace).
   118  		DefaultNamespace().
   119  		Stream(reader, "").
   120  		Flatten().
   121  		Do()
   122  }
   123  
   124  func (c *Client) validator() validation.Schema {
   125  	schema, err := c.Validator(true)
   126  	if err != nil {
   127  		c.Log("warning: failed to load schema: %s", err)
   128  	}
   129  	return schema
   130  }
   131  
   132  // BuildUnstructured validates for Kubernetes objects and returns unstructured infos.
   133  func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) {
   134  	var result Result
   135  
   136  	result, err := c.NewBuilder().
   137  		Unstructured().
   138  		ContinueOnError().
   139  		NamespaceParam(namespace).
   140  		DefaultNamespace().
   141  		Stream(reader, "").
   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][]core.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  		objs[vk] = append(objs[vk], info.AsInternal())
   182  
   183  		//Get the relation pods
   184  		objPods, err = c.getSelectRelationPod(info, objPods)
   185  		if err != nil {
   186  			c.Log("Warning: get the relation pod is failed, err:%s", err.Error())
   187  		}
   188  
   189  		return nil
   190  	})
   191  	if err != nil {
   192  		return "", err
   193  	}
   194  
   195  	//here, we will add the objPods to the objs
   196  	for key, podItems := range objPods {
   197  		for i := range podItems {
   198  			objs[key+"(related)"] = append(objs[key+"(related)"], &podItems[i])
   199  		}
   200  	}
   201  
   202  	// Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so
   203  	// spin through them and print them. Printer is cool since it prints the header only when
   204  	// an object type changes, so we can just rely on that. Problem is it doesn't seem to keep
   205  	// track of tab widths.
   206  	buf := new(bytes.Buffer)
   207  	p, err := cmdutil.PrinterForOptions(&printers.PrintOptions{})
   208  	if err != nil {
   209  		return "", err
   210  	}
   211  	for t, ot := range objs {
   212  		if _, err = buf.WriteString("==> " + t + "\n"); err != nil {
   213  			return "", err
   214  		}
   215  		for _, o := range ot {
   216  			if err := p.PrintObj(o, buf); err != nil {
   217  				c.Log("failed to print object type %s, object: %q :\n %v", t, o, err)
   218  				return "", err
   219  			}
   220  		}
   221  		if _, err := buf.WriteString("\n"); err != nil {
   222  			return "", err
   223  		}
   224  	}
   225  	if len(missing) > 0 {
   226  		buf.WriteString(MissingGetHeader)
   227  		for _, s := range missing {
   228  			fmt.Fprintln(buf, s)
   229  		}
   230  	}
   231  	return buf.String(), nil
   232  }
   233  
   234  // Update reads in the current configuration and a target configuration from io.reader
   235  // and creates resources that don't already exists, updates resources that have been modified
   236  // in the target configuration and deletes resources from the current configuration that are
   237  // not present in the target configuration.
   238  //
   239  // Namespace will set the namespaces.
   240  func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error {
   241  	original, err := c.BuildUnstructured(namespace, originalReader)
   242  	if err != nil {
   243  		return fmt.Errorf("failed decoding reader into objects: %s", err)
   244  	}
   245  
   246  	c.Log("building resources from updated manifest")
   247  	target, err := c.BuildUnstructured(namespace, targetReader)
   248  	if err != nil {
   249  		return fmt.Errorf("failed decoding reader into objects: %s", err)
   250  	}
   251  
   252  	updateErrors := []string{}
   253  
   254  	c.Log("checking %d resources for changes", len(target))
   255  	err = target.Visit(func(info *resource.Info, err error) error {
   256  		if err != nil {
   257  			return err
   258  		}
   259  
   260  		helper := resource.NewHelper(info.Client, info.Mapping)
   261  		if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil {
   262  			if !errors.IsNotFound(err) {
   263  				return fmt.Errorf("Could not get information about the resource: %s", err)
   264  			}
   265  
   266  			// Since the resource does not exist, create it.
   267  			if err := createResource(info); err != nil {
   268  				return fmt.Errorf("failed to create resource: %s", err)
   269  			}
   270  
   271  			kind := info.Mapping.GroupVersionKind.Kind
   272  			c.Log("Created a new %s called %q\n", kind, info.Name)
   273  			return nil
   274  		}
   275  
   276  		originalInfo := original.Get(info)
   277  		if originalInfo == nil {
   278  			kind := info.Mapping.GroupVersionKind.Kind
   279  			return fmt.Errorf("no %s with the name %q found", kind, info.Name)
   280  		}
   281  
   282  		if err := updateResource(c, info, originalInfo.Object, force, recreate); err != nil {
   283  			c.Log("error updating the resource %q:\n\t %v", info.Name, err)
   284  			updateErrors = append(updateErrors, err.Error())
   285  		}
   286  
   287  		return nil
   288  	})
   289  
   290  	switch {
   291  	case err != nil:
   292  		return err
   293  	case len(updateErrors) != 0:
   294  		return fmt.Errorf(strings.Join(updateErrors, " && "))
   295  	}
   296  
   297  	for _, info := range original.Difference(target) {
   298  		c.Log("Deleting %q in %s...", info.Name, info.Namespace)
   299  		if err := deleteResource(c, info); err != nil {
   300  			c.Log("Failed to delete %q, err: %s", info.Name, err)
   301  		}
   302  	}
   303  	if shouldWait {
   304  		return c.waitForResources(time.Duration(timeout)*time.Second, target)
   305  	}
   306  	return nil
   307  }
   308  
   309  // Delete deletes Kubernetes resources from an io.reader.
   310  //
   311  // Namespace will set the namespace.
   312  func (c *Client) Delete(namespace string, reader io.Reader) error {
   313  	infos, err := c.BuildUnstructured(namespace, reader)
   314  	if err != nil {
   315  		return err
   316  	}
   317  	return perform(infos, func(info *resource.Info) error {
   318  		c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind)
   319  		err := deleteResource(c, info)
   320  		return c.skipIfNotFound(err)
   321  	})
   322  }
   323  
   324  func (c *Client) skipIfNotFound(err error) error {
   325  	if errors.IsNotFound(err) {
   326  		c.Log("%v", err)
   327  		return nil
   328  	}
   329  	return err
   330  }
   331  
   332  func (c *Client) watchTimeout(t time.Duration) ResourceActorFunc {
   333  	return func(info *resource.Info) error {
   334  		return c.watchUntilReady(t, info)
   335  	}
   336  }
   337  
   338  // WatchUntilReady watches the resource given in the reader, and waits until it is ready.
   339  //
   340  // This function is mainly for hook implementations. It watches for a resource to
   341  // hit a particular milestone. The milestone depends on the Kind.
   342  //
   343  // For most kinds, it checks to see if the resource is marked as Added or Modified
   344  // by the Kubernetes event stream. For some kinds, it does more:
   345  //
   346  // - Jobs: A job is marked "Ready" when it has successfully completed. This is
   347  //   ascertained by watching the Status fields in a job's output.
   348  //
   349  // Handling for other kinds will be added as necessary.
   350  func (c *Client) WatchUntilReady(namespace string, reader io.Reader, timeout int64, shouldWait bool) error {
   351  	infos, err := c.Build(namespace, reader)
   352  	if err != nil {
   353  		return err
   354  	}
   355  	// For jobs, there's also the option to do poll c.Jobs(namespace).Get():
   356  	// https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300
   357  	return perform(infos, c.watchTimeout(time.Duration(timeout)*time.Second))
   358  }
   359  
   360  func perform(infos Result, fn ResourceActorFunc) error {
   361  	if len(infos) == 0 {
   362  		return ErrNoObjectsVisited
   363  	}
   364  
   365  	for _, info := range infos {
   366  		if err := fn(info); err != nil {
   367  			return err
   368  		}
   369  	}
   370  	return nil
   371  }
   372  
   373  func createResource(info *resource.Info) error {
   374  	obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
   375  	if err != nil {
   376  		return err
   377  	}
   378  	return info.Refresh(obj, true)
   379  }
   380  
   381  func deleteResource(c *Client, info *resource.Info) error {
   382  	reaper, err := c.Reaper(info.Mapping)
   383  	if err != nil {
   384  		// If there is no reaper for this resources, delete it.
   385  		if kubectl.IsNoSuchReaperError(err) {
   386  			return resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name)
   387  		}
   388  		return err
   389  	}
   390  	c.Log("Using reaper for deleting %q", info.Name)
   391  	return reaper.Stop(info.Namespace, info.Name, 0, nil)
   392  }
   393  
   394  func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.PatchType, error) {
   395  	oldData, err := json.Marshal(current)
   396  	if err != nil {
   397  		return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %s", err)
   398  	}
   399  	newData, err := json.Marshal(target.Object)
   400  	if err != nil {
   401  		return nil, types.StrategicMergePatchType, fmt.Errorf("serializing target configuration: %s", err)
   402  	}
   403  
   404  	// While different objects need different merge types, the parent function
   405  	// that calls this does not try to create a patch when the data (first
   406  	// returned object) is nil. We can skip calculating the the merge type as
   407  	// the returned merge type is ignored.
   408  	if apiequality.Semantic.DeepEqual(oldData, newData) {
   409  		return nil, types.StrategicMergePatchType, nil
   410  	}
   411  
   412  	// Get a versioned object
   413  	versionedObject, err := target.Versioned()
   414  
   415  	// Unstructured objects, such as CRDs, may not have an not registered error
   416  	// returned from ConvertToVersion. Anything that's unstructured should
   417  	// use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported
   418  	// on objects like CRDs.
   419  	_, isUnstructured := versionedObject.(runtime.Unstructured)
   420  
   421  	switch {
   422  	case runtime.IsNotRegisteredError(err), isUnstructured:
   423  		// fall back to generic JSON merge patch
   424  		patch, err := jsonpatch.CreateMergePatch(oldData, newData)
   425  		return patch, types.MergePatchType, err
   426  	case err != nil:
   427  		return nil, types.StrategicMergePatchType, fmt.Errorf("failed to get versionedObject: %s", err)
   428  	default:
   429  		patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject)
   430  		return patch, types.StrategicMergePatchType, err
   431  	}
   432  }
   433  
   434  func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error {
   435  	patch, patchType, err := createPatch(target, currentObj)
   436  	if err != nil {
   437  		return fmt.Errorf("failed to create patch: %s", err)
   438  	}
   439  	if patch == nil {
   440  		c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name)
   441  		// This needs to happen to make sure that tiller has the latest info from the API
   442  		// Otherwise there will be no labels and other functions that use labels will panic
   443  		if err := target.Get(); err != nil {
   444  			return fmt.Errorf("error trying to refresh resource information: %v", err)
   445  		}
   446  	} else {
   447  		// send patch to server
   448  		helper := resource.NewHelper(target.Client, target.Mapping)
   449  
   450  		obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch)
   451  		if err != nil {
   452  			kind := target.Mapping.GroupVersionKind.Kind
   453  			log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err)
   454  
   455  			if force {
   456  				// Attempt to delete...
   457  				if err := deleteResource(c, target); err != nil {
   458  					return err
   459  				}
   460  				log.Printf("Deleted %s: %q", kind, target.Name)
   461  
   462  				// ... and recreate
   463  				if err := createResource(target); err != nil {
   464  					return fmt.Errorf("Failed to recreate resource: %s", err)
   465  				}
   466  				log.Printf("Created a new %s called %q\n", kind, target.Name)
   467  
   468  				// No need to refresh the target, as we recreated the resource based
   469  				// on it. In addition, it might not exist yet and a call to `Refresh`
   470  				// may fail.
   471  			} else {
   472  				log.Print("Use --force to force recreation of the resource")
   473  				return err
   474  			}
   475  		} else {
   476  			// When patch succeeds without needing to recreate, refresh target.
   477  			target.Refresh(obj, true)
   478  		}
   479  	}
   480  
   481  	if !recreate {
   482  		return nil
   483  	}
   484  
   485  	versioned := target.AsVersioned()
   486  	selector, ok := getSelectorFromObject(versioned)
   487  	if !ok {
   488  		return nil
   489  	}
   490  
   491  	client, err := c.ClientSet()
   492  	if err != nil {
   493  		return err
   494  	}
   495  
   496  	pods, err := client.Core().Pods(target.Namespace).List(metav1.ListOptions{
   497  		FieldSelector: fields.Everything().String(),
   498  		LabelSelector: labels.Set(selector).AsSelector().String(),
   499  	})
   500  	if err != nil {
   501  		return err
   502  	}
   503  
   504  	// Restart pods
   505  	for _, pod := range pods.Items {
   506  		c.Log("Restarting pod: %v/%v", pod.Namespace, pod.Name)
   507  
   508  		// Delete each pod for get them restarted with changed spec.
   509  		if err := client.Core().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil {
   510  			return err
   511  		}
   512  	}
   513  	return nil
   514  }
   515  
   516  func getSelectorFromObject(obj runtime.Object) (map[string]string, bool) {
   517  	switch typed := obj.(type) {
   518  
   519  	case *v1.ReplicationController:
   520  		return typed.Spec.Selector, true
   521  
   522  	case *extv1beta1.ReplicaSet:
   523  		return typed.Spec.Selector.MatchLabels, true
   524  	case *appsv1.ReplicaSet:
   525  		return typed.Spec.Selector.MatchLabels, true
   526  
   527  	case *extv1beta1.Deployment:
   528  		return typed.Spec.Selector.MatchLabels, true
   529  	case *appsv1beta1.Deployment:
   530  		return typed.Spec.Selector.MatchLabels, true
   531  	case *appsv1beta2.Deployment:
   532  		return typed.Spec.Selector.MatchLabels, true
   533  	case *appsv1.Deployment:
   534  		return typed.Spec.Selector.MatchLabels, true
   535  
   536  	case *extv1beta1.DaemonSet:
   537  		return typed.Spec.Selector.MatchLabels, true
   538  	case *appsv1beta2.DaemonSet:
   539  		return typed.Spec.Selector.MatchLabels, true
   540  	case *appsv1.DaemonSet:
   541  		return typed.Spec.Selector.MatchLabels, true
   542  
   543  	case *batch.Job:
   544  		return typed.Spec.Selector.MatchLabels, true
   545  
   546  	case *appsv1beta1.StatefulSet:
   547  		return typed.Spec.Selector.MatchLabels, true
   548  	case *appsv1beta2.StatefulSet:
   549  		return typed.Spec.Selector.MatchLabels, true
   550  	case *appsv1.StatefulSet:
   551  		return typed.Spec.Selector.MatchLabels, true
   552  
   553  	default:
   554  		return nil, false
   555  	}
   556  }
   557  
   558  func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error {
   559  	w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
   560  	if err != nil {
   561  		return err
   562  	}
   563  
   564  	kind := info.Mapping.GroupVersionKind.Kind
   565  	c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout)
   566  
   567  	// What we watch for depends on the Kind.
   568  	// - For a Job, we watch for completion.
   569  	// - For all else, we watch until Ready.
   570  	// In the future, we might want to add some special logic for types
   571  	// like Ingress, Volume, etc.
   572  
   573  	_, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) {
   574  		switch e.Type {
   575  		case watch.Added, watch.Modified:
   576  			// For things like a secret or a config map, this is the best indicator
   577  			// we get. We care mostly about jobs, where what we want to see is
   578  			// the status go into a good state. For other types, like ReplicaSet
   579  			// we don't really do anything to support these as hooks.
   580  			c.Log("Add/Modify event for %s: %v", info.Name, e.Type)
   581  			if kind == "Job" {
   582  				return c.waitForJob(e, info.Name)
   583  			}
   584  			return true, nil
   585  		case watch.Deleted:
   586  			c.Log("Deleted event for %s", info.Name)
   587  			return true, nil
   588  		case watch.Error:
   589  			// Handle error and return with an error.
   590  			c.Log("Error event for %s", info.Name)
   591  			return true, fmt.Errorf("Failed to deploy %s", info.Name)
   592  		default:
   593  			return false, nil
   594  		}
   595  	})
   596  	return err
   597  }
   598  
   599  // waitForJob is a helper that waits for a job to complete.
   600  //
   601  // This operates on an event returned from a watcher.
   602  func (c *Client) waitForJob(e watch.Event, name string) (bool, error) {
   603  	o, ok := e.Object.(*batchinternal.Job)
   604  	if !ok {
   605  		return true, fmt.Errorf("Expected %s to be a *batch.Job, got %T", name, e.Object)
   606  	}
   607  
   608  	for _, c := range o.Status.Conditions {
   609  		if c.Type == batchinternal.JobComplete && c.Status == core.ConditionTrue {
   610  			return true, nil
   611  		} else if c.Type == batchinternal.JobFailed && c.Status == core.ConditionTrue {
   612  			return true, fmt.Errorf("Job failed: %s", c.Reason)
   613  		}
   614  	}
   615  
   616  	c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded)
   617  	return false, nil
   618  }
   619  
   620  // scrubValidationError removes kubectl info from the message.
   621  func scrubValidationError(err error) error {
   622  	if err == nil {
   623  		return nil
   624  	}
   625  	const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false"
   626  
   627  	if strings.Contains(err.Error(), stopValidateMessage) {
   628  		return goerrors.New(strings.Replace(err.Error(), "; "+stopValidateMessage, "", -1))
   629  	}
   630  	return err
   631  }
   632  
   633  // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
   634  // and returns said phase (PodSucceeded or PodFailed qualify).
   635  func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) {
   636  	infos, err := c.Build(namespace, reader)
   637  	if err != nil {
   638  		return core.PodUnknown, err
   639  	}
   640  	info := infos[0]
   641  
   642  	kind := info.Mapping.GroupVersionKind.Kind
   643  	if kind != "Pod" {
   644  		return core.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name)
   645  	}
   646  
   647  	if err := c.watchPodUntilComplete(timeout, info); err != nil {
   648  		return core.PodUnknown, err
   649  	}
   650  
   651  	if err := info.Get(); err != nil {
   652  		return core.PodUnknown, err
   653  	}
   654  	status := info.Object.(*core.Pod).Status.Phase
   655  
   656  	return status, nil
   657  }
   658  
   659  func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error {
   660  	w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
   661  	if err != nil {
   662  		return err
   663  	}
   664  
   665  	c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout)
   666  	_, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) {
   667  		return isPodComplete(e)
   668  	})
   669  
   670  	return err
   671  }
   672  
   673  func isPodComplete(event watch.Event) (bool, error) {
   674  	o, ok := event.Object.(*core.Pod)
   675  	if !ok {
   676  		return true, fmt.Errorf("expected a *core.Pod, got %T", event.Object)
   677  	}
   678  	if event.Type == watch.Deleted {
   679  		return false, fmt.Errorf("pod not found")
   680  	}
   681  	switch o.Status.Phase {
   682  	case core.PodFailed, core.PodSucceeded:
   683  		return true, nil
   684  	}
   685  	return false, nil
   686  }
   687  
   688  //get a kubernetes resources' relation pods
   689  // kubernetes resource used select labels to relate pods
   690  func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]core.Pod) (map[string][]core.Pod, error) {
   691  	if info == nil {
   692  		return objPods, nil
   693  	}
   694  
   695  	c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name)
   696  
   697  	versioned, err := info.Versioned()
   698  	switch {
   699  	case runtime.IsNotRegisteredError(err):
   700  		return objPods, nil
   701  	case err != nil:
   702  		return objPods, err
   703  	}
   704  
   705  	selector, ok := getSelectorFromObject(versioned)
   706  	if !ok {
   707  		return objPods, nil
   708  	}
   709  
   710  	client, _ := c.ClientSet()
   711  
   712  	pods, err := client.Core().Pods(info.Namespace).List(metav1.ListOptions{
   713  		FieldSelector: fields.Everything().String(),
   714  		LabelSelector: labels.Set(selector).AsSelector().String(),
   715  	})
   716  	if err != nil {
   717  		return objPods, err
   718  	}
   719  
   720  	for _, pod := range pods.Items {
   721  		if pod.APIVersion == "" {
   722  			pod.APIVersion = "v1"
   723  		}
   724  
   725  		if pod.Kind == "" {
   726  			pod.Kind = "Pod"
   727  		}
   728  		vk := pod.GroupVersionKind().Version + "/" + pod.GroupVersionKind().Kind
   729  
   730  		if !isFoundPod(objPods[vk], pod) {
   731  			objPods[vk] = append(objPods[vk], pod)
   732  		}
   733  	}
   734  	return objPods, nil
   735  }
   736  
   737  func isFoundPod(podItem []core.Pod, pod core.Pod) bool {
   738  	for _, value := range podItem {
   739  		if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) {
   740  			return true
   741  		}
   742  	}
   743  	return false
   744  }