github.com/argoproj/argo-cd/v3@v3.2.1/util/argo/managedfields/managed_fields_test.go (about) 1 package managedfields_test 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "github.com/stretchr/testify/require" 8 arv1 "k8s.io/api/admissionregistration/v1" 9 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 "k8s.io/apimachinery/pkg/runtime" 11 "sigs.k8s.io/yaml" 12 13 "github.com/argoproj/gitops-engine/pkg/utils/kube/scheme" 14 15 "github.com/argoproj/argo-cd/v3/util/argo/managedfields" 16 "github.com/argoproj/argo-cd/v3/util/argo/testdata" 17 ) 18 19 func TestNormalize(t *testing.T) { 20 parser := scheme.StaticParser() 21 t.Run("will remove conflicting fields if managed by trusted managers", func(t *testing.T) { 22 // given 23 desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml) 24 liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml) 25 trustedManagers := []string{"kube-controller-manager", "revision-history-manager"} 26 pt := parser.Type("io.k8s.api.apps.v1.Deployment") 27 28 // when 29 liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt) 30 31 // then 32 require.NoError(t, err) 33 require.NotNil(t, liveResult) 34 require.NotNil(t, desiredResult) 35 desiredReplicas, ok, err := unstructured.NestedFloat64(desiredResult.Object, "spec", "replicas") 36 assert.False(t, ok) 37 require.NoError(t, err) 38 liveReplicas, ok, err := unstructured.NestedFloat64(liveResult.Object, "spec", "replicas") 39 assert.False(t, ok) 40 require.NoError(t, err) 41 assert.Zero(t, desiredReplicas) 42 assert.Zero(t, liveReplicas) 43 liveRevisionHistory, ok, err := unstructured.NestedFloat64(liveResult.Object, "spec", "revisionHistoryLimit") 44 assert.False(t, ok) 45 require.NoError(t, err) 46 desiredRevisionHistory, ok, err := unstructured.NestedFloat64(desiredResult.Object, "spec", "revisionHistoryLimit") 47 assert.False(t, ok) 48 require.NoError(t, err) 49 assert.Zero(t, desiredRevisionHistory) 50 assert.Zero(t, liveRevisionHistory) 51 }) 52 t.Run("will keep conflicting fields if not from trusted manager", func(t *testing.T) { 53 // given 54 desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml) 55 liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml) 56 trustedManagers := []string{"another-manager"} 57 pt := parser.Type("io.k8s.api.apps.v1.Deployment") 58 59 // when 60 liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt) 61 62 // then 63 require.NoError(t, err) 64 validateNestedFloat64(t, float64(3), desiredResult, "spec", "replicas") 65 validateNestedFloat64(t, float64(1), desiredResult, "spec", "revisionHistoryLimit") 66 validateNestedFloat64(t, float64(2), liveResult, "spec", "replicas") 67 validateNestedFloat64(t, float64(3), liveResult, "spec", "revisionHistoryLimit") 68 }) 69 t.Run("no-op if live state is nil", func(t *testing.T) { 70 // given 71 desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml) 72 trustedManagers := []string{"kube-controller-manager"} 73 pt := parser.Type("io.k8s.api.apps.v1.Deployment") 74 75 // when 76 liveResult, desiredResult, err := managedfields.Normalize(nil, desiredState, trustedManagers, &pt) 77 78 // then 79 require.NoError(t, err) 80 assert.Nil(t, liveResult) 81 assert.Nil(t, desiredResult) 82 validateNestedFloat64(t, float64(3), desiredState, "spec", "replicas") 83 validateNestedFloat64(t, float64(1), desiredState, "spec", "revisionHistoryLimit") 84 }) 85 t.Run("no-op if desired state is nil", func(t *testing.T) { 86 // given 87 liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml) 88 trustedManagers := []string{"kube-controller-manager"} 89 pt := parser.Type("io.k8s.api.apps.v1.Deployment") 90 91 // when 92 liveResult, desiredResult, err := managedfields.Normalize(liveState, nil, trustedManagers, &pt) 93 94 // then 95 require.NoError(t, err) 96 assert.Nil(t, liveResult) 97 assert.Nil(t, desiredResult) 98 validateNestedFloat64(t, float64(2), liveState, "spec", "replicas") 99 validateNestedFloat64(t, float64(3), liveState, "spec", "revisionHistoryLimit") 100 }) 101 t.Run("no-op if trusted manager list is empty", func(t *testing.T) { 102 // given 103 desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml) 104 liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml) 105 pt := parser.Type("io.k8s.api.apps.v1.Deployment") 106 107 // when 108 liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, []string{}, &pt) 109 110 // then 111 require.NoError(t, err) 112 assert.Nil(t, liveResult) 113 assert.Nil(t, desiredResult) 114 validateNestedFloat64(t, float64(3), desiredState, "spec", "replicas") 115 validateNestedFloat64(t, float64(1), desiredState, "spec", "revisionHistoryLimit") 116 validateNestedFloat64(t, float64(2), liveState, "spec", "replicas") 117 validateNestedFloat64(t, float64(3), liveState, "spec", "revisionHistoryLimit") 118 }) 119 t.Run("will normalize successfully inside a list", func(t *testing.T) { 120 // given 121 desiredState := StrToUnstructured(testdata.DesiredValidatingWebhookYaml) 122 liveState := StrToUnstructured(testdata.LiveValidatingWebhookYaml) 123 trustedManagers := []string{"external-secrets"} 124 pt := parser.Type("io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration") 125 126 // when 127 liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt) 128 129 // then 130 require.NoError(t, err) 131 require.NotNil(t, liveResult) 132 require.NotNil(t, desiredResult) 133 134 var vwcLive arv1.ValidatingWebhookConfiguration 135 err = runtime.DefaultUnstructuredConverter.FromUnstructured(liveResult.Object, &vwcLive) 136 require.NoError(t, err) 137 assert.Len(t, vwcLive.Webhooks, 1) 138 assert.Empty(t, string(vwcLive.Webhooks[0].ClientConfig.CABundle)) 139 140 var vwcConfig arv1.ValidatingWebhookConfiguration 141 err = runtime.DefaultUnstructuredConverter.FromUnstructured(desiredResult.Object, &vwcConfig) 142 require.NoError(t, err) 143 assert.Len(t, vwcConfig.Webhooks, 1) 144 assert.Empty(t, string(vwcConfig.Webhooks[0].ClientConfig.CABundle)) 145 }) 146 t.Run("does not fail if object fails validation schema", func(t *testing.T) { 147 desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml) 148 require.NoError(t, unstructured.SetNestedField(desiredState.Object, "spec", "hello", "world")) 149 liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml) 150 151 pt := parser.Type("io.k8s.api.apps.v1.Deployment") 152 153 _, _, err := managedfields.Normalize(liveState, desiredState, []string{}, &pt) 154 require.NoError(t, err) 155 }) 156 } 157 158 func validateNestedFloat64(t *testing.T, expected float64, obj *unstructured.Unstructured, fields ...string) { 159 t.Helper() 160 current := getNestedFloat64(t, obj, fields...) 161 assert.InEpsilon(t, expected, current, 0.0001) 162 } 163 164 func getNestedFloat64(t *testing.T, obj *unstructured.Unstructured, fields ...string) float64 { 165 t.Helper() 166 current, ok, err := unstructured.NestedFloat64(obj.Object, fields...) 167 assert.True(t, ok, "nested field not found") 168 require.NoError(t, err) 169 return current 170 } 171 172 func StrToUnstructured(jsonStr string) *unstructured.Unstructured { 173 obj := make(map[string]any) 174 err := yaml.Unmarshal([]byte(jsonStr), &obj) 175 if err != nil { 176 panic(err) 177 } 178 return &unstructured.Unstructured{Object: obj} 179 }