github.com/argoproj/argo-cd/v3@v3.2.1/util/argo/normalizers/diff_normalizer_test.go (about) 1 package normalizers 2 3 import ( 4 "encoding/json" 5 "errors" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 11 "sigs.k8s.io/yaml" 12 13 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 14 "github.com/argoproj/argo-cd/v3/test" 15 ) 16 17 func TestNormalizeObjectWithMatchedGroupKind(t *testing.T) { 18 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{{ 19 Group: "apps", 20 Kind: "Deployment", 21 JSONPointers: []string{"/not-matching-path", "/spec/template/spec/containers"}, 22 }}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{}) 23 24 require.NoError(t, err) 25 26 deployment := test.NewDeployment() 27 28 _, has, err := unstructured.NestedSlice(deployment.Object, "spec", "template", "spec", "containers") 29 require.NoError(t, err) 30 assert.True(t, has) 31 32 err = normalizer.Normalize(deployment) 33 require.NoError(t, err) 34 _, has, err = unstructured.NestedSlice(deployment.Object, "spec", "template", "spec", "containers") 35 require.NoError(t, err) 36 assert.False(t, has) 37 38 err = normalizer.Normalize(nil) 39 require.Error(t, err) 40 } 41 42 func TestNormalizeNoMatchedGroupKinds(t *testing.T) { 43 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{{ 44 Group: "", 45 Kind: "Service", 46 JSONPointers: []string{"/spec"}, 47 }}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{}) 48 49 require.NoError(t, err) 50 51 deployment := test.NewDeployment() 52 53 err = normalizer.Normalize(deployment) 54 require.NoError(t, err) 55 56 _, hasSpec, err := unstructured.NestedMap(deployment.Object, "spec") 57 require.NoError(t, err) 58 assert.True(t, hasSpec) 59 } 60 61 func TestNormalizeMatchedResourceOverrides(t *testing.T) { 62 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{ 63 "apps/Deployment": { 64 IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/spec/template/spec/containers"}}, 65 }, 66 }, IgnoreNormalizerOpts{}) 67 68 require.NoError(t, err) 69 70 deployment := test.NewDeployment() 71 72 _, has, err := unstructured.NestedSlice(deployment.Object, "spec", "template", "spec", "containers") 73 require.NoError(t, err) 74 assert.True(t, has) 75 76 err = normalizer.Normalize(deployment) 77 require.NoError(t, err) 78 _, has, err = unstructured.NestedSlice(deployment.Object, "spec", "template", "spec", "containers") 79 require.NoError(t, err) 80 assert.False(t, has) 81 } 82 83 const testCRDYAML = ` 84 apiVersion: apiextensions.k8s.io/v1 85 kind: CustomResourceDefinition 86 metadata: 87 name: certificates.cert-manager.io 88 spec: 89 conversion: 90 strategy: None 91 group: cert-manager.io 92 names: 93 kind: Certificate 94 listKind: CertificateList 95 plural: certificates 96 shortNames: 97 - cert 98 - certs 99 singular: certificate 100 scope: Namespaced 101 version: v1alpha1 102 versions: 103 - name: v1alpha1 104 served: true 105 storage: true 106 schema: 107 openAPIV3Schema: 108 type: object 109 properties: 110 json: 111 x-kubernetes-preserve-unknown-fields: true` 112 113 func TestNormalizeMissingJsonPointer(t *testing.T) { 114 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{ 115 "apps/Deployment": { 116 IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/garbage"}}, 117 }, 118 "apiextensions.k8s.io/CustomResourceDefinition": { 119 IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/spec/additionalPrinterColumns/0/priority"}}, 120 }, 121 }, IgnoreNormalizerOpts{}) 122 require.NoError(t, err) 123 124 deployment := test.NewDeployment() 125 126 err = normalizer.Normalize(deployment) 127 require.NoError(t, err) 128 129 crd := unstructured.Unstructured{} 130 err = yaml.Unmarshal([]byte(testCRDYAML), &crd) 131 require.NoError(t, err) 132 133 err = normalizer.Normalize(&crd) 134 require.NoError(t, err) 135 } 136 137 func TestNormalizeGlobMatch(t *testing.T) { 138 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{ 139 "*/*": { 140 IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/spec/template/spec/containers"}}, 141 }, 142 }, IgnoreNormalizerOpts{}) 143 144 require.NoError(t, err) 145 146 deployment := test.NewDeployment() 147 148 _, has, err := unstructured.NestedSlice(deployment.Object, "spec", "template", "spec", "containers") 149 require.NoError(t, err) 150 assert.True(t, has) 151 152 err = normalizer.Normalize(deployment) 153 require.NoError(t, err) 154 _, has, err = unstructured.NestedSlice(deployment.Object, "spec", "template", "spec", "containers") 155 require.NoError(t, err) 156 assert.False(t, has) 157 } 158 159 func TestNormalizeJQPathExpression(t *testing.T) { 160 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{{ 161 Group: "apps", 162 Kind: "Deployment", 163 JQPathExpressions: []string{".spec.template.spec.initContainers[] | select(.name == \"init-container-0\")"}, 164 }}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{}) 165 166 require.NoError(t, err) 167 168 deployment := test.NewDeployment() 169 170 var initContainers []any 171 initContainers = append(initContainers, map[string]any{"name": "init-container-0"}) 172 initContainers = append(initContainers, map[string]any{"name": "init-container-1"}) 173 err = unstructured.SetNestedSlice(deployment.Object, initContainers, "spec", "template", "spec", "initContainers") 174 require.NoError(t, err) 175 176 actualInitContainers, has, err := unstructured.NestedSlice(deployment.Object, "spec", "template", "spec", "initContainers") 177 require.NoError(t, err) 178 assert.True(t, has) 179 assert.Len(t, actualInitContainers, 2) 180 181 err = normalizer.Normalize(deployment) 182 require.NoError(t, err) 183 actualInitContainers, has, err = unstructured.NestedSlice(deployment.Object, "spec", "template", "spec", "initContainers") 184 require.NoError(t, err) 185 assert.True(t, has) 186 assert.Len(t, actualInitContainers, 1) 187 188 actualInitContainerName, has, err := unstructured.NestedString(actualInitContainers[0].(map[string]any), "name") 189 require.NoError(t, err) 190 assert.True(t, has) 191 assert.Equal(t, "init-container-1", actualInitContainerName) 192 } 193 194 func TestNormalizeIllegalJQPathExpression(t *testing.T) { 195 _, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{{ 196 Group: "apps", 197 Kind: "Deployment", 198 JQPathExpressions: []string{".spec.template.spec.containers[] | select(.name == \"missing-quote)"}, 199 // JSONPointers: []string{"no-starting-slash"}, 200 }}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{}) 201 202 require.Error(t, err) 203 } 204 205 func TestNormalizeJQPathExpressionWithError(t *testing.T) { 206 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{{ 207 Group: "apps", 208 Kind: "Deployment", 209 JQPathExpressions: []string{".spec.fakeField.foo[]"}, 210 }}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{}) 211 212 require.NoError(t, err) 213 214 deployment := test.NewDeployment() 215 originalDeployment, err := deployment.MarshalJSON() 216 require.NoError(t, err) 217 218 err = normalizer.Normalize(deployment) 219 require.NoError(t, err) 220 221 normalizedDeployment, err := deployment.MarshalJSON() 222 require.NoError(t, err) 223 assert.Equal(t, originalDeployment, normalizedDeployment) 224 } 225 226 func TestNormalizeExpectedErrorAreSilenced(t *testing.T) { 227 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{ 228 "*/*": { 229 IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{ 230 JSONPointers: []string{"/invalid", "/invalid/json/path"}, 231 }, 232 }, 233 }, IgnoreNormalizerOpts{}) 234 require.NoError(t, err) 235 236 ignoreNormalizer := normalizer.(*ignoreNormalizer) 237 assert.Len(t, ignoreNormalizer.patches, 2) 238 jsonPatch := ignoreNormalizer.patches[0] 239 jqPatch := ignoreNormalizer.patches[1] 240 241 deployment := test.NewDeployment() 242 deploymentData, err := json.Marshal(deployment) 243 require.NoError(t, err) 244 245 // Error: "error in remove for path: '/invalid': Unable to remove nonexistent key: invalid: missing value" 246 _, err = jsonPatch.Apply(deploymentData) 247 assert.False(t, shouldLogError(err)) 248 249 // Error: "remove operation does not apply: doc is missing path: \"/invalid/json/path\": missing value" 250 _, err = jqPatch.Apply(deploymentData) 251 assert.False(t, shouldLogError(err)) 252 253 assert.True(t, shouldLogError(errors.New("An error that should not be ignored"))) 254 } 255 256 func TestJqPathExpressionFailWithTimeout(t *testing.T) { 257 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{ 258 "*/*": { 259 IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{ 260 JQPathExpressions: []string{"until(true==false; [.] + [1])"}, 261 }, 262 }, 263 }, IgnoreNormalizerOpts{}) 264 require.NoError(t, err) 265 266 ignoreNormalizer := normalizer.(*ignoreNormalizer) 267 assert.Len(t, ignoreNormalizer.patches, 1) 268 jqPatch := ignoreNormalizer.patches[0] 269 270 deployment := test.NewDeployment() 271 deploymentData, err := json.Marshal(deployment) 272 require.NoError(t, err) 273 274 _, err = jqPatch.Apply(deploymentData) 275 assert.ErrorContains(t, err, "JQ patch execution timed out") 276 } 277 278 func TestJQPathExpressionReturnsHelpfulError(t *testing.T) { 279 normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{{ 280 Kind: "ConfigMap", 281 // This is a really wild expression, but it does trigger the desired error. 282 JQPathExpressions: []string{`.nothing) | .data["config.yaml"] |= (fromjson | del(.auth) | tojson`}, 283 }}, nil, IgnoreNormalizerOpts{}) 284 285 require.NoError(t, err) 286 287 configMap := test.NewConfigMap() 288 require.NoError(t, err) 289 290 out := test.CaptureLogEntries(func() { 291 err = normalizer.Normalize(configMap) 292 require.NoError(t, err) 293 }) 294 assert.Contains(t, out, "fromjson cannot be applied") 295 }