github.com/argoproj/argo-cd/v3@v3.2.1/util/argo/resource_tracking_test.go (about) 1 package argo 2 3 import ( 4 "os" 5 "testing" 6 7 "github.com/argoproj/argo-cd/v3/util/kube" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 "gopkg.in/yaml.v2" 12 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 13 14 "github.com/argoproj/argo-cd/v3/common" 15 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 16 ) 17 18 func TestSetAppInstanceLabel(t *testing.T) { 19 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 20 require.NoError(t, err) 21 22 var obj unstructured.Unstructured 23 err = yaml.Unmarshal(yamlBytes, &obj) 24 require.NoError(t, err) 25 26 resourceTracking := NewResourceTracking() 27 28 err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "") 29 require.NoError(t, err) 30 app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodLabel, "") 31 assert.Equal(t, "my-app", app) 32 } 33 34 func TestSetAppInstanceAnnotation(t *testing.T) { 35 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 36 require.NoError(t, err) 37 38 var obj unstructured.Unstructured 39 err = yaml.Unmarshal(yamlBytes, &obj) 40 require.NoError(t, err) 41 42 resourceTracking := NewResourceTracking() 43 44 err = resourceTracking.SetAppInstance(&obj, common.AnnotationKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodAnnotation, "") 45 require.NoError(t, err) 46 47 app := resourceTracking.GetAppName(&obj, common.AnnotationKeyAppInstance, v1alpha1.TrackingMethodAnnotation, "") 48 assert.Equal(t, "my-app", app) 49 } 50 51 func TestSetAppInstanceAnnotationAndLabel(t *testing.T) { 52 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 53 require.NoError(t, err) 54 var obj unstructured.Unstructured 55 err = yaml.Unmarshal(yamlBytes, &obj) 56 require.NoError(t, err) 57 58 resourceTracking := NewResourceTracking() 59 60 err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodAnnotationAndLabel, "") 61 require.NoError(t, err) 62 63 app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodAnnotationAndLabel, "") 64 assert.Equal(t, "my-app", app) 65 } 66 67 func TestSetAppInstanceAnnotationAndLabelLongName(t *testing.T) { 68 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 69 require.NoError(t, err) 70 var obj unstructured.Unstructured 71 err = yaml.Unmarshal(yamlBytes, &obj) 72 require.NoError(t, err) 73 74 resourceTracking := NewResourceTracking() 75 76 err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app-with-an-extremely-long-name-that-is-over-sixty-three-characters", "", v1alpha1.TrackingMethodAnnotationAndLabel, "") 77 require.NoError(t, err) 78 79 // the annotation should still work, so the name from GetAppName should not be truncated 80 app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodAnnotationAndLabel, "") 81 assert.Equal(t, "my-app-with-an-extremely-long-name-that-is-over-sixty-three-characters", app) 82 83 // the label should be truncated to 63 characters 84 assert.Equal(t, "my-app-with-an-extremely-long-name-that-is-over-sixty-three-cha", obj.GetLabels()[common.LabelKeyAppInstance]) 85 } 86 87 func TestSetAppInstanceAnnotationAndLabelLongNameBadEnding(t *testing.T) { 88 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 89 require.NoError(t, err) 90 var obj unstructured.Unstructured 91 err = yaml.Unmarshal(yamlBytes, &obj) 92 require.NoError(t, err) 93 94 resourceTracking := NewResourceTracking() 95 96 err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "the-very-suspicious-name-with-precisely-sixty-three-characters-with-hyphen", "", v1alpha1.TrackingMethodAnnotationAndLabel, "") 97 require.NoError(t, err) 98 99 // the annotation should still work, so the name from GetAppName should not be truncated 100 app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodAnnotationAndLabel, "") 101 assert.Equal(t, "the-very-suspicious-name-with-precisely-sixty-three-characters-with-hyphen", app) 102 103 // the label should be truncated to 63 characters, AND the hyphen should be removed 104 assert.Equal(t, "the-very-suspicious-name-with-precisely-sixty-three-characters", obj.GetLabels()[common.LabelKeyAppInstance]) 105 } 106 107 func TestSetAppInstanceAnnotationAndLabelOutOfBounds(t *testing.T) { 108 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 109 require.NoError(t, err) 110 var obj unstructured.Unstructured 111 err = yaml.Unmarshal(yamlBytes, &obj) 112 require.NoError(t, err) 113 114 resourceTracking := NewResourceTracking() 115 116 err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "----------------------------------------------------------------", "", v1alpha1.TrackingMethodAnnotationAndLabel, "") 117 // this should error because it can't truncate to a valid value 118 assert.EqualError(t, err, "failed to set app instance label: unable to truncate label to not end with a special character") 119 } 120 121 func TestRemoveAppInstance_LabelOnly(t *testing.T) { 122 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 123 require.NoError(t, err) 124 var obj unstructured.Unstructured 125 err = yaml.Unmarshal(yamlBytes, &obj) 126 require.NoError(t, err) 127 128 rt := NewResourceTracking() 129 130 err = rt.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "") 131 require.NoError(t, err) 132 133 err = rt.RemoveAppInstance(&obj, string(v1alpha1.TrackingMethodLabel)) 134 require.NoError(t, err) 135 136 _, exists := obj.GetLabels()[common.LabelKeyAppInstance] 137 assert.False(t, exists) 138 } 139 140 func TestRemoveAppInstance_AnnotationOnly(t *testing.T) { 141 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 142 require.NoError(t, err) 143 144 var obj unstructured.Unstructured 145 err = yaml.Unmarshal(yamlBytes, &obj) 146 require.NoError(t, err) 147 148 rt := NewResourceTracking() 149 150 err = rt.SetAppInstance(&obj, common.AnnotationKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodAnnotation, "") 151 require.NoError(t, err) 152 153 err = rt.RemoveAppInstance(&obj, string(v1alpha1.TrackingMethodAnnotation)) 154 require.NoError(t, err) 155 156 annotations := obj.GetAnnotations() 157 assert.NotContains(t, annotations, common.AnnotationKeyAppInstance) 158 assert.NotContains(t, annotations, common.AnnotationInstallationID) 159 assert.NotContains(t, annotations, v1alpha1.TrackingMethodAnnotation) 160 } 161 162 func TestRemoveAppInstance_AnnotationAndLabel(t *testing.T) { 163 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 164 require.NoError(t, err) 165 166 var obj unstructured.Unstructured 167 err = yaml.Unmarshal(yamlBytes, &obj) 168 require.NoError(t, err) 169 170 rt := NewResourceTracking() 171 172 err = rt.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodAnnotationAndLabel, "") 173 require.NoError(t, err) 174 175 err = rt.RemoveAppInstance(&obj, string(v1alpha1.TrackingMethodAnnotationAndLabel)) 176 require.NoError(t, err) 177 178 assert.NotContains(t, obj.GetAnnotations(), common.AnnotationKeyAppInstance) 179 assert.NotContains(t, obj.GetAnnotations(), common.AnnotationInstallationID) 180 assert.NotContains(t, obj.GetLabels(), common.LabelKeyAppInstance) 181 } 182 183 func TestRemoveAppInstance_DefaultCase(t *testing.T) { 184 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 185 require.NoError(t, err) 186 187 var obj unstructured.Unstructured 188 err = yaml.Unmarshal(yamlBytes, &obj) 189 require.NoError(t, err) 190 191 // Add a label manually to verify if this custom label exists at the end 192 obj.SetLabels(map[string]string{ 193 "my-custom-label": "keep-me", 194 }) 195 196 rt := NewResourceTracking() 197 198 err = rt.SetAppInstance(&obj, common.AnnotationKeyAppInstance, "my-app", "", "", "") 199 require.NoError(t, err) 200 201 err = rt.RemoveAppInstance(&obj, "unknown-method") 202 require.NoError(t, err) 203 204 assert.NotContains(t, obj.GetAnnotations(), common.AnnotationKeyAppInstance) 205 assert.NotContains(t, obj.GetAnnotations(), common.AnnotationInstallationID) 206 207 // Argo CD app-instance label was never added, so it shouldn't exist 208 _, argocdLabelExists := obj.GetLabels()[common.LabelKeyAppInstance] 209 assert.False(t, argocdLabelExists) 210 // Custom label should still exist 211 assert.Equal(t, "keep-me", obj.GetLabels()["my-custom-label"]) 212 } 213 214 func TestRemoveAppInstance_AnnotationAndLabel_LongName(t *testing.T) { 215 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 216 require.NoError(t, err) 217 218 var obj unstructured.Unstructured 219 err = yaml.Unmarshal(yamlBytes, &obj) 220 require.NoError(t, err) 221 222 rt := NewResourceTracking() 223 224 longName := "my-app-with-an-extremely-long-name-that-is-over-sixty-three-characters" 225 err = rt.SetAppInstance(&obj, common.LabelKeyAppInstance, longName, "", v1alpha1.TrackingMethodAnnotationAndLabel, "") 226 require.NoError(t, err) 227 228 err = rt.RemoveAppInstance(&obj, string(v1alpha1.TrackingMethodAnnotationAndLabel)) 229 require.NoError(t, err) 230 231 assert.NotContains(t, obj.GetAnnotations(), common.AnnotationKeyAppInstance) 232 assert.NotContains(t, obj.GetLabels(), common.LabelKeyAppInstance) 233 } 234 235 func TestSetAppInstanceAnnotationNotFound(t *testing.T) { 236 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 237 require.NoError(t, err) 238 239 var obj unstructured.Unstructured 240 err = yaml.Unmarshal(yamlBytes, &obj) 241 require.NoError(t, err) 242 243 resourceTracking := NewResourceTracking() 244 245 app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodAnnotation, "") 246 assert.Empty(t, app) 247 } 248 249 func TestParseAppInstanceValue(t *testing.T) { 250 resourceTracking := NewResourceTracking() 251 appInstanceValue, err := resourceTracking.ParseAppInstanceValue("app:<group>/<kind>:<namespace>/<name>") 252 require.NoError(t, err) 253 assert.Equal(t, "app", appInstanceValue.ApplicationName) 254 assert.Equal(t, "<group>", appInstanceValue.Group) 255 assert.Equal(t, "<kind>", appInstanceValue.Kind) 256 assert.Equal(t, "<namespace>", appInstanceValue.Namespace) 257 assert.Equal(t, "<name>", appInstanceValue.Name) 258 } 259 260 func TestParseAppInstanceValueColon(t *testing.T) { 261 resourceTracking := NewResourceTracking() 262 appInstanceValue, err := resourceTracking.ParseAppInstanceValue("app:<group>/<kind>:<namespace>/<name>:<colon>") 263 require.NoError(t, err) 264 assert.Equal(t, "app", appInstanceValue.ApplicationName) 265 assert.Equal(t, "<group>", appInstanceValue.Group) 266 assert.Equal(t, "<kind>", appInstanceValue.Kind) 267 assert.Equal(t, "<namespace>", appInstanceValue.Namespace) 268 assert.Equal(t, "<name>:<colon>", appInstanceValue.Name) 269 } 270 271 func TestParseAppInstanceValueWrongFormat1(t *testing.T) { 272 resourceTracking := NewResourceTracking() 273 _, err := resourceTracking.ParseAppInstanceValue("app") 274 require.ErrorIs(t, err, ErrWrongResourceTrackingFormat) 275 } 276 277 func TestParseAppInstanceValueWrongFormat2(t *testing.T) { 278 resourceTracking := NewResourceTracking() 279 _, err := resourceTracking.ParseAppInstanceValue("app;group/kind/ns") 280 require.ErrorIs(t, err, ErrWrongResourceTrackingFormat) 281 } 282 283 func TestParseAppInstanceValueCorrectFormat(t *testing.T) { 284 resourceTracking := NewResourceTracking() 285 _, err := resourceTracking.ParseAppInstanceValue("app:group/kind:test/ns") 286 require.NoError(t, err) 287 } 288 289 func sampleResource(t *testing.T) *unstructured.Unstructured { 290 t.Helper() 291 yamlBytes, err := os.ReadFile("testdata/svc.yaml") 292 require.NoError(t, err) 293 var obj *unstructured.Unstructured 294 err = yaml.Unmarshal(yamlBytes, &obj) 295 require.NoError(t, err) 296 return obj 297 } 298 299 func TestResourceIdNormalizer_Normalize(t *testing.T) { 300 rt := NewResourceTracking() 301 302 // live object is a resource that has old style tracking label 303 liveObj := sampleResource(t) 304 err := rt.SetAppInstance(liveObj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "") 305 require.NoError(t, err) 306 307 // config object is a resource that has new style tracking annotation 308 configObj := sampleResource(t) 309 err = rt.SetAppInstance(configObj, common.AnnotationKeyAppInstance, "my-app2", "", v1alpha1.TrackingMethodAnnotation, "") 310 require.NoError(t, err) 311 312 _ = rt.Normalize(configObj, liveObj, common.LabelKeyAppInstance, string(v1alpha1.TrackingMethodAnnotation)) 313 314 // the normalization should affect add the new style annotation and drop old tracking label from live object 315 annotation, err := kube.GetAppInstanceAnnotation(configObj, common.AnnotationKeyAppInstance) 316 require.NoError(t, err) 317 assert.Equal(t, liveObj.GetAnnotations()[common.AnnotationKeyAppInstance], annotation) 318 _, hasOldLabel := liveObj.GetLabels()[common.LabelKeyAppInstance] 319 assert.False(t, hasOldLabel) 320 } 321 322 func TestResourceIdNormalizer_NormalizeCRD(t *testing.T) { 323 rt := NewResourceTracking() 324 325 // live object is a CRD resource 326 liveObj := &unstructured.Unstructured{ 327 Object: map[string]any{ 328 "apiVersion": "apiextensions.k8s.io/v1", 329 "kind": "CustomResourceDefinition", 330 "metadata": map[string]any{ 331 "name": "crontabs.stable.example.com", 332 "labels": map[string]any{ 333 common.LabelKeyAppInstance: "my-app", 334 }, 335 }, 336 "spec": map[string]any{ 337 "group": "stable.example.com", 338 "scope": "Namespaced", 339 }, 340 }, 341 } 342 343 // config object is a CRD resource 344 configObj := &unstructured.Unstructured{ 345 Object: map[string]any{ 346 "apiVersion": "apiextensions.k8s.io/v1", 347 "kind": "CustomResourceDefinition", 348 "metadata": map[string]any{ 349 "name": "crontabs.stable.example.com", 350 "labels": map[string]any{ 351 common.LabelKeyAppInstance: "my-app", 352 }, 353 }, 354 "spec": map[string]any{ 355 "group": "stable.example.com", 356 "scope": "Namespaced", 357 }, 358 }, 359 } 360 361 require.NoError(t, rt.Normalize(configObj, liveObj, common.LabelKeyAppInstance, string(v1alpha1.TrackingMethodAnnotation))) 362 // the normalization should not apply any changes to the live object 363 require.NotContains(t, liveObj.GetAnnotations(), common.AnnotationKeyAppInstance) 364 } 365 366 func TestResourceIdNormalizer_Normalize_ConfigHasOldLabel(t *testing.T) { 367 rt := NewResourceTracking() 368 369 // live object is a resource that has old style tracking label 370 liveObj := sampleResource(t) 371 err := rt.SetAppInstance(liveObj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "") 372 require.NoError(t, err) 373 374 // config object is a resource that has new style tracking annotation 375 configObj := sampleResource(t) 376 err = rt.SetAppInstance(configObj, common.AnnotationKeyAppInstance, "my-app2", "", v1alpha1.TrackingMethodAnnotation, "") 377 require.NoError(t, err) 378 err = rt.SetAppInstance(configObj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "") 379 require.NoError(t, err) 380 381 _ = rt.Normalize(configObj, liveObj, common.LabelKeyAppInstance, string(v1alpha1.TrackingMethodAnnotation)) 382 383 // the normalization should affect add the new style annotation and drop old tracking label from live object 384 annotation, err := kube.GetAppInstanceAnnotation(configObj, common.AnnotationKeyAppInstance) 385 require.NoError(t, err) 386 assert.Equal(t, liveObj.GetAnnotations()[common.AnnotationKeyAppInstance], annotation) 387 _, hasOldLabel := liveObj.GetLabels()[common.LabelKeyAppInstance] 388 assert.True(t, hasOldLabel) 389 } 390 391 func TestIsOldTrackingMethod(t *testing.T) { 392 assert.True(t, IsOldTrackingMethod(string(v1alpha1.TrackingMethodLabel))) 393 }