github.com/doitroot/helm@v3.0.0-beta.3+incompatible/pkg/storage/driver/cfgmaps.go (about)

     1  /*
     2  Copyright The Helm Authors.
     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 "helm.sh/helm/pkg/storage/driver"
    18  
    19  import (
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/pkg/errors"
    25  	v1 "k8s.io/api/core/v1"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	kblabels "k8s.io/apimachinery/pkg/labels"
    29  	"k8s.io/apimachinery/pkg/util/validation"
    30  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    31  
    32  	rspb "helm.sh/helm/pkg/release"
    33  )
    34  
    35  var _ Driver = (*ConfigMaps)(nil)
    36  
    37  // ConfigMapsDriverName is the string name of the driver.
    38  const ConfigMapsDriverName = "ConfigMap"
    39  
    40  // ConfigMaps is a wrapper around an implementation of a kubernetes
    41  // ConfigMapsInterface.
    42  type ConfigMaps struct {
    43  	impl corev1.ConfigMapInterface
    44  	Log  func(string, ...interface{})
    45  }
    46  
    47  // NewConfigMaps initializes a new ConfigMaps wrapping an implementation of
    48  // the kubernetes ConfigMapsInterface.
    49  func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps {
    50  	return &ConfigMaps{
    51  		impl: impl,
    52  		Log:  func(_ string, _ ...interface{}) {},
    53  	}
    54  }
    55  
    56  // Name returns the name of the driver.
    57  func (cfgmaps *ConfigMaps) Name() string {
    58  	return ConfigMapsDriverName
    59  }
    60  
    61  // Get fetches the release named by key. The corresponding release is returned
    62  // or error if not found.
    63  func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) {
    64  	// fetch the configmap holding the release named by key
    65  	obj, err := cfgmaps.impl.Get(key, metav1.GetOptions{})
    66  	if err != nil {
    67  		if apierrors.IsNotFound(err) {
    68  			return nil, ErrReleaseNotFound
    69  		}
    70  
    71  		cfgmaps.Log("get: failed to get %q: %s", key, err)
    72  		return nil, err
    73  	}
    74  	// found the configmap, decode the base64 data string
    75  	r, err := decodeRelease(obj.Data["release"])
    76  	if err != nil {
    77  		cfgmaps.Log("get: failed to decode data %q: %s", key, err)
    78  		return nil, err
    79  	}
    80  	// return the release object
    81  	return r, nil
    82  }
    83  
    84  // List fetches all releases and returns the list releases such
    85  // that filter(release) == true. An error is returned if the
    86  // configmap fails to retrieve the releases.
    87  func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
    88  	lsel := kblabels.Set{"owner": "helm"}.AsSelector()
    89  	opts := metav1.ListOptions{LabelSelector: lsel.String()}
    90  
    91  	list, err := cfgmaps.impl.List(opts)
    92  	if err != nil {
    93  		cfgmaps.Log("list: failed to list: %s", err)
    94  		return nil, err
    95  	}
    96  
    97  	var results []*rspb.Release
    98  
    99  	// iterate over the configmaps object list
   100  	// and decode each release
   101  	for _, item := range list.Items {
   102  		rls, err := decodeRelease(item.Data["release"])
   103  		if err != nil {
   104  			cfgmaps.Log("list: failed to decode release: %v: %s", item, err)
   105  			continue
   106  		}
   107  		if filter(rls) {
   108  			results = append(results, rls)
   109  		}
   110  	}
   111  	return results, nil
   112  }
   113  
   114  // Query fetches all releases that match the provided map of labels.
   115  // An error is returned if the configmap fails to retrieve the releases.
   116  func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, error) {
   117  	ls := kblabels.Set{}
   118  	for k, v := range labels {
   119  		if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
   120  			return nil, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
   121  		}
   122  		ls[k] = v
   123  	}
   124  
   125  	opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()}
   126  
   127  	list, err := cfgmaps.impl.List(opts)
   128  	if err != nil {
   129  		cfgmaps.Log("query: failed to query with labels: %s", err)
   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  			cfgmaps.Log("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("createdAt", 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  		cfgmaps.Log("create: failed to encode release %q: %s", rls.Name, err)
   162  		return err
   163  	}
   164  	// push the configmap object out into the kubiverse
   165  	if _, err := cfgmaps.impl.Create(obj); err != nil {
   166  		if apierrors.IsAlreadyExists(err) {
   167  			return ErrReleaseExists
   168  		}
   169  
   170  		cfgmaps.Log("create: failed to create: %s", err)
   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("modifiedAt", 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  		cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err)
   189  		return err
   190  	}
   191  	// push the configmap object out into the kubiverse
   192  	_, err = cfgmaps.impl.Update(obj)
   193  	if err != nil {
   194  		cfgmaps.Log("update: failed to update: %s", err)
   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 apierrors.IsNotFound(err) {
   205  			return nil, ErrReleaseExists
   206  		}
   207  
   208  		cfgmaps.Log("delete: failed to get release %q: %s", key, err)
   209  		return nil, err
   210  	}
   211  	// delete the release
   212  	if err = cfgmaps.impl.Delete(key, &metav1.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  //    "modifiedAt"     - timestamp indicating when this configmap was last modified. (set in Update)
   225  //    "createdAt"      - 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 "helm".
   229  //    "name"           - name of the release.
   230  //
   231  func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigMap, error) {
   232  	const owner = "helm"
   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", rls.Info.Status.String())
   248  	lbs.set("version", strconv.Itoa(rls.Version))
   249  
   250  	// create and return configmap object
   251  	return &v1.ConfigMap{
   252  		ObjectMeta: metav1.ObjectMeta{
   253  			Name:   key,
   254  			Labels: lbs.toMap(),
   255  		},
   256  		Data: map[string]string{"release": s},
   257  	}, nil
   258  }