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