| /* |
| Copyright 2017 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package env |
| |
| import ( |
| "fmt" |
| "math" |
| "strconv" |
| "strings" |
| |
| corev1 "k8s.io/api/core/v1" |
| "k8s.io/apimachinery/pkg/api/meta" |
| "k8s.io/apimachinery/pkg/api/resource" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/util/sets" |
| "k8s.io/apimachinery/pkg/util/validation" |
| "k8s.io/client-go/kubernetes" |
| ) |
| |
| // ResourceStore defines a new resource store data structure. |
| type ResourceStore struct { |
| SecretStore map[string]*corev1.Secret |
| ConfigMapStore map[string]*corev1.ConfigMap |
| } |
| |
| // NewResourceStore returns a pointer to a new resource store data structure. |
| func NewResourceStore() *ResourceStore { |
| return &ResourceStore{ |
| SecretStore: make(map[string]*corev1.Secret), |
| ConfigMapStore: make(map[string]*corev1.ConfigMap), |
| } |
| } |
| |
| // getSecretRefValue returns the value of a secret in the supplied namespace |
| func getSecretRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, secretSelector *corev1.SecretKeySelector) (string, error) { |
| secret, ok := store.SecretStore[secretSelector.Name] |
| if !ok { |
| var err error |
| secret, err = client.CoreV1().Secrets(namespace).Get(secretSelector.Name, metav1.GetOptions{}) |
| if err != nil { |
| return "", err |
| } |
| store.SecretStore[secretSelector.Name] = secret |
| } |
| if data, ok := secret.Data[secretSelector.Key]; ok { |
| return string(data), nil |
| } |
| return "", fmt.Errorf("key %s not found in secret %s", secretSelector.Key, secretSelector.Name) |
| |
| } |
| |
| // getConfigMapRefValue returns the value of a configmap in the supplied namespace |
| func getConfigMapRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, configMapSelector *corev1.ConfigMapKeySelector) (string, error) { |
| configMap, ok := store.ConfigMapStore[configMapSelector.Name] |
| if !ok { |
| var err error |
| configMap, err = client.CoreV1().ConfigMaps(namespace).Get(configMapSelector.Name, metav1.GetOptions{}) |
| if err != nil { |
| return "", err |
| } |
| store.ConfigMapStore[configMapSelector.Name] = configMap |
| } |
| if data, ok := configMap.Data[configMapSelector.Key]; ok { |
| return string(data), nil |
| } |
| return "", fmt.Errorf("key %s not found in config map %s", configMapSelector.Key, configMapSelector.Name) |
| } |
| |
| // getFieldRef returns the value of the supplied path in the given object |
| func getFieldRef(obj runtime.Object, from *corev1.EnvVarSource) (string, error) { |
| return extractFieldPathAsString(obj, from.FieldRef.FieldPath) |
| } |
| |
| // extractFieldPathAsString extracts the field from the given object |
| // and returns it as a string. The object must be a pointer to an |
| // API type. |
| func extractFieldPathAsString(obj interface{}, fieldPath string) (string, error) { |
| accessor, err := meta.Accessor(obj) |
| if err != nil { |
| return "", nil |
| } |
| |
| if path, subscript, ok := splitMaybeSubscriptedPath(fieldPath); ok { |
| switch path { |
| case "metadata.annotations": |
| if errs := validation.IsQualifiedName(strings.ToLower(subscript)); len(errs) != 0 { |
| return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";")) |
| } |
| return accessor.GetAnnotations()[subscript], nil |
| case "metadata.labels": |
| if errs := validation.IsQualifiedName(subscript); len(errs) != 0 { |
| return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";")) |
| } |
| return accessor.GetLabels()[subscript], nil |
| default: |
| return "", fmt.Errorf("fieldPath %q does not support subscript", fieldPath) |
| } |
| } |
| |
| switch fieldPath { |
| case "metadata.annotations": |
| return formatMap(accessor.GetAnnotations()), nil |
| case "metadata.labels": |
| return formatMap(accessor.GetLabels()), nil |
| case "metadata.name": |
| return accessor.GetName(), nil |
| case "metadata.namespace": |
| return accessor.GetNamespace(), nil |
| case "metadata.uid": |
| return string(accessor.GetUID()), nil |
| } |
| |
| return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath) |
| } |
| |
| // splitMaybeSubscriptedPath checks whether the specified fieldPath is |
| // subscripted, and |
| // - if yes, this function splits the fieldPath into path and subscript, and |
| // returns (path, subscript, true). |
| // - if no, this function returns (fieldPath, "", false). |
| // |
| // Example inputs and outputs: |
| // - "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey", true) |
| // - "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c", true) |
| // - "metadata.labels['']" --> ("metadata.labels", "", true) |
| // - "metadata.labels" --> ("metadata.labels", "", false) |
| func splitMaybeSubscriptedPath(fieldPath string) (string, string, bool) { |
| if !strings.HasSuffix(fieldPath, "']") { |
| return fieldPath, "", false |
| } |
| s := strings.TrimSuffix(fieldPath, "']") |
| parts := strings.SplitN(s, "['", 2) |
| if len(parts) < 2 { |
| return fieldPath, "", false |
| } |
| if len(parts[0]) == 0 { |
| return fieldPath, "", false |
| } |
| return parts[0], parts[1], true |
| } |
| |
| // formatMap formats map[string]string to a string. |
| func formatMap(m map[string]string) (fmtStr string) { |
| // output with keys in sorted order to provide stable output |
| keys := sets.NewString() |
| for key := range m { |
| keys.Insert(key) |
| } |
| for _, key := range keys.List() { |
| fmtStr += fmt.Sprintf("%v=%q\n", key, m[key]) |
| } |
| fmtStr = strings.TrimSuffix(fmtStr, "\n") |
| |
| return |
| } |
| |
| // getResourceFieldRef returns the value of a resource in the given container |
| func getResourceFieldRef(from *corev1.EnvVarSource, container *corev1.Container) (string, error) { |
| return extractContainerResourceValue(from.ResourceFieldRef, container) |
| } |
| |
| // ExtractContainerResourceValue extracts the value of a resource |
| // in an already known container |
| func extractContainerResourceValue(fs *corev1.ResourceFieldSelector, container *corev1.Container) (string, error) { |
| divisor := resource.Quantity{} |
| if divisor.Cmp(fs.Divisor) == 0 { |
| divisor = resource.MustParse("1") |
| } else { |
| divisor = fs.Divisor |
| } |
| |
| switch fs.Resource { |
| case "limits.cpu": |
| return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor) |
| case "limits.memory": |
| return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor) |
| case "limits.ephemeral-storage": |
| return convertResourceEphemeralStorageToString(container.Resources.Limits.StorageEphemeral(), divisor) |
| case "requests.cpu": |
| return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor) |
| case "requests.memory": |
| return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor) |
| case "requests.ephemeral-storage": |
| return convertResourceEphemeralStorageToString(container.Resources.Requests.StorageEphemeral(), divisor) |
| } |
| |
| return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource) |
| } |
| |
| // convertResourceCPUToString converts cpu value to the format of divisor and returns |
| // ceiling of the value. |
| func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) { |
| c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue()))) |
| return strconv.FormatInt(c, 10), nil |
| } |
| |
| // convertResourceMemoryToString converts memory value to the format of divisor and returns |
| // ceiling of the value. |
| func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) { |
| m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value()))) |
| return strconv.FormatInt(m, 10), nil |
| } |
| |
| // convertResourceEphemeralStorageToString converts ephemeral storage value to the format of divisor and returns |
| // ceiling of the value. |
| func convertResourceEphemeralStorageToString(ephemeralStorage *resource.Quantity, divisor resource.Quantity) (string, error) { |
| m := int64(math.Ceil(float64(ephemeralStorage.Value()) / float64(divisor.Value()))) |
| return strconv.FormatInt(m, 10), nil |
| } |
| |
| // GetEnvVarRefValue returns the value referenced by the supplied EnvVarSource given the other supplied information. |
| func GetEnvVarRefValue(kc kubernetes.Interface, ns string, store *ResourceStore, from *corev1.EnvVarSource, obj runtime.Object, c *corev1.Container) (string, error) { |
| if from.SecretKeyRef != nil { |
| return getSecretRefValue(kc, ns, store, from.SecretKeyRef) |
| } |
| |
| if from.ConfigMapKeyRef != nil { |
| return getConfigMapRefValue(kc, ns, store, from.ConfigMapKeyRef) |
| } |
| |
| if from.FieldRef != nil { |
| return getFieldRef(obj, from) |
| } |
| |
| if from.ResourceFieldRef != nil { |
| return getResourceFieldRef(from, c) |
| } |
| |
| return "", fmt.Errorf("invalid valueFrom") |
| } |
| |
| // GetEnvVarRefString returns a text description of whichever field is set within the supplied EnvVarSource argument. |
| func GetEnvVarRefString(from *corev1.EnvVarSource) string { |
| if from.ConfigMapKeyRef != nil { |
| return fmt.Sprintf("configmap %s, key %s", from.ConfigMapKeyRef.Name, from.ConfigMapKeyRef.Key) |
| } |
| |
| if from.SecretKeyRef != nil { |
| return fmt.Sprintf("secret %s, key %s", from.SecretKeyRef.Name, from.SecretKeyRef.Key) |
| } |
| |
| if from.FieldRef != nil { |
| return fmt.Sprintf("field path %s", from.FieldRef.FieldPath) |
| } |
| |
| if from.ResourceFieldRef != nil { |
| containerPrefix := "" |
| if from.ResourceFieldRef.ContainerName != "" { |
| containerPrefix = fmt.Sprintf("%s/", from.ResourceFieldRef.ContainerName) |
| } |
| return fmt.Sprintf("resource field %s%s", containerPrefix, from.ResourceFieldRef.Resource) |
| } |
| |
| return "invalid valueFrom" |
| } |