github.com/argoproj/argo-cd/v2@v2.10.9/server/cluster/cluster_test.go (about)

     1  package cluster
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"reflect"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/argoproj/argo-cd/v2/common"
    12  	"github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
    13  	clusterapi "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
    14  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    15  	appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    16  	servercache "github.com/argoproj/argo-cd/v2/server/cache"
    17  	"github.com/argoproj/argo-cd/v2/test"
    18  	cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
    19  	appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
    20  	"github.com/argoproj/argo-cd/v2/util/db"
    21  	dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
    22  	"github.com/argoproj/argo-cd/v2/util/rbac"
    23  	"github.com/argoproj/argo-cd/v2/util/settings"
    24  	"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/mock"
    27  	"github.com/stretchr/testify/require"
    28  	corev1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/client-go/kubernetes/fake"
    33  	"k8s.io/utils/pointer"
    34  )
    35  
    36  func newServerInMemoryCache() *servercache.Cache {
    37  	return servercache.NewCache(
    38  		appstatecache.NewCache(
    39  			cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Hour)),
    40  			1*time.Minute,
    41  		),
    42  		1*time.Minute,
    43  		1*time.Minute,
    44  		1*time.Minute,
    45  	)
    46  }
    47  
    48  func newNoopEnforcer() *rbac.Enforcer {
    49  	enf := rbac.NewEnforcer(fake.NewSimpleClientset(test.NewFakeConfigMap()), test.FakeArgoCDNamespace, common.ArgoCDConfigMapName, nil)
    50  	enf.EnableEnforce(false)
    51  	return enf
    52  }
    53  
    54  func TestUpdateCluster_RejectInvalidParams(t *testing.T) {
    55  	testCases := []struct {
    56  		name    string
    57  		request clusterapi.ClusterUpdateRequest
    58  	}{
    59  		{
    60  			name:    "allowed cluster URL in body, disallowed cluster URL in query",
    61  			request: clusterapi.ClusterUpdateRequest{Cluster: &v1alpha1.Cluster{Name: "", Server: "https://127.0.0.1", Project: "", ClusterResources: true}, Id: &clusterapi.ClusterID{Type: "", Value: "https://127.0.0.2"}, UpdatedFields: []string{"clusterResources", "project"}},
    62  		},
    63  		{
    64  			name:    "allowed cluster URL in body, disallowed cluster name in query",
    65  			request: clusterapi.ClusterUpdateRequest{Cluster: &v1alpha1.Cluster{Name: "", Server: "https://127.0.0.1", Project: "", ClusterResources: true}, Id: &clusterapi.ClusterID{Type: "name", Value: "disallowed-unscoped"}, UpdatedFields: []string{"clusterResources", "project"}},
    66  		},
    67  		{
    68  			name:    "allowed cluster URL in body, disallowed cluster name in query, changing unscoped to scoped",
    69  			request: clusterapi.ClusterUpdateRequest{Cluster: &v1alpha1.Cluster{Name: "", Server: "https://127.0.0.1", Project: "allowed-project", ClusterResources: true}, Id: &clusterapi.ClusterID{Type: "", Value: "https://127.0.0.2"}, UpdatedFields: []string{"clusterResources", "project"}},
    70  		},
    71  		{
    72  			name:    "allowed cluster URL in body, disallowed cluster URL in query, changing unscoped to scoped",
    73  			request: clusterapi.ClusterUpdateRequest{Cluster: &v1alpha1.Cluster{Name: "", Server: "https://127.0.0.1", Project: "allowed-project", ClusterResources: true}, Id: &clusterapi.ClusterID{Type: "name", Value: "disallowed-unscoped"}, UpdatedFields: []string{"clusterResources", "project"}},
    74  		},
    75  	}
    76  
    77  	db := &dbmocks.ArgoDB{}
    78  
    79  	clusters := []v1alpha1.Cluster{
    80  		{
    81  			Name:   "allowed-unscoped",
    82  			Server: "https://127.0.0.1",
    83  		},
    84  		{
    85  			Name:   "disallowed-unscoped",
    86  			Server: "https://127.0.0.2",
    87  		},
    88  		{
    89  			Name:    "allowed-scoped",
    90  			Server:  "https://127.0.0.3",
    91  			Project: "allowed-project",
    92  		},
    93  		{
    94  			Name:    "disallowed-scoped",
    95  			Server:  "https://127.0.0.4",
    96  			Project: "disallowed-project",
    97  		},
    98  	}
    99  
   100  	db.On("ListClusters", mock.Anything).Return(
   101  		func(ctx context.Context) *v1alpha1.ClusterList {
   102  			return &v1alpha1.ClusterList{
   103  				ListMeta: v1.ListMeta{},
   104  				Items:    clusters,
   105  			}
   106  		},
   107  		func(ctx context.Context) error {
   108  			return nil
   109  		},
   110  	)
   111  	db.On("UpdateCluster", mock.Anything, mock.Anything).Return(
   112  		func(ctx context.Context, c *v1alpha1.Cluster) *v1alpha1.Cluster {
   113  			for _, cluster := range clusters {
   114  				if c.Server == cluster.Server {
   115  					return c
   116  				}
   117  			}
   118  			return nil
   119  		},
   120  		func(ctx context.Context, c *v1alpha1.Cluster) error {
   121  			for _, cluster := range clusters {
   122  				if c.Server == cluster.Server {
   123  					return nil
   124  				}
   125  			}
   126  			return fmt.Errorf("cluster '%s' not found", c.Server)
   127  		},
   128  	)
   129  	db.On("GetCluster", mock.Anything, mock.Anything).Return(
   130  		func(ctx context.Context, server string) *v1alpha1.Cluster {
   131  			for _, cluster := range clusters {
   132  				if server == cluster.Server {
   133  					return &cluster
   134  				}
   135  			}
   136  			return nil
   137  		},
   138  		func(ctx context.Context, server string) error {
   139  			for _, cluster := range clusters {
   140  				if server == cluster.Server {
   141  					return nil
   142  				}
   143  			}
   144  			return fmt.Errorf("cluster '%s' not found", server)
   145  		},
   146  	)
   147  
   148  	enf := rbac.NewEnforcer(fake.NewSimpleClientset(test.NewFakeConfigMap()), test.FakeArgoCDNamespace, common.ArgoCDConfigMapName, nil)
   149  	_ = enf.SetBuiltinPolicy(`p, role:test, clusters, *, https://127.0.0.1, allow
   150  p, role:test, clusters, *, allowed-project/*, allow`)
   151  	enf.SetDefaultRole("role:test")
   152  	server := NewServer(db, enf, newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
   153  
   154  	for _, c := range testCases {
   155  		cc := c
   156  		t.Run(cc.name, func(t *testing.T) {
   157  			t.Parallel()
   158  			out, err := server.Update(context.Background(), &cc.request)
   159  			require.Nil(t, out)
   160  			assert.ErrorIs(t, err, common.PermissionDeniedAPIError)
   161  		})
   162  	}
   163  }
   164  
   165  func TestGetCluster_UrlEncodedName(t *testing.T) {
   166  	db := &dbmocks.ArgoDB{}
   167  
   168  	mockCluster := v1alpha1.Cluster{
   169  		Name:       "test/ing",
   170  		Server:     "https://127.0.0.1",
   171  		Namespaces: []string{"default", "kube-system"},
   172  	}
   173  	mockClusterList := v1alpha1.ClusterList{
   174  		ListMeta: v1.ListMeta{},
   175  		Items: []v1alpha1.Cluster{
   176  			mockCluster,
   177  		},
   178  	}
   179  
   180  	db.On("ListClusters", mock.Anything).Return(&mockClusterList, nil)
   181  
   182  	server := NewServer(db, newNoopEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
   183  
   184  	cluster, err := server.Get(context.Background(), &clusterapi.ClusterQuery{
   185  		Id: &clusterapi.ClusterID{
   186  			Type:  "name_escaped",
   187  			Value: "test%2fing",
   188  		},
   189  	})
   190  	require.NoError(t, err)
   191  
   192  	assert.Equal(t, cluster.Name, "test/ing")
   193  }
   194  
   195  func TestGetCluster_NameWithUrlEncodingButShouldNotBeUnescaped(t *testing.T) {
   196  	db := &dbmocks.ArgoDB{}
   197  
   198  	mockCluster := v1alpha1.Cluster{
   199  		Name:       "test%2fing",
   200  		Server:     "https://127.0.0.1",
   201  		Namespaces: []string{"default", "kube-system"},
   202  	}
   203  	mockClusterList := v1alpha1.ClusterList{
   204  		ListMeta: v1.ListMeta{},
   205  		Items: []v1alpha1.Cluster{
   206  			mockCluster,
   207  		},
   208  	}
   209  
   210  	db.On("ListClusters", mock.Anything).Return(&mockClusterList, nil)
   211  
   212  	server := NewServer(db, newNoopEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
   213  
   214  	cluster, err := server.Get(context.Background(), &clusterapi.ClusterQuery{
   215  		Id: &clusterapi.ClusterID{
   216  			Type:  "name",
   217  			Value: "test%2fing",
   218  		},
   219  	})
   220  	require.NoError(t, err)
   221  
   222  	assert.Equal(t, cluster.Name, "test%2fing")
   223  }
   224  
   225  func TestUpdateCluster_NoFieldsPaths(t *testing.T) {
   226  	db := &dbmocks.ArgoDB{}
   227  	var updated *v1alpha1.Cluster
   228  
   229  	clusters := []v1alpha1.Cluster{
   230  		{
   231  			Name:       "minikube",
   232  			Server:     "https://127.0.0.1",
   233  			Namespaces: []string{"default", "kube-system"},
   234  		},
   235  	}
   236  
   237  	clusterList := v1alpha1.ClusterList{
   238  		ListMeta: v1.ListMeta{},
   239  		Items:    clusters,
   240  	}
   241  
   242  	db.On("ListClusters", mock.Anything).Return(&clusterList, nil)
   243  	db.On("UpdateCluster", mock.Anything, mock.MatchedBy(func(c *v1alpha1.Cluster) bool {
   244  		updated = c
   245  		return true
   246  	})).Return(&v1alpha1.Cluster{}, nil)
   247  
   248  	server := NewServer(db, newNoopEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
   249  
   250  	_, err := server.Update(context.Background(), &clusterapi.ClusterUpdateRequest{
   251  		Cluster: &v1alpha1.Cluster{
   252  			Name:       "minikube",
   253  			Namespaces: []string{"default", "kube-system"},
   254  		},
   255  	})
   256  
   257  	require.NoError(t, err)
   258  
   259  	assert.Equal(t, updated.Name, "minikube")
   260  	assert.Equal(t, updated.Namespaces, []string{"default", "kube-system"})
   261  }
   262  
   263  func TestUpdateCluster_FieldsPathSet(t *testing.T) {
   264  	db := &dbmocks.ArgoDB{}
   265  	var updated *v1alpha1.Cluster
   266  	db.On("GetCluster", mock.Anything, "https://127.0.0.1").Return(&v1alpha1.Cluster{
   267  		Name:       "minikube",
   268  		Server:     "https://127.0.0.1",
   269  		Namespaces: []string{"default", "kube-system"},
   270  	}, nil)
   271  	db.On("UpdateCluster", mock.Anything, mock.MatchedBy(func(c *v1alpha1.Cluster) bool {
   272  		updated = c
   273  		return true
   274  	})).Return(&v1alpha1.Cluster{}, nil)
   275  
   276  	server := NewServer(db, newNoopEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
   277  
   278  	_, err := server.Update(context.Background(), &clusterapi.ClusterUpdateRequest{
   279  		Cluster: &v1alpha1.Cluster{
   280  			Server: "https://127.0.0.1",
   281  			Shard:  pointer.Int64(1),
   282  		},
   283  		UpdatedFields: []string{"shard"},
   284  	})
   285  
   286  	require.NoError(t, err)
   287  
   288  	assert.Equal(t, updated.Name, "minikube")
   289  	assert.Equal(t, updated.Namespaces, []string{"default", "kube-system"})
   290  	assert.Equal(t, *updated.Shard, int64(1))
   291  
   292  	labelEnv := map[string]string{
   293  		"env": "qa",
   294  	}
   295  	_, err = server.Update(context.Background(), &clusterapi.ClusterUpdateRequest{
   296  		Cluster: &v1alpha1.Cluster{
   297  			Server: "https://127.0.0.1",
   298  			Labels: labelEnv,
   299  		},
   300  		UpdatedFields: []string{"labels"},
   301  	})
   302  
   303  	require.NoError(t, err)
   304  
   305  	assert.Equal(t, updated.Name, "minikube")
   306  	assert.Equal(t, updated.Namespaces, []string{"default", "kube-system"})
   307  	assert.Equal(t, updated.Labels, labelEnv)
   308  
   309  	annotationEnv := map[string]string{
   310  		"env": "qa",
   311  	}
   312  	_, err = server.Update(context.Background(), &clusterapi.ClusterUpdateRequest{
   313  		Cluster: &v1alpha1.Cluster{
   314  			Server:      "https://127.0.0.1",
   315  			Annotations: annotationEnv,
   316  		},
   317  		UpdatedFields: []string{"annotations"},
   318  	})
   319  
   320  	require.NoError(t, err)
   321  
   322  	assert.Equal(t, updated.Name, "minikube")
   323  	assert.Equal(t, updated.Namespaces, []string{"default", "kube-system"})
   324  	assert.Equal(t, updated.Annotations, annotationEnv)
   325  
   326  	_, err = server.Update(context.Background(), &clusterapi.ClusterUpdateRequest{
   327  		Cluster: &v1alpha1.Cluster{
   328  			Server:  "https://127.0.0.1",
   329  			Project: "new-project",
   330  		},
   331  		UpdatedFields: []string{"project"},
   332  	})
   333  
   334  	require.NoError(t, err)
   335  
   336  	assert.Equal(t, updated.Name, "minikube")
   337  	assert.Equal(t, updated.Namespaces, []string{"default", "kube-system"})
   338  	assert.Equal(t, updated.Project, "new-project")
   339  }
   340  
   341  func TestDeleteClusterByName(t *testing.T) {
   342  	testNamespace := "default"
   343  	clientset := getClientset(nil, testNamespace, &corev1.Secret{
   344  		ObjectMeta: metav1.ObjectMeta{
   345  			Name:      "my-cluster-secret",
   346  			Namespace: testNamespace,
   347  			Labels: map[string]string{
   348  				common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
   349  			},
   350  			Annotations: map[string]string{
   351  				common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD,
   352  			},
   353  		},
   354  		Data: map[string][]byte{
   355  			"name":   []byte("my-cluster-name"),
   356  			"server": []byte("https://my-cluster-server"),
   357  			"config": []byte("{}"),
   358  		},
   359  	})
   360  	db := db.NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset)
   361  	server := NewServer(db, newNoopEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
   362  
   363  	t.Run("Delete Fails When Deleting by Unknown Name", func(t *testing.T) {
   364  		_, err := server.Delete(context.Background(), &clusterapi.ClusterQuery{
   365  			Name: "foo",
   366  		})
   367  
   368  		assert.EqualError(t, err, `rpc error: code = PermissionDenied desc = permission denied`)
   369  	})
   370  
   371  	t.Run("Delete Succeeds When Deleting by Name", func(t *testing.T) {
   372  		_, err := server.Delete(context.Background(), &clusterapi.ClusterQuery{
   373  			Name: "my-cluster-name",
   374  		})
   375  		assert.Nil(t, err)
   376  
   377  		_, err = db.GetCluster(context.Background(), "https://my-cluster-server")
   378  		assert.EqualError(t, err, `rpc error: code = NotFound desc = cluster "https://my-cluster-server" not found`)
   379  	})
   380  }
   381  
   382  func TestRotateAuth(t *testing.T) {
   383  	testNamespace := "kube-system"
   384  	token := "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhcmdvY2QtbWFuYWdlci10b2tlbi10ajc5ciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhcmdvY2QtbWFuYWdlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjkxZGQzN2NmLThkOTItMTFlOS1hMDkxLWQ2NWYyYWU3ZmE4ZCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphcmdvY2QtbWFuYWdlciJ9.ytZjt2pDV8-A7DBMR06zQ3wt9cuVEfq262TQw7sdra-KRpDpMPnziMhc8bkwvgW-LGhTWUh5iu1y-1QhEx6mtbCt7vQArlBRxfvM5ys6ClFkplzq5c2TtZ7EzGSD0Up7tdxuG9dvR6TGXYdfFcG779yCdZo2H48sz5OSJfdEriduMEY1iL5suZd3ebOoVi1fGflmqFEkZX6SvxkoArl5mtNP6TvZ1eTcn64xh4ws152hxio42E-eSnl_CET4tpB5vgP5BVlSKW2xB7w2GJxqdETA5LJRI_OilY77dTOp8cMr_Ck3EOeda3zHfh4Okflg8rZFEeAuJYahQNeAILLkcA"
   385  	config := v1alpha1.ClusterConfig{
   386  		BearerToken: token,
   387  	}
   388  
   389  	configMarshal, err := json.Marshal(config)
   390  	if err != nil {
   391  		t.Errorf("failed to marshal config for test: %v", err)
   392  	}
   393  
   394  	clientset := getClientset(nil, testNamespace,
   395  		&corev1.Secret{
   396  			ObjectMeta: metav1.ObjectMeta{
   397  				Name:      "my-cluster-secret",
   398  				Namespace: testNamespace,
   399  				Labels: map[string]string{
   400  					common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
   401  				},
   402  				Annotations: map[string]string{
   403  					common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD,
   404  				},
   405  			},
   406  			Data: map[string][]byte{
   407  				"name":   []byte("my-cluster-name"),
   408  				"server": []byte("https://my-cluster-name"),
   409  				"config": configMarshal,
   410  			},
   411  		},
   412  		&corev1.Namespace{
   413  			ObjectMeta: metav1.ObjectMeta{
   414  				Name: "kube-system",
   415  			},
   416  		},
   417  		&corev1.Secret{
   418  			ObjectMeta: metav1.ObjectMeta{
   419  				Name:      "argocd-manager-token-tj79r",
   420  				Namespace: "kube-system",
   421  			},
   422  			Data: map[string][]byte{
   423  				"token": []byte(token),
   424  			},
   425  		},
   426  		&corev1.ServiceAccount{
   427  			ObjectMeta: metav1.ObjectMeta{
   428  				Name:      "argocd-manager",
   429  				Namespace: "kube-system",
   430  			},
   431  			Secrets: []corev1.ObjectReference{
   432  				{
   433  					Kind: "Secret",
   434  					Name: "argocd-manager-token-tj79r",
   435  				},
   436  			},
   437  		})
   438  
   439  	db := db.NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset)
   440  	server := NewServer(db, newNoopEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
   441  
   442  	t.Run("RotateAuth by Unknown Name", func(t *testing.T) {
   443  		_, err := server.RotateAuth(context.Background(), &clusterapi.ClusterQuery{
   444  			Name: "foo",
   445  		})
   446  
   447  		assert.EqualError(t, err, `rpc error: code = PermissionDenied desc = permission denied`)
   448  	})
   449  
   450  	// While the tests results for the next two tests result in an error, they do
   451  	// demonstrate the proper mapping of cluster names/server to server info (i.e. my-cluster-name
   452  	// results in https://my-cluster-name info being used and https://my-cluster-name results in https://my-cluster-name).
   453  	t.Run("RotateAuth by Name - Error from no such host", func(t *testing.T) {
   454  		_, err := server.RotateAuth(context.Background(), &clusterapi.ClusterQuery{
   455  			Name: "my-cluster-name",
   456  		})
   457  
   458  		require.NotNil(t, err)
   459  		assert.Contains(t, err.Error(), "Get \"https://my-cluster-name/")
   460  	})
   461  
   462  	t.Run("RotateAuth by Server - Error from no such host", func(t *testing.T) {
   463  		_, err := server.RotateAuth(context.Background(), &clusterapi.ClusterQuery{
   464  			Server: "https://my-cluster-name",
   465  		})
   466  
   467  		require.NotNil(t, err)
   468  		assert.Contains(t, err.Error(), "Get \"https://my-cluster-name/")
   469  	})
   470  }
   471  
   472  func getClientset(config map[string]string, ns string, objects ...runtime.Object) *fake.Clientset {
   473  	secret := corev1.Secret{
   474  		ObjectMeta: metav1.ObjectMeta{
   475  			Name:      "argocd-secret",
   476  			Namespace: ns,
   477  		},
   478  		Data: map[string][]byte{
   479  			"admin.password":   []byte("test"),
   480  			"server.secretkey": []byte("test"),
   481  		},
   482  	}
   483  	cm := corev1.ConfigMap{
   484  		ObjectMeta: metav1.ObjectMeta{
   485  			Name:      "argocd-cm",
   486  			Namespace: ns,
   487  			Labels: map[string]string{
   488  				"app.kubernetes.io/part-of": "argocd",
   489  			},
   490  		},
   491  		Data: config,
   492  	}
   493  	return fake.NewSimpleClientset(append(objects, &cm, &secret)...)
   494  }
   495  
   496  func TestListCluster(t *testing.T) {
   497  	db := &dbmocks.ArgoDB{}
   498  
   499  	fooCluster := v1alpha1.Cluster{
   500  		Name:       "foo",
   501  		Server:     "https://127.0.0.1",
   502  		Namespaces: []string{"default", "kube-system"},
   503  	}
   504  	barCluster := v1alpha1.Cluster{
   505  		Name:       "bar",
   506  		Server:     "https://192.168.0.1",
   507  		Namespaces: []string{"default", "kube-system"},
   508  	}
   509  	bazCluster := v1alpha1.Cluster{
   510  		Name:       "test/ing",
   511  		Server:     "https://testing.com",
   512  		Namespaces: []string{"default", "kube-system"},
   513  	}
   514  
   515  	mockClusterList := v1alpha1.ClusterList{
   516  		ListMeta: v1.ListMeta{},
   517  		Items:    []v1alpha1.Cluster{fooCluster, barCluster, bazCluster},
   518  	}
   519  
   520  	db.On("ListClusters", mock.Anything).Return(&mockClusterList, nil)
   521  
   522  	s := NewServer(db, newNoopEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
   523  
   524  	tests := []struct {
   525  		name    string
   526  		q       *cluster.ClusterQuery
   527  		want    *appv1.ClusterList
   528  		wantErr bool
   529  	}{
   530  		{
   531  			name: "filter by name",
   532  			q: &clusterapi.ClusterQuery{
   533  				Name: fooCluster.Name,
   534  			},
   535  			want: &v1alpha1.ClusterList{
   536  				ListMeta: v1.ListMeta{},
   537  				Items:    []v1alpha1.Cluster{fooCluster},
   538  			},
   539  		},
   540  		{
   541  			name: "filter by server",
   542  			q: &clusterapi.ClusterQuery{
   543  				Server: barCluster.Server,
   544  			},
   545  			want: &v1alpha1.ClusterList{
   546  				ListMeta: v1.ListMeta{},
   547  				Items:    []v1alpha1.Cluster{barCluster},
   548  			},
   549  		},
   550  		{
   551  			name: "filter by id - name",
   552  			q: &clusterapi.ClusterQuery{
   553  				Id: &clusterapi.ClusterID{
   554  					Type:  "name",
   555  					Value: fooCluster.Name,
   556  				},
   557  			},
   558  			want: &v1alpha1.ClusterList{
   559  				ListMeta: v1.ListMeta{},
   560  				Items:    []v1alpha1.Cluster{fooCluster},
   561  			},
   562  		},
   563  		{
   564  			name: "filter by id - name_escaped",
   565  			q: &clusterapi.ClusterQuery{
   566  				Id: &clusterapi.ClusterID{
   567  					Type:  "name_escaped",
   568  					Value: "test%2fing",
   569  				},
   570  			},
   571  			want: &v1alpha1.ClusterList{
   572  				ListMeta: v1.ListMeta{},
   573  				Items:    []v1alpha1.Cluster{bazCluster},
   574  			},
   575  		},
   576  		{
   577  			name: "filter by id - server",
   578  			q: &clusterapi.ClusterQuery{
   579  				Id: &clusterapi.ClusterID{
   580  					Type:  "server",
   581  					Value: barCluster.Server,
   582  				},
   583  			},
   584  			want: &v1alpha1.ClusterList{
   585  				ListMeta: v1.ListMeta{},
   586  				Items:    []v1alpha1.Cluster{barCluster},
   587  			},
   588  		},
   589  	}
   590  	for _, tt := range tests {
   591  		tt := tt
   592  
   593  		t.Run(tt.name, func(t *testing.T) {
   594  			t.Parallel()
   595  
   596  			got, err := s.List(context.Background(), tt.q)
   597  			if (err != nil) != tt.wantErr {
   598  				t.Errorf("Server.List() error = %v, wantErr %v", err, tt.wantErr)
   599  				return
   600  			}
   601  			if !reflect.DeepEqual(got, tt.want) {
   602  				t.Errorf("Server.List() = %v, want %v", got, tt.want)
   603  			}
   604  		})
   605  	}
   606  }