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