github.com/danielqsj/helm@v2.0.0-alpha.4.0.20160908204436-976e0ba5199b+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  	"fmt"
    22  	"io"
    23  	"log"
    24  	"reflect"
    25  	"strings"
    26  	"time"
    27  
    28  	"k8s.io/kubernetes/pkg/api"
    29  	"k8s.io/kubernetes/pkg/api/errors"
    30  	"k8s.io/kubernetes/pkg/api/unversioned"
    31  	"k8s.io/kubernetes/pkg/apimachinery/registered"
    32  	"k8s.io/kubernetes/pkg/apis/batch"
    33  	unversionedclient "k8s.io/kubernetes/pkg/client/unversioned"
    34  	"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
    35  	"k8s.io/kubernetes/pkg/kubectl"
    36  	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
    37  	"k8s.io/kubernetes/pkg/kubectl/resource"
    38  	"k8s.io/kubernetes/pkg/runtime"
    39  	"k8s.io/kubernetes/pkg/util/strategicpatch"
    40  	"k8s.io/kubernetes/pkg/util/yaml"
    41  	"k8s.io/kubernetes/pkg/watch"
    42  )
    43  
    44  // Client represents a client capable of communicating with the Kubernetes API.
    45  type Client struct {
    46  	*cmdutil.Factory
    47  }
    48  
    49  // New create a new Client
    50  func New(config clientcmd.ClientConfig) *Client {
    51  	return &Client{
    52  		Factory: cmdutil.NewFactory(config),
    53  	}
    54  }
    55  
    56  // ResourceActorFunc performs an action on a single resource.
    57  type ResourceActorFunc func(*resource.Info) error
    58  
    59  // APIClient returns a Kubernetes API client.
    60  //
    61  // This is necessary because cmdutil.Client is a field, not a method, which
    62  // means it can't satisfy an interface's method requirement. In order to ensure
    63  // that an implementation of environment.KubeClient can access the raw API client,
    64  // it is necessary to add this method.
    65  func (c *Client) APIClient() (unversionedclient.Interface, error) {
    66  	return c.Client()
    67  }
    68  
    69  // Create creates kubernetes resources from an io.reader
    70  //
    71  // Namespace will set the namespace
    72  func (c *Client) Create(namespace string, reader io.Reader) error {
    73  	if err := c.ensureNamespace(namespace); err != nil {
    74  		return err
    75  	}
    76  	return perform(c, namespace, reader, createResource)
    77  }
    78  
    79  // Get gets kubernetes resources as pretty printed string
    80  //
    81  // Namespace will set the namespace
    82  func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
    83  	// Since we don't know what order the objects come in, let's group them by the types, so
    84  	// that when we print them, they come looking good (headers apply to subgroups, etc.)
    85  	objs := make(map[string][]runtime.Object)
    86  	err := perform(c, namespace, reader, func(info *resource.Info) error {
    87  		log.Printf("Doing get for: '%s'", info.Name)
    88  		obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name, info.Export)
    89  		if err != nil {
    90  			return err
    91  		}
    92  		// We need to grab the ObjectReference so we can correctly group the objects.
    93  		or, err := api.GetReference(obj)
    94  		if err != nil {
    95  			log.Printf("FAILED GetReference for: %#v\n%v", obj, err)
    96  			return err
    97  		}
    98  
    99  		// Use APIVersion/Kind as grouping mechanism. I'm not sure if you can have multiple
   100  		// versions per cluster, but this certainly won't hurt anything, so let's be safe.
   101  		objType := or.APIVersion + "/" + or.Kind
   102  		objs[objType] = append(objs[objType], obj)
   103  		return nil
   104  	})
   105  
   106  	// Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so
   107  	// spin through them and print them. Printer is cool since it prints the header only when
   108  	// an object type changes, so we can just rely on that. Problem is it doesn't seem to keep
   109  	// track of tab widths
   110  	buf := new(bytes.Buffer)
   111  	p := kubectl.NewHumanReadablePrinter(false, false, false, false, false, false, []string{})
   112  	for t, ot := range objs {
   113  		_, err = buf.WriteString("==> " + t + "\n")
   114  		if err != nil {
   115  			return "", err
   116  		}
   117  		for _, o := range ot {
   118  			err = p.PrintObj(o, buf)
   119  			if err != nil {
   120  				log.Printf("failed to print object type '%s', object: '%s' :\n %v", t, o, err)
   121  				return "", err
   122  			}
   123  		}
   124  		_, err := buf.WriteString("\n")
   125  		if err != nil {
   126  			return "", err
   127  		}
   128  	}
   129  	return buf.String(), err
   130  }
   131  
   132  // Update reads in the current configuration and a target configuration from io.reader
   133  //  and creates resources that don't already exists, updates resources that have been modified
   134  //  in the target configuration and deletes resources from the current configuration that are
   135  //  not present in the target configuration
   136  //
   137  // Namespace will set the namespaces
   138  func (c *Client) Update(namespace string, currentReader, targetReader io.Reader) error {
   139  	current := c.NewBuilder(includeThirdPartyAPIs).
   140  		ContinueOnError().
   141  		NamespaceParam(namespace).
   142  		DefaultNamespace().
   143  		Stream(currentReader, "").
   144  		Flatten().
   145  		Do()
   146  
   147  	target := c.NewBuilder(includeThirdPartyAPIs).
   148  		ContinueOnError().
   149  		NamespaceParam(namespace).
   150  		DefaultNamespace().
   151  		Stream(targetReader, "").
   152  		Flatten().
   153  		Do()
   154  
   155  	currentInfos, err := current.Infos()
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	targetInfos := []*resource.Info{}
   161  	updateErrors := []string{}
   162  
   163  	err = target.Visit(func(info *resource.Info, err error) error {
   164  		targetInfos = append(targetInfos, info)
   165  		if err != nil {
   166  			return err
   167  		}
   168  		resourceName := info.Name
   169  
   170  		helper := resource.NewHelper(info.Client, info.Mapping)
   171  		if _, err := helper.Get(info.Namespace, resourceName, info.Export); err != nil {
   172  			if !errors.IsNotFound(err) {
   173  				return fmt.Errorf("Could not get information about the resource: err: %s", err)
   174  			}
   175  
   176  			// Since the resource does not exist, create it.
   177  			if err := createResource(info); err != nil {
   178  				return err
   179  			}
   180  
   181  			kind := info.Mapping.GroupVersionKind.Kind
   182  			log.Printf("Created a new %s called %s\n", kind, resourceName)
   183  			return nil
   184  		}
   185  
   186  		currentObj, err := getCurrentObject(resourceName, currentInfos)
   187  		if err != nil {
   188  			return err
   189  		}
   190  
   191  		if err := updateResource(info, currentObj); err != nil {
   192  			log.Printf("error updating the resource %s:\n\t %v", resourceName, err)
   193  			updateErrors = append(updateErrors, err.Error())
   194  		}
   195  
   196  		return nil
   197  	})
   198  
   199  	deleteUnwantedResources(currentInfos, targetInfos)
   200  
   201  	if err != nil {
   202  		return err
   203  	} else if len(updateErrors) != 0 {
   204  		return fmt.Errorf(strings.Join(updateErrors, " && "))
   205  
   206  	}
   207  
   208  	return nil
   209  
   210  }
   211  
   212  // Delete deletes kubernetes resources from an io.reader
   213  //
   214  // Namespace will set the namespace
   215  func (c *Client) Delete(namespace string, reader io.Reader) error {
   216  	return perform(c, namespace, reader, func(info *resource.Info) error {
   217  		log.Printf("Starting delete for %s", info.Name)
   218  
   219  		reaper, err := c.Reaper(info.Mapping)
   220  		if err != nil {
   221  			// If there is no reaper for this resources, delete it.
   222  			if kubectl.IsNoSuchReaperError(err) {
   223  				err := resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name)
   224  				return skipIfNotFound(err)
   225  			}
   226  
   227  			return err
   228  		}
   229  
   230  		log.Printf("Using reaper for deleting %s", info.Name)
   231  		err = reaper.Stop(info.Namespace, info.Name, 0, nil)
   232  		return skipIfNotFound(err)
   233  	})
   234  }
   235  
   236  func skipIfNotFound(err error) error {
   237  	if err != nil && errors.IsNotFound(err) {
   238  		log.Printf("%v", err)
   239  		return nil
   240  	}
   241  
   242  	return err
   243  }
   244  
   245  // WatchUntilReady watches the resource given in the reader, and waits until it is ready.
   246  //
   247  // This function is mainly for hook implementations. It watches for a resource to
   248  // hit a particular milestone. The milestone depends on the Kind.
   249  //
   250  // For most kinds, it checks to see if the resource is marked as Added or Modified
   251  // by the Kubernetes event stream. For some kinds, it does more:
   252  //
   253  // - Jobs: A job is marked "Ready" when it has successfully completed. This is
   254  //   ascertained by watching the Status fields in a job's output.
   255  //
   256  // Handling for other kinds will be added as necessary.
   257  func (c *Client) WatchUntilReady(namespace string, reader io.Reader) error {
   258  	// For jobs, there's also the option to do poll c.Jobs(namespace).Get():
   259  	// https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300
   260  	return perform(c, namespace, reader, watchUntilReady)
   261  }
   262  
   263  const includeThirdPartyAPIs = false
   264  
   265  func perform(c *Client, namespace string, reader io.Reader, fn ResourceActorFunc) error {
   266  	r := c.NewBuilder(includeThirdPartyAPIs).
   267  		ContinueOnError().
   268  		NamespaceParam(namespace).
   269  		DefaultNamespace().
   270  		Stream(reader, "").
   271  		Flatten().
   272  		Do()
   273  
   274  	if r.Err() != nil {
   275  		return r.Err()
   276  	}
   277  
   278  	count := 0
   279  	err := r.Visit(func(info *resource.Info, err error) error {
   280  		if err != nil {
   281  			return err
   282  		}
   283  		err = fn(info)
   284  
   285  		if err == nil {
   286  			count++
   287  		}
   288  		return err
   289  	})
   290  
   291  	if err != nil {
   292  		return err
   293  	}
   294  	if count == 0 {
   295  		return fmt.Errorf("no objects passed to create")
   296  	}
   297  	return nil
   298  }
   299  
   300  func createResource(info *resource.Info) error {
   301  	_, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
   302  	return err
   303  }
   304  
   305  func deleteResource(info *resource.Info) error {
   306  	return resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name)
   307  }
   308  
   309  func updateResource(target *resource.Info, currentObj runtime.Object) error {
   310  
   311  	encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...)
   312  	originalSerialization, err := runtime.Encode(encoder, currentObj)
   313  	if err != nil {
   314  		return err
   315  	}
   316  
   317  	editedSerialization, err := runtime.Encode(encoder, target.Object)
   318  	if err != nil {
   319  		return err
   320  	}
   321  
   322  	originalJS, err := yaml.ToJSON(originalSerialization)
   323  	if err != nil {
   324  		return err
   325  	}
   326  
   327  	editedJS, err := yaml.ToJSON(editedSerialization)
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	if reflect.DeepEqual(originalJS, editedJS) {
   333  		return fmt.Errorf("Looks like there are no changes for %s", target.Name)
   334  	}
   335  
   336  	patch, err := strategicpatch.CreateStrategicMergePatch(originalJS, editedJS, currentObj)
   337  	if err != nil {
   338  		return err
   339  	}
   340  
   341  	// send patch to server
   342  	helper := resource.NewHelper(target.Client, target.Mapping)
   343  	if _, err = helper.Patch(target.Namespace, target.Name, api.StrategicMergePatchType, patch); err != nil {
   344  		return err
   345  	}
   346  
   347  	return nil
   348  }
   349  
   350  func watchUntilReady(info *resource.Info) error {
   351  	w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
   352  	if err != nil {
   353  		return err
   354  	}
   355  
   356  	kind := info.Mapping.GroupVersionKind.Kind
   357  	log.Printf("Watching for changes to %s %s", kind, info.Name)
   358  	timeout := time.Minute * 5
   359  
   360  	// What we watch for depends on the Kind.
   361  	// - For a Job, we watch for completion.
   362  	// - For all else, we watch until Ready.
   363  	// In the future, we might want to add some special logic for types
   364  	// like Ingress, Volume, etc.
   365  
   366  	_, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) {
   367  		switch e.Type {
   368  		case watch.Added, watch.Modified:
   369  			// For things like a secret or a config map, this is the best indicator
   370  			// we get. We care mostly about jobs, where what we want to see is
   371  			// the status go into a good state. For other types, like ReplicaSet
   372  			// we don't really do anything to support these as hooks.
   373  			log.Printf("Add/Modify event for %s: %v", info.Name, e.Type)
   374  			if kind == "Job" {
   375  				return waitForJob(e, info.Name)
   376  			}
   377  			return true, nil
   378  		case watch.Deleted:
   379  			log.Printf("Deleted event for %s", info.Name)
   380  			return true, nil
   381  		case watch.Error:
   382  			// Handle error and return with an error.
   383  			log.Printf("Error event for %s", info.Name)
   384  			return true, fmt.Errorf("Failed to deploy %s", info.Name)
   385  		default:
   386  			return false, nil
   387  		}
   388  	})
   389  	return err
   390  }
   391  
   392  // waitForJob is a helper that waits for a job to complete.
   393  //
   394  // This operates on an event returned from a watcher.
   395  func waitForJob(e watch.Event, name string) (bool, error) {
   396  	o, ok := e.Object.(*batch.Job)
   397  	if !ok {
   398  		return true, fmt.Errorf("Expected %s to be a *batch.Job, got %T", name, o)
   399  	}
   400  
   401  	for _, c := range o.Status.Conditions {
   402  		if c.Type == batch.JobComplete && c.Status == api.ConditionTrue {
   403  			return true, nil
   404  		} else if c.Type == batch.JobFailed && c.Status == api.ConditionTrue {
   405  			return true, fmt.Errorf("Job failed: %s", c.Reason)
   406  		}
   407  	}
   408  
   409  	log.Printf("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded)
   410  	return false, nil
   411  }
   412  
   413  func (c *Client) ensureNamespace(namespace string) error {
   414  	client, err := c.Client()
   415  	if err != nil {
   416  		return err
   417  	}
   418  
   419  	ns := &api.Namespace{}
   420  	ns.Name = namespace
   421  	_, err = client.Namespaces().Create(ns)
   422  	if err != nil && !errors.IsAlreadyExists(err) {
   423  		return err
   424  	}
   425  	return nil
   426  }
   427  
   428  func deleteUnwantedResources(currentInfos, targetInfos []*resource.Info) {
   429  	for _, cInfo := range currentInfos {
   430  		found := false
   431  		for _, m := range targetInfos {
   432  			if m.Name == cInfo.Name {
   433  				found = true
   434  			}
   435  		}
   436  		if !found {
   437  			log.Printf("Deleting %s...", cInfo.Name)
   438  			if err := deleteResource(cInfo); err != nil {
   439  				log.Printf("Failed to delete %s, err: %s", cInfo.Name, err)
   440  			}
   441  		}
   442  	}
   443  }
   444  
   445  func getCurrentObject(targetName string, infos []*resource.Info) (runtime.Object, error) {
   446  	var curr *resource.Info
   447  	for _, currInfo := range infos {
   448  		if currInfo.Name == targetName {
   449  			curr = currInfo
   450  		}
   451  	}
   452  
   453  	if curr == nil {
   454  		return nil, fmt.Errorf("No resource with the name %s found.", targetName)
   455  	}
   456  
   457  	encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...)
   458  	defaultVersion := unversioned.GroupVersion{}
   459  	return resource.AsVersionedObject([]*resource.Info{curr}, false, defaultVersion, encoder)
   460  }