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