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