package mapper import ( "encoding/json" "fmt" "slices" "github.com/fluxcd/pkg/apis/kustomize" corev1 "k8s.io/api/core/v1" virtv1 "kubevirt.io/api/core/v1" "edge-infra.dev/pkg/edge/api/graph/model" persistence "edge-infra.dev/pkg/edge/apis/persistence/v1alpha1" "edge-infra.dev/pkg/edge/capabilities" kinformmodel "edge-infra.dev/pkg/f8n/kinform/model" v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1" "edge-infra.dev/pkg/sds/lib/jsonpatch" ) type HelmReleasePostRenderer struct { installationType model.WorkloadInstallationType customLabels []*model.Label helmEdgeID string } func (h *HelmReleasePostRenderer) GetNodeSelectorTerm() corev1.NodeSelectorTerm { operator := corev1.NodeSelectorOpIn key := "node.ncr.com/class" if h.installationType == model.WorkloadInstallationTypeTouchpoint { operator = corev1.NodeSelectorOpNotIn } return corev1.NodeSelectorTerm{ MatchExpressions: []corev1.NodeSelectorRequirement{ { Key: key, Operator: operator, Values: []string{"server"}, }, }, } } func (h *HelmReleasePostRenderer) GetAffinityPatchStr() string { affinity := h.nodeAffinity() jsonAffinityByteArr, err := json.Marshal(affinity) if err != nil { fmt.Println(err) } return string(jsonAffinityByteArr) } func (h *HelmReleasePostRenderer) nodeAffinity() corev1.NodeAffinity { if h.installationType == model.WorkloadInstallationTypeAdvanced && h.customLabels != nil { return h.CreateCustomLabelsAffinity() } return h.CreateAffinity() } func (h *HelmReleasePostRenderer) GetNodeSelectorPatchStr() string { var jsonNodeSelectorByteArr []byte var err error if h.installationType == model.WorkloadInstallationTypeAdvanced && h.customLabels != nil { jsonNodeSelectorByteArr, err = json.Marshal(h.createCustomLabelNodeSelectorTerms()) } else { jsonNodeSelectorByteArr, err = json.Marshal([]corev1.NodeSelectorTerm{ h.GetNodeSelectorTerm(), }) } if err != nil { fmt.Println(err) } return string(jsonNodeSelectorByteArr) } // TODO(pa250194): Pending Investigation and fixing in Q1 2025 // We will disable drift detection for the Kinds: IENPatch, StatefulSet, Daemonset // Bug Tickets: // https://github.com/ncrvoyix-swt-retail/edge-roadmap/issues/12785 // https://github.com/ncrvoyix-swt-retail/edge-roadmap/issues/12747 func disableDriftDetectionForProblemKinds() []kustomize.Patch { patches := make([]kustomize.Patch, 0) problemKinds := []string{"IENPatch", "StatefulSet", "DaemonSet"} for _, problemKind := range problemKinds { patch := kustomize.Patch{ Target: &kustomize.Selector{ Version: "v1", Kind: problemKind, }, Patch: "- op: add\n path: /metadata/annotations/helm.toolkit.fluxcd.io~1driftDetection\n value: disabled", } patches = append(patches, patch) } return patches } func (h *HelmReleasePostRenderer) GetKustomizePatches() ([]kustomize.Patch, error) { patchKindsByInstallationType := map[model.WorkloadInstallationType][]string{ model.WorkloadInstallationTypeServerPreferred: {"Deployment", "StatefulSet", "ReplicaSet", "Job", "CronJob", "VirtualMachine"}, model.WorkloadInstallationTypeAny: {"Deployment", "StatefulSet", "ReplicaSet", "Job", "CronJob", "VirtualMachine"}, model.WorkloadInstallationTypeAdvanced: {"Deployment", "StatefulSet", "DaemonSet", "ReplicaSet", "Job", "CronJob", "VirtualMachine", persistence.Kind}, model.WorkloadInstallationTypeTouchpoint: {"Deployment", "StatefulSet", "DaemonSet", "ReplicaSet", "Job", "CronJob", "VirtualMachine", persistence.Kind}, model.WorkloadInstallationTypeServer: {"Deployment", "StatefulSet", "DaemonSet", "ReplicaSet", "Job", "CronJob", "VirtualMachine", persistence.Kind}, } patchesKinds := patchKindsByInstallationType[h.installationType] jsonAffinityStr := h.GetAffinityPatchStr() jsonNodeSelectorStr := h.GetNodeSelectorPatchStr() var patches []kustomize.Patch for _, patchKind := range patchesKinds { kustomizeSelector := kustomize.Selector{ Version: "v1", Kind: patchKind, } kubePatch := kustomize.Patch{} switch patchKind { case persistence.Kind: kustomizeSelector.Version = persistence.GroupVersion.Version kustomizeSelector.Group = persistence.GroupVersion.Group kubePatch.Patch = fmt.Sprintf("- op: replace\n path: /spec/nodeSelectorTerms\n value: %s", jsonNodeSelectorStr) kubePatch.Target = &kustomizeSelector case "CronJob": kubePatch.Patch = fmt.Sprintf("- op: add\n path: /spec/jobTemplate/spec/template/spec/affinity\n value: {\"nodeAffinity\": %s }", jsonAffinityStr) kubePatch.Target = &kustomizeSelector case "VirtualMachine": var err error kubePatch, err = h.vmPatch() if err != nil { return nil, err } default: kubePatch.Patch = fmt.Sprintf("- op: add\n path: /spec/template/spec/affinity\n value: {\"nodeAffinity\": %s }", jsonAffinityStr) kubePatch.Target = &kustomizeSelector } patches = append(patches, kubePatch) } return patches, nil } func (h *HelmReleasePostRenderer) vmPatch() (kustomize.Patch, error) { kustomizeSelector := kustomize.Selector{ Version: virtv1.SchemeGroupVersion.Version, Group: virtv1.SchemeGroupVersion.Group, Kind: "VirtualMachine", } ops := []jsonpatch.Operation{} affinity := h.nodeAffinity() ops = append(ops, jsonpatch.Operation{ Op: jsonpatch.Add, Path: "/spec/template/spec/affinity", Value: corev1.Affinity{NodeAffinity: &affinity}, }) ops = append(ops, jsonpatch.Operation{ Op: jsonpatch.Add, Path: fmt.Sprintf("/spec/template/metadata/annotations/%s", kinformmodel.HelmEdgeIDAnnotation), Value: h.helmEdgeID, }) patchStr, err := jsonpatch.NewPatch(ops...).String() if err != nil { return kustomize.Patch{}, err } return kustomize.Patch{ Patch: patchStr, Target: &kustomizeSelector, }, nil } func (h *HelmReleasePostRenderer) CreateAffinity() corev1.NodeAffinity { affinity := corev1.NodeAffinity{} if h.installationType == model.WorkloadInstallationTypeAny || h.installationType == model.WorkloadInstallationTypeServerPreferred { affinity.PreferredDuringSchedulingIgnoredDuringExecution = []corev1.PreferredSchedulingTerm{ { Weight: 1, Preference: h.GetNodeSelectorTerm(), }, } } else { affinity.RequiredDuringSchedulingIgnoredDuringExecution = &corev1.NodeSelector{ NodeSelectorTerms: []corev1.NodeSelectorTerm{ h.GetNodeSelectorTerm(), }, } } return affinity } func (h *HelmReleasePostRenderer) CreateCustomLabelsAffinity() corev1.NodeAffinity { affinity := corev1.NodeAffinity{} nodeSelectorTerms := h.createCustomLabelNodeSelectorTerms() affinity.RequiredDuringSchedulingIgnoredDuringExecution = &corev1.NodeSelector{ NodeSelectorTerms: nodeSelectorTerms, } return affinity } func (h *HelmReleasePostRenderer) createCustomLabelNodeSelectorTerms() []corev1.NodeSelectorTerm { nodeSelectorTerm := corev1.NodeSelectorTerm{} if h.customLabels != nil { for _, label := range h.customLabels { key := label.Key if !slices.Contains(capabilities.EdgeAutomatedCapabilityLabelTypes, label.Type) { key = fmt.Sprintf(v1ien.CustomNodeLabel, label.Key) } matchExpression := corev1.NodeSelectorRequirement{ Key: key, Operator: corev1.NodeSelectorOpExists, } nodeSelectorTerm.MatchExpressions = append(nodeSelectorTerm.MatchExpressions, matchExpression) } } return []corev1.NodeSelectorTerm{ nodeSelectorTerm, } } func (h *HelmReleasePostRenderer) CreatePostrender() (interface{}, error) { return h.GetKustomizePatches() }