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  }