...

Source file src/k8s.io/kubectl/pkg/cmd/set/env/env_resolve.go

Documentation: k8s.io/kubectl/pkg/cmd/set/env

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     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 env
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math"
    23  	"strconv"
    24  	"strings"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/api/meta"
    28  	"k8s.io/apimachinery/pkg/api/resource"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	"k8s.io/apimachinery/pkg/util/validation"
    33  	"k8s.io/client-go/kubernetes"
    34  )
    35  
    36  // ResourceStore defines a new resource store data structure.
    37  type ResourceStore struct {
    38  	SecretStore    map[string]*corev1.Secret
    39  	ConfigMapStore map[string]*corev1.ConfigMap
    40  }
    41  
    42  // NewResourceStore returns a pointer to a new resource store data structure.
    43  func NewResourceStore() *ResourceStore {
    44  	return &ResourceStore{
    45  		SecretStore:    make(map[string]*corev1.Secret),
    46  		ConfigMapStore: make(map[string]*corev1.ConfigMap),
    47  	}
    48  }
    49  
    50  // getSecretRefValue returns the value of a secret in the supplied namespace
    51  func getSecretRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, secretSelector *corev1.SecretKeySelector) (string, error) {
    52  	secret, ok := store.SecretStore[secretSelector.Name]
    53  	if !ok {
    54  		var err error
    55  		secret, err = client.CoreV1().Secrets(namespace).Get(context.TODO(), secretSelector.Name, metav1.GetOptions{})
    56  		if err != nil {
    57  			return "", err
    58  		}
    59  		store.SecretStore[secretSelector.Name] = secret
    60  	}
    61  	if data, ok := secret.Data[secretSelector.Key]; ok {
    62  		return string(data), nil
    63  	}
    64  	return "", fmt.Errorf("key %s not found in secret %s", secretSelector.Key, secretSelector.Name)
    65  
    66  }
    67  
    68  // getConfigMapRefValue returns the value of a configmap in the supplied namespace
    69  func getConfigMapRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, configMapSelector *corev1.ConfigMapKeySelector) (string, error) {
    70  	configMap, ok := store.ConfigMapStore[configMapSelector.Name]
    71  	if !ok {
    72  		var err error
    73  		configMap, err = client.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configMapSelector.Name, metav1.GetOptions{})
    74  		if err != nil {
    75  			return "", err
    76  		}
    77  		store.ConfigMapStore[configMapSelector.Name] = configMap
    78  	}
    79  	if data, ok := configMap.Data[configMapSelector.Key]; ok {
    80  		return string(data), nil
    81  	}
    82  	return "", fmt.Errorf("key %s not found in config map %s", configMapSelector.Key, configMapSelector.Name)
    83  }
    84  
    85  // getFieldRef returns the value of the supplied path in the given object
    86  func getFieldRef(obj runtime.Object, from *corev1.EnvVarSource) (string, error) {
    87  	return extractFieldPathAsString(obj, from.FieldRef.FieldPath)
    88  }
    89  
    90  // extractFieldPathAsString extracts the field from the given object
    91  // and returns it as a string.  The object must be a pointer to an
    92  // API type.
    93  func extractFieldPathAsString(obj interface{}, fieldPath string) (string, error) {
    94  	accessor, err := meta.Accessor(obj)
    95  	if err != nil {
    96  		return "", nil
    97  	}
    98  
    99  	if path, subscript, ok := splitMaybeSubscriptedPath(fieldPath); ok {
   100  		switch path {
   101  		case "metadata.annotations":
   102  			if errs := validation.IsQualifiedName(strings.ToLower(subscript)); len(errs) != 0 {
   103  				return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";"))
   104  			}
   105  			return accessor.GetAnnotations()[subscript], nil
   106  		case "metadata.labels":
   107  			if errs := validation.IsQualifiedName(subscript); len(errs) != 0 {
   108  				return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";"))
   109  			}
   110  			return accessor.GetLabels()[subscript], nil
   111  		default:
   112  			return "", fmt.Errorf("fieldPath %q does not support subscript", fieldPath)
   113  		}
   114  	}
   115  
   116  	switch fieldPath {
   117  	case "metadata.annotations":
   118  		return formatMap(accessor.GetAnnotations()), nil
   119  	case "metadata.labels":
   120  		return formatMap(accessor.GetLabels()), nil
   121  	case "metadata.name":
   122  		return accessor.GetName(), nil
   123  	case "metadata.namespace":
   124  		return accessor.GetNamespace(), nil
   125  	case "metadata.uid":
   126  		return string(accessor.GetUID()), nil
   127  	}
   128  
   129  	return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath)
   130  }
   131  
   132  // splitMaybeSubscriptedPath checks whether the specified fieldPath is
   133  // subscripted, and
   134  //   - if yes, this function splits the fieldPath into path and subscript, and
   135  //     returns (path, subscript, true).
   136  //   - if no, this function returns (fieldPath, "", false).
   137  //
   138  // Example inputs and outputs:
   139  //   - "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey", true)
   140  //   - "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c", true)
   141  //   - "metadata.labels[”]"           --> ("metadata.labels", "", true)
   142  //   - "metadata.labels"               --> ("metadata.labels", "", false)
   143  func splitMaybeSubscriptedPath(fieldPath string) (string, string, bool) {
   144  	if !strings.HasSuffix(fieldPath, "']") {
   145  		return fieldPath, "", false
   146  	}
   147  	s := strings.TrimSuffix(fieldPath, "']")
   148  	parts := strings.SplitN(s, "['", 2)
   149  	if len(parts) < 2 {
   150  		return fieldPath, "", false
   151  	}
   152  	if len(parts[0]) == 0 {
   153  		return fieldPath, "", false
   154  	}
   155  	return parts[0], parts[1], true
   156  }
   157  
   158  // formatMap formats map[string]string to a string.
   159  func formatMap(m map[string]string) (fmtStr string) {
   160  	// output with keys in sorted order to provide stable output
   161  	keys := sets.NewString()
   162  	for key := range m {
   163  		keys.Insert(key)
   164  	}
   165  	for _, key := range keys.List() {
   166  		fmtStr += fmt.Sprintf("%v=%q\n", key, m[key])
   167  	}
   168  	fmtStr = strings.TrimSuffix(fmtStr, "\n")
   169  
   170  	return
   171  }
   172  
   173  // getResourceFieldRef returns the value of a resource in the given container
   174  func getResourceFieldRef(from *corev1.EnvVarSource, container *corev1.Container) (string, error) {
   175  	return extractContainerResourceValue(from.ResourceFieldRef, container)
   176  }
   177  
   178  // ExtractContainerResourceValue extracts the value of a resource
   179  // in an already known container
   180  func extractContainerResourceValue(fs *corev1.ResourceFieldSelector, container *corev1.Container) (string, error) {
   181  	divisor := resource.Quantity{}
   182  	if divisor.Cmp(fs.Divisor) == 0 {
   183  		divisor = resource.MustParse("1")
   184  	} else {
   185  		divisor = fs.Divisor
   186  	}
   187  
   188  	switch fs.Resource {
   189  	case "limits.cpu":
   190  		return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
   191  	case "limits.memory":
   192  		return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
   193  	case "limits.ephemeral-storage":
   194  		return convertResourceEphemeralStorageToString(container.Resources.Limits.StorageEphemeral(), divisor)
   195  	case "requests.cpu":
   196  		return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
   197  	case "requests.memory":
   198  		return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
   199  	case "requests.ephemeral-storage":
   200  		return convertResourceEphemeralStorageToString(container.Resources.Requests.StorageEphemeral(), divisor)
   201  	}
   202  	// handle extended standard resources with dynamic names
   203  	// example: requests.hugepages-<pageSize> or limits.hugepages-<pageSize>
   204  	if strings.HasPrefix(fs.Resource, "requests.") {
   205  		resourceName := corev1.ResourceName(strings.TrimPrefix(fs.Resource, "requests."))
   206  		if IsHugePageResourceName(resourceName) {
   207  			return convertResourceHugePagesToString(container.Resources.Requests.Name(resourceName, resource.BinarySI), divisor)
   208  		}
   209  	}
   210  	if strings.HasPrefix(fs.Resource, "limits.") {
   211  		resourceName := corev1.ResourceName(strings.TrimPrefix(fs.Resource, "limits."))
   212  		if IsHugePageResourceName(resourceName) {
   213  			return convertResourceHugePagesToString(container.Resources.Limits.Name(resourceName, resource.BinarySI), divisor)
   214  		}
   215  	}
   216  	return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource)
   217  }
   218  
   219  // convertResourceCPUToString converts cpu value to the format of divisor and returns
   220  // ceiling of the value.
   221  func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) {
   222  	c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue())))
   223  	return strconv.FormatInt(c, 10), nil
   224  }
   225  
   226  // convertResourceMemoryToString converts memory value to the format of divisor and returns
   227  // ceiling of the value.
   228  func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) {
   229  	m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value())))
   230  	return strconv.FormatInt(m, 10), nil
   231  }
   232  
   233  // convertResourceHugePagesToString converts hugepages value to the format of divisor and returns
   234  // ceiling of the value.
   235  func convertResourceHugePagesToString(hugePages *resource.Quantity, divisor resource.Quantity) (string, error) {
   236  	m := int64(math.Ceil(float64(hugePages.Value()) / float64(divisor.Value())))
   237  	return strconv.FormatInt(m, 10), nil
   238  }
   239  
   240  // convertResourceEphemeralStorageToString converts ephemeral storage value to the format of divisor and returns
   241  // ceiling of the value.
   242  func convertResourceEphemeralStorageToString(ephemeralStorage *resource.Quantity, divisor resource.Quantity) (string, error) {
   243  	m := int64(math.Ceil(float64(ephemeralStorage.Value()) / float64(divisor.Value())))
   244  	return strconv.FormatInt(m, 10), nil
   245  }
   246  
   247  // GetEnvVarRefValue returns the value referenced by the supplied EnvVarSource given the other supplied information.
   248  func GetEnvVarRefValue(kc kubernetes.Interface, ns string, store *ResourceStore, from *corev1.EnvVarSource, obj runtime.Object, c *corev1.Container) (string, error) {
   249  	if from.SecretKeyRef != nil {
   250  		return getSecretRefValue(kc, ns, store, from.SecretKeyRef)
   251  	}
   252  
   253  	if from.ConfigMapKeyRef != nil {
   254  		return getConfigMapRefValue(kc, ns, store, from.ConfigMapKeyRef)
   255  	}
   256  
   257  	if from.FieldRef != nil {
   258  		return getFieldRef(obj, from)
   259  	}
   260  
   261  	if from.ResourceFieldRef != nil {
   262  		return getResourceFieldRef(from, c)
   263  	}
   264  
   265  	return "", fmt.Errorf("invalid valueFrom")
   266  }
   267  
   268  // GetEnvVarRefString returns a text description of whichever field is set within the supplied EnvVarSource argument.
   269  func GetEnvVarRefString(from *corev1.EnvVarSource) string {
   270  	if from.ConfigMapKeyRef != nil {
   271  		return fmt.Sprintf("configmap %s, key %s", from.ConfigMapKeyRef.Name, from.ConfigMapKeyRef.Key)
   272  	}
   273  
   274  	if from.SecretKeyRef != nil {
   275  		return fmt.Sprintf("secret %s, key %s", from.SecretKeyRef.Name, from.SecretKeyRef.Key)
   276  	}
   277  
   278  	if from.FieldRef != nil {
   279  		return fmt.Sprintf("field path %s", from.FieldRef.FieldPath)
   280  	}
   281  
   282  	if from.ResourceFieldRef != nil {
   283  		containerPrefix := ""
   284  		if from.ResourceFieldRef.ContainerName != "" {
   285  			containerPrefix = fmt.Sprintf("%s/", from.ResourceFieldRef.ContainerName)
   286  		}
   287  		return fmt.Sprintf("resource field %s%s", containerPrefix, from.ResourceFieldRef.Resource)
   288  	}
   289  
   290  	return "invalid valueFrom"
   291  }
   292  
   293  // IsHugePageResourceName returns true if the resource name has the huge page
   294  // resource prefix.
   295  func IsHugePageResourceName(name corev1.ResourceName) bool {
   296  	return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix)
   297  }
   298  

View as plain text