github.com/argoproj/argo-cd/v2@v2.10.5/util/db/cluster_test.go (about)

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  	"google.golang.org/grpc/codes"
    11  	"google.golang.org/grpc/status"
    12  	v1 "k8s.io/api/core/v1"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/client-go/kubernetes/fake"
    15  
    16  	"github.com/argoproj/argo-cd/v2/common"
    17  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    18  	appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    19  	"github.com/argoproj/argo-cd/v2/util/settings"
    20  )
    21  
    22  const (
    23  	fakeNamespace = "fake-ns"
    24  )
    25  
    26  func Test_URIToSecretName(t *testing.T) {
    27  	name, err := URIToSecretName("cluster", "http://foo")
    28  	assert.NoError(t, err)
    29  	assert.Equal(t, "cluster-foo-752281925", name)
    30  
    31  	name, err = URIToSecretName("cluster", "http://thelongestdomainnameintheworld.argocd-project.com:3000")
    32  	assert.NoError(t, err)
    33  	assert.Equal(t, "cluster-thelongestdomainnameintheworld.argocd-project.com-2721640553", name)
    34  
    35  	name, err = URIToSecretName("cluster", "http://[fe80::1ff:fe23:4567:890a]")
    36  	assert.NoError(t, err)
    37  	assert.Equal(t, "cluster-fe80--1ff-fe23-4567-890a-3877258831", name)
    38  
    39  	name, err = URIToSecretName("cluster", "http://[fe80::1ff:fe23:4567:890a]:8000")
    40  	assert.NoError(t, err)
    41  	assert.Equal(t, "cluster-fe80--1ff-fe23-4567-890a-664858999", name)
    42  
    43  	name, err = URIToSecretName("cluster", "http://[FE80::1FF:FE23:4567:890A]:8000")
    44  	assert.NoError(t, err)
    45  	assert.Equal(t, "cluster-fe80--1ff-fe23-4567-890a-682802007", name)
    46  
    47  	name, err = URIToSecretName("cluster", "http://:/abc")
    48  	assert.NoError(t, err)
    49  	assert.Equal(t, "cluster--1969338796", name)
    50  }
    51  
    52  func Test_secretToCluster(t *testing.T) {
    53  	labels := map[string]string{"key1": "val1"}
    54  	annotations := map[string]string{"key2": "val2"}
    55  	secret := &v1.Secret{
    56  		ObjectMeta: metav1.ObjectMeta{
    57  			Name:        "mycluster",
    58  			Namespace:   fakeNamespace,
    59  			Labels:      labels,
    60  			Annotations: annotations,
    61  		},
    62  		Data: map[string][]byte{
    63  			"name":   []byte("test"),
    64  			"server": []byte("http://mycluster"),
    65  			"config": []byte("{\"username\":\"foo\"}"),
    66  		},
    67  	}
    68  	cluster, err := SecretToCluster(secret)
    69  	require.NoError(t, err)
    70  	assert.Equal(t, *cluster, v1alpha1.Cluster{
    71  		Name:   "test",
    72  		Server: "http://mycluster",
    73  		Config: v1alpha1.ClusterConfig{
    74  			Username: "foo",
    75  		},
    76  		Labels:      labels,
    77  		Annotations: annotations,
    78  	})
    79  }
    80  
    81  func Test_secretToCluster_LastAppliedConfigurationDropped(t *testing.T) {
    82  	secret := &v1.Secret{
    83  		ObjectMeta: metav1.ObjectMeta{
    84  			Name:        "mycluster",
    85  			Namespace:   fakeNamespace,
    86  			Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
    87  		},
    88  		Data: map[string][]byte{
    89  			"name":   []byte("test"),
    90  			"server": []byte("http://mycluster"),
    91  			"config": []byte("{\"username\":\"foo\"}"),
    92  		},
    93  	}
    94  	cluster, err := SecretToCluster(secret)
    95  	require.NoError(t, err)
    96  	assert.Len(t, cluster.Annotations, 0)
    97  }
    98  
    99  func TestClusterToSecret(t *testing.T) {
   100  	cluster := &appv1.Cluster{
   101  		Server:      "server",
   102  		Labels:      map[string]string{"test": "label"},
   103  		Annotations: map[string]string{"test": "annotation"},
   104  		Name:        "test",
   105  		Config:      v1alpha1.ClusterConfig{},
   106  		Project:     "project",
   107  		Namespaces:  []string{"default"},
   108  	}
   109  	s := &v1.Secret{}
   110  	err := clusterToSecret(cluster, s)
   111  	assert.NoError(t, err)
   112  
   113  	assert.Equal(t, []byte(cluster.Server), s.Data["server"])
   114  	assert.Equal(t, []byte(cluster.Name), s.Data["name"])
   115  	assert.Equal(t, []byte(cluster.Project), s.Data["project"])
   116  	assert.Equal(t, []byte("default"), s.Data["namespaces"])
   117  	assert.Equal(t, cluster.Annotations, s.Annotations)
   118  	assert.Equal(t, cluster.Labels, s.Labels)
   119  }
   120  
   121  func TestClusterToSecret_LastAppliedConfigurationRejected(t *testing.T) {
   122  	cluster := &appv1.Cluster{
   123  		Server:      "server",
   124  		Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
   125  		Name:        "test",
   126  		Config:      v1alpha1.ClusterConfig{},
   127  		Project:     "project",
   128  		Namespaces:  []string{"default"},
   129  	}
   130  	s := &v1.Secret{}
   131  	err := clusterToSecret(cluster, s)
   132  	require.Error(t, err)
   133  	require.Equal(t, codes.InvalidArgument, status.Code(err))
   134  }
   135  
   136  func Test_secretToCluster_NoConfig(t *testing.T) {
   137  	secret := &v1.Secret{
   138  		ObjectMeta: metav1.ObjectMeta{
   139  			Name:      "mycluster",
   140  			Namespace: fakeNamespace,
   141  		},
   142  		Data: map[string][]byte{
   143  			"name":   []byte("test"),
   144  			"server": []byte("http://mycluster"),
   145  		},
   146  	}
   147  	cluster, err := SecretToCluster(secret)
   148  	assert.NoError(t, err)
   149  	assert.Equal(t, *cluster, v1alpha1.Cluster{
   150  		Name:        "test",
   151  		Server:      "http://mycluster",
   152  		Labels:      map[string]string{},
   153  		Annotations: map[string]string{},
   154  	})
   155  }
   156  
   157  func Test_secretToCluster_InvalidConfig(t *testing.T) {
   158  	secret := &v1.Secret{
   159  		ObjectMeta: metav1.ObjectMeta{
   160  			Name:      "mycluster",
   161  			Namespace: fakeNamespace,
   162  		},
   163  		Data: map[string][]byte{
   164  			"name":   []byte("test"),
   165  			"server": []byte("http://mycluster"),
   166  			"config": []byte("{'tlsClientConfig':{'insecure':false}}"),
   167  		},
   168  	}
   169  	cluster, err := SecretToCluster(secret)
   170  	require.Error(t, err)
   171  	assert.Nil(t, cluster)
   172  }
   173  
   174  func TestUpdateCluster(t *testing.T) {
   175  	kubeclientset := fake.NewSimpleClientset(&v1.Secret{
   176  		ObjectMeta: metav1.ObjectMeta{
   177  			Name:      "mycluster",
   178  			Namespace: fakeNamespace,
   179  			Labels: map[string]string{
   180  				common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
   181  			},
   182  		},
   183  		Data: map[string][]byte{
   184  			"server": []byte("http://mycluster"),
   185  			"config": []byte("{}"),
   186  		},
   187  	})
   188  	settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
   189  	db := NewDB(fakeNamespace, settingsManager, kubeclientset)
   190  	requestedAt := metav1.Now()
   191  	_, err := db.UpdateCluster(context.Background(), &v1alpha1.Cluster{
   192  		Name:               "test",
   193  		Server:             "http://mycluster",
   194  		RefreshRequestedAt: &requestedAt,
   195  	})
   196  	if !assert.NoError(t, err) {
   197  		return
   198  	}
   199  
   200  	secret, err := kubeclientset.CoreV1().Secrets(fakeNamespace).Get(context.Background(), "mycluster", metav1.GetOptions{})
   201  	if !assert.NoError(t, err) {
   202  		return
   203  	}
   204  
   205  	assert.Equal(t, secret.Annotations[v1alpha1.AnnotationKeyRefresh], requestedAt.Format(time.RFC3339))
   206  }
   207  
   208  func TestDeleteUnknownCluster(t *testing.T) {
   209  	kubeclientset := fake.NewSimpleClientset(&v1.Secret{
   210  		ObjectMeta: metav1.ObjectMeta{
   211  			Name:      "mycluster",
   212  			Namespace: fakeNamespace,
   213  			Labels: map[string]string{
   214  				common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
   215  			},
   216  		},
   217  		Data: map[string][]byte{
   218  			"server": []byte("http://mycluster"),
   219  			"name":   []byte("mycluster"),
   220  		},
   221  	})
   222  	settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
   223  	db := NewDB(fakeNamespace, settingsManager, kubeclientset)
   224  	assert.EqualError(t, db.DeleteCluster(context.Background(), "http://unknown"), `rpc error: code = NotFound desc = cluster "http://unknown" not found`)
   225  }
   226  
   227  func TestRejectCreationForInClusterWhenDisabled(t *testing.T) {
   228  	argoCDConfigMapWithInClusterServerAddressDisabled := &v1.ConfigMap{
   229  		ObjectMeta: metav1.ObjectMeta{
   230  			Name:      common.ArgoCDConfigMapName,
   231  			Namespace: fakeNamespace,
   232  			Labels: map[string]string{
   233  				"app.kubernetes.io/part-of": "argocd",
   234  			},
   235  		},
   236  		Data: map[string]string{"cluster.inClusterEnabled": "false"},
   237  	}
   238  	argoCDSecret := &v1.Secret{
   239  		ObjectMeta: metav1.ObjectMeta{
   240  			Name:      common.ArgoCDSecretName,
   241  			Namespace: fakeNamespace,
   242  			Labels: map[string]string{
   243  				"app.kubernetes.io/part-of": "argocd",
   244  			},
   245  		},
   246  		Data: map[string][]byte{
   247  			"admin.password":   nil,
   248  			"server.secretkey": nil,
   249  		},
   250  	}
   251  	kubeclientset := fake.NewSimpleClientset(argoCDConfigMapWithInClusterServerAddressDisabled, argoCDSecret)
   252  	settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
   253  	db := NewDB(fakeNamespace, settingsManager, kubeclientset)
   254  	_, err := db.CreateCluster(context.Background(), &appv1.Cluster{
   255  		Server: appv1.KubernetesInternalAPIServerAddr,
   256  		Name:   "incluster-name",
   257  	})
   258  	assert.Error(t, err)
   259  }
   260  
   261  func runWatchTest(t *testing.T, db ArgoDB, actions []func(old *v1alpha1.Cluster, new *v1alpha1.Cluster)) {
   262  	ctx, cancel := context.WithCancel(context.Background())
   263  	defer cancel()
   264  
   265  	timeout := time.Second * 5
   266  
   267  	allDone := make(chan bool, 1)
   268  
   269  	doNext := func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
   270  		if len(actions) == 0 {
   271  			assert.Fail(t, "Unexpected event")
   272  		}
   273  		next := actions[0]
   274  		next(old, new)
   275  		if t.Failed() {
   276  			allDone <- true
   277  		}
   278  		if len(actions) == 1 {
   279  			allDone <- true
   280  		} else {
   281  			actions = actions[1:]
   282  		}
   283  	}
   284  
   285  	go func() {
   286  		assert.NoError(t, db.WatchClusters(ctx, func(cluster *v1alpha1.Cluster) {
   287  			doNext(nil, cluster)
   288  		}, func(oldCluster *v1alpha1.Cluster, newCluster *v1alpha1.Cluster) {
   289  			doNext(oldCluster, newCluster)
   290  		}, func(clusterServer string) {
   291  			doNext(&v1alpha1.Cluster{Server: clusterServer}, nil)
   292  		}))
   293  	}()
   294  
   295  	select {
   296  	case <-allDone:
   297  	case <-time.After(timeout):
   298  		assert.Fail(t, "Failed due to timeout")
   299  	}
   300  
   301  }
   302  
   303  func TestListClusters(t *testing.T) {
   304  	emptyArgoCDConfigMap := &v1.ConfigMap{
   305  		ObjectMeta: metav1.ObjectMeta{
   306  			Name:      common.ArgoCDConfigMapName,
   307  			Namespace: fakeNamespace,
   308  			Labels: map[string]string{
   309  				"app.kubernetes.io/part-of": "argocd",
   310  			},
   311  		},
   312  		Data: map[string]string{},
   313  	}
   314  	argoCDConfigMapWithInClusterServerAddressDisabled := &v1.ConfigMap{
   315  		ObjectMeta: metav1.ObjectMeta{
   316  			Name:      common.ArgoCDConfigMapName,
   317  			Namespace: fakeNamespace,
   318  			Labels: map[string]string{
   319  				"app.kubernetes.io/part-of": "argocd",
   320  			},
   321  		},
   322  		Data: map[string]string{"cluster.inClusterEnabled": "false"},
   323  	}
   324  	argoCDSecret := &v1.Secret{
   325  		ObjectMeta: metav1.ObjectMeta{
   326  			Name:      common.ArgoCDSecretName,
   327  			Namespace: fakeNamespace,
   328  			Labels: map[string]string{
   329  				"app.kubernetes.io/part-of": "argocd",
   330  			},
   331  		},
   332  		Data: map[string][]byte{
   333  			"admin.password":   nil,
   334  			"server.secretkey": nil,
   335  		},
   336  	}
   337  	secretForServerWithInClusterAddr := &v1.Secret{
   338  		ObjectMeta: metav1.ObjectMeta{
   339  			Name:      "mycluster1",
   340  			Namespace: fakeNamespace,
   341  			Labels: map[string]string{
   342  				common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
   343  			},
   344  		},
   345  		Data: map[string][]byte{
   346  			"server": []byte(appv1.KubernetesInternalAPIServerAddr),
   347  			"name":   []byte("in-cluster"),
   348  		},
   349  	}
   350  
   351  	secretForServerWithExternalClusterAddr := &v1.Secret{
   352  		ObjectMeta: metav1.ObjectMeta{
   353  			Name:      "mycluster2",
   354  			Namespace: fakeNamespace,
   355  			Labels: map[string]string{
   356  				common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
   357  			},
   358  		},
   359  		Data: map[string][]byte{
   360  			"server": []byte("http://mycluster2"),
   361  			"name":   []byte("mycluster2"),
   362  		},
   363  	}
   364  
   365  	invalidSecret := &v1.Secret{
   366  		ObjectMeta: metav1.ObjectMeta{
   367  			Name:      "mycluster3",
   368  			Namespace: fakeNamespace,
   369  		},
   370  		Data: map[string][]byte{
   371  			"name":   []byte("test"),
   372  			"server": []byte("http://mycluster3"),
   373  			"config": []byte("{'tlsClientConfig':{'insecure':false}}"),
   374  		},
   375  	}
   376  
   377  	t.Run("Valid clusters", func(t *testing.T) {
   378  		kubeclientset := fake.NewSimpleClientset(secretForServerWithInClusterAddr, secretForServerWithExternalClusterAddr, emptyArgoCDConfigMap, argoCDSecret)
   379  		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
   380  		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
   381  
   382  		clusters, err := db.ListClusters(context.TODO())
   383  		require.NoError(t, err)
   384  		assert.Len(t, clusters.Items, 2)
   385  	})
   386  
   387  	t.Run("Cluster list with invalid cluster", func(t *testing.T) {
   388  		kubeclientset := fake.NewSimpleClientset(secretForServerWithInClusterAddr, secretForServerWithExternalClusterAddr, invalidSecret, emptyArgoCDConfigMap, argoCDSecret)
   389  		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
   390  		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
   391  
   392  		clusters, err := db.ListClusters(context.TODO())
   393  		require.NoError(t, err)
   394  		assert.Len(t, clusters.Items, 2)
   395  	})
   396  
   397  	t.Run("Implicit in-cluster secret", func(t *testing.T) {
   398  		kubeclientset := fake.NewSimpleClientset(secretForServerWithExternalClusterAddr, emptyArgoCDConfigMap, argoCDSecret)
   399  		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
   400  		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
   401  
   402  		clusters, err := db.ListClusters(context.TODO())
   403  		require.NoError(t, err)
   404  		// ListClusters() should have added an implicit in-cluster secret to the list
   405  		assert.Len(t, clusters.Items, 2)
   406  	})
   407  
   408  	t.Run("ListClusters() should not add the cluster with in-cluster server address since in-cluster is disabled", func(t *testing.T) {
   409  		kubeclientset := fake.NewSimpleClientset(secretForServerWithInClusterAddr, argoCDConfigMapWithInClusterServerAddressDisabled, argoCDSecret)
   410  		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
   411  		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
   412  
   413  		clusters, err := db.ListClusters(context.TODO())
   414  		require.NoError(t, err)
   415  		assert.Len(t, clusters.Items, 0)
   416  	})
   417  
   418  	t.Run("ListClusters() should add this cluster since it does not contain in-cluster server address even though in-cluster is disabled", func(t *testing.T) {
   419  		kubeclientset := fake.NewSimpleClientset(secretForServerWithExternalClusterAddr, argoCDConfigMapWithInClusterServerAddressDisabled, argoCDSecret)
   420  		settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
   421  		db := NewDB(fakeNamespace, settingsManager, kubeclientset)
   422  
   423  		clusters, err := db.ListClusters(context.TODO())
   424  		require.NoError(t, err)
   425  		assert.Len(t, clusters.Items, 1)
   426  	})
   427  }