github.com/TarasLykhenko/helm@v3.0.0-beta.3+incompatible/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 "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 = (*Secrets)(nil)
    36  
    37  // SecretsDriverName is the string name of the driver.
    38  const SecretsDriverName = "Secret"
    39  
    40  // Secrets is a wrapper around an implementation of a kubernetes
    41  // SecretsInterface.
    42  type Secrets struct {
    43  	impl corev1.SecretInterface
    44  	Log  func(string, ...interface{})
    45  }
    46  
    47  // NewSecrets initializes a new Secrets wrapping an implmenetation of
    48  // the kubernetes SecretsInterface.
    49  func NewSecrets(impl corev1.SecretInterface) *Secrets {
    50  	return &Secrets{
    51  		impl: impl,
    52  		Log:  func(_ string, _ ...interface{}) {},
    53  	}
    54  }
    55  
    56  // Name returns the name of the driver.
    57  func (secrets *Secrets) Name() string {
    58  	return SecretsDriverName
    59  }
    60  
    61  // Get fetches the release named by key. The corresponding release is returned
    62  // or error if not found.
    63  func (secrets *Secrets) Get(key string) (*rspb.Release, error) {
    64  	// fetch the secret holding the release named by key
    65  	obj, err := secrets.impl.Get(key, metav1.GetOptions{})
    66  	if err != nil {
    67  		if apierrors.IsNotFound(err) {
    68  			return nil, ErrReleaseNotFound
    69  		}
    70  		return nil, errors.Wrapf(err, "get: failed to get %q", key)
    71  	}
    72  	// found the secret, decode the base64 data string
    73  	r, err := decodeRelease(string(obj.Data["release"]))
    74  	return r, errors.Wrapf(err, "get: failed to decode data %q", key)
    75  }
    76  
    77  // List fetches all releases and returns the list releases such
    78  // that filter(release) == true. An error is returned if the
    79  // secret fails to retrieve the releases.
    80  func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
    81  	lsel := kblabels.Set{"owner": "helm"}.AsSelector()
    82  	opts := metav1.ListOptions{LabelSelector: lsel.String()}
    83  
    84  	list, err := secrets.impl.List(opts)
    85  	if err != nil {
    86  		return nil, errors.Wrap(err, "list: failed to list")
    87  	}
    88  
    89  	var results []*rspb.Release
    90  
    91  	// iterate over the secrets object list
    92  	// and decode each release
    93  	for _, item := range list.Items {
    94  		rls, err := decodeRelease(string(item.Data["release"]))
    95  		if err != nil {
    96  			secrets.Log("list: failed to decode release: %v: %s", item, err)
    97  			continue
    98  		}
    99  		if filter(rls) {
   100  			results = append(results, rls)
   101  		}
   102  	}
   103  	return results, nil
   104  }
   105  
   106  // Query fetches all releases that match the provided map of labels.
   107  // An error is returned if the secret fails to retrieve the releases.
   108  func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) {
   109  	ls := kblabels.Set{}
   110  	for k, v := range labels {
   111  		if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
   112  			return nil, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
   113  		}
   114  		ls[k] = v
   115  	}
   116  
   117  	opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()}
   118  
   119  	list, err := secrets.impl.List(opts)
   120  	if err != nil {
   121  		return nil, errors.Wrap(err, "query: failed to query with labels")
   122  	}
   123  
   124  	if len(list.Items) == 0 {
   125  		return nil, ErrReleaseNotFound
   126  	}
   127  
   128  	var results []*rspb.Release
   129  	for _, item := range list.Items {
   130  		rls, err := decodeRelease(string(item.Data["release"]))
   131  		if err != nil {
   132  			secrets.Log("query: failed to decode release: %s", err)
   133  			continue
   134  		}
   135  		results = append(results, rls)
   136  	}
   137  	return results, nil
   138  }
   139  
   140  // Create creates a new Secret holding the release. If the
   141  // Secret already exists, ErrReleaseExists is returned.
   142  func (secrets *Secrets) Create(key string, rls *rspb.Release) error {
   143  	// set labels for secrets object meta data
   144  	var lbs labels
   145  
   146  	lbs.init()
   147  	lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix())))
   148  
   149  	// create a new secret to hold the release
   150  	obj, err := newSecretsObject(key, rls, lbs)
   151  	if err != nil {
   152  		return errors.Wrapf(err, "create: failed to encode release %q", rls.Name)
   153  	}
   154  	// push the secret object out into the kubiverse
   155  	if _, err := secrets.impl.Create(obj); err != nil {
   156  		if apierrors.IsAlreadyExists(err) {
   157  			return ErrReleaseExists
   158  		}
   159  
   160  		return errors.Wrap(err, "create: failed to create")
   161  	}
   162  	return nil
   163  }
   164  
   165  // Update updates the Secret holding the release. If not found
   166  // the Secret is created to hold the release.
   167  func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
   168  	// set labels for secrets object meta data
   169  	var lbs labels
   170  
   171  	lbs.init()
   172  	lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix())))
   173  
   174  	// create a new secret object to hold the release
   175  	obj, err := newSecretsObject(key, rls, lbs)
   176  	if err != nil {
   177  		return errors.Wrapf(err, "update: failed to encode release %q", rls.Name)
   178  	}
   179  	// push the secret object out into the kubiverse
   180  	_, err = secrets.impl.Update(obj)
   181  	return errors.Wrap(err, "update: failed to update")
   182  }
   183  
   184  // Delete deletes the Secret holding the release named by key.
   185  func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) {
   186  	// fetch the release to check existence
   187  	if rls, err = secrets.Get(key); err != nil {
   188  		if apierrors.IsNotFound(err) {
   189  			return nil, ErrReleaseExists
   190  		}
   191  
   192  		return nil, errors.Wrapf(err, "delete: failed to get release %q", key)
   193  	}
   194  	// delete the release
   195  	err = secrets.impl.Delete(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 string of a release's binary protobuf encoding.
   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 proto/hapi/release.status.pb.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  	return &v1.Secret{
   233  		ObjectMeta: metav1.ObjectMeta{
   234  			Name:   key,
   235  			Labels: lbs.toMap(),
   236  		},
   237  		Type: "helm.sh/release",
   238  		Data: map[string][]byte{"release": []byte(s)},
   239  	}, nil
   240  }