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 }