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 }