Merge pull request #5591 from sophiefeifeifeiya/feat-fieldoverrider

feat: add fieldoverrider
This commit is contained in:
karmada-bot 2024-10-09 09:43:21 +08:00 committed by GitHub
commit 2a78e2b2f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 768 additions and 5 deletions

2
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/emirpasic/gods v1.18.1
github.com/evanphx/json-patch/v5 v5.9.0
github.com/go-co-op/gocron v1.30.1
github.com/go-openapi/jsonpointer v0.20.2
github.com/gogo/protobuf v1.3.2
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.5.0
@ -90,7 +91,6 @@ require (
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/jsonreference v0.20.4 // indirect
github.com/go-openapi/swag v0.22.7 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect

View File

@ -19,14 +19,18 @@ package overridemanager
import (
"context"
"encoding/json"
"fmt"
"reflect"
"sort"
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/go-openapi/jsonpointer"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/tools/record"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
@ -280,6 +284,52 @@ func applyJSONPatch(obj *unstructured.Unstructured, overrides []overrideOption)
return err
}
// applyRawJSONPatch applies the override on to the given raw json object.
func applyRawJSONPatch(raw []byte, overrides []overrideOption) ([]byte, error) {
jsonPatchBytes, err := json.Marshal(overrides)
if err != nil {
return nil, err
}
patch, err := jsonpatch.DecodePatch(jsonPatchBytes)
if err != nil {
return nil, err
}
return patch.Apply(raw)
}
func applyRawYAMLPatch(raw []byte, overrides []overrideOption) ([]byte, error) {
rawJSON, err := yaml.YAMLToJSON(raw)
if err != nil {
klog.ErrorS(err, "Failed to convert yaml to json")
return nil, err
}
jsonPatchBytes, err := json.Marshal(overrides)
if err != nil {
return nil, err
}
patch, err := jsonpatch.DecodePatch(jsonPatchBytes)
if err != nil {
return nil, err
}
rawJSON, err = patch.Apply(rawJSON)
if err != nil {
return nil, err
}
rawYAML, err := yaml.JSONToYAML(rawJSON)
if err != nil {
klog.Errorf("Failed to convert json to yaml, error: %v", err)
return nil, err
}
return rawYAML, nil
}
// applyPolicyOverriders applies OverridePolicy/ClusterOverridePolicy overriders to target object
func applyPolicyOverriders(rawObj *unstructured.Unstructured, overriders policyv1alpha1.Overriders) error {
err := applyImageOverriders(rawObj, overriders.ImageOverrider)
@ -300,6 +350,9 @@ func applyPolicyOverriders(rawObj *unstructured.Unstructured, overriders policyv
if err := applyAnnotationsOverriders(rawObj, overriders.AnnotationsOverrider); err != nil {
return err
}
if err := applyFieldOverriders(rawObj, overriders.FieldOverrider); err != nil {
return err
}
return applyJSONPatch(rawObj, parseJSONPatchesByPlaintext(overriders.Plaintext))
}
@ -352,6 +405,50 @@ func applyArgsOverriders(rawObj *unstructured.Unstructured, argsOverriders []pol
return nil
}
func applyFieldOverriders(rawObj *unstructured.Unstructured, FieldOverriders []policyv1alpha1.FieldOverrider) error {
if len(FieldOverriders) == 0 {
return nil
}
for index := range FieldOverriders {
pointer, err := jsonpointer.New(FieldOverriders[index].FieldPath)
if err != nil {
klog.Errorf("Build jsonpointer with overrider's path err: %v", err)
return err
}
res, kind, err := pointer.Get(rawObj.Object)
if err != nil {
klog.Errorf("Get value by overrider's path err: %v", err)
return err
}
if kind != reflect.String {
errMsg := fmt.Sprintf("Get object's value by overrider's path(%s) is not string", FieldOverriders[index].FieldPath)
klog.Errorf(errMsg)
return fmt.Errorf(errMsg)
}
dataBytes := []byte(res.(string))
klog.V(4).Infof("Parsed JSON patches by FieldOverriders[%d](%+v)", index, FieldOverriders[index])
var appliedRawData []byte
if len(FieldOverriders[index].YAML) > 0 {
appliedRawData, err = applyRawYAMLPatch(dataBytes, parseYAMLPatchesByField(FieldOverriders[index].YAML))
if err != nil {
klog.Errorf("Error applying raw JSON patch: %v", err)
return err
}
} else if len(FieldOverriders[index].JSON) > 0 {
appliedRawData, err = applyRawJSONPatch(dataBytes, parseJSONPatchesByField(FieldOverriders[index].JSON))
if err != nil {
klog.Errorf("Error applying raw YAML patch: %v", err)
return err
}
}
_, err = pointer.Set(rawObj.Object, string(appliedRawData))
if err != nil {
return err
}
}
return nil
}
func parseJSONPatchesByPlaintext(overriders []policyv1alpha1.PlaintextOverrider) []overrideOption {
patches := make([]overrideOption, 0, len(overriders))
for i := range overriders {
@ -363,3 +460,27 @@ func parseJSONPatchesByPlaintext(overriders []policyv1alpha1.PlaintextOverrider)
}
return patches
}
func parseYAMLPatchesByField(overriders []policyv1alpha1.YAMLPatchOperation) []overrideOption {
patches := make([]overrideOption, 0, len(overriders))
for i := range overriders {
patches = append(patches, overrideOption{
Op: string(overriders[i].Operator),
Path: overriders[i].SubPath,
Value: overriders[i].Value,
})
}
return patches
}
func parseJSONPatchesByField(overriders []policyv1alpha1.JSONPatchOperation) []overrideOption {
patches := make([]overrideOption, 0, len(overriders))
for i := range overriders {
patches = append(patches, overrideOption{
Op: string(overriders[i].Operator),
Path: overriders[i].SubPath,
Value: overriders[i].Value,
})
}
return patches
}

View File

@ -34,7 +34,7 @@ import (
"github.com/karmada-io/karmada/test/helper"
)
func Test_overrideManagerImpl_ApplyOverridePolicies(t *testing.T) {
func Test_overrideManagerImpl_ApplyLabelAnnotationOverriderPolicies(t *testing.T) {
deployment := helper.NewDeployment(metav1.NamespaceDefault, "test1")
deployment.Labels = map[string]string{
"testLabel": "testLabel",
@ -442,3 +442,242 @@ func TestGetMatchingOverridePolicies(t *testing.T) {
})
}
}
func Test_overrideManagerImpl_ApplyFieldOverriderPolicies_YAML(t *testing.T) {
configmap := helper.NewConfigMap(metav1.NamespaceDefault, "test1", map[string]string{
"test.yaml": `
key:
key1: value
`,
})
configmapObj, _ := utilhelper.ToUnstructured(configmap)
type fields struct {
Client client.Client
EventRecorder record.EventRecorder
}
type args struct {
rawObj *unstructured.Unstructured
clusterName string
}
tests := []struct {
name string
fields fields
args args
wantCOP *AppliedOverrides
wantOP *AppliedOverrides
wantErr bool
}{
{
name: "test yaml overridePolicies",
fields: fields{
Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).WithObjects(helper.NewCluster("test1"),
&policyv1alpha1.OverridePolicy{
ObjectMeta: metav1.ObjectMeta{Name: "test1", Namespace: metav1.NamespaceDefault},
Spec: policyv1alpha1.OverrideSpec{
ResourceSelectors: []policyv1alpha1.ResourceSelector{
{
APIVersion: "v1",
Kind: "ConfigMap",
Namespace: "default",
Name: "test1",
},
},
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"test1"}},
Overriders: policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "/data/test.yaml",
YAML: []policyv1alpha1.YAMLPatchOperation{
{
SubPath: "/key/key1",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte(`"updated_value"`)},
},
},
},
},
},
},
},
},
},
).Build(),
EventRecorder: &record.FakeRecorder{},
},
args: args{
rawObj: configmapObj,
clusterName: "test1",
},
wantCOP: nil,
wantOP: &AppliedOverrides{
AppliedItems: []OverridePolicyShadow{
{
PolicyName: "test1",
Overriders: policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "/data/test.yaml",
YAML: []policyv1alpha1.YAMLPatchOperation{
{
SubPath: "/key/key1",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte(`"updated_value"`)},
},
},
},
},
},
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &overrideManagerImpl{
Client: tt.fields.Client,
EventRecorder: tt.fields.EventRecorder,
}
gotCOP, gotOP, err := o.ApplyOverridePolicies(tt.args.rawObj, tt.args.clusterName)
if (err != nil) != tt.wantErr {
t.Errorf("ApplyOverridePolicies() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotCOP, tt.wantCOP) {
t.Errorf("ApplyOverridePolicies() gotCOP = %v, wantCOP %v", gotCOP, tt.wantCOP)
}
if !reflect.DeepEqual(gotOP, tt.wantOP) {
t.Errorf("ApplyOverridePolicies() gotOP = %v, wantOP %v", gotOP, tt.wantOP)
}
wantData := map[string]interface{}{
"test.yaml": `key:
key1: updated_value
`,
}
if !reflect.DeepEqual(tt.args.rawObj.Object["data"], wantData) {
t.Errorf("ApplyOverridePolicies() gotData = %v, wantData %v", tt.args.rawObj.Object["data"], wantData)
}
})
}
}
func Test_overrideManagerImpl_ApplyJSONOverridePolicies_JSON(t *testing.T) {
configmap := helper.NewConfigMap(metav1.NamespaceDefault, "test1", map[string]string{
"test.json": `{"key":{"key1":"value"}}`,
})
configmapObj, _ := utilhelper.ToUnstructured(configmap)
type fields struct {
Client client.Client
EventRecorder record.EventRecorder
}
type args struct {
rawObj *unstructured.Unstructured
clusterName string
}
tests := []struct {
name string
fields fields
args args
wantCOP *AppliedOverrides
wantOP *AppliedOverrides
wantErr bool
}{
{
name: "test yaml overridePolicies",
fields: fields{
Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).WithObjects(helper.NewCluster("test1"),
&policyv1alpha1.OverridePolicy{
ObjectMeta: metav1.ObjectMeta{Name: "test1", Namespace: metav1.NamespaceDefault},
Spec: policyv1alpha1.OverrideSpec{
ResourceSelectors: []policyv1alpha1.ResourceSelector{
{
APIVersion: "v1",
Kind: "ConfigMap",
Namespace: "default",
Name: "test1",
},
},
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{ClusterNames: []string{"test1"}},
Overriders: policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "/data/test.json",
JSON: []policyv1alpha1.JSONPatchOperation{
{
SubPath: "/key/key1",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte(`"updated_value"`)},
},
},
},
},
},
},
},
},
},
).Build(),
EventRecorder: &record.FakeRecorder{},
},
args: args{
rawObj: configmapObj,
clusterName: "test1",
},
wantCOP: nil,
wantOP: &AppliedOverrides{
AppliedItems: []OverridePolicyShadow{
{
PolicyName: "test1",
Overriders: policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "/data/test.json",
JSON: []policyv1alpha1.JSONPatchOperation{
{
SubPath: "/key/key1",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte(`"updated_value"`)},
},
},
},
},
},
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &overrideManagerImpl{
Client: tt.fields.Client,
EventRecorder: tt.fields.EventRecorder,
}
gotCOP, gotOP, err := o.ApplyOverridePolicies(tt.args.rawObj, tt.args.clusterName)
if (err != nil) != tt.wantErr {
t.Errorf("ApplyOverridePolicies() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotCOP, tt.wantCOP) {
t.Errorf("ApplyOverridePolicies() gotCOP = %v, wantCOP %v", gotCOP, tt.wantCOP)
}
if !reflect.DeepEqual(gotOP, tt.wantOP) {
t.Errorf("ApplyOverridePolicies() gotOP = %v, wantOP %v", gotOP, tt.wantOP)
}
wantData := map[string]interface{}{
"test.json": `{"key":{"key1":"updated_value"}}`,
}
if !reflect.DeepEqual(tt.args.rawObj.Object["data"], wantData) {
t.Errorf("ApplyOverridePolicies() gotData = %v, wantData %v", tt.args.rawObj.Object["data"], wantData)
}
})
}
}

View File

@ -18,8 +18,8 @@ package validation
import (
"fmt"
"strings"
"github.com/go-openapi/jsonpointer"
corev1 "k8s.io/api/core/v1"
apivalidation "k8s.io/apimachinery/pkg/api/validation"
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
@ -304,13 +304,53 @@ func ValidateOverrideRules(overrideRules []policyv1alpha1.RuleWithCluster, fldPa
// validates predicate path.
for imageIndex, image := range rule.Overriders.ImageOverrider {
imagePath := rulePath.Child("overriders").Child("imageOverrider").Index(imageIndex)
if image.Predicate != nil && !strings.HasPrefix(image.Predicate.Path, "/") {
allErrs = append(allErrs, field.Invalid(imagePath.Child("predicate").Child("path"), image.Predicate.Path, "path should be start with / character"))
if image.Predicate != nil {
if _, err := jsonpointer.New(image.Predicate.Path); err != nil {
allErrs = append(allErrs, field.Invalid(imagePath.Child("predicate").Child("path"), image.Predicate.Path, err.Error()))
}
}
}
for fieldIndex, fieldOverrider := range rule.Overriders.FieldOverrider {
fieldPath := rulePath.Child("overriders").Child("fieldOverrider").Index(fieldIndex)
// validates that either YAML or JSON is selected for each field overrider.
if len(fieldOverrider.YAML) > 0 && len(fieldOverrider.JSON) > 0 {
allErrs = append(allErrs, field.Invalid(fieldPath, fieldOverrider, "FieldOverrider has both YAML and JSON set. Only one is allowed"))
}
// validates the field path.
if _, err := jsonpointer.New(fieldOverrider.FieldPath); err != nil {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("fieldPath"), fieldOverrider.FieldPath, err.Error()))
}
// validates the JSON patch operations sub path.
allErrs = append(allErrs, validateJSONPatchSubPaths(fieldOverrider.JSON, fieldPath.Child("json"))...)
// validates the YAML patch operations sub path.
allErrs = append(allErrs, validateYAMLPatchSubPaths(fieldOverrider.YAML, fieldPath.Child("yaml"))...)
}
// validates the targetCluster.
allErrs = append(allErrs, ValidateClusterAffinity(rule.TargetCluster, rulePath.Child("targetCluster"))...)
}
return allErrs
}
func validateJSONPatchSubPaths(patches []policyv1alpha1.JSONPatchOperation, fieldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
for index, patch := range patches {
patchPath := fieldPath.Index(index)
if _, err := jsonpointer.New(patch.SubPath); err != nil {
allErrs = append(allErrs, field.Invalid(patchPath.Child("subPath"), patch.SubPath, err.Error()))
}
}
return allErrs
}
func validateYAMLPatchSubPaths(patches []policyv1alpha1.YAMLPatchOperation, fieldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
for index, patch := range patches {
patchPath := fieldPath.Index(index)
if _, err := jsonpointer.New(patch.SubPath); err != nil {
allErrs = append(allErrs, field.Invalid(patchPath.Child("subPath"), patch.SubPath, err.Error()))
}
}
return allErrs
}

View File

@ -24,6 +24,7 @@ import (
"strings"
"testing"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
@ -154,6 +155,147 @@ func TestValidatingAdmission_Handle(t *testing.T) {
Message: "",
},
},
{
name: "Handle_FieldOverrider_ContainsBothYAMLAndJSON_DeniesAdmission",
decoder: &fakeValidationDecoder{
obj: &policyv1alpha1.OverridePolicy{
Spec: policyv1alpha1.OverrideSpec{
ResourceSelectors: []policyv1alpha1.ResourceSelector{
{APIVersion: "test-apiversion", Kind: "test"},
},
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"member1"},
},
Overriders: policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "/data/config",
JSON: []policyv1alpha1.JSONPatchOperation{
{
SubPath: "/db-config",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte(`{"db": "new"}`)},
},
},
YAML: []policyv1alpha1.YAMLPatchOperation{
{
SubPath: "/db-config",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte("db: new")},
},
},
},
},
},
},
},
},
},
},
req: admission.Request{},
want: TestResponse{
Type: Denied,
Message: "FieldOverrider has both YAML and JSON set. Only one is allowed",
},
},
{
name: "Handle_InvalidFieldPathInYAML_DeniesAdmission",
decoder: &fakeValidationDecoder{
obj: &policyv1alpha1.OverridePolicy{
Spec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
Overriders: policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "invalidPath",
YAML: []policyv1alpha1.YAMLPatchOperation{
{
SubPath: "/db-config",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte("db: new")},
},
},
},
},
},
},
},
},
},
},
req: admission.Request{},
want: TestResponse{
Type: Denied,
Message: "spec.overrideRules[0].overriders.fieldOverrider[0].fieldPath: Invalid value: \"invalidPath\": JSON pointer must be empty or start with a \"/",
},
},
{
name: "Handle_InvalidJSONSubPath_DeniesAdmission",
decoder: &fakeValidationDecoder{
obj: &policyv1alpha1.OverridePolicy{
Spec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
Overriders: policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "/data/config",
JSON: []policyv1alpha1.JSONPatchOperation{
{
SubPath: "invalidSubPath",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte(`{"db": "new"}`)},
},
},
},
},
},
},
},
},
},
},
req: admission.Request{},
want: TestResponse{
Type: Denied,
Message: "spec.overrideRules[0].overriders.fieldOverrider[0].json[0].subPath: Invalid value: \"invalidSubPath\": JSON pointer must be empty or start with a \"/",
},
},
{
name: "Handle_InvalidYAMLSubPath_DeniesAdmission",
decoder: &fakeValidationDecoder{
obj: &policyv1alpha1.OverridePolicy{
Spec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
Overriders: policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "/data/config",
YAML: []policyv1alpha1.YAMLPatchOperation{
{
SubPath: "invalidSubPath",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte("db: new")},
},
},
},
},
},
},
},
},
},
},
req: admission.Request{},
want: TestResponse{
Type: Denied,
Message: "spec.overrideRules[0].overriders.fieldOverrider[0].yaml[0].subPath: Invalid value: \"invalidSubPath\": JSON pointer must be empty or start with a \"/",
},
},
}
for _, tt := range tests {

View File

@ -8,6 +8,8 @@
| Check if the OverridePolicy will update the deployment's image value | deployment imageOverride testing | |
| Check if the OverridePolicy will update the pod's image value | pod imageOverride testing | |
| Check if the OverridePolicy will update the specific image value | deployment imageOverride testing | |
| Check if the OverridePolicy will update the value inside JSON | deployment fieldOverride testing | |
| Check if the OverridePolicy will update the value inside YAML | deployment fieldOverride testing | |
#### OverridePolicy with nil resourceSelector testing
| Test Case | E2E Describe Text | Comments |

View File

@ -17,9 +17,13 @@ limitations under the License.
package e2e
import (
"fmt"
"strings"
"github.com/onsi/ginkgo/v2"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/util/rand"
"k8s.io/klog/v2"
@ -425,6 +429,221 @@ var _ = ginkgo.Describe("[OverridePolicy] apply overriders testing", func() {
})
})
})
ginkgo.Context("[FieldOverrider] apply field overrider testing to update JSON values in ConfigMap", func() {
var configMapNamespace, configMapName string
var configMap *corev1.ConfigMap
ginkgo.BeforeEach(func() {
configMapNamespace = testNamespace
configMapName = configMapNamePrefix + rand.String(RandomStrLength)
propagationPolicyNamespace = testNamespace
propagationPolicyName = configMapName
overridePolicyNamespace = testNamespace
overridePolicyName = configMapName
configMapData := map[string]string{
"deploy.json": fmt.Sprintf(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deploy",
"namespace": "%s"
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.19.0"
}
]
}
}
}
}`, configMapNamespace),
}
configMap = helper.NewConfigMap(configMapNamespace, configMapName, configMapData)
propagationPolicy = helper.NewPropagationPolicy(propagationPolicyNamespace, propagationPolicyName, []policyv1alpha1.ResourceSelector{
{
APIVersion: configMap.APIVersion,
Kind: configMap.Kind,
Name: configMap.Name,
},
}, policyv1alpha1.Placement{
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
ClusterNames: framework.ClusterNames(),
},
})
overridePolicy = helper.NewOverridePolicy(overridePolicyNamespace, overridePolicyName, []policyv1alpha1.ResourceSelector{
{
APIVersion: configMap.APIVersion,
Kind: configMap.Kind,
Name: configMap.Name,
},
}, policyv1alpha1.ClusterAffinity{
ClusterNames: framework.ClusterNames(),
}, policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "/data/deploy.json",
JSON: []policyv1alpha1.JSONPatchOperation{
{
SubPath: "/spec/replicas",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte(`5`)},
},
{
SubPath: "/spec/template/spec/containers/-",
Operator: policyv1alpha1.OverriderOpAdd,
Value: apiextensionsv1.JSON{Raw: []byte(`{"name": "nginx-helper", "image": "nginx:1.19.1"}`)},
},
{
SubPath: "/spec/template/spec/containers/0/image",
Operator: policyv1alpha1.OverriderOpRemove,
},
},
},
},
})
})
ginkgo.BeforeEach(func() {
framework.CreatePropagationPolicy(karmadaClient, propagationPolicy)
framework.CreateOverridePolicy(karmadaClient, overridePolicy)
framework.CreateConfigMap(kubeClient, configMap)
ginkgo.DeferCleanup(func() {
framework.RemovePropagationPolicy(karmadaClient, propagationPolicy.Namespace, propagationPolicy.Name)
framework.RemoveOverridePolicy(karmadaClient, overridePolicy.Namespace, overridePolicy.Name)
framework.RemoveConfigMap(kubeClient, configMap.Namespace, configMap.Name)
})
})
ginkgo.It("should override JSON field in ConfigMap", func() {
klog.Infof("check if configMap present on member clusters has the correct JSON field value.")
framework.WaitConfigMapPresentOnClustersFitWith(framework.ClusterNames(), configMap.Namespace, configMap.Name,
func(cm *corev1.ConfigMap) bool {
return strings.Contains(cm.Data["deploy.json"], `"replicas":5`) &&
strings.Contains(cm.Data["deploy.json"], `"name":"nginx-helper"`) &&
!strings.Contains(cm.Data["deploy.json"], `"image":"nginx:1.19.0"`)
})
})
})
ginkgo.Context("[FieldOverrider] apply field overrider testing to update YAML values in ConfigMap", func() {
var configMapNamespace, configMapName string
var configMap *corev1.ConfigMap
ginkgo.BeforeEach(func() {
configMapNamespace = testNamespace
configMapName = configMapNamePrefix + rand.String(RandomStrLength)
propagationPolicyNamespace = testNamespace
propagationPolicyName = configMapName
overridePolicyNamespace = testNamespace
overridePolicyName = configMapName
// Define the ConfigMap data
configMapData := map[string]string{
"nginx.yaml": `
server:
listen: 80
server_name: localhost
location /:
root: /usr/share/nginx/html
index:
- index.html
- index.htm
error_page:
- code: 500
- code: 502
- code: 503
- code: 504
location /50x.html:
root: /usr/share/nginx/html
`,
}
configMap = helper.NewConfigMap(configMapNamespace, configMapName, configMapData)
propagationPolicy = helper.NewPropagationPolicy(propagationPolicyNamespace, propagationPolicyName, []policyv1alpha1.ResourceSelector{
{
APIVersion: configMap.APIVersion,
Kind: configMap.Kind,
Name: configMap.Name,
},
}, policyv1alpha1.Placement{
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
ClusterNames: framework.ClusterNames(),
},
})
overridePolicy = helper.NewOverridePolicy(overridePolicyNamespace, overridePolicyName, []policyv1alpha1.ResourceSelector{
{
APIVersion: configMap.APIVersion,
Kind: configMap.Kind,
Name: configMap.Name,
},
}, policyv1alpha1.ClusterAffinity{
ClusterNames: framework.ClusterNames(),
}, policyv1alpha1.Overriders{
FieldOverrider: []policyv1alpha1.FieldOverrider{
{
FieldPath: "/data/nginx.yaml",
YAML: []policyv1alpha1.YAMLPatchOperation{
{
SubPath: "/server/location ~1/root",
Operator: policyv1alpha1.OverriderOpReplace,
Value: apiextensionsv1.JSON{Raw: []byte(`"/var/www/html"`)},
},
{
SubPath: "/server/error_page/-",
Operator: policyv1alpha1.OverriderOpAdd,
Value: apiextensionsv1.JSON{Raw: []byte(`{"code": 400}`)},
},
{
SubPath: "/server/location ~1/index",
Operator: policyv1alpha1.OverriderOpRemove,
},
},
},
},
})
})
ginkgo.BeforeEach(func() {
framework.CreatePropagationPolicy(karmadaClient, propagationPolicy)
framework.CreateOverridePolicy(karmadaClient, overridePolicy)
framework.CreateConfigMap(kubeClient, configMap)
ginkgo.DeferCleanup(func() {
framework.RemovePropagationPolicy(karmadaClient, propagationPolicy.Namespace, propagationPolicy.Name)
framework.RemoveOverridePolicy(karmadaClient, overridePolicy.Namespace, overridePolicy.Name)
framework.RemoveConfigMap(kubeClient, configMap.Namespace, configMap.Name)
})
})
ginkgo.It("should override YAML field in ConfigMap", func() {
klog.Infof("check if configMap present on member clusters has the correct YAML field value.")
framework.WaitConfigMapPresentOnClustersFitWith(framework.ClusterNames(), configMap.Namespace, configMap.Name,
func(cm *corev1.ConfigMap) bool {
return strings.Contains(cm.Data["nginx.yaml"], "root: /var/www/html") &&
strings.Contains(cm.Data["nginx.yaml"], "code: 400") &&
!strings.Contains(cm.Data["nginx.yaml"], "- index.html")
})
})
})
})
var _ = framework.SerialDescribe("OverridePolicy with nil resourceSelector testing", func() {