From 0438e375ccd782db663c3ed92b31b3fbda89e55b Mon Sep 17 00:00:00 2001 From: "chentao.cht" Date: Tue, 14 Nov 2023 21:29:35 +0800 Subject: [PATCH 1/3] feat(override): add command,args,annotations and labels overrider --- ...ubeadmiral.io_clusteroverridepolicies.yaml | 92 +++++++++- .../core.kubeadmiral.io_overridepolicies.yaml | 92 +++++++++- .../core/v1alpha1/types_overridepolicy.go | 56 +++++- .../core/v1alpha1/zz_generated.deepcopy.go | 72 ++++++++ .../override/annotationslabelspatch.go | 110 ++++++++++++ pkg/controllers/override/commandargspatch.go | 159 ++++++++++++++++++ pkg/controllers/override/util.go | 35 +++- 7 files changed, 612 insertions(+), 4 deletions(-) create mode 100644 pkg/controllers/override/annotationslabelspatch.go create mode 100644 pkg/controllers/override/commandargspatch.go diff --git a/config/crds/core.kubeadmiral.io_clusteroverridepolicies.yaml b/config/crds/core.kubeadmiral.io_clusteroverridepolicies.yaml index 395f3428..ea49c15a 100644 --- a/config/crds/core.kubeadmiral.io_clusteroverridepolicies.yaml +++ b/config/crds/core.kubeadmiral.io_clusteroverridepolicies.yaml @@ -40,8 +40,77 @@ spec: overriders: description: Overriders specify the overriders to be applied in the target clusters. properties: + annotations: + description: Annotation specifies overriders that apply to the resource annotations. + items: + description: StringMapOverrider represents the rules dedicated to handling resource labels/annotations + properties: + operator: + description: Operator specifies the operation. If omitted, defaults to "overwrite". + enum: + - addIfAbsent + - overwrite + - delete + type: string + value: + additionalProperties: + type: string + description: Value is the value(s) that will be applied to annotations/labels of resource. If Operator is 'addIfAbsent', items in Value (empty is not allowed) will be added in annotations/labels. - For 'addIfAbsent' Operator, the keys in Value cannot conflict with annotations/labels. If Operator is 'overwrite', items in Value which match in annotations/labels will be replaced. If Operator is 'delete', items in Value which match in annotations/labels will be deleted. + type: object + required: + - value + type: object + type: array + args: + description: Args specifies overriders that apply to the container arguments. + items: + properties: + containerName: + description: ContainerName targets the specified container or init container in the pod template. + type: string + operator: + description: Operator specifies the operation. If omitted, defaults to "overwrite". + enum: + - append + - overwrite + - delete + type: string + value: + description: Value is the value(s) that will be applied to command/args of ContainerName. If Operator is 'append', items in Value (empty is not allowed) will be appended to command/args. If Operator is 'overwrite', current command/args of ContainerName will be completely replaced by Value. If Operator is 'delete', items in Value that match in command/args will be deleted. + items: + type: string + type: array + required: + - containerName + - value + type: object + type: array + command: + description: Command specifies overriders that apply to the container commands. + items: + properties: + containerName: + description: ContainerName targets the specified container or init container in the pod template. + type: string + operator: + description: Operator specifies the operation. If omitted, defaults to "overwrite". + enum: + - append + - overwrite + - delete + type: string + value: + description: Value is the value(s) that will be applied to command/args of ContainerName. If Operator is 'append', items in Value (empty is not allowed) will be appended to command/args. If Operator is 'overwrite', current command/args of ContainerName will be completely replaced by Value. If Operator is 'delete', items in Value that match in command/args will be deleted. + items: + type: string + type: array + required: + - containerName + - value + type: object + type: array image: - description: Image specifies the overriders that applies to the image. + description: Image specifies the overriders that apply to the image. items: properties: containerNames: @@ -99,6 +168,27 @@ spec: - path type: object type: array + labels: + description: Label specifies overriders that apply to the resource labels. + items: + description: StringMapOverrider represents the rules dedicated to handling resource labels/annotations + properties: + operator: + description: Operator specifies the operation. If omitted, defaults to "overwrite". + enum: + - addIfAbsent + - overwrite + - delete + type: string + value: + additionalProperties: + type: string + description: Value is the value(s) that will be applied to annotations/labels of resource. If Operator is 'addIfAbsent', items in Value (empty is not allowed) will be added in annotations/labels. - For 'addIfAbsent' Operator, the keys in Value cannot conflict with annotations/labels. If Operator is 'overwrite', items in Value which match in annotations/labels will be replaced. If Operator is 'delete', items in Value which match in annotations/labels will be deleted. + type: object + required: + - value + type: object + type: array type: object targetClusters: description: TargetClusters selects the clusters in which the overriders in this rule should be applied. If multiple types of selectors are specified, the overall result is the intersection of all of them. diff --git a/config/crds/core.kubeadmiral.io_overridepolicies.yaml b/config/crds/core.kubeadmiral.io_overridepolicies.yaml index 053827a4..bc776c47 100644 --- a/config/crds/core.kubeadmiral.io_overridepolicies.yaml +++ b/config/crds/core.kubeadmiral.io_overridepolicies.yaml @@ -40,8 +40,77 @@ spec: overriders: description: Overriders specify the overriders to be applied in the target clusters. properties: + annotations: + description: Annotation specifies overriders that apply to the resource annotations. + items: + description: StringMapOverrider represents the rules dedicated to handling resource labels/annotations + properties: + operator: + description: Operator specifies the operation. If omitted, defaults to "overwrite". + enum: + - addIfAbsent + - overwrite + - delete + type: string + value: + additionalProperties: + type: string + description: Value is the value(s) that will be applied to annotations/labels of resource. If Operator is 'addIfAbsent', items in Value (empty is not allowed) will be added in annotations/labels. - For 'addIfAbsent' Operator, the keys in Value cannot conflict with annotations/labels. If Operator is 'overwrite', items in Value which match in annotations/labels will be replaced. If Operator is 'delete', items in Value which match in annotations/labels will be deleted. + type: object + required: + - value + type: object + type: array + args: + description: Args specifies overriders that apply to the container arguments. + items: + properties: + containerName: + description: ContainerName targets the specified container or init container in the pod template. + type: string + operator: + description: Operator specifies the operation. If omitted, defaults to "overwrite". + enum: + - append + - overwrite + - delete + type: string + value: + description: Value is the value(s) that will be applied to command/args of ContainerName. If Operator is 'append', items in Value (empty is not allowed) will be appended to command/args. If Operator is 'overwrite', current command/args of ContainerName will be completely replaced by Value. If Operator is 'delete', items in Value that match in command/args will be deleted. + items: + type: string + type: array + required: + - containerName + - value + type: object + type: array + command: + description: Command specifies overriders that apply to the container commands. + items: + properties: + containerName: + description: ContainerName targets the specified container or init container in the pod template. + type: string + operator: + description: Operator specifies the operation. If omitted, defaults to "overwrite". + enum: + - append + - overwrite + - delete + type: string + value: + description: Value is the value(s) that will be applied to command/args of ContainerName. If Operator is 'append', items in Value (empty is not allowed) will be appended to command/args. If Operator is 'overwrite', current command/args of ContainerName will be completely replaced by Value. If Operator is 'delete', items in Value that match in command/args will be deleted. + items: + type: string + type: array + required: + - containerName + - value + type: object + type: array image: - description: Image specifies the overriders that applies to the image. + description: Image specifies the overriders that apply to the image. items: properties: containerNames: @@ -99,6 +168,27 @@ spec: - path type: object type: array + labels: + description: Label specifies overriders that apply to the resource labels. + items: + description: StringMapOverrider represents the rules dedicated to handling resource labels/annotations + properties: + operator: + description: Operator specifies the operation. If omitted, defaults to "overwrite". + enum: + - addIfAbsent + - overwrite + - delete + type: string + value: + additionalProperties: + type: string + description: Value is the value(s) that will be applied to annotations/labels of resource. If Operator is 'addIfAbsent', items in Value (empty is not allowed) will be added in annotations/labels. - For 'addIfAbsent' Operator, the keys in Value cannot conflict with annotations/labels. If Operator is 'overwrite', items in Value which match in annotations/labels will be replaced. If Operator is 'delete', items in Value which match in annotations/labels will be deleted. + type: object + required: + - value + type: object + type: array type: object targetClusters: description: TargetClusters selects the clusters in which the overriders in this rule should be applied. If multiple types of selectors are specified, the overall result is the intersection of all of them. diff --git a/pkg/apis/core/v1alpha1/types_overridepolicy.go b/pkg/apis/core/v1alpha1/types_overridepolicy.go index 3b26679c..341543a2 100644 --- a/pkg/apis/core/v1alpha1/types_overridepolicy.go +++ b/pkg/apis/core/v1alpha1/types_overridepolicy.go @@ -84,16 +84,70 @@ type TargetClusters struct { // Overriders contains a list of override patches. // The order in which the override patches take effect is: // - Image +// - Command +// - Args +// - Annotations +// - Labels // - JsonPatch type Overriders struct { - // Image specifies the overriders that applies to the image. + // Image specifies the overriders that apply to the image. // +optional Image []ImageOverrider `json:"image,omitempty"` + + // Command specifies overriders that apply to the container commands. + // +optional + Command []EntrypointOverrider `json:"command,omitempty"` + + // Args specifies overriders that apply to the container arguments. + // +optional + Args []EntrypointOverrider `json:"args,omitempty"` + + // Annotation specifies overriders that apply to the resource annotations. + // +optional + Annotations []StringMapOverrider `json:"annotations,omitempty"` + + // Label specifies overriders that apply to the resource labels. + // +optional + Labels []StringMapOverrider `json:"labels,omitempty"` + // JsonPatch specifies overriders in a syntax similar to RFC6902 JSON Patch. // +optional JsonPatch []JsonPatchOverrider `json:"jsonpatch,omitempty"` } +type EntrypointOverrider struct { + // ContainerName targets the specified container or init container in the pod template. + ContainerName string `json:"containerName"` + + // Operator specifies the operation. + // If omitted, defaults to "overwrite". + // +kubebuilder:validation:Enum=append;overwrite;delete + // +optional + Operator string `json:"operator,omitempty"` + + // Value is the value(s) that will be applied to command/args of ContainerName. + // If Operator is 'append', items in Value (empty is not allowed) will be appended to command/args. + // If Operator is 'overwrite', current command/args of ContainerName will be completely replaced by Value. + // If Operator is 'delete', items in Value that match in command/args will be deleted. + Value []string `json:"value"` +} + +// StringMapOverrider represents the rules dedicated to handling resource labels/annotations +type StringMapOverrider struct { + // Operator specifies the operation. + // If omitted, defaults to "overwrite". + // +kubebuilder:validation:Enum=addIfAbsent;overwrite;delete + // +optional + Operator string `json:"operator,omitempty"` + + // Value is the value(s) that will be applied to annotations/labels of resource. + // If Operator is 'addIfAbsent', items in Value (empty is not allowed) will be added in annotations/labels. + // - For 'addIfAbsent' Operator, the keys in Value cannot conflict with annotations/labels. + // If Operator is 'overwrite', items in Value which match in annotations/labels will be replaced. + // If Operator is 'delete', items in Value which match in annotations/labels will be deleted. + Value map[string]string `json:"value"` +} + type ImageOverrider struct { // ContainerNames are ignored when ImagePath is set. // If empty, the image override rule applies to all containers. diff --git a/pkg/apis/core/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/core/v1alpha1/zz_generated.deepcopy.go index 22f021d8..e7c54780 100644 --- a/pkg/apis/core/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/core/v1alpha1/zz_generated.deepcopy.go @@ -602,6 +602,27 @@ func (in *DesiredPlacement) DeepCopy() *DesiredPlacement { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EntrypointOverrider) DeepCopyInto(out *EntrypointOverrider) { + *out = *in + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EntrypointOverrider. +func (in *EntrypointOverrider) DeepCopy() *EntrypointOverrider { + if in == nil { + return nil + } + out := new(EntrypointOverrider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederatedCluster) DeepCopyInto(out *FederatedCluster) { *out = *in @@ -1298,6 +1319,34 @@ func (in *Overriders) DeepCopyInto(out *Overriders) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]EntrypointOverrider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]EntrypointOverrider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make([]StringMapOverrider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make([]StringMapOverrider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.JsonPatch != nil { in, out := &in.JsonPatch, &out.JsonPatch *out = make([]JsonPatchOverrider, len(*in)) @@ -2008,6 +2057,29 @@ func (in *StatusCollectionConfig) DeepCopy() *StatusCollectionConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StringMapOverrider) DeepCopyInto(out *StringMapOverrider) { + *out = *in + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringMapOverrider. +func (in *StringMapOverrider) DeepCopy() *StringMapOverrider { + if in == nil { + return nil + } + out := new(StringMapOverrider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TargetClusters) DeepCopyInto(out *TargetClusters) { *out = *in diff --git a/pkg/controllers/override/annotationslabelspatch.go b/pkg/controllers/override/annotationslabelspatch.go new file mode 100644 index 00000000..31006328 --- /dev/null +++ b/pkg/controllers/override/annotationslabelspatch.go @@ -0,0 +1,110 @@ +/* +Copyright 2023 The KubeAdmiral 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 override + +import ( + "encoding/json" + "fmt" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + fedcorev1a1 "github.com/kubewharf/kubeadmiral/pkg/apis/core/v1alpha1" +) + +func parseStringMapOverriders( + fedObject fedcorev1a1.GenericFederatedObject, + overriders []fedcorev1a1.StringMapOverrider, + target string, +) (fedcorev1a1.OverridePatches, error) { + if len(overriders) == 0 { + return fedcorev1a1.OverridePatches{}, nil + } + + sourceObj, err := fedObject.GetSpec().GetTemplateAsUnstructured() + if err != nil { + return nil, fmt.Errorf("failed to get sourceObj from fedObj: %w", err) + } + + // get labels or annotations of sourceObj + mapValue := getLabelsOrAnnotationsFromObject(sourceObj, target) + + // apply StringMapOverriders to mapValue + for index := range overriders { + mapValue, err = applyStringMapOverrider(mapValue, &overriders[index]) + if err != nil { + return nil, fmt.Errorf("failed to apply %s overrider: %w", target, err) + } + } + + // marshal mapValue into jsonBytes + jsonBytes, err := json.Marshal(mapValue) + if err != nil { + return nil, fmt.Errorf("failed to marshal %s value(%v): %w", target, mapValue, err) + } + + return fedcorev1a1.OverridePatches{ + { + Op: OperatorReplace, + Path: fmt.Sprintf("/metadata/%s", target), + Value: apiextensionsv1.JSON{Raw: jsonBytes}, + }, + }, nil +} + +func getLabelsOrAnnotationsFromObject(rawObj *unstructured.Unstructured, target string) map[string]string { + if target == LabelsTarget { + return rawObj.GetLabels() + } + return rawObj.GetAnnotations() +} + +// applyStringMapOverrider +func applyStringMapOverrider(mapValue map[string]string, stringMapOverrider *fedcorev1a1.StringMapOverrider) (map[string]string, error) { + if mapValue == nil { + mapValue = make(map[string]string) + } + overriderMap := stringMapOverrider.Value + + operator := stringMapOverrider.Operator + if operator == "" { + operator = OperatorOverwrite + } + + if operator == OperatorAddIfAbsent && len(stringMapOverrider.Value) == 0 { + return nil, fmt.Errorf("%s operation needs value", OperatorAddIfAbsent) + } + + for key, value := range overriderMap { + switch operator { + case OperatorAddIfAbsent: + // addIfAbsent should not operate on existing key-value pairs + if _, ok := mapValue[key]; ok { + return nil, fmt.Errorf("%s is not allowed to operate on exist key:%s", OperatorAddIfAbsent, key) + } + mapValue[key] = value + case OperatorOverwrite: + // this operation only overwrites the keys that already exist in the map + if _, ok := mapValue[key]; ok { + mapValue[key] = value + } + case OperatorDelete: + delete(mapValue, key) + } + } + return mapValue, nil +} diff --git a/pkg/controllers/override/commandargspatch.go b/pkg/controllers/override/commandargspatch.go new file mode 100644 index 00000000..40cf2e68 --- /dev/null +++ b/pkg/controllers/override/commandargspatch.go @@ -0,0 +1,159 @@ +/* +Copyright 2023 The KubeAdmiral 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 override + +import ( + "encoding/json" + "fmt" + "sort" + + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/util/sets" + + fedcorev1a1 "github.com/kubewharf/kubeadmiral/pkg/apis/core/v1alpha1" + podutil "github.com/kubewharf/kubeadmiral/pkg/util/pod" +) + +// parseEntrypointOverriders parse OverridePatches from command/args overriders +func parseEntrypointOverriders( + fedObject fedcorev1a1.GenericFederatedObject, + overriders []fedcorev1a1.EntrypointOverrider, + target string, +) (fedcorev1a1.OverridePatches, error) { + if len(overriders) == 0 { + return fedcorev1a1.OverridePatches{}, nil + } + + // patchMap() is used to store the newest command/args values of each command path + patchMap := make(map[string][]string) + + // get gvk from fedObj + gvk, err := getGVKFromFederatedObject(fedObject) + if err != nil { + return nil, err + } + podSpec, err := podutil.GetResourcePodSpec(fedObject, gvk) + if err != nil { + return nil, fmt.Errorf("failed to get podSpec from sourceObj: %w", err) + } + + // parse patches and store the new command/args values in patchMap + for index := range overriders { + entrypointOverrider := &overriders[index] + containerKind, containerIndex, container := lookForMatchedContainer(podSpec, entrypointOverrider.ContainerName) + // if there is no matched container, this overrider is skipped + if container == nil { + continue + } + + targetPath, err := generateTargetPathForPodSpec(gvk, containerKind, target, containerIndex) + if err != nil { + return nil, err + } + + var currentValue []string + if _, ok := patchMap[targetPath]; ok { + currentValue = patchMap[targetPath] + } else { + currentValue = getCommandOrArgsFromContainer(container, target) + } + + // apply entrypoint overrider + if patchMap[targetPath], err = applyEntrypointOverrider(currentValue, entrypointOverrider); err != nil { + return nil, fmt.Errorf("failed to apply %s override: %w", target, err) + } + } + + // convert patchMap to patchList and return + return convertPatchMapToPatches(patchMap, target) +} + +// lookForMatchedContainer find matchedContainer from init containers or containers +func lookForMatchedContainer( + podSpec *corev1.PodSpec, + containerName string, +) (containerKind string, containerIndex int, container *corev1.Container) { + containerKinds := []string{InitContainers, Containers} + for i, containers := range [][]corev1.Container{podSpec.InitContainers, podSpec.Containers} { + for index, item := range containers { + if containerName == item.Name { + containerKind = containerKinds[i] + containerIndex = index + container = &containers[index] + return + } + } + } + return +} + +func getCommandOrArgsFromContainer(container *corev1.Container, target string) []string { + if target == CommandTarget { + return container.Command + } + return container.Args +} + +// applyEntrypointOverrider applies overrider to old list to generate new list +func applyEntrypointOverrider(oldValue []string, entrypointOverrider *fedcorev1a1.EntrypointOverrider) ([]string, error) { + operator := entrypointOverrider.Operator + var newValue []string + switch operator { + case OperatorAppend: + if len(entrypointOverrider.Value) == 0 { + return nil, fmt.Errorf("%s operation needs non-empty list", OperatorAppend) + } + newValue = append(oldValue, entrypointOverrider.Value...) + case OperatorOverwrite: + newValue = entrypointOverrider.Value + case OperatorDelete: + entrypointOverriderSet := sets.NewString(entrypointOverrider.Value...) + for _, value := range oldValue { + if !entrypointOverriderSet.Has(value) { + newValue = append(newValue, value) + } + } + default: + return nil, fmt.Errorf("unsupported operator:%s", operator) + } + return newValue, nil +} + +// convertPatchMapToPatches converts patchMap to patchList +func convertPatchMapToPatches(patchMap map[string][]string, target string) (fedcorev1a1.OverridePatches, error) { + patches := make(fedcorev1a1.OverridePatches, len(patchMap)) + index := 0 + for path, value := range patchMap { + jsonBytes, err := json.Marshal(value) + if err != nil { + return nil, fmt.Errorf("failed to marshal %s value(%v): %w", target, value, err) + } + patches[index] = fedcorev1a1.OverridePatch{ + Op: OperatorReplace, + Path: path, + Value: apiextensionsv1.JSON{Raw: jsonBytes}, + } + index++ + } + + // sort by path to avoid unnecessary updates due to disorder + sort.Slice(patches, func(i, j int) bool { + return patches[i].Path < patches[j].Path + }) + return patches, nil +} diff --git a/pkg/controllers/override/util.go b/pkg/controllers/override/util.go index f7d1b87b..919419f4 100644 --- a/pkg/controllers/override/util.go +++ b/pkg/controllers/override/util.go @@ -38,10 +38,15 @@ const ( OperatorReplace = "replace" OperatorAddIfAbsent = "addIfAbsent" + OperatorAppend = "append" OperatorOverwrite = "overwrite" OperatorDelete = "delete" - ImageTarget = "image" + ImageTarget = "image" + CommandTarget = "command" + ArgsTarget = "args" + AnnotationsTarget = "annotations" + LabelsTarget = "labels" InitContainers = "initContainers" Containers = "containers" @@ -252,6 +257,34 @@ func parsePatchesFromOverriders( patches = append(patches, imagePatches...) } + // parse patches from command overriders + if commandPatches, err := parseEntrypointOverriders(fedObject, overriders.Command, CommandTarget); err != nil { + return nil, fmt.Errorf("failed to parse command overriders: %w", err) + } else { + patches = append(patches, commandPatches...) + } + + // parse patches from args overriders + if argsPatches, err := parseEntrypointOverriders(fedObject, overriders.Args, ArgsTarget); err != nil { + return nil, fmt.Errorf("failed to parse args overriders: %w", err) + } else { + patches = append(patches, argsPatches...) + } + + // parse patches from annotation overriders + if annotationsPatches, err := parseStringMapOverriders(fedObject, overriders.Annotations, AnnotationsTarget); err != nil { + return nil, fmt.Errorf("failed to parse annotations overriders: %w", err) + } else { + patches = append(patches, annotationsPatches...) + } + + // parse patches from labels overriders + if labelsPatches, err := parseStringMapOverriders(fedObject, overriders.Labels, LabelsTarget); err != nil { + return nil, fmt.Errorf("failed to parse labels overriders: %w", err) + } else { + patches = append(patches, labelsPatches...) + } + // parse patches from jsonPatch overriders if jsonPatches, err := parseJSONPatchOverriders(overriders.JsonPatch); err != nil { return nil, fmt.Errorf("failed to parse jsonPatch overriders: %w", err) From ba5582266205b5d170eaf2df971da97dc70492e2 Mon Sep 17 00:00:00 2001 From: "chentao.cht" Date: Tue, 14 Nov 2023 21:30:04 +0800 Subject: [PATCH 2/3] test(override): add unit tests for command and args overrider --- .../override/commandargspatch_test.go | 658 ++++++++++++++++++ 1 file changed, 658 insertions(+) create mode 100644 pkg/controllers/override/commandargspatch_test.go diff --git a/pkg/controllers/override/commandargspatch_test.go b/pkg/controllers/override/commandargspatch_test.go new file mode 100644 index 00000000..f026fe89 --- /dev/null +++ b/pkg/controllers/override/commandargspatch_test.go @@ -0,0 +1,658 @@ +/* +Copyright 2023 The KubeAdmiral 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 override + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + fedcorev1a1 "github.com/kubewharf/kubeadmiral/pkg/apis/core/v1alpha1" + "github.com/kubewharf/kubeadmiral/pkg/controllers/common" +) + +func generateEntrypointOverrider(containerName, operator string, values ...string) fedcorev1a1.EntrypointOverrider { + return fedcorev1a1.EntrypointOverrider{ + ContainerName: containerName, + Operator: operator, + Value: values, + } +} + +func generateFedWorkloadWithCommandOrArgs( + kind string, target string, containerNum, initContainerNum int, values ...interface{}, +) *fedcorev1a1.FederatedObject { + var workload *unstructured.Unstructured + initPath := []string{"spec", "template", "spec", "initContainers"} + path := []string{"spec", "template", "spec", "containers"} + switch kind { + case common.DeploymentKind: + workload = basicDeploymentTemplate.DeepCopy() + case common.DaemonSetKind: + workload = basicDaemonSetTemplate.DeepCopy() + case common.StatefulSetKind: + workload = basicStatefulSetTemplate.DeepCopy() + case common.JobKind: + workload = basicJobTemplate.DeepCopy() + case common.CronJobKind: + workload = basicCronJobTemplate.DeepCopy() + initPath = []string{"spec", "jobTemplate", "spec", "template", "spec", "initContainers"} + path = []string{"spec", "jobTemplate", "spec", "template", "spec", "containers"} + case common.PodKind: + workload = basicPodTemplate.DeepCopy() + initPath = []string{"spec", "initContainers"} + path = []string{"spec", "containers"} + } + + var containers, initContainers []interface{} + + for i := 0; i < containerNum; i++ { + containers = append(containers, generateContainerWithCommandOrArgs(fmt.Sprintf("container-%d", i), target, values)) + } + for i := 0; i < initContainerNum; i++ { + initContainers = append(initContainers, generateContainerWithCommandOrArgs(fmt.Sprintf("init-container-%d", i), target, values)) + } + + if len(initContainers) > 0 { + _ = unstructured.SetNestedSlice( + workload.Object, initContainers, initPath...) + } + if len(containers) > 0 { + _ = unstructured.SetNestedSlice( + workload.Object, containers, path...) + } + + return generateFedObj(workload) +} + +func generateContainerWithCommandOrArgs(containerName string, target string, values []interface{}) map[string]interface{} { + container := map[string]interface{}{"name": containerName} + if (target == CommandTarget || target == ArgsTarget) && len(values) > 0 { + container[target] = values + } + return container +} + +type entrypointTestCases map[string]struct { + fedObject fedcorev1a1.GenericFederatedObject + entrypointOverriders []fedcorev1a1.EntrypointOverrider + expectedOverridePatches fedcorev1a1.OverridePatches + isErrorExpected bool +} + +func Test_parseCommandOrArgsOverriders(t *testing.T) { + testTargets := []string{CommandTarget, ArgsTarget} + generateTestCases := func(target string) entrypointTestCases { + return map[string]struct { + fedObject fedcorev1a1.GenericFederatedObject + entrypointOverriders []fedcorev1a1.EntrypointOverrider + expectedOverridePatches fedcorev1a1.OverridePatches + isErrorExpected bool + }{ + // Test workload's command or args scenarios + // test operations on workload(one container) + // Deployment append, overwrite, delete + fmt.Sprintf("apply %sOverriders to Deployment(one container), originValue: empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DeploymentKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment(one container), originValue: non-empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DeploymentKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15", "/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment(one container), originValue: empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DeploymentKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment(one container), originValue: non-empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DeploymentKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "echo", "world"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"echo", "world"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment(one container), originValue: empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DeploymentKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + nil), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment(one container), originValue: non-empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DeploymentKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "echo"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"sleep 15"}), + }, + isErrorExpected: false, + }, + + // DaemonSet append, overwrite, delete + fmt.Sprintf("apply %sOverriders to DaemonSet(one container), originValue: empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DaemonSetKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to DaemonSet(one container), originValue: non-empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DaemonSetKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15", "/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to DaemonSet(one container), originValue: empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DaemonSetKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to DaemonSet(one container), originValue: non-empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DaemonSetKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "echo", "world"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"echo", "world"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to DaemonSet(one container), originValue: empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DaemonSetKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + nil), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to DaemonSet(one container), originValue: non-empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DaemonSetKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "echo"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"sleep 15"}), + }, + isErrorExpected: false, + }, + + // StatefulSet append, overwrite, delete + fmt.Sprintf("apply %sOverriders to StatefulSet(one container), originValue: empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.StatefulSetKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to StatefulSet(one container), originValue: non-empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.StatefulSetKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15", "/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to StatefulSet(one container), originValue: empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.StatefulSetKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to StatefulSet(one container), originValue: non-empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.StatefulSetKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "echo", "world"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"echo", "world"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to StatefulSet(one container), originValue: empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.StatefulSetKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + nil), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to StatefulSet(one container), originValue: non-empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.StatefulSetKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "echo"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"sleep 15"}), + }, + isErrorExpected: false, + }, + + // Job append, overwrite, delete + fmt.Sprintf("apply %sOverriders to Job(one container), originValue: empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.JobKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Job(one container), originValue: non-empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.JobKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15", "/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Job(one container), originValue: empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.JobKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Job(one container), originValue: non-empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.JobKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "echo", "world"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"echo", "world"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Job(one container), originValue: empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.JobKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + nil), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Job(one container), originValue: non-empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.JobKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "echo"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/template/spec/containers/0/%s", target), + []string{"sleep 15"}), + }, + isErrorExpected: false, + }, + + // CronJob append, overwrite, delete + fmt.Sprintf("apply %sOverriders to CronJob(one container), originValue: empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.CronJobKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/jobTemplate/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to CronJob(one container), originValue: non-empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.CronJobKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/jobTemplate/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15", "/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to CronJob(one container), originValue: empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.CronJobKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/jobTemplate/spec/template/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to CronJob(one container), originValue: non-empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.CronJobKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "echo", "world"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/jobTemplate/spec/template/spec/containers/0/%s", target), + []string{"echo", "world"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to CronJob(one container), originValue: empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.CronJobKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/jobTemplate/spec/template/spec/containers/0/%s", target), + nil), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to CronJob(one container), originValue: non-empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.CronJobKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "echo"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/jobTemplate/spec/template/spec/containers/0/%s", target), + []string{"sleep 15"}), + }, + isErrorExpected: false, + }, + + // Pod append, overwrite, delete + fmt.Sprintf("apply %sOverriders to Pod(one container), originValue: empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Pod(one container), originValue: non-empty, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15", "/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Pod(one container), originValue: empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Pod(one container), originValue: non-empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "echo", "world"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/containers/0/%s", target), + []string{"echo", "world"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Pod(one container), originValue: empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/containers/0/%s", target), + nil), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Pod(one container), originValue: non-empty, operator: delete", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorDelete, "/bin/bash", "echo"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/containers/0/%s", target), + []string{"sleep 15"}), + }, + isErrorExpected: false, + }, + + // test operations on workload(multiple container) + fmt.Sprintf("apply %sOverriders to Pod(one container of multiple containers), originValue: empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 2, 2), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/containers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Pod(one container of multiple containers), originValue: non-empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 2, 2, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorOverwrite, "echo", "world"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/containers/0/%s", target), + []string{"echo", "world"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Pod(one initContainer of multiple containers), originValue: empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 2, 2), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("init-container-0", OperatorOverwrite, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/initContainers/0/%s", target), + []string{"/bin/bash", "sleep 15"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Pod(one initContainer of multiple containers), originValue: non-empty, "+ + "operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 2, 2, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("init-container-0", OperatorOverwrite, "echo", "world"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/spec/initContainers/0/%s", target), + []string{"echo", "world"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Pod(nomatched container of multiple containers), "+ + "originValue: non-empty, operator: overwrite", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.PodKind, target, 2, 2, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("test", OperatorOverwrite, "echo", "world"), + generateEntrypointOverrider("test", OperatorOverwrite, "echo", "world"), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{}, + isErrorExpected: false, + }, + + // Test error scenarios + // test unsupported operator + fmt.Sprintf("apply %sOverriders to Deployment(one container), originValue: empty, operator: invalid", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DeploymentKind, target, 1, 0), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", "invalid", "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: nil, + isErrorExpected: true, + }, + // test append operation on deployment without value + fmt.Sprintf("apply %sOverriders to Deployment(one container) without value, operator: append", target): { + fedObject: generateFedWorkloadWithCommandOrArgs(common.DeploymentKind, target, 1, 0, "/bin/bash", "sleep 15"), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend), + }, + expectedOverridePatches: nil, + isErrorExpected: true, + }, + // test apply entrypointOverrider to unsupported resources + fmt.Sprintf("apply %sOverriders to ArgoWorkflow, operator: append", target): { + fedObject: generateFedArgoWorkflowWithImage(""), + entrypointOverriders: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("container-0", OperatorAppend, "/bin/bash", "sleep 15"), + }, + expectedOverridePatches: nil, + isErrorExpected: true, + }, + } + } + + for _, testTarget := range testTargets { + for testName, testCase := range generateTestCases(testTarget) { + t.Run(testName, func(t *testing.T) { + overridePatches, err := parseEntrypointOverriders(testCase.fedObject, testCase.entrypointOverriders, testTarget) + if (err != nil) != testCase.isErrorExpected { + t.Fatalf("err = %v, but testCase.isErrorExpected = %v", err, testCase.isErrorExpected) + } + + assert.Equal(t, testCase.expectedOverridePatches, overridePatches) + }) + } + } +} From c42cd493ca7d72f794f64e679db03ae152a55e9a Mon Sep 17 00:00:00 2001 From: "chentao.cht" Date: Tue, 14 Nov 2023 21:30:22 +0800 Subject: [PATCH 3/3] test(override): add unit tests for annotations and labels overrider --- .../override/annotationslabelspatch_test.go | 297 ++++++++++++++++++ pkg/controllers/override/util_test.go | 289 +++++++++++++++++ 2 files changed, 586 insertions(+) create mode 100644 pkg/controllers/override/annotationslabelspatch_test.go diff --git a/pkg/controllers/override/annotationslabelspatch_test.go b/pkg/controllers/override/annotationslabelspatch_test.go new file mode 100644 index 00000000..40dac253 --- /dev/null +++ b/pkg/controllers/override/annotationslabelspatch_test.go @@ -0,0 +1,297 @@ +/* +Copyright 2023 The KubeAdmiral 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 override + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + fedcorev1a1 "github.com/kubewharf/kubeadmiral/pkg/apis/core/v1alpha1" + "github.com/kubewharf/kubeadmiral/pkg/controllers/common" +) + +type stringMapTestCases map[string]struct { + fedObject fedcorev1a1.GenericFederatedObject + stringMapOverriders []fedcorev1a1.StringMapOverrider + expectedOverridePatches fedcorev1a1.OverridePatches + isErrorExpected bool +} + +func Test_parseAnnotationsOrLabelsOverriders(t *testing.T) { + testTargets := []string{AnnotationsTarget, LabelsTarget} + generateTestCases := func(testTarget string) stringMapTestCases { + return map[string]struct { + fedObject fedcorev1a1.GenericFederatedObject + stringMapOverriders []fedcorev1a1.StringMapOverrider + expectedOverridePatches fedcorev1a1.OverridePatches + isErrorExpected bool + }{ + // Test workload annotations or labels scenarios + // addIfAbsent + fmt.Sprintf("apply %sOverriders to Deployment, originValue: empty, key conflict: false, operator: addIfAbsent", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, nil), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorAddIfAbsent, map[string]string{ + "abc": "xxx", + "def": "xxx", + }), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{"abc": "xxx", "def": "xxx"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment, originValue: non-empty, key conflict: false, operator: addIfAbsent", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, map[string]string{ + "abc": "xxx", + }), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorAddIfAbsent, map[string]string{ + "def": "xxx", + }), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{"abc": "xxx", "def": "xxx"}), + }, + isErrorExpected: false, + }, + + // overwrite + fmt.Sprintf("apply %sOverriders to Deployment, originValue: empty, key conflict: false, operator: empty", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, nil), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider("", map[string]string{ + "abc": "xxx", + "def": "xxx", + }), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment, originValue: empty, key conflict: false, operator: overwrite", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, nil), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorOverwrite, map[string]string{ + "abc": "xxx", + "def": "xxx", + }), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment, originValue: non-empty, "+ + "key conflict: false, operator: overwrite", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, map[string]string{ + "abc": "xxx", + "def": "xxx", + }), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorOverwrite, map[string]string{ + "ghi": "xxx", + }), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{"abc": "xxx", "def": "xxx"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment, originValue: non-empty, key conflict: true, operator: overwrite", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, map[string]string{ + "abc": "xxx", + }), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorOverwrite, map[string]string{ + "abc": "test", + "def": "xxx", + }), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{"abc": "test"}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment without value, originValue: non-empty, operator: overwrite", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, map[string]string{ + "abc": "xxx", + }), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorOverwrite, map[string]string{}), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{"abc": "xxx"}), + }, + isErrorExpected: false, + }, + + // delete + fmt.Sprintf("apply %sOverriders to Deployment, originValue: empty, key conflict: false, operator: delete", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, nil), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorDelete, map[string]string{ + "abc": "test", + "def": "xxx", + }), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{}), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment, originValue: non-empty, key conflict: false, operator: delete", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, map[string]string{ + "abc": "xxx", + }), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorDelete, map[string]string{ + "def": "xxx", + }), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{ + "abc": "xxx", + }), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment, originValue: non-empty, key conflict: true, operator: delete", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, map[string]string{ + "abc": "xxx", + "def": "xxx", + }), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorDelete, map[string]string{ + "def": "", + }), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{ + "abc": "xxx", + }), + }, + isErrorExpected: false, + }, + fmt.Sprintf("apply %sOverriders to Deployment without value, originValue: non-empty, operator: delete", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, map[string]string{ + "abc": "xxx", + }), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorDelete, map[string]string{}), + }, + expectedOverridePatches: fedcorev1a1.OverridePatches{ + generatePatch("replace", + fmt.Sprintf("/metadata/%s", testTarget), + map[string]string{"abc": "xxx"}), + }, + isErrorExpected: false, + }, + + // Test error scenarios + fmt.Sprintf("apply %sOverriders to Deployment, originValue: non-empty, key conflict: true, operator: addIfAbsent", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, map[string]string{"abc": "xxx"}), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorAddIfAbsent, map[string]string{ + "abc": "xxx", + "def": "xxx", + }), + }, + expectedOverridePatches: nil, + isErrorExpected: true, + }, + fmt.Sprintf("apply %sOverriders to Deployment without value, originValue: non-empty, operator: addIfAbsent", testTarget): { + fedObject: generateFedWorkloadWithAnnotationsOrLabels(common.DeploymentKind, testTarget, map[string]string{"abc": "xxx"}), + stringMapOverriders: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorAddIfAbsent, map[string]string{}), + }, + expectedOverridePatches: nil, + isErrorExpected: true, + }, + } + } + for _, testTarget := range testTargets { + testCases := generateTestCases(testTarget) + for testName, testCase := range testCases { + t.Run(testName, func(t *testing.T) { + overridePatches, err := parseStringMapOverriders(testCase.fedObject, testCase.stringMapOverriders, testTarget) + if (err != nil) != testCase.isErrorExpected { + t.Fatalf("err = %v, but testCase.isErrorExpected = %v", err, testCase.isErrorExpected) + } + + assert.Equal(t, testCase.expectedOverridePatches, overridePatches) + }) + } + } +} + +func generateStringMapOverrider(operator string, values map[string]string) fedcorev1a1.StringMapOverrider { + return fedcorev1a1.StringMapOverrider{ + Operator: operator, + Value: values, + } +} + +func generateFedWorkloadWithAnnotationsOrLabels(kind string, target string, values map[string]string) *fedcorev1a1.FederatedObject { + var workload *unstructured.Unstructured + switch kind { + case common.DeploymentKind: + workload = basicDeploymentTemplate.DeepCopy() + case common.DaemonSetKind: + workload = basicDaemonSetTemplate.DeepCopy() + case common.StatefulSetKind: + workload = basicStatefulSetTemplate.DeepCopy() + case common.JobKind: + workload = basicJobTemplate.DeepCopy() + case common.CronJobKind: + workload = basicCronJobTemplate.DeepCopy() + case common.PodKind: + workload = basicPodTemplate.DeepCopy() + } + + if target == AnnotationsTarget { + workload.SetAnnotations(values) + } else { + workload.SetLabels(values) + } + + return generateFedObj(workload) +} diff --git a/pkg/controllers/override/util_test.go b/pkg/controllers/override/util_test.go index b0a16f9e..1c756480 100644 --- a/pkg/controllers/override/util_test.go +++ b/pkg/controllers/override/util_test.go @@ -849,6 +849,295 @@ func TestParseOverrides(t *testing.T) { expectedOverridesMap: make(overridesMap), isErrorExpected: false, }, + "multiple clusters multiple Overrides(image, command, args, annotations, labels, jsonPatch)" + + "- should return overrides for each cluster in order": { + fedObject: generateFedObjWithPodWithTwoNormalAndTwoInit( + "docker.io/ealen/echo-server:latest@sha256:bbbbf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726"), + policy: &fedcorev1a1.OverridePolicy{ + Spec: fedcorev1a1.GenericOverridePolicySpec{ + OverrideRules: []fedcorev1a1.OverrideRule{ + { + TargetClusters: &fedcorev1a1.TargetClusters{ + // should match all clusters + ClusterSelector: map[string]string{}, + }, + Overriders: &fedcorev1a1.Overriders{ + Image: []fedcorev1a1.ImageOverrider{ + { + ContainerNames: []string{"server-1"}, + Operations: generateOperationsOnFullComponent( + OperatorOverwrite, + "all.cluster.io", + "all-cluster/echo-server", + "all", + "sha256:aaaaf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726"), + }, + }, + Command: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("server-1", OperatorAppend, "/bin/bash", "sleep 15"), + generateEntrypointOverrider("init-server-1", OperatorAppend, "/bin/bash", "sleep 15"), + generateEntrypointOverrider("server-1", OperatorDelete, "sleep 15"), + }, + Args: []fedcorev1a1.EntrypointOverrider{ + generateEntrypointOverrider("server-1", OperatorAppend, "-a", "-b"), + generateEntrypointOverrider("init-server-1", OperatorAppend, "-a", "-b"), + generateEntrypointOverrider("server-1", OperatorOverwrite, "-c", "-d"), + }, + Annotations: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorAddIfAbsent, map[string]string{ + "abc": "xxx", + "def": "xxx", + }), + generateStringMapOverrider(OperatorDelete, map[string]string{ + "def": "xxx", + }), + }, + Labels: []fedcorev1a1.StringMapOverrider{ + generateStringMapOverrider(OperatorAddIfAbsent, map[string]string{ + "abc": "xxx", + "def": "xxx", + }), + generateStringMapOverrider(OperatorOverwrite, map[string]string{ + "def": "test", + }), + }, + JsonPatch: []fedcorev1a1.JsonPatchOverrider{ + { + Operator: "add", + Path: "/a/b", + Value: apiextensionsv1.JSON{ + Raw: []byte(`1`), + }, + }, + { + Operator: "replace", + Path: "/aa/bb", + Value: apiextensionsv1.JSON{ + Raw: []byte(`["banana","mango"]`), + }, + }, + }, + }, + }, + { + TargetClusters: &fedcorev1a1.TargetClusters{ + Clusters: []string{ + "cluster1", + }, + }, + Overriders: &fedcorev1a1.Overriders{ + JsonPatch: []fedcorev1a1.JsonPatchOverrider{ + { + Operator: "replace", + Path: "/c/d", + Value: apiextensionsv1.JSON{ + Raw: []byte(`1`), + }, + }, + { + Operator: "replace", + Path: "/cc/dd", + Value: apiextensionsv1.JSON{ + Raw: []byte(`{"key":"value"}`), + }, + }, + }, + Image: []fedcorev1a1.ImageOverrider{ + { + ContainerNames: []string{"server-1"}, + Operations: generateOperationsOnFullComponent( + OperatorOverwrite, + "cluster.one.io", + "cluster-one/echo-server", + "one", + "sha256:aaaaf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726"), + }, + }, + }, + }, + { + TargetClusters: &fedcorev1a1.TargetClusters{ + Clusters: []string{ + "cluster2", + }, + }, + Overriders: &fedcorev1a1.Overriders{ + JsonPatch: []fedcorev1a1.JsonPatchOverrider{ + { + Operator: "remove", + Path: "/e/f", + }, + { + Operator: "add", + Path: "/ee/ff", + Value: apiextensionsv1.JSON{ + Raw: []byte(`"some string"`), + }, + }, + }, + Image: []fedcorev1a1.ImageOverrider{ + { + ContainerNames: []string{"server-1"}, + Operations: generateOperationsOnFullComponent( + OperatorOverwrite, + "cluster.two.io", + "cluster-two/echo-server", + "two", + "sha256:aaaaf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726"), + }, + }, + }, + }, + }, + }, + }, + clusters: []*fedcorev1a1.FederatedCluster{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster2", + }, + }, + }, + expectedOverridesMap: overridesMap{ + "cluster1": fedcorev1a1.OverridePatches{ + // patches from overrideRules which apply to all clusters + // image + generatePatch( + OperatorReplace, + "/spec/containers/0/image", + "all.cluster.io/all-cluster/echo-server:all@sha256:aaaaf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726", + ), + // command + generatePatch("replace", + "/spec/containers/0/command", + []string{"/bin/bash"}, + ), + generatePatch("replace", + "/spec/initContainers/0/command", + []string{"/bin/bash", "sleep 15"}, + ), + // args + generatePatch("replace", + "/spec/containers/0/args", + []string{"-c", "-d"}, + ), + generatePatch("replace", + "/spec/initContainers/0/args", + []string{"-a", "-b"}, + ), + // annotations + generatePatch("replace", + "/metadata/annotations", + map[string]string{"abc": "xxx"}, + ), + // labels + generatePatch("replace", + "/metadata/labels", + map[string]string{"abc": "xxx", "def": "test"}, + ), + // jsonPatch + { + Op: "add", + Path: "/a/b", + Value: asJSON(float64(1)), + }, + { + Op: "replace", + Path: "/aa/bb", + Value: asJSON([]interface{}{"banana", "mango"}), + }, + // patches from overrideRules which apply to cluster-1 + // image + generatePatch( + OperatorReplace, + "/spec/containers/0/image", + "cluster.one.io/cluster-one/echo-server:one@sha256:aaaaf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726", + ), + // jsonPatch + { + Op: "replace", + Path: "/c/d", + Value: asJSON(float64(1)), + }, + { + Op: "replace", + Path: "/cc/dd", + Value: asJSON(map[string]interface{}{ + "key": "value", + }), + }, + }, + "cluster2": fedcorev1a1.OverridePatches{ + // patches from overrideRules which apply to all clusters + // image + generatePatch( + OperatorReplace, + "/spec/containers/0/image", + "all.cluster.io/all-cluster/echo-server:all@sha256:aaaaf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726", + ), + // command + generatePatch("replace", + "/spec/containers/0/command", + []string{"/bin/bash"}, + ), + generatePatch("replace", + "/spec/initContainers/0/command", + []string{"/bin/bash", "sleep 15"}, + ), + // args + generatePatch("replace", + "/spec/containers/0/args", + []string{"-c", "-d"}, + ), + generatePatch("replace", + "/spec/initContainers/0/args", + []string{"-a", "-b"}, + ), + // annotations + generatePatch("replace", + "/metadata/annotations", + map[string]string{"abc": "xxx"}, + ), + // labels + generatePatch("replace", + "/metadata/labels", + map[string]string{"abc": "xxx", "def": "test"}, + ), + // jsonPatch + { + Op: "add", + Path: "/a/b", + Value: asJSON(float64(1)), + }, + { + Op: "replace", + Path: "/aa/bb", + Value: asJSON([]interface{}{"banana", "mango"}), + }, + // patches from overrideRules which apply to cluster-2 + generatePatch( + OperatorReplace, + "/spec/containers/0/image", + "cluster.two.io/cluster-two/echo-server:two@sha256:aaaaf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726", + ), + { + Op: "remove", + Path: "/e/f", + }, + { + Op: "add", + Path: "/ee/ff", + Value: asJSON("some string"), + }, + }, + }, + isErrorExpected: false, + }, } for testName, testCase := range testCases {