...

Source file src/helm.sh/helm/v3/pkg/storage/driver/cfgmaps.go

Documentation: helm.sh/helm/v3/pkg/storage/driver

     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/v3/pkg/storage/driver"
    18  
    19  import (
    20  	"context"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/pkg/errors"
    26  	v1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	kblabels "k8s.io/apimachinery/pkg/labels"
    30  	"k8s.io/apimachinery/pkg/util/validation"
    31  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    32  
    33  	rspb "helm.sh/helm/v3/pkg/release"
    34  )
    35  
    36  var _ Driver = (*ConfigMaps)(nil)
    37  
    38  // ConfigMapsDriverName is the string name of the driver.
    39  const ConfigMapsDriverName = "ConfigMap"
    40  
    41  // ConfigMaps is a wrapper around an implementation of a kubernetes
    42  // ConfigMapsInterface.
    43  type ConfigMaps struct {
    44  	impl corev1.ConfigMapInterface
    45  	Log  func(string, ...interface{})
    46  }
    47  
    48  // NewConfigMaps initializes a new ConfigMaps wrapping an implementation of
    49  // the kubernetes ConfigMapsInterface.
    50  func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps {
    51  	return &ConfigMaps{
    52  		impl: impl,
    53  		Log:  func(_ string, _ ...interface{}) {},
    54  	}
    55  }
    56  
    57  // Name returns the name of the driver.
    58  func (cfgmaps *ConfigMaps) Name() string {
    59  	return ConfigMapsDriverName
    60  }
    61  
    62  // Get fetches the release named by key. The corresponding release is returned
    63  // or error if not found.
    64  func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) {
    65  	// fetch the configmap holding the release named by key
    66  	obj, err := cfgmaps.impl.Get(context.Background(), key, metav1.GetOptions{})
    67  	if err != nil {
    68  		if apierrors.IsNotFound(err) {
    69  			return nil, ErrReleaseNotFound
    70  		}
    71  
    72  		cfgmaps.Log("get: failed to get %q: %s", key, err)
    73  		return nil, err
    74  	}
    75  	// found the configmap, decode the base64 data string
    76  	r, err := decodeRelease(obj.Data["release"])
    77  	if err != nil {
    78  		cfgmaps.Log("get: failed to decode data %q: %s", key, err)
    79  		return nil, err
    80  	}
    81  	r.Labels = filterSystemLabels(obj.ObjectMeta.Labels)
    82  	// return the release object
    83  	return r, nil
    84  }
    85  
    86  // List fetches all releases and returns the list releases such
    87  // that filter(release) == true. An error is returned if the
    88  // configmap fails to retrieve the releases.
    89  func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
    90  	lsel := kblabels.Set{"owner": "helm"}.AsSelector()
    91  	opts := metav1.ListOptions{LabelSelector: lsel.String()}
    92  
    93  	list, err := cfgmaps.impl.List(context.Background(), opts)
    94  	if err != nil {
    95  		cfgmaps.Log("list: failed to list: %s", err)
    96  		return nil, err
    97  	}
    98  
    99  	var results []*rspb.Release
   100  
   101  	// iterate over the configmaps object list
   102  	// and decode each release
   103  	for _, item := range list.Items {
   104  		rls, err := decodeRelease(item.Data["release"])
   105  		if err != nil {
   106  			cfgmaps.Log("list: failed to decode release: %v: %s", item, err)
   107  			continue
   108  		}
   109  
   110  		rls.Labels = item.ObjectMeta.Labels
   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, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
   126  		}
   127  		ls[k] = v
   128  	}
   129  
   130  	opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()}
   131  
   132  	list, err := cfgmaps.impl.List(context.Background(), opts)
   133  	if err != nil {
   134  		cfgmaps.Log("query: failed to query with labels: %s", err)
   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  			cfgmaps.Log("query: failed to decode release: %s", err)
   147  			continue
   148  		}
   149  		rls.Labels = item.ObjectMeta.Labels
   150  		results = append(results, rls)
   151  	}
   152  	return results, nil
   153  }
   154  
   155  // Create creates a new ConfigMap holding the release. If the
   156  // ConfigMap already exists, ErrReleaseExists is returned.
   157  func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error {
   158  	// set labels for configmaps object meta data
   159  	var lbs labels
   160  
   161  	lbs.init()
   162  	lbs.fromMap(rls.Labels)
   163  	lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix())))
   164  
   165  	// create a new configmap to hold the release
   166  	obj, err := newConfigMapsObject(key, rls, lbs)
   167  	if err != nil {
   168  		cfgmaps.Log("create: failed to encode release %q: %s", rls.Name, err)
   169  		return err
   170  	}
   171  	// push the configmap object out into the kubiverse
   172  	if _, err := cfgmaps.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil {
   173  		if apierrors.IsAlreadyExists(err) {
   174  			return ErrReleaseExists
   175  		}
   176  
   177  		cfgmaps.Log("create: failed to create: %s", err)
   178  		return err
   179  	}
   180  	return nil
   181  }
   182  
   183  // Update updates the ConfigMap holding the release. If not found
   184  // the ConfigMap is created to hold the release.
   185  func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
   186  	// set labels for configmaps object meta data
   187  	var lbs labels
   188  
   189  	lbs.init()
   190  	lbs.fromMap(rls.Labels)
   191  	lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix())))
   192  
   193  	// create a new configmap object to hold the release
   194  	obj, err := newConfigMapsObject(key, rls, lbs)
   195  	if err != nil {
   196  		cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err)
   197  		return err
   198  	}
   199  	// push the configmap object out into the kubiverse
   200  	_, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{})
   201  	if err != nil {
   202  		cfgmaps.Log("update: failed to update: %s", err)
   203  		return err
   204  	}
   205  	return nil
   206  }
   207  
   208  // Delete deletes the ConfigMap holding the release named by key.
   209  func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) {
   210  	// fetch the release to check existence
   211  	if rls, err = cfgmaps.Get(key); err != nil {
   212  		return nil, err
   213  	}
   214  	// delete the release
   215  	if err = cfgmaps.impl.Delete(context.Background(), key, metav1.DeleteOptions{}); err != nil {
   216  		return rls, err
   217  	}
   218  	return rls, nil
   219  }
   220  
   221  // newConfigMapsObject constructs a kubernetes ConfigMap object
   222  // to store a release. Each configmap data entry is the base64
   223  // encoded gzipped string of a release.
   224  //
   225  // The following labels are used within each configmap:
   226  //
   227  //	"modifiedAt"     - timestamp indicating when this configmap was last modified. (set in Update)
   228  //	"createdAt"      - timestamp indicating when this configmap was created. (set in Create)
   229  //	"version"        - version of the release.
   230  //	"status"         - status of the release (see pkg/release/status.go for variants)
   231  //	"owner"          - owner of the configmap, currently "helm".
   232  //	"name"           - name of the release.
   233  func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigMap, error) {
   234  	const owner = "helm"
   235  
   236  	// encode the release
   237  	s, err := encodeRelease(rls)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	if lbs == nil {
   243  		lbs.init()
   244  	}
   245  
   246  	// apply custom labels
   247  	lbs.fromMap(rls.Labels)
   248  
   249  	// apply labels
   250  	lbs.set("name", rls.Name)
   251  	lbs.set("owner", owner)
   252  	lbs.set("status", rls.Info.Status.String())
   253  	lbs.set("version", strconv.Itoa(rls.Version))
   254  
   255  	// create and return configmap object
   256  	return &v1.ConfigMap{
   257  		ObjectMeta: metav1.ObjectMeta{
   258  			Name:   key,
   259  			Labels: lbs.toMap(),
   260  		},
   261  		Data: map[string]string{"release": s},
   262  	}, nil
   263  }
   264  

View as plain text