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