github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/libvirttools/extdata.go (about) 1 /* 2 Copyright 2017 Mirantis 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 libvirttools 18 19 import ( 20 "encoding/base64" 21 "fmt" 22 "strings" 23 24 // use this instead of "gopkg.in/yaml.v2" so we don't get 25 // map[interface{}]interface{} when unmarshalling cloud-init data 26 "github.com/ghodss/yaml" 27 meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/client-go/kubernetes" 29 30 "github.com/Mirantis/virtlet/pkg/metadata/types" 31 "github.com/Mirantis/virtlet/pkg/utils" 32 ) 33 34 func init() { 35 types.SetExternalDataLoader(&defaultExternalDataLoader{}) 36 } 37 38 type defaultExternalDataLoader struct { 39 kubeClient kubernetes.Interface 40 } 41 42 var _ types.ExternalDataLoader = &defaultExternalDataLoader{} 43 44 // LoadCloudInitData implements LoadCloudInitData method of ExternalDataLoader interface. 45 func (l *defaultExternalDataLoader) LoadCloudInitData(va *types.VirtletAnnotations, namespace string, podAnnotations map[string]string) error { 46 if namespace == "" { 47 return nil 48 } 49 var err error 50 userDataSourceKey := podAnnotations[types.CloudInitUserDataSourceKeyName] 51 sshKeySourceKey := podAnnotations[types.SSHKeySourceKeyName] 52 if userDataSourceKey != "" { 53 err = l.loadUserDataFromDataSource(va, namespace, userDataSourceKey) 54 if err != nil { 55 return err 56 } 57 } 58 if sshKeySourceKey != "" { 59 err = l.loadSSHKeysFromDataSource(va, namespace, sshKeySourceKey) 60 if err != nil { 61 return err 62 } 63 } 64 return nil 65 } 66 67 // LoadFileMap implements LoadFileMap method of ExternalDataLoader interface. 68 func (l *defaultExternalDataLoader) LoadFileMap(namespace, dsSpec string) (map[string][]byte, error) { 69 if namespace == "" { 70 return nil, nil 71 } 72 parts := strings.Split(dsSpec, "/") 73 if len(parts) != 2 { 74 return nil, fmt.Errorf("invalid %q annotation format. Expected kind/name, but insted got %q", types.FilesFromDSKeyName, dsSpec) 75 } 76 77 data, err := l.readK8sKeySource(parts[0], parts[1], namespace, "") 78 if err != nil { 79 return nil, err 80 } 81 82 return parseDataAsFileMap(data) 83 } 84 85 func (l *defaultExternalDataLoader) loadUserDataFromDataSource(va *types.VirtletAnnotations, namespace, key string) error { 86 parts := strings.Split(key, "/") 87 if len(parts) != 2 { 88 return fmt.Errorf("invalid %q annotation format. Expected kind/name, but insted got %s", types.CloudInitUserDataSourceKeyName, key) 89 } 90 ud, err := l.readK8sKeySource(parts[0], parts[1], namespace, "") 91 if err != nil { 92 return err 93 } 94 va.UserData = map[string]interface{}{} 95 for k, v := range ud { 96 var value interface{} 97 if yaml.Unmarshal([]byte(v), &value) == nil { 98 va.UserData[k] = value 99 } 100 } 101 return nil 102 } 103 104 func (l *defaultExternalDataLoader) loadSSHKeysFromDataSource(va *types.VirtletAnnotations, namespace, key string) error { 105 parts := strings.Split(key, "/") 106 if len(parts) != 2 && len(parts) != 3 { 107 return fmt.Errorf("invalid %s annotation format. Expected kind/name[/key], but insted got %s", types.SSHKeySourceKeyName, key) 108 } 109 dataKey := "authorized_keys" 110 if len(parts) == 3 { 111 dataKey = parts[2] 112 } 113 ud, err := l.readK8sKeySource(parts[0], parts[1], namespace, dataKey) 114 if err != nil { 115 return err 116 } 117 sshKeys := ud[dataKey] 118 keys := strings.Split(sshKeys, "\n") 119 for _, k := range keys { 120 k = strings.TrimSpace(k) 121 if k != "" { 122 va.SSHKeys = append(va.SSHKeys, k) 123 } 124 } 125 return nil 126 } 127 128 func (l *defaultExternalDataLoader) ensureKubeClient() error { 129 if l.kubeClient == nil { 130 var err error 131 l.kubeClient, err = utils.GetK8sClientset(nil) 132 if err != nil { 133 return err 134 } 135 } 136 return nil 137 } 138 139 func (l *defaultExternalDataLoader) readK8sKeySource(sourceType, sourceName, namespace, key string) (map[string]string, error) { 140 if err := l.ensureKubeClient(); err != nil { 141 return nil, err 142 } 143 sourceType = strings.ToLower(sourceType) 144 switch sourceType { 145 case "secret": 146 secret, err := l.kubeClient.CoreV1().Secrets(namespace).Get(sourceName, meta_v1.GetOptions{}) 147 if err != nil { 148 return nil, err 149 } 150 if key != "" { 151 return map[string]string{key: string(secret.Data[key])}, nil 152 } 153 result := map[string]string{} 154 for k, v := range secret.Data { 155 result[k] = string(v) 156 } 157 return result, nil 158 case "configmap": 159 configmap, err := l.kubeClient.CoreV1().ConfigMaps(namespace).Get(sourceName, meta_v1.GetOptions{}) 160 if err != nil { 161 return nil, err 162 } 163 if key != "" { 164 return map[string]string{key: configmap.Data[key]}, nil 165 } 166 result := map[string]string{} 167 for k, v := range configmap.Data { 168 result[k] = v 169 } 170 return result, nil 171 default: 172 return nil, fmt.Errorf("unsupported source kind %s. Must be one of (secret, configmap)", sourceType) 173 } 174 } 175 176 func parseDataAsFileMap(data map[string]string) (map[string][]byte, error) { 177 files := make(map[string][]byte) 178 for k, v := range data { 179 if strings.HasSuffix(k, "_path") || strings.HasSuffix(k, "_encoding") { 180 continue 181 } 182 183 path, pOk := data[k+"_path"] 184 if !pOk { 185 return nil, fmt.Errorf("missing path for %q entry", k) 186 } 187 188 encoding, eOk := data[k+"_encoding"] 189 if !eOk { 190 encoding = "base64" 191 } 192 193 switch encoding { 194 case "plain": 195 files[path] = []byte(v) 196 case "base64": 197 data, err := base64.StdEncoding.DecodeString(v) 198 if err != nil { 199 return nil, fmt.Errorf("cannot decode data under %q key as base64 encoded: %v", k, err) 200 } 201 files[path] = data 202 default: 203 return nil, fmt.Errorf("unkonwn encoding %q for %q", encoding, k) 204 } 205 } 206 return files, nil 207 }