github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+incompatible/pkg/storage/driver/cfgmaps.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 driver // import "k8s.io/helm/pkg/storage/driver"
    18  
    19  import (
    20  	"bytes"
    21  	"compress/gzip"
    22  	"encoding/base64"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"log"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/golang/protobuf/proto"
    31  	"k8s.io/kubernetes/pkg/api"
    32  	kberrs "k8s.io/kubernetes/pkg/api/errors"
    33  	"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
    34  	kblabels "k8s.io/kubernetes/pkg/labels"
    35  	"k8s.io/kubernetes/pkg/util/validation"
    36  
    37  	rspb "k8s.io/helm/pkg/proto/hapi/release"
    38  )
    39  
    40  var _ Driver = (*ConfigMaps)(nil)
    41  
    42  // ConfigMapsDriverName is the string name of the driver.
    43  const ConfigMapsDriverName = "ConfigMap"
    44  
    45  var b64 = base64.StdEncoding
    46  
    47  var magicGzip = []byte{0x1f, 0x8b, 0x08}
    48  
    49  // ConfigMaps is a wrapper around an implementation of a kubernetes
    50  // ConfigMapsInterface.
    51  type ConfigMaps struct {
    52  	impl internalversion.ConfigMapInterface
    53  }
    54  
    55  // NewConfigMaps initializes a new ConfigMaps wrapping an implmenetation of
    56  // the kubernetes ConfigMapsInterface.
    57  func NewConfigMaps(impl internalversion.ConfigMapInterface) *ConfigMaps {
    58  	return &ConfigMaps{impl: impl}
    59  }
    60  
    61  // Name returns the name of the driver.
    62  func (cfgmaps *ConfigMaps) Name() string {
    63  	return ConfigMapsDriverName
    64  }
    65  
    66  // Get fetches the release named by key. The corresponding release is returned
    67  // or error if not found.
    68  func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) {
    69  	// fetch the configmap holding the release named by key
    70  	obj, err := cfgmaps.impl.Get(key)
    71  	if err != nil {
    72  		if kberrs.IsNotFound(err) {
    73  			return nil, ErrReleaseNotFound
    74  		}
    75  
    76  		logerrf(err, "get: failed to get %q", key)
    77  		return nil, err
    78  	}
    79  	// found the configmap, decode the base64 data string
    80  	r, err := decodeRelease(obj.Data["release"])
    81  	if err != nil {
    82  		logerrf(err, "get: failed to decode data %q", key)
    83  		return nil, err
    84  	}
    85  	// return the release object
    86  	return r, nil
    87  }
    88  
    89  // List fetches all releases and returns the list releases such
    90  // that filter(release) == true. An error is returned if the
    91  // configmap fails to retrieve the releases.
    92  func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
    93  	lsel := kblabels.Set{"OWNER": "TILLER"}.AsSelector()
    94  	opts := api.ListOptions{LabelSelector: lsel}
    95  
    96  	list, err := cfgmaps.impl.List(opts)
    97  	if err != nil {
    98  		logerrf(err, "list: failed to list")
    99  		return nil, err
   100  	}
   101  
   102  	var results []*rspb.Release
   103  
   104  	// iterate over the configmaps object list
   105  	// and decode each release
   106  	for _, item := range list.Items {
   107  		rls, err := decodeRelease(item.Data["release"])
   108  		if err != nil {
   109  			logerrf(err, "list: failed to decode release: %v", item)
   110  			continue
   111  		}
   112  		if filter(rls) {
   113  			results = append(results, rls)
   114  		}
   115  	}
   116  	return results, nil
   117  }
   118  
   119  // Query fetches all releases that match the provided map of labels.
   120  // An error is returned if the configmap fails to retrieve the releases.
   121  func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, error) {
   122  	ls := kblabels.Set{}
   123  	for k, v := range labels {
   124  		if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
   125  			return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
   126  		}
   127  		ls[k] = v
   128  	}
   129  
   130  	opts := api.ListOptions{LabelSelector: ls.AsSelector()}
   131  
   132  	list, err := cfgmaps.impl.List(opts)
   133  	if err != nil {
   134  		logerrf(err, "query: failed to query with labels")
   135  		return nil, err
   136  	}
   137  
   138  	if len(list.Items) == 0 {
   139  		return nil, ErrReleaseNotFound
   140  	}
   141  
   142  	var results []*rspb.Release
   143  	for _, item := range list.Items {
   144  		rls, err := decodeRelease(item.Data["release"])
   145  		if err != nil {
   146  			logerrf(err, "query: failed to decode release: %s", err)
   147  			continue
   148  		}
   149  		results = append(results, rls)
   150  	}
   151  	return results, nil
   152  }
   153  
   154  // Create creates a new ConfigMap holding the release. If the
   155  // ConfigMap already exists, ErrReleaseExists is returned.
   156  func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error {
   157  	// set labels for configmaps object meta data
   158  	var lbs labels
   159  
   160  	lbs.init()
   161  	lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix())))
   162  
   163  	// create a new configmap to hold the release
   164  	obj, err := newConfigMapsObject(key, rls, lbs)
   165  	if err != nil {
   166  		logerrf(err, "create: failed to encode release %q", rls.Name)
   167  		return err
   168  	}
   169  	// push the configmap object out into the kubiverse
   170  	if _, err := cfgmaps.impl.Create(obj); err != nil {
   171  		if kberrs.IsAlreadyExists(err) {
   172  			return ErrReleaseExists
   173  		}
   174  
   175  		logerrf(err, "create: failed to create")
   176  		return err
   177  	}
   178  	return nil
   179  }
   180  
   181  // Update updates the ConfigMap holding the release. If not found
   182  // the ConfigMap is created to hold the release.
   183  func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
   184  	// set labels for configmaps object meta data
   185  	var lbs labels
   186  
   187  	lbs.init()
   188  	lbs.set("MODIFIED_AT", strconv.Itoa(int(time.Now().Unix())))
   189  
   190  	// create a new configmap object to hold the release
   191  	obj, err := newConfigMapsObject(key, rls, lbs)
   192  	if err != nil {
   193  		logerrf(err, "update: failed to encode release %q", rls.Name)
   194  		return err
   195  	}
   196  	// push the configmap object out into the kubiverse
   197  	_, err = cfgmaps.impl.Update(obj)
   198  	if err != nil {
   199  		logerrf(err, "update: failed to update")
   200  		return err
   201  	}
   202  	return nil
   203  }
   204  
   205  // Delete deletes the ConfigMap holding the release named by key.
   206  func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) {
   207  	// fetch the release to check existence
   208  	if rls, err = cfgmaps.Get(key); err != nil {
   209  		if kberrs.IsNotFound(err) {
   210  			return nil, ErrReleaseNotFound
   211  		}
   212  
   213  		logerrf(err, "delete: failed to get release %q", key)
   214  		return nil, err
   215  	}
   216  	// delete the release
   217  	if err = cfgmaps.impl.Delete(key, &api.DeleteOptions{}); err != nil {
   218  		return rls, err
   219  	}
   220  	return rls, nil
   221  }
   222  
   223  // newConfigMapsObject constructs a kubernetes ConfigMap object
   224  // to store a release. Each configmap data entry is the base64
   225  // encoded string of a release's binary protobuf encoding.
   226  //
   227  // The following labels are used within each configmap:
   228  //
   229  //    "MODIFIED_AT"    - timestamp indicating when this configmap was last modified. (set in Update)
   230  //    "CREATED_AT"     - timestamp indicating when this configmap was created. (set in Create)
   231  //    "VERSION"        - version of the release.
   232  //    "STATUS"         - status of the release (see proto/hapi/release.status.pb.go for variants)
   233  //    "OWNER"          - owner of the configmap, currently "TILLER".
   234  //    "NAME"           - name of the release.
   235  //
   236  func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*api.ConfigMap, error) {
   237  	const owner = "TILLER"
   238  
   239  	// encode the release
   240  	s, err := encodeRelease(rls)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	if lbs == nil {
   246  		lbs.init()
   247  	}
   248  
   249  	// apply labels
   250  	lbs.set("NAME", rls.Name)
   251  	lbs.set("OWNER", owner)
   252  	lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)])
   253  	lbs.set("VERSION", strconv.Itoa(int(rls.Version)))
   254  
   255  	// create and return configmap object
   256  	return &api.ConfigMap{
   257  		ObjectMeta: api.ObjectMeta{
   258  			Name:   key,
   259  			Labels: lbs.toMap(),
   260  		},
   261  		Data: map[string]string{"release": s},
   262  	}, nil
   263  }
   264  
   265  // encodeRelease encodes a release returning a base64 encoded
   266  // gzipped binary protobuf encoding representation, or error.
   267  func encodeRelease(rls *rspb.Release) (string, error) {
   268  	b, err := proto.Marshal(rls)
   269  	if err != nil {
   270  		return "", err
   271  	}
   272  	var buf bytes.Buffer
   273  	w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
   274  	if err != nil {
   275  		return "", err
   276  	}
   277  	if _, err = w.Write(b); err != nil {
   278  		return "", err
   279  	}
   280  	w.Close()
   281  
   282  	return b64.EncodeToString(buf.Bytes()), nil
   283  }
   284  
   285  // decodeRelease decodes the bytes in data into a release
   286  // type. Data must contain a base64 encoded string of a
   287  // valid protobuf encoding of a release, otherwise
   288  // an error is returned.
   289  func decodeRelease(data string) (*rspb.Release, error) {
   290  	// base64 decode string
   291  	b, err := b64.DecodeString(data)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	// For backwards compatibility with releases that were stored before
   297  	// compression was introduced we skip decompression if the
   298  	// gzip magic header is not found
   299  	if bytes.Equal(b[0:3], magicGzip) {
   300  		r, err := gzip.NewReader(bytes.NewReader(b))
   301  		if err != nil {
   302  			return nil, err
   303  		}
   304  		b2, err := ioutil.ReadAll(r)
   305  		if err != nil {
   306  			return nil, err
   307  		}
   308  		b = b2
   309  	}
   310  
   311  	var rls rspb.Release
   312  	// unmarshal protobuf bytes
   313  	if err := proto.Unmarshal(b, &rls); err != nil {
   314  		return nil, err
   315  	}
   316  	return &rls, nil
   317  }
   318  
   319  // logerrf wraps an error with a formatted string (used for debugging)
   320  func logerrf(err error, format string, args ...interface{}) {
   321  	log.Printf("configmaps: %s: %s\n", fmt.Sprintf(format, args...), err)
   322  }