github.com/argoproj/argo-cd/v3@v3.2.1/server/deeplinks/deeplinks_test.go (about)

     1  package deeplinks
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  	corev1 "k8s.io/api/core/v1"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    14  	"k8s.io/utils/ptr"
    15  
    16  	"github.com/argoproj/argo-cd/v3/pkg/apiclient/application"
    17  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    18  	"github.com/argoproj/argo-cd/v3/util/settings"
    19  )
    20  
    21  type deepLinkTC struct {
    22  	name        string
    23  	appObj      *unstructured.Unstructured
    24  	clusterObj  *unstructured.Unstructured
    25  	resourceObj *unstructured.Unstructured
    26  	projectObj  *unstructured.Unstructured
    27  	inputLinks  []settings.DeepLink
    28  	outputLinks []*application.LinkInfo
    29  	error       []string
    30  }
    31  
    32  func TestDeepLinks(t *testing.T) {
    33  	t.Parallel()
    34  
    35  	appObj, err := kube.ToUnstructured(&v1alpha1.Application{
    36  		ObjectMeta: metav1.ObjectMeta{
    37  			Name:      "test",
    38  			Namespace: "test",
    39  		},
    40  		Spec: v1alpha1.ApplicationSpec{
    41  			Destination: v1alpha1.ApplicationDestination{
    42  				Server:    "test.example.com",
    43  				Namespace: "testns",
    44  			},
    45  		},
    46  	})
    47  	require.NoError(t, err)
    48  	resourceObj, err := kube.ToUnstructured(&corev1.ConfigMap{
    49  		ObjectMeta: metav1.ObjectMeta{
    50  			Name:      "test-cm",
    51  			Namespace: "test-cm",
    52  			Labels:    map[string]string{"test-label": "cm-value"},
    53  		},
    54  		Data: map[string]string{
    55  			"key": "value1",
    56  		},
    57  	})
    58  	require.NoError(t, err)
    59  	clusterObj, err := kube.ToUnstructured(&ClusterLinksData{
    60  		Server: "test-svc.com",
    61  		Name:   "test-cluster",
    62  	})
    63  	require.NoError(t, err)
    64  	projectObj, err := kube.ToUnstructured(&v1alpha1.AppProject{
    65  		ObjectMeta: metav1.ObjectMeta{
    66  			Name:      "test-project",
    67  			Namespace: "test-project",
    68  		},
    69  		Spec: v1alpha1.AppProjectSpec{
    70  			SourceRepos: []string{"test-repo.git"},
    71  		},
    72  	})
    73  	require.NoError(t, err)
    74  	testTable := []deepLinkTC{
    75  		{
    76  			name:        "link to git repo per cluster",
    77  			appObj:      appObj,
    78  			resourceObj: resourceObj,
    79  			projectObj:  projectObj,
    80  			clusterObj:  clusterObj,
    81  			inputLinks: []settings.DeepLink{{
    82  				Title:     "link",
    83  				URL:       "http://example.com/{{ .application.metadata.name }}&{{ .resource.data.key }}&{{ index .project.spec.sourceRepos 0}}&{{ .cluster.name }}",
    84  				Condition: ptr.To(`application.metadata.name == "test" && project.metadata.name == "test-project"`),
    85  			}},
    86  			outputLinks: []*application.LinkInfo{{
    87  				Title: ptr.To("link"),
    88  				Url:   ptr.To("http://example.com/test&value1&test-repo.git&test-cluster"),
    89  			}},
    90  			error: []string{},
    91  		},
    92  		{
    93  			name:        "link to git repo per cluster with abbreviated name",
    94  			appObj:      appObj,
    95  			resourceObj: resourceObj,
    96  			projectObj:  projectObj,
    97  			clusterObj:  clusterObj,
    98  			inputLinks: []settings.DeepLink{{
    99  				Title:     "link",
   100  				URL:       "http://example.com/{{ .app.metadata.name }}&{{ .resource.data.key }}&{{ index .project.spec.sourceRepos 0}}&{{ .cluster.name }}",
   101  				Condition: ptr.To(`app.metadata.name == "test" && project.metadata.name == "test-project"`),
   102  			}},
   103  			outputLinks: []*application.LinkInfo{{
   104  				Title: ptr.To("link"),
   105  				Url:   ptr.To("http://example.com/test&value1&test-repo.git&test-cluster"),
   106  			}},
   107  			error: []string{},
   108  		},
   109  		{
   110  			name:        "condition on missing key",
   111  			appObj:      appObj,
   112  			resourceObj: resourceObj,
   113  			projectObj:  projectObj,
   114  			inputLinks: []settings.DeepLink{
   115  				{
   116  					Title:     "link",
   117  					URL:       "http://example.com/{{ .application.metadata.name }}&{{ .application.spec.destination.namespace }}",
   118  					Condition: ptr.To(`application.metadata.name matches "test"`),
   119  				},
   120  				{
   121  					Title:     "link1",
   122  					URL:       "http://example.com/{{ .application.metadata.name }}&{{ .application.spec.destination.namespace }}",
   123  					Condition: ptr.To(`application.metadata.name matches "test1"`),
   124  				},
   125  				{
   126  					Title:     "link2",
   127  					URL:       "http://example.com/{{ .application.metadata.name }}&{{ .application.spec.destination.namespace }}",
   128  					Condition: ptr.To(`application.metadata.test matches "test"`),
   129  				},
   130  			},
   131  			outputLinks: []*application.LinkInfo{{
   132  				Title: ptr.To("link"),
   133  				Url:   ptr.To("http://example.com/test&testns"),
   134  			}},
   135  			error: []string{},
   136  		},
   137  		{
   138  			name:        "condition on invalid expression",
   139  			appObj:      appObj,
   140  			resourceObj: resourceObj,
   141  			projectObj:  projectObj,
   142  			inputLinks: []settings.DeepLink{
   143  				{
   144  					Title:     "link",
   145  					URL:       "http://example.com/{{ .application.metadata.name }}&{{ .application.spec.destination.namespace }}",
   146  					Condition: ptr.To(`application.metadata.name matches "test"`),
   147  				},
   148  				{
   149  					Title:     "link1",
   150  					URL:       "http://example.com/{{ .application.metadata.name }}&{{ .application.spec.destination.namespace }}",
   151  					Condition: ptr.To(`1 + 1`),
   152  				},
   153  			},
   154  			outputLinks: []*application.LinkInfo{{
   155  				Title: ptr.To("link"),
   156  				Url:   ptr.To("http://example.com/test&testns"),
   157  			}},
   158  			error: []string{"link condition '1 + 1' evaluated to non-boolean value for resource test"},
   159  		},
   160  		{
   161  			name:        "condition on app and project name",
   162  			appObj:      appObj,
   163  			resourceObj: resourceObj,
   164  			projectObj:  projectObj,
   165  			clusterObj:  clusterObj,
   166  			inputLinks: []settings.DeepLink{{
   167  				Title:     "link",
   168  				URL:       "http://example.com/{{ .cluster.name | replace \"-\" \"_\" }}&{{ first .project.spec.sourceRepos }}",
   169  				Condition: ptr.To(`application.metadata.name == "test" && project.metadata.name == "test-project"`),
   170  			}},
   171  			outputLinks: []*application.LinkInfo{{
   172  				Title: ptr.To("link"),
   173  				Url:   ptr.To("http://example.com/test_cluster&test-repo.git"),
   174  			}},
   175  			error: []string{},
   176  		},
   177  		{
   178  			name:        "evaluate template for valid condition",
   179  			appObj:      appObj,
   180  			resourceObj: resourceObj,
   181  			projectObj:  projectObj,
   182  			inputLinks: []settings.DeepLink{
   183  				{
   184  					Title:     "link",
   185  					URL:       "http://not-evaluated.com/{{ index \"invalid\" .application.metadata.labels }}",
   186  					Condition: ptr.To(`false`),
   187  				},
   188  				{
   189  					Title:     "link",
   190  					URL:       "http://evaluated.com/{{ index \"invalid\" .application.metadata.labels }}",
   191  					Condition: ptr.To(`true`),
   192  				},
   193  			},
   194  			outputLinks: []*application.LinkInfo{},
   195  			error: []string{
   196  				"failed to evaluate link template 'http://evaluated.com/{{ index \"invalid\" .application.metadata.labels }}' with resource test, error=template: deep-link:1:24: executing \"deep-link\" at <index \"invalid\" .application.metadata.labels>: error calling index: cannot index slice/array with nil",
   197  			},
   198  		},
   199  	}
   200  
   201  	for _, tc := range testTable {
   202  		tcc := tc
   203  		t.Run(tc.name, func(t *testing.T) {
   204  			t.Parallel()
   205  			objs := CreateDeepLinksObject(tcc.resourceObj, tcc.appObj, tcc.clusterObj, tcc.projectObj)
   206  			output, err := EvaluateDeepLinksResponse(objs, tcc.appObj.GetName(), tcc.inputLinks)
   207  			assert.Equal(t, tcc.error, err, strings.Join(err, ","))
   208  			assert.True(t, reflect.DeepEqual(output.Items, tcc.outputLinks))
   209  		})
   210  	}
   211  }