github.com/danielqsj/helm@v2.0.0-alpha.4.0.20160908204436-976e0ba5199b+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  	"encoding/base64"
    21  	"fmt"
    22  	"log"
    23  	"strconv"
    24  	"time"
    25  
    26  	"github.com/golang/protobuf/proto"
    27  
    28  	rspb "k8s.io/helm/pkg/proto/hapi/release"
    29  
    30  	"k8s.io/kubernetes/pkg/api"
    31  	kberrs "k8s.io/kubernetes/pkg/api/errors"
    32  	client "k8s.io/kubernetes/pkg/client/unversioned"
    33  )
    34  
    35  // ConfigMapsDriverName is the string name of the driver.
    36  const ConfigMapsDriverName = "ConfigMap"
    37  
    38  var b64 = base64.StdEncoding
    39  
    40  // labels is a map of key value pairs to be included as metadata in a configmap object.
    41  type labels map[string]string
    42  
    43  func (lbs *labels) init()                   { *lbs = labels(make(map[string]string)) }
    44  func (lbs labels) get(key string) string    { return lbs[key] }
    45  func (lbs labels) set(key, val string)      { lbs[key] = val }
    46  func (lbs labels) toMap() map[string]string { return lbs }
    47  
    48  // ConfigMaps is a wrapper around an implementation of a kubernetes
    49  // ConfigMapsInterface.
    50  type ConfigMaps struct {
    51  	impl client.ConfigMapsInterface
    52  }
    53  
    54  // NewConfigMaps initializes a new ConfigMaps wrapping an implmenetation of
    55  // the kubernetes ConfigMapsInterface.
    56  func NewConfigMaps(impl client.ConfigMapsInterface) *ConfigMaps {
    57  	return &ConfigMaps{impl: impl}
    58  }
    59  
    60  // Name returns the name of the driver.
    61  func (cfgmaps *ConfigMaps) Name() string {
    62  	return ConfigMapsDriverName
    63  }
    64  
    65  // Get fetches the release named by key. The corresponding release is returned
    66  // or error if not found.
    67  func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) {
    68  	// fetch the configmap holding the release named by key
    69  	obj, err := cfgmaps.impl.Get(key)
    70  	if err != nil {
    71  		if kberrs.IsNotFound(err) {
    72  			return nil, ErrReleaseNotFound
    73  		}
    74  
    75  		logerrf(err, "get: failed to get %q", key)
    76  		return nil, err
    77  	}
    78  	// found the configmap, decode the base64 data string
    79  	r, err := decodeRelease(obj.Data["release"])
    80  	if err != nil {
    81  		logerrf(err, "get: failed to decode data %q", key)
    82  		return nil, err
    83  	}
    84  	// return the release object
    85  	return r, nil
    86  }
    87  
    88  // List fetches all releases and returns the list releases such
    89  // that filter(release) == true. An error is returned if the
    90  // configmap fails to retrieve the releases.
    91  func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
    92  	list, err := cfgmaps.impl.List(api.ListOptions{})
    93  	if err != nil {
    94  		logerrf(err, "list: failed to list")
    95  		return nil, err
    96  	}
    97  
    98  	var results []*rspb.Release
    99  
   100  	// iterate over the configmaps object list
   101  	// and decode each release
   102  	for _, item := range list.Items {
   103  		rls, err := decodeRelease(item.Data["release"])
   104  		if err != nil {
   105  			logerrf(err, "list: failed to decode release: %s", rls)
   106  			continue
   107  		}
   108  		if filter(rls) {
   109  			results = append(results, rls)
   110  		}
   111  	}
   112  	return results, nil
   113  }
   114  
   115  // Create creates a new ConfigMap holding the release. If the
   116  // ConfigMap already exists, ErrReleaseExists is returned.
   117  func (cfgmaps *ConfigMaps) Create(rls *rspb.Release) error {
   118  	// set labels for configmaps object meta data
   119  	var lbs labels
   120  
   121  	lbs.init()
   122  	lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix())))
   123  
   124  	// create a new configmap to hold the release
   125  	obj, err := newConfigMapsObject(rls, lbs)
   126  	if err != nil {
   127  		logerrf(err, "create: failed to encode release %q", rls.Name)
   128  		return err
   129  	}
   130  	// push the configmap object out into the kubiverse
   131  	if _, err := cfgmaps.impl.Create(obj); err != nil {
   132  		if kberrs.IsAlreadyExists(err) {
   133  			return ErrReleaseExists
   134  		}
   135  
   136  		logerrf(err, "create: failed to create")
   137  		return err
   138  	}
   139  	return nil
   140  }
   141  
   142  // Update updates the ConfigMap holding the release. If not found
   143  // the ConfigMap is created to hold the release.
   144  func (cfgmaps *ConfigMaps) Update(rls *rspb.Release) error {
   145  	// set labels for configmaps object meta data
   146  	var lbs labels
   147  
   148  	lbs.init()
   149  	lbs.set("MODIFIED_AT", strconv.Itoa(int(time.Now().Unix())))
   150  
   151  	// create a new configmap object to hold the release
   152  	obj, err := newConfigMapsObject(rls, lbs)
   153  	if err != nil {
   154  		logerrf(err, "update: failed to encode release %q", rls.Name)
   155  		return err
   156  	}
   157  	// push the configmap object out into the kubiverse
   158  	_, err = cfgmaps.impl.Update(obj)
   159  	if err != nil {
   160  		logerrf(err, "update: failed to update")
   161  		return err
   162  	}
   163  	return nil
   164  }
   165  
   166  // Delete deletes the ConfigMap holding the release named by key.
   167  func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) {
   168  	// fetch the release to check existence
   169  	if rls, err = cfgmaps.Get(key); err != nil {
   170  		if kberrs.IsNotFound(err) {
   171  			return nil, ErrReleaseNotFound
   172  		}
   173  
   174  		logerrf(err, "delete: failed to get release %q", rls.Name)
   175  		return nil, err
   176  	}
   177  	// delete the release
   178  	if err = cfgmaps.impl.Delete(key); err != nil {
   179  		return rls, err
   180  	}
   181  	return rls, nil
   182  }
   183  
   184  // newConfigMapsObject constructs a kubernetes ConfigMap object
   185  // to store a release. Each configmap data entry is the base64
   186  // encoded string of a release's binary protobuf encoding.
   187  //
   188  // The following labels are used within each configmap:
   189  //
   190  //    "MODIFIED_AT"    - timestamp indicating when this configmap was last modified. (set in Update)
   191  //    "CREATED_AT"     - timestamp indicating when this configmap was created. (set in Create)
   192  //    "VERSION"        - version of the release.
   193  //    "STATUS"         - status of the release (see proto/hapi/release.status.pb.go for variants)
   194  //    "OWNER"          - owner of the configmap, currently "TILLER".
   195  //    "NAME"           - name of the release.
   196  //
   197  func newConfigMapsObject(rls *rspb.Release, lbs labels) (*api.ConfigMap, error) {
   198  	const owner = "TILLER"
   199  
   200  	// encode the release
   201  	s, err := encodeRelease(rls)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	if lbs == nil {
   207  		lbs.init()
   208  	}
   209  
   210  	// apply labels
   211  	lbs.set("NAME", rls.Name)
   212  	lbs.set("OWNER", owner)
   213  	lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)])
   214  	lbs.set("VERSION", strconv.Itoa(int(rls.Version)))
   215  
   216  	// create and return configmap object
   217  	return &api.ConfigMap{
   218  		ObjectMeta: api.ObjectMeta{
   219  			Name:   rls.Name,
   220  			Labels: lbs.toMap(),
   221  		},
   222  		Data: map[string]string{"release": s},
   223  	}, nil
   224  }
   225  
   226  // encodeRelease encodes a release returning a base64 encoded
   227  // binary protobuf encoding representation, or error.
   228  func encodeRelease(rls *rspb.Release) (string, error) {
   229  	b, err := proto.Marshal(rls)
   230  	if err != nil {
   231  		return "", err
   232  	}
   233  	return b64.EncodeToString(b), nil
   234  }
   235  
   236  // decodeRelease decodes the bytes in data into a release
   237  // type. Data must contain a base64 encoded string of a
   238  // valid protobuf encoding of a release, otherwise
   239  // an error is returned.
   240  func decodeRelease(data string) (*rspb.Release, error) {
   241  	// base64 decode string
   242  	b, err := b64.DecodeString(data)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	var rls rspb.Release
   248  	// unmarshal protobuf bytes
   249  	if err := proto.Unmarshal(b, &rls); err != nil {
   250  		return nil, err
   251  	}
   252  	return &rls, nil
   253  }
   254  
   255  // logerrf wraps an error with the a formatted string (used for debugging)
   256  func logerrf(err error, format string, args ...interface{}) {
   257  	log.Printf("configmaps: %s: %s\n", fmt.Sprintf(format, args...), err)
   258  }