Ctrl K
ComunidadGuías…

pkg/envresolve/resolver.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package envresolve

import (
	"fmt"
	"sort"
	"strings"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/validation"
	"k8s.io/kubectl/pkg/util/fieldpath"
	kubectlresource "k8s.io/kubectl/pkg/util/resource"
)

type binding struct {
	row       Row
	value     string
	available bool
}

func ResolvePod(pod *corev1.Pod, sources map[SourceID]SourceData, node NodeData) PodEnvironment {
	if pod == nil {
		return PodEnvironment{}
	}
	containers := make([]Container, 0, len(pod.Spec.InitContainers)+len(pod.Spec.Containers))
	for i := range pod.Spec.InitContainers {
		role := "init"
		if pod.Spec.InitContainers[i].RestartPolicy != nil && *pod.Spec.InitContainers[i].RestartPolicy == corev1.ContainerRestartPolicyAlways {
			role = "sidecar"
		}
		containers = append(containers, resolveContainer(pod, &pod.Spec.InitContainers[i], role, sources, node))
	}
	for i := range pod.Spec.Containers {
		containers = append(containers, resolveContainer(pod, &pod.Spec.Containers[i], "container", sources, node))
	}
	return PodEnvironment{Containers: containers}
}

func ResolveVariable(pod *corev1.Pod, containerName, variable string, sources map[SourceID]SourceData, node NodeData) (Row, string, bool, bool) {
	if pod == nil {
		return Row{}, "", false, false
	}
	for i := range pod.Spec.InitContainers {
		if pod.Spec.InitContainers[i].Name == containerName {
			return resolvedVariable(pod, &pod.Spec.InitContainers[i], variable, sources, node)
		}
	}
	for i := range pod.Spec.Containers {
		if pod.Spec.Containers[i].Name == containerName {
			return resolvedVariable(pod, &pod.Spec.Containers[i], variable, sources, node)
		}
	}
	return Row{}, "", false, false
}

func resolvedVariable(pod *corev1.Pod, container *corev1.Container, variable string, sources map[SourceID]SourceData, node NodeData) (Row, string, bool, bool) {
	bindings, shadowed := resolveBindings(pod, container, sources, node)
	current, ok := bindings[variable]
	if !ok {
		return Row{}, "", false, false
	}
	current.row.ShadowedSources = compactRefs(shadowed[variable])
	return current.row, current.value, current.available, true
}

func resolveContainer(pod *corev1.Pod, container *corev1.Container, role string, sources map[SourceID]SourceData, node NodeData) Container {
	bindings, shadowed := resolveBindings(pod, container, sources, node)
	rows := make([]Row, 0, len(bindings))
	for name, current := range bindings {
		current.row.ShadowedSources = compactRefs(shadowed[name])
		if current.row.State == ValueMissing && !current.row.Optional {
			current.row.MissingImpact = RequiredMissingImpact(pod, container.Name)
			current.row.Message = missingImpactMessage(current.row.Message, current.row.MissingImpact)
		}
		if !current.row.Sensitive {
			current.row.Value = current.value
		}
		rows = append(rows, current.row)
	}
	sort.Slice(rows, func(i, j int) bool { return rows[i].Name < rows[j].Name })
	return Container{Name: container.Name, Role: role, Rows: rows}
}

func RequiredMissingImpact(pod *corev1.Pod, containerName string) MissingImpact {
	if pod == nil {
		return MissingImpactStartupBlocked
	}
	statuses := append([]corev1.ContainerStatus(nil), pod.Status.InitContainerStatuses...)
	statuses = append(statuses, pod.Status.ContainerStatuses...)
	for _, status := range statuses {
		if status.Name != containerName {
			continue
		}
		if status.State.Waiting != nil && status.State.Waiting.Reason == "CreateContainerConfigError" {
			return MissingImpactStartupBlocked
		}
		if status.State.Waiting != nil {
			return MissingImpactStartupBlocked
		}
		if status.State.Running != nil || status.State.Terminated != nil || status.LastTerminationState.Terminated != nil || status.ContainerID != "" {
			return MissingImpactRestartBlocked
		}
		break
	}
	return MissingImpactStartupBlocked
}

func missingImpactMessage(message string, impact MissingImpact) string {
	suffix := "This prevents the container from starting."
	if impact == MissingImpactRestartBlocked {
		suffix = "The Pod has already started, but this container cannot start again while this is missing."
	}
	if message == "" {
		return suffix
	}
	return message + " " + suffix
}

func resolveBindings(pod *corev1.Pod, container *corev1.Container, sources map[SourceID]SourceData, node NodeData) (map[string]binding, map[string][]SourceRef) {
	bindings := make(map[string]binding)
	shadowed := make(map[string][]SourceRef)
	for _, from := range container.EnvFrom {
		kind, name, optional := envFromIdentity(from)
		if name == "" {
			continue
		}
		source := sources[SourceID{Kind: kind, Name: name}]
		if source.Kind == "" {
			source = SourceData{Kind: kind, Name: name, State: SourceMissing, Optional: optional}
		}
		keys := append([]string(nil), source.Keys...)
		if len(keys) == 0 {
			for key := range source.Values {
				keys = append(keys, key)
			}
		}
		sort.Strings(keys)
		if source.State != SourceAvailable {
			name := from.Prefix + "* (" + kind + "/" + source.Name + ")"
			state, message := stateForSource(source.State, optional, kind, source.Name)
			if source.Message != "" {
				message = source.Message
			}
			bindings[name] = binding{row: Row{Name: name, State: state, Sensitive: kind == "Secret", Source: SourceRef{Kind: kind, Name: source.Name}, Message: message, Optional: optional, Placeholder: true}}
			continue
		}
		for _, key := range keys {
			name := from.Prefix + key
			if len(validation.IsEnvVarName(name)) != 0 {
				continue
			}
			ref := SourceRef{Kind: kind, Name: source.Name, Key: key}
			if previous, ok := bindings[name]; ok {
				shadowed[name] = append(shadowed[name], previous.row.Source)
			}
			value, available := source.Values[key]
			state := ValueResolved
			if source.Sensitive {
				state = ValueMasked
			}
			bindings[name] = binding{row: Row{Name: name, State: state, Sensitive: source.Sensitive, Source: ref, Optional: optional}, value: value, available: available}
		}
	}

	for _, env := range container.Env {
		if previous, ok := bindings[env.Name]; ok {
			shadowed[env.Name] = append(shadowed[env.Name], previous.row.Source)
		}
		bindings[env.Name] = resolveExplicit(pod, container, env, bindings, sources, node)
	}

	return bindings, shadowed
}

func resolveExplicit(pod *corev1.Pod, container *corev1.Container, env corev1.EnvVar, bindings map[string]binding, sources map[SourceID]SourceData, node NodeData) binding {
	if env.ValueFrom == nil {
		value, deps, sensitive, available, runtimeDependent, dependencyState, dependencyMessage := expand(env.Value, bindings)
		state := ValueResolved
		if dependencyState != ValueResolved {
			state = dependencyState
		} else if sensitive {
			state = ValueMasked
		} else if !available || runtimeDependent {
			state = ValueUnavailable
		}
		message := dependencyMessage
		if runtimeDependent && message == "" {
			message = "This value is completed when the container starts because it uses another variable that is not declared on the Pod."
		}
		return binding{row: Row{Name: env.Name, State: state, Sensitive: sensitive, Source: SourceRef{Kind: "Direct"}, Dependencies: deps, Message: message, RuntimeDependent: runtimeDependent}, value: value, available: available && !runtimeDependent}
	}
	if ref := env.ValueFrom.ConfigMapKeyRef; ref != nil {
		return resolveKeyRef(env.Name, "ConfigMap", ref.Name, ref.Key, ref.Optional != nil && *ref.Optional, sources)
	}
	if ref := env.ValueFrom.SecretKeyRef; ref != nil {
		return resolveKeyRef(env.Name, "Secret", ref.Name, ref.Key, ref.Optional != nil && *ref.Optional, sources)
	}
	if ref := env.ValueFrom.FieldRef; ref != nil {
		value, err := podFieldValue(pod, ref.FieldPath)
		if err != nil {
			return unavailable(env.Name, "Pod", err.Error())
		}
		return binding{row: Row{Name: env.Name, State: ValueResolved, Source: SourceRef{Kind: "Pod", Key: ref.FieldPath}, CurrentPodValue: true, Message: "This is the current Pod value; the container evaluated it when it started."}, value: value, available: true}
	}
	if ref := env.ValueFrom.ResourceFieldRef; ref != nil {
		resourceContainer, err := resourceFieldContainer(pod, container, ref.ContainerName)
		if err != nil {
			return unavailable(env.Name, "Container resources", err.Error())
		}
		if resourceName, ok := missingLimitResource(resourceContainer, ref.Resource); ok {
			if node.State != SourceAvailable {
				state, message := stateForSource(node.State, false, "Node", pod.Spec.NodeName)
				return binding{row: Row{Name: env.Name, State: state, Source: SourceRef{Kind: "Container resources", Key: ref.Resource}, Message: message}}
			}
			quantity, ok := node.Allocatable[resourceName]
			if !ok {
				return unavailable(env.Name, "Container resources", "The assigned Node does not report this resource.")
			}
			copy := resourceContainer.DeepCopy()
			if copy.Resources.Limits == nil {
				copy.Resources.Limits = corev1.ResourceList{}
			}
			copy.Resources.Limits[resourceName] = quantity
			resourceContainer = copy
		}
		value, err := kubectlresource.ExtractContainerResourceValue(ref, resourceContainer)
		if err != nil {
			return unavailable(env.Name, "Container resources", err.Error())
		}
		return binding{row: Row{Name: env.Name, State: ValueResolved, Source: SourceRef{Kind: "Container resources", Key: ref.Resource}, CurrentPodValue: true, Message: "This is the current resource value; the container evaluated it when it started."}, value: value, available: true}
	}
	return unavailable(env.Name, "Unknown", "Unsupported value source")
}

func podFieldValue(pod *corev1.Pod, path string) (string, error) {
	switch path {
	case "spec.nodeName":
		return pod.Spec.NodeName, nil
	case "spec.serviceAccountName":
		return pod.Spec.ServiceAccountName, nil
	case "status.hostIP":
		return pod.Status.HostIP, nil
	case "status.hostIPs":
		values := make([]string, 0, len(pod.Status.HostIPs))
		for _, ip := range pod.Status.HostIPs {
			values = append(values, ip.IP)
		}
		return strings.Join(values, ","), nil
	case "status.podIP":
		return pod.Status.PodIP, nil
	case "status.podIPs":
		values := make([]string, 0, len(pod.Status.PodIPs))
		for _, ip := range pod.Status.PodIPs {
			values = append(values, ip.IP)
		}
		return strings.Join(values, ","), nil
	default:
		return fieldpath.ExtractFieldPathAsString(pod, path)
	}
}

func missingLimitResource(container *corev1.Container, selector string) (corev1.ResourceName, bool) {
	if !strings.HasPrefix(selector, "limits.") {
		return "", false
	}
	name := corev1.ResourceName(strings.TrimPrefix(selector, "limits."))
	_, exists := container.Resources.Limits[name]
	return name, !exists
}

func PodEnvironmentNeedsNode(pod *corev1.Pod) bool {
	if pod == nil {
		return false
	}
	containers := append([]corev1.Container(nil), pod.Spec.InitContainers...)
	containers = append(containers, pod.Spec.Containers...)
	for i := range containers {
		for _, env := range containers[i].Env {
			if env.ValueFrom == nil || env.ValueFrom.ResourceFieldRef == nil {
				continue
			}
			ref := env.ValueFrom.ResourceFieldRef
			resourceContainer, err := resourceFieldContainer(pod, &containers[i], ref.ContainerName)
			if err != nil {
				continue
			}
			if _, missing := missingLimitResource(resourceContainer, ref.Resource); missing {
				return true
			}
		}
	}
	return false
}

func resourceFieldContainer(pod *corev1.Pod, current *corev1.Container, name string) (*corev1.Container, error) {
	if name == "" || name == current.Name {
		return current, nil
	}
	for i := range pod.Spec.InitContainers {
		if pod.Spec.InitContainers[i].Name == name {
			return &pod.Spec.InitContainers[i], nil
		}
	}
	for i := range pod.Spec.Containers {
		if pod.Spec.Containers[i].Name == name {
			return &pod.Spec.Containers[i], nil
		}
	}
	return nil, fmt.Errorf("container %q was not found", name)
}

func resolveKeyRef(envName, kind, name, key string, optional bool, sources map[SourceID]SourceData) binding {
	source := sources[SourceID{Kind: kind, Name: name}]
	if source.Kind == "" {
		source = SourceData{Kind: kind, Name: name, State: SourceMissing}
	}
	ref := SourceRef{Kind: kind, Name: name, Key: key}
	if source.State != SourceAvailable {
		state, message := stateForSource(source.State, optional, kind, name)
		if source.Message != "" {
			message = source.Message
		}
		return binding{row: Row{Name: envName, State: state, Sensitive: kind == "Secret", Source: ref, Message: message, Optional: optional}}
	}
	value, ok := source.Values[key]
	if !ok && source.Sensitive {
		for _, candidate := range source.Keys {
			if candidate == key {
				return binding{row: Row{Name: envName, State: ValueMasked, Sensitive: true, Source: ref, Optional: optional}}
			}
		}
	}
	if !ok {
		message := fmt.Sprintf("Key %s is missing from %s/%s.", key, kind, name)
		if optional {
			message = fmt.Sprintf("Optional key %s is absent from %s/%s.", key, kind, name)
		}
		return binding{row: Row{Name: envName, State: ValueMissing, Sensitive: source.Sensitive, Source: ref, Message: message, Optional: optional}}
	}
	state := ValueResolved
	if source.Sensitive {
		state = ValueMasked
	}
	return binding{row: Row{Name: envName, State: state, Sensitive: source.Sensitive, Source: ref, Optional: optional}, value: value, available: true}
}

func expand(input string, bindings map[string]binding) (string, []SourceRef, bool, bool, bool, ValueState, string) {
	var out strings.Builder
	var deps []SourceRef
	sensitive, available, runtimeDependent := false, true, false
	dependencyState, dependencyMessage := ValueResolved, ""
	for i := 0; i < len(input); {
		if input[i] != '$' || i+1 >= len(input) {
			out.WriteByte(input[i])
			i++
			continue
		}
		if input[i+1] == '$' {
			out.WriteByte('$')
			i += 2
			continue
		}
		if input[i+1] != '(' {
			out.WriteByte(input[i])
			i++
			continue
		}
		end := strings.IndexByte(input[i+2:], ')')
		if end < 0 {
			out.WriteString(input[i:])
			break
		}
		end += i + 2
		name := input[i+2 : end]
		dep, ok := bindings[name]
		if !ok {
			out.WriteString(input[i : end+1])
			runtimeDependent = true
		} else {
			if dep.row.State == ValueMissing && dep.row.Optional {
				out.WriteString(input[i : end+1])
				i = end + 1
				continue
			}
			deps = append(deps, SourceRef{Kind: "Variable", Variable: name})
			if dep.row.Source.Kind != "Direct" {
				deps = append(deps, dep.row.Source)
			}
			deps = append(deps, dep.row.Dependencies...)
			sensitive = sensitive || dep.row.Sensitive
			available = available && dep.available
			runtimeDependent = runtimeDependent || dep.row.RuntimeDependent
			if rankValueState(dep.row.State) > rankValueState(dependencyState) {
				dependencyState = dep.row.State
				dependencyMessage = dep.row.Message
			}
			out.WriteString(dep.value)
		}
		i = end + 1
	}
	return out.String(), compactRefs(deps), sensitive, available, runtimeDependent, dependencyState, dependencyMessage
}

func rankValueState(state ValueState) int {
	switch state {
	case ValueDenied:
		return 3
	case ValueMissing:
		return 2
	case ValueUnavailable:
		return 1
	default:
		return 0
	}
}

func envFromIdentity(from corev1.EnvFromSource) (string, string, bool) {
	if from.ConfigMapRef != nil {
		return "ConfigMap", from.ConfigMapRef.Name, from.ConfigMapRef.Optional != nil && *from.ConfigMapRef.Optional
	}
	if from.SecretRef != nil {
		return "Secret", from.SecretRef.Name, from.SecretRef.Optional != nil && *from.SecretRef.Optional
	}
	return "", "", false
}

func stateForSource(state SourceState, optional bool, kind, name string) (ValueState, string) {
	if state == SourceDenied {
		return ValueDenied, fmt.Sprintf("Access to %s/%s is required to resolve these variables.", kind, name)
	}
	if state == SourceUnavailable {
		return ValueUnavailable, fmt.Sprintf("%s/%s could not be read.", kind, name)
	}
	if optional {
		return ValueMissing, fmt.Sprintf("Optional %s/%s is absent.", kind, name)
	}
	return ValueMissing, fmt.Sprintf("%s/%s is missing.", kind, name)
}

func unavailable(name, kind, message string) binding {
	return binding{row: Row{Name: name, State: ValueUnavailable, Source: SourceRef{Kind: kind}, Message: message}}
}

func compactRefs(refs []SourceRef) []SourceRef {
	seen := make(map[string]bool)
	out := make([]SourceRef, 0, len(refs))
	for _, ref := range refs {
		key := ref.Kind + "\x00" + ref.Name + "\x00" + ref.Key + "\x00" + ref.Variable
		if ref.Kind == "" || seen[key] {
			continue
		}
		seen[key] = true
		out = append(out, ref)
	}
	return out
}