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  }