github.com/migueleliasweb/helm@v2.6.1+incompatible/pkg/storage/driver/cfgmaps.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 "k8s.io/helm/pkg/storage/driver" 18 19 import ( 20 "bytes" 21 "compress/gzip" 22 "encoding/base64" 23 "fmt" 24 "io/ioutil" 25 "strconv" 26 "strings" 27 "time" 28 29 "github.com/golang/protobuf/proto" 30 apierrors "k8s.io/apimachinery/pkg/api/errors" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 kblabels "k8s.io/apimachinery/pkg/labels" 33 "k8s.io/apimachinery/pkg/util/validation" 34 "k8s.io/kubernetes/pkg/api" 35 "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" 36 37 rspb "k8s.io/helm/pkg/proto/hapi/release" 38 ) 39 40 var _ Driver = (*ConfigMaps)(nil) 41 42 // ConfigMapsDriverName is the string name of the driver. 43 const ConfigMapsDriverName = "ConfigMap" 44 45 var b64 = base64.StdEncoding 46 47 var magicGzip = []byte{0x1f, 0x8b, 0x08} 48 49 // ConfigMaps is a wrapper around an implementation of a kubernetes 50 // ConfigMapsInterface. 51 type ConfigMaps struct { 52 impl internalversion.ConfigMapInterface 53 Log func(string, ...interface{}) 54 } 55 56 // NewConfigMaps initializes a new ConfigMaps wrapping an implmenetation of 57 // the kubernetes ConfigMapsInterface. 58 func NewConfigMaps(impl internalversion.ConfigMapInterface) *ConfigMaps { 59 return &ConfigMaps{ 60 impl: impl, 61 Log: func(_ string, _ ...interface{}) {}, 62 } 63 } 64 65 // Name returns the name of the driver. 66 func (cfgmaps *ConfigMaps) Name() string { 67 return ConfigMapsDriverName 68 } 69 70 // Get fetches the release named by key. The corresponding release is returned 71 // or error if not found. 72 func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) { 73 // fetch the configmap holding the release named by key 74 obj, err := cfgmaps.impl.Get(key, metav1.GetOptions{}) 75 if err != nil { 76 if apierrors.IsNotFound(err) { 77 return nil, ErrReleaseNotFound(key) 78 } 79 80 cfgmaps.Log("get: failed to get %q: %s", key, err) 81 return nil, err 82 } 83 // found the configmap, decode the base64 data string 84 r, err := decodeRelease(obj.Data["release"]) 85 if err != nil { 86 cfgmaps.Log("get: failed to decode data %q: %s", key, err) 87 return nil, err 88 } 89 // return the release object 90 return r, nil 91 } 92 93 // List fetches all releases and returns the list releases such 94 // that filter(release) == true. An error is returned if the 95 // configmap fails to retrieve the releases. 96 func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { 97 lsel := kblabels.Set{"OWNER": "TILLER"}.AsSelector() 98 opts := metav1.ListOptions{LabelSelector: lsel.String()} 99 100 list, err := cfgmaps.impl.List(opts) 101 if err != nil { 102 cfgmaps.Log("list: failed to list: %s", err) 103 return nil, err 104 } 105 106 var results []*rspb.Release 107 108 // iterate over the configmaps object list 109 // and decode each release 110 for _, item := range list.Items { 111 rls, err := decodeRelease(item.Data["release"]) 112 if err != nil { 113 cfgmaps.Log("list: failed to decode release: %v: %s", item, err) 114 continue 115 } 116 if filter(rls) { 117 results = append(results, rls) 118 } 119 } 120 return results, nil 121 } 122 123 // Query fetches all releases that match the provided map of labels. 124 // An error is returned if the configmap fails to retrieve the releases. 125 func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, error) { 126 ls := kblabels.Set{} 127 for k, v := range labels { 128 if errs := validation.IsValidLabelValue(v); len(errs) != 0 { 129 return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) 130 } 131 ls[k] = v 132 } 133 134 opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} 135 136 list, err := cfgmaps.impl.List(opts) 137 if err != nil { 138 cfgmaps.Log("query: failed to query with labels: %s", err) 139 return nil, err 140 } 141 142 if len(list.Items) == 0 { 143 return nil, ErrReleaseNotFound(labels["NAME"]) 144 } 145 146 var results []*rspb.Release 147 for _, item := range list.Items { 148 rls, err := decodeRelease(item.Data["release"]) 149 if err != nil { 150 cfgmaps.Log("query: failed to decode release: %s", err) 151 continue 152 } 153 results = append(results, rls) 154 } 155 return results, nil 156 } 157 158 // Create creates a new ConfigMap holding the release. If the 159 // ConfigMap already exists, ErrReleaseExists is returned. 160 func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { 161 // set labels for configmaps object meta data 162 var lbs labels 163 164 lbs.init() 165 lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix()))) 166 167 // create a new configmap to hold the release 168 obj, err := newConfigMapsObject(key, rls, lbs) 169 if err != nil { 170 cfgmaps.Log("create: failed to encode release %q: %s", rls.Name, err) 171 return err 172 } 173 // push the configmap object out into the kubiverse 174 if _, err := cfgmaps.impl.Create(obj); err != nil { 175 if apierrors.IsAlreadyExists(err) { 176 return ErrReleaseExists(key) 177 } 178 179 cfgmaps.Log("create: failed to create: %s", err) 180 return err 181 } 182 return nil 183 } 184 185 // Update updates the ConfigMap holding the release. If not found 186 // the ConfigMap is created to hold the release. 187 func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error { 188 // set labels for configmaps object meta data 189 var lbs labels 190 191 lbs.init() 192 lbs.set("MODIFIED_AT", strconv.Itoa(int(time.Now().Unix()))) 193 194 // create a new configmap object to hold the release 195 obj, err := newConfigMapsObject(key, rls, lbs) 196 if err != nil { 197 cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err) 198 return err 199 } 200 // push the configmap object out into the kubiverse 201 _, err = cfgmaps.impl.Update(obj) 202 if err != nil { 203 cfgmaps.Log("update: failed to update: %s", err) 204 return err 205 } 206 return nil 207 } 208 209 // Delete deletes the ConfigMap holding the release named by key. 210 func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) { 211 // fetch the release to check existence 212 if rls, err = cfgmaps.Get(key); err != nil { 213 if apierrors.IsNotFound(err) { 214 return nil, ErrReleaseExists(rls.Name) 215 } 216 217 cfgmaps.Log("delete: failed to get release %q: %s", key, err) 218 return nil, err 219 } 220 // delete the release 221 if err = cfgmaps.impl.Delete(key, &metav1.DeleteOptions{}); err != nil { 222 return rls, err 223 } 224 return rls, nil 225 } 226 227 // newConfigMapsObject constructs a kubernetes ConfigMap object 228 // to store a release. Each configmap data entry is the base64 229 // encoded string of a release's binary protobuf encoding. 230 // 231 // The following labels are used within each configmap: 232 // 233 // "MODIFIED_AT" - timestamp indicating when this configmap was last modified. (set in Update) 234 // "CREATED_AT" - timestamp indicating when this configmap was created. (set in Create) 235 // "VERSION" - version of the release. 236 // "STATUS" - status of the release (see proto/hapi/release.status.pb.go for variants) 237 // "OWNER" - owner of the configmap, currently "TILLER". 238 // "NAME" - name of the release. 239 // 240 func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*api.ConfigMap, error) { 241 const owner = "TILLER" 242 243 // encode the release 244 s, err := encodeRelease(rls) 245 if err != nil { 246 return nil, err 247 } 248 249 if lbs == nil { 250 lbs.init() 251 } 252 253 // apply labels 254 lbs.set("NAME", rls.Name) 255 lbs.set("OWNER", owner) 256 lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) 257 lbs.set("VERSION", strconv.Itoa(int(rls.Version))) 258 259 // create and return configmap object 260 return &api.ConfigMap{ 261 ObjectMeta: metav1.ObjectMeta{ 262 Name: key, 263 Labels: lbs.toMap(), 264 }, 265 Data: map[string]string{"release": s}, 266 }, nil 267 } 268 269 // encodeRelease encodes a release returning a base64 encoded 270 // gzipped binary protobuf encoding representation, or error. 271 func encodeRelease(rls *rspb.Release) (string, error) { 272 b, err := proto.Marshal(rls) 273 if err != nil { 274 return "", err 275 } 276 var buf bytes.Buffer 277 w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) 278 if err != nil { 279 return "", err 280 } 281 if _, err = w.Write(b); err != nil { 282 return "", err 283 } 284 w.Close() 285 286 return b64.EncodeToString(buf.Bytes()), nil 287 } 288 289 // decodeRelease decodes the bytes in data into a release 290 // type. Data must contain a base64 encoded string of a 291 // valid protobuf encoding of a release, otherwise 292 // an error is returned. 293 func decodeRelease(data string) (*rspb.Release, error) { 294 // base64 decode string 295 b, err := b64.DecodeString(data) 296 if err != nil { 297 return nil, err 298 } 299 300 // For backwards compatibility with releases that were stored before 301 // compression was introduced we skip decompression if the 302 // gzip magic header is not found 303 if bytes.Equal(b[0:3], magicGzip) { 304 r, err := gzip.NewReader(bytes.NewReader(b)) 305 if err != nil { 306 return nil, err 307 } 308 b2, err := ioutil.ReadAll(r) 309 if err != nil { 310 return nil, err 311 } 312 b = b2 313 } 314 315 var rls rspb.Release 316 // unmarshal protobuf bytes 317 if err := proto.Unmarshal(b, &rls); err != nil { 318 return nil, err 319 } 320 return &rls, nil 321 }