...

Source file src/helm.sh/helm/v3/pkg/storage/driver/secrets.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 = (*Secrets)(nil)
    37  
    38  // SecretsDriverName is the string name of the driver.
    39  const SecretsDriverName = "Secret"
    40  
    41  // Secrets is a wrapper around an implementation of a kubernetes
    42  // SecretsInterface.
    43  type Secrets struct {
    44  	impl corev1.SecretInterface
    45  	Log  func(string, ...interface{})
    46  }
    47  
    48  // NewSecrets initializes a new Secrets wrapping an implementation of
    49  // the kubernetes SecretsInterface.
    50  func NewSecrets(impl corev1.SecretInterface) *Secrets {
    51  	return &Secrets{
    52  		impl: impl,
    53  		Log:  func(_ string, _ ...interface{}) {},
    54  	}
    55  }
    56  
    57  // Name returns the name of the driver.
    58  func (secrets *Secrets) Name() string {
    59  	return SecretsDriverName
    60  }
    61  
    62  // Get fetches the release named by key. The corresponding release is returned
    63  // or error if not found.
    64  func (secrets *Secrets) Get(key string) (*rspb.Release, error) {
    65  	// fetch the secret holding the release named by key
    66  	obj, err := secrets.impl.Get(context.Background(), key, metav1.GetOptions{})
    67  	if err != nil {
    68  		if apierrors.IsNotFound(err) {
    69  			return nil, ErrReleaseNotFound
    70  		}
    71  		return nil, errors.Wrapf(err, "get: failed to get %q", key)
    72  	}
    73  	// found the secret, decode the base64 data string
    74  	r, err := decodeRelease(string(obj.Data["release"]))
    75  	r.Labels = filterSystemLabels(obj.ObjectMeta.Labels)
    76  	return r, errors.Wrapf(err, "get: failed to decode data %q", key)
    77  }
    78  
    79  // List fetches all releases and returns the list releases such
    80  // that filter(release) == true. An error is returned if the
    81  // secret fails to retrieve the releases.
    82  func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
    83  	lsel := kblabels.Set{"owner": "helm"}.AsSelector()
    84  	opts := metav1.ListOptions{LabelSelector: lsel.String()}
    85  
    86  	list, err := secrets.impl.List(context.Background(), opts)
    87  	if err != nil {
    88  		return nil, errors.Wrap(err, "list: failed to list")
    89  	}
    90  
    91  	var results []*rspb.Release
    92  
    93  	// iterate over the secrets object list
    94  	// and decode each release
    95  	for _, item := range list.Items {
    96  		rls, err := decodeRelease(string(item.Data["release"]))
    97  		if err != nil {
    98  			secrets.Log("list: failed to decode release: %v: %s", item, err)
    99  			continue
   100  		}
   101  
   102  		rls.Labels = item.ObjectMeta.Labels
   103  
   104  		if filter(rls) {
   105  			results = append(results, rls)
   106  		}
   107  	}
   108  	return results, nil
   109  }
   110  
   111  // Query fetches all releases that match the provided map of labels.
   112  // An error is returned if the secret fails to retrieve the releases.
   113  func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) {
   114  	ls := kblabels.Set{}
   115  	for k, v := range labels {
   116  		if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
   117  			return nil, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
   118  		}
   119  		ls[k] = v
   120  	}
   121  
   122  	opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()}
   123  
   124  	list, err := secrets.impl.List(context.Background(), opts)
   125  	if err != nil {
   126  		return nil, errors.Wrap(err, "query: failed to query with labels")
   127  	}
   128  
   129  	if len(list.Items) == 0 {
   130  		return nil, ErrReleaseNotFound
   131  	}
   132  
   133  	var results []*rspb.Release
   134  	for _, item := range list.Items {
   135  		rls, err := decodeRelease(string(item.Data["release"]))
   136  		if err != nil {
   137  			secrets.Log("query: failed to decode release: %s", err)
   138  			continue
   139  		}
   140  		rls.Labels = item.ObjectMeta.Labels
   141  		results = append(results, rls)
   142  	}
   143  	return results, nil
   144  }
   145  
   146  // Create creates a new Secret holding the release. If the
   147  // Secret already exists, ErrReleaseExists is returned.
   148  func (secrets *Secrets) Create(key string, rls *rspb.Release) error {
   149  	// set labels for secrets object meta data
   150  	var lbs labels
   151  
   152  	lbs.init()
   153  	lbs.fromMap(rls.Labels)
   154  	lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix())))
   155  
   156  	// create a new secret to hold the release
   157  	obj, err := newSecretsObject(key, rls, lbs)
   158  	if err != nil {
   159  		return errors.Wrapf(err, "create: failed to encode release %q", rls.Name)
   160  	}
   161  	// push the secret object out into the kubiverse
   162  	if _, err := secrets.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil {
   163  		if apierrors.IsAlreadyExists(err) {
   164  			return ErrReleaseExists
   165  		}
   166  
   167  		return errors.Wrap(err, "create: failed to create")
   168  	}
   169  	return nil
   170  }
   171  
   172  // Update updates the Secret holding the release. If not found
   173  // the Secret is created to hold the release.
   174  func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
   175  	// set labels for secrets object meta data
   176  	var lbs labels
   177  
   178  	lbs.init()
   179  	lbs.fromMap(rls.Labels)
   180  	lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix())))
   181  
   182  	// create a new secret object to hold the release
   183  	obj, err := newSecretsObject(key, rls, lbs)
   184  	if err != nil {
   185  		return errors.Wrapf(err, "update: failed to encode release %q", rls.Name)
   186  	}
   187  	// push the secret object out into the kubiverse
   188  	_, err = secrets.impl.Update(context.Background(), obj, metav1.UpdateOptions{})
   189  	return errors.Wrap(err, "update: failed to update")
   190  }
   191  
   192  // Delete deletes the Secret holding the release named by key.
   193  func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) {
   194  	// fetch the release to check existence
   195  	if rls, err = secrets.Get(key); err != nil {
   196  		return nil, err
   197  	}
   198  	// delete the release
   199  	err = secrets.impl.Delete(context.Background(), key, metav1.DeleteOptions{})
   200  	return rls, err
   201  }
   202  
   203  // newSecretsObject constructs a kubernetes Secret object
   204  // to store a release. Each secret data entry is the base64
   205  // encoded gzipped string of a release.
   206  //
   207  // The following labels are used within each secret:
   208  //
   209  //	"modifiedAt"    - timestamp indicating when this secret was last modified. (set in Update)
   210  //	"createdAt"     - timestamp indicating when this secret was created. (set in Create)
   211  //	"version"        - version of the release.
   212  //	"status"         - status of the release (see pkg/release/status.go for variants)
   213  //	"owner"          - owner of the secret, currently "helm".
   214  //	"name"           - name of the release.
   215  func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*v1.Secret, error) {
   216  	const owner = "helm"
   217  
   218  	// encode the release
   219  	s, err := encodeRelease(rls)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	if lbs == nil {
   225  		lbs.init()
   226  	}
   227  
   228  	// apply custom labels
   229  	lbs.fromMap(rls.Labels)
   230  
   231  	// apply labels
   232  	lbs.set("name", rls.Name)
   233  	lbs.set("owner", owner)
   234  	lbs.set("status", rls.Info.Status.String())
   235  	lbs.set("version", strconv.Itoa(rls.Version))
   236  
   237  	// create and return secret object.
   238  	// Helm 3 introduced setting the 'Type' field
   239  	// in the Kubernetes storage object.
   240  	// Helm defines the field content as follows:
   241  	// <helm_domain>/<helm_object>.v<helm_object_version>
   242  	// Type field for Helm 3: helm.sh/release.v1
   243  	// Note: Version starts at 'v1' for Helm 3 and
   244  	// should be incremented if the release object
   245  	// metadata is modified.
   246  	// This would potentially be a breaking change
   247  	// and should only happen between major versions.
   248  	return &v1.Secret{
   249  		ObjectMeta: metav1.ObjectMeta{
   250  			Name:   key,
   251  			Labels: lbs.toMap(),
   252  		},
   253  		Type: "helm.sh/release.v1",
   254  		Data: map[string][]byte{"release": []byte(s)},
   255  	}, nil
   256  }
   257  

View as plain text