github.com/felipejfc/helm@v2.1.2+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 "log" 26 "strconv" 27 "time" 28 29 "github.com/golang/protobuf/proto" 30 "k8s.io/kubernetes/pkg/api" 31 kberrs "k8s.io/kubernetes/pkg/api/errors" 32 "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" 33 kblabels "k8s.io/kubernetes/pkg/labels" 34 35 rspb "k8s.io/helm/pkg/proto/hapi/release" 36 ) 37 38 var _ Driver = (*ConfigMaps)(nil) 39 40 // ConfigMapsDriverName is the string name of the driver. 41 const ConfigMapsDriverName = "ConfigMap" 42 43 var b64 = base64.StdEncoding 44 45 var magicGzip = []byte{0x1f, 0x8b, 0x08} 46 47 // ConfigMaps is a wrapper around an implementation of a kubernetes 48 // ConfigMapsInterface. 49 type ConfigMaps struct { 50 impl internalversion.ConfigMapInterface 51 } 52 53 // NewConfigMaps initializes a new ConfigMaps wrapping an implmenetation of 54 // the kubernetes ConfigMapsInterface. 55 func NewConfigMaps(impl internalversion.ConfigMapInterface) *ConfigMaps { 56 return &ConfigMaps{impl: impl} 57 } 58 59 // Name returns the name of the driver. 60 func (cfgmaps *ConfigMaps) Name() string { 61 return ConfigMapsDriverName 62 } 63 64 // Get fetches the release named by key. The corresponding release is returned 65 // or error if not found. 66 func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) { 67 // fetch the configmap holding the release named by key 68 obj, err := cfgmaps.impl.Get(key) 69 if err != nil { 70 if kberrs.IsNotFound(err) { 71 return nil, ErrReleaseNotFound 72 } 73 74 logerrf(err, "get: failed to get %q", key) 75 return nil, err 76 } 77 // found the configmap, decode the base64 data string 78 r, err := decodeRelease(obj.Data["release"]) 79 if err != nil { 80 logerrf(err, "get: failed to decode data %q", key) 81 return nil, err 82 } 83 // return the release object 84 return r, nil 85 } 86 87 // List fetches all releases and returns the list releases such 88 // that filter(release) == true. An error is returned if the 89 // configmap fails to retrieve the releases. 90 func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { 91 lsel := kblabels.Set{"OWNER": "TILLER"}.AsSelector() 92 opts := api.ListOptions{LabelSelector: lsel} 93 94 list, err := cfgmaps.impl.List(opts) 95 if err != nil { 96 logerrf(err, "list: failed to list") 97 return nil, err 98 } 99 100 var results []*rspb.Release 101 102 // iterate over the configmaps object list 103 // and decode each release 104 for _, item := range list.Items { 105 rls, err := decodeRelease(item.Data["release"]) 106 if err != nil { 107 logerrf(err, "list: failed to decode release: %v", item) 108 continue 109 } 110 if filter(rls) { 111 results = append(results, rls) 112 } 113 } 114 return results, nil 115 } 116 117 // Query fetches all releases that match the provided map of labels. 118 // An error is returned if the configmap fails to retrieve the releases. 119 func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, error) { 120 ls := kblabels.Set{} 121 for k, v := range labels { 122 ls[k] = v 123 } 124 125 opts := api.ListOptions{LabelSelector: ls.AsSelector()} 126 127 list, err := cfgmaps.impl.List(opts) 128 if err != nil { 129 logerrf(err, "query: failed to query with labels") 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 logerrf(err, "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("CREATED_AT", 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 logerrf(err, "create: failed to encode release %q", rls.Name) 162 return err 163 } 164 // push the configmap object out into the kubiverse 165 if _, err := cfgmaps.impl.Create(obj); err != nil { 166 if kberrs.IsAlreadyExists(err) { 167 return ErrReleaseExists 168 } 169 170 logerrf(err, "create: failed to create") 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("MODIFIED_AT", 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 logerrf(err, "update: failed to encode release %q", rls.Name) 189 return err 190 } 191 // push the configmap object out into the kubiverse 192 _, err = cfgmaps.impl.Update(obj) 193 if err != nil { 194 logerrf(err, "update: failed to update") 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 kberrs.IsNotFound(err) { 205 return nil, ErrReleaseNotFound 206 } 207 208 logerrf(err, "delete: failed to get release %q", key) 209 return nil, err 210 } 211 // delete the release 212 if err = cfgmaps.impl.Delete(key, &api.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 // "MODIFIED_AT" - timestamp indicating when this configmap was last modified. (set in Update) 225 // "CREATED_AT" - 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 "TILLER". 229 // "NAME" - name of the release. 230 // 231 func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*api.ConfigMap, error) { 232 const owner = "TILLER" 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", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) 248 lbs.set("VERSION", strconv.Itoa(int(rls.Version))) 249 250 // create and return configmap object 251 return &api.ConfigMap{ 252 ObjectMeta: api.ObjectMeta{ 253 Name: key, 254 Labels: lbs.toMap(), 255 }, 256 Data: map[string]string{"release": s}, 257 }, nil 258 } 259 260 // encodeRelease encodes a release returning a base64 encoded 261 // gzipped binary protobuf encoding representation, or error. 262 func encodeRelease(rls *rspb.Release) (string, error) { 263 b, err := proto.Marshal(rls) 264 if err != nil { 265 return "", err 266 } 267 var buf bytes.Buffer 268 w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) 269 if err != nil { 270 return "", err 271 } 272 if _, err = w.Write(b); err != nil { 273 return "", err 274 } 275 w.Close() 276 277 return b64.EncodeToString(buf.Bytes()), nil 278 } 279 280 // decodeRelease decodes the bytes in data into a release 281 // type. Data must contain a base64 encoded string of a 282 // valid protobuf encoding of a release, otherwise 283 // an error is returned. 284 func decodeRelease(data string) (*rspb.Release, error) { 285 // base64 decode string 286 b, err := b64.DecodeString(data) 287 if err != nil { 288 return nil, err 289 } 290 291 // For backwards compatibility with releases that were stored before 292 // compression was introduced we skip decompression if the 293 // gzip magic header is not found 294 if bytes.Equal(b[0:3], magicGzip) { 295 r, err := gzip.NewReader(bytes.NewReader(b)) 296 if err != nil { 297 return nil, err 298 } 299 b2, err := ioutil.ReadAll(r) 300 if err != nil { 301 return nil, err 302 } 303 b = b2 304 } 305 306 var rls rspb.Release 307 // unmarshal protobuf bytes 308 if err := proto.Unmarshal(b, &rls); err != nil { 309 return nil, err 310 } 311 return &rls, nil 312 } 313 314 // logerrf wraps an error with the a formatted string (used for debugging) 315 func logerrf(err error, format string, args ...interface{}) { 316 log.Printf("configmaps: %s: %s\n", fmt.Sprintf(format, args...), err) 317 }