github.com/argoproj/argo-cd/v3@v3.2.1/util/db/repository_secrets_test.go (about) 1 package db 2 3 import ( 4 "fmt" 5 "strconv" 6 "sync" 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 "google.golang.org/grpc/codes" 12 "google.golang.org/grpc/status" 13 corev1 "k8s.io/api/core/v1" 14 apierrors "k8s.io/apimachinery/pkg/api/errors" 15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 "k8s.io/apimachinery/pkg/runtime" 17 "k8s.io/apimachinery/pkg/runtime/schema" 18 "k8s.io/apimachinery/pkg/watch" 19 "k8s.io/client-go/kubernetes/fake" 20 k8stesting "k8s.io/client-go/testing" 21 22 "github.com/argoproj/argo-cd/v3/common" 23 appsv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 24 "github.com/argoproj/argo-cd/v3/util/settings" 25 ) 26 27 func TestSecretsRepositoryBackend_CreateRepository(t *testing.T) { 28 t.Parallel() 29 30 type fixture struct { 31 clientSet *fake.Clientset 32 repoBackend *secretsRepositoryBackend 33 } 34 repo := &appsv1.Repository{ 35 Name: "ArgoCD", 36 Repo: "git@github.com:argoproj/argo-cd.git", 37 Username: "someUsername", 38 Password: "somePassword", 39 InsecureIgnoreHostKey: false, 40 EnableLFS: true, 41 } 42 setupWithK8sObjects := func(objects ...runtime.Object) *fixture { 43 clientset := getClientset(objects...) 44 settingsMgr := settings.NewSettingsManager(t.Context(), clientset, testNamespace) 45 repoBackend := &secretsRepositoryBackend{db: &db{ 46 ns: testNamespace, 47 kubeclientset: clientset, 48 settingsMgr: settingsMgr, 49 }} 50 return &fixture{ 51 clientSet: clientset, 52 repoBackend: repoBackend, 53 } 54 } 55 t.Run("will create repository successfully", func(t *testing.T) { 56 // given 57 t.Parallel() 58 f := setupWithK8sObjects() 59 60 // when 61 output, err := f.repoBackend.CreateRepository(t.Context(), repo) 62 63 // then 64 require.NoError(t, err) 65 assert.Same(t, repo, output) 66 67 secret, err := f.clientSet.CoreV1().Secrets(testNamespace).Get( 68 t.Context(), 69 RepoURLToSecretName(repoSecretPrefix, repo.Repo, ""), 70 metav1.GetOptions{}, 71 ) 72 assert.NotNil(t, secret) 73 require.NoError(t, err) 74 75 assert.Equal(t, common.AnnotationValueManagedByArgoCD, secret.Annotations[common.AnnotationKeyManagedBy]) 76 assert.Equal(t, common.LabelValueSecretTypeRepository, secret.Labels[common.LabelKeySecretType]) 77 78 assert.Equal(t, repo.Name, string(secret.Data["name"])) 79 assert.Equal(t, repo.Repo, string(secret.Data["url"])) 80 assert.Equal(t, repo.Username, string(secret.Data["username"])) 81 assert.Equal(t, repo.Password, string(secret.Data["password"])) 82 assert.Empty(t, string(secret.Data["insecureIgnoreHostKey"])) 83 assert.Equal(t, strconv.FormatBool(repo.EnableLFS), string(secret.Data["enableLfs"])) 84 }) 85 t.Run("will return proper error if secret does not have expected label", func(t *testing.T) { 86 // given 87 t.Parallel() 88 secret := &corev1.Secret{} 89 s := secretsRepositoryBackend{} 90 updatedSecret := s.repositoryToSecret(repo, secret) 91 delete(updatedSecret.Labels, common.LabelKeySecretType) 92 f := setupWithK8sObjects(updatedSecret) 93 f.clientSet.ReactionChain = nil 94 f.clientSet.AddReactor("create", "secrets", func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) { 95 gr := schema.GroupResource{ 96 Group: "v1", 97 Resource: "secrets", 98 } 99 return true, nil, apierrors.NewAlreadyExists(gr, "already exists") 100 }) 101 102 // when 103 output, err := f.repoBackend.CreateRepository(t.Context(), repo) 104 105 // then 106 require.Error(t, err) 107 assert.Nil(t, output) 108 status, ok := status.FromError(err) 109 assert.True(t, ok) 110 assert.Equal(t, codes.InvalidArgument, status.Code()) 111 }) 112 t.Run("will return proper error if secret already exists", func(t *testing.T) { 113 // given 114 t.Parallel() 115 secName := RepoURLToSecretName(repoSecretPrefix, repo.Repo, "") 116 secret := &corev1.Secret{ 117 TypeMeta: metav1.TypeMeta{ 118 Kind: "Secret", 119 APIVersion: "v1", 120 }, 121 ObjectMeta: metav1.ObjectMeta{ 122 Name: secName, 123 Namespace: "default", 124 }, 125 } 126 s := secretsRepositoryBackend{} 127 updatedSecret := s.repositoryToSecret(repo, secret) 128 f := setupWithK8sObjects(updatedSecret) 129 f.clientSet.ReactionChain = nil 130 f.clientSet.WatchReactionChain = nil 131 f.clientSet.AddReactor("create", "secrets", func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) { 132 gr := schema.GroupResource{ 133 Group: "v1", 134 Resource: "secrets", 135 } 136 return true, nil, apierrors.NewAlreadyExists(gr, "already exists") 137 }) 138 watcher := watch.NewFakeWithChanSize(1, true) 139 watcher.Add(updatedSecret) 140 f.clientSet.AddWatchReactor("secrets", func(_ k8stesting.Action) (handled bool, ret watch.Interface, err error) { 141 return true, watcher, nil 142 }) 143 144 // when 145 output, err := f.repoBackend.CreateRepository(t.Context(), repo) 146 147 // then 148 require.Error(t, err) 149 assert.Nil(t, output) 150 status, ok := status.FromError(err) 151 assert.True(t, ok) 152 assert.Equal(t, codes.AlreadyExists, status.Code()) 153 }) 154 } 155 156 func TestSecretsRepositoryBackend_GetRepository(t *testing.T) { 157 repoSecrets := []runtime.Object{ 158 &corev1.Secret{ 159 ObjectMeta: metav1.ObjectMeta{ 160 Namespace: testNamespace, 161 Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""), 162 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 163 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 164 }, 165 Data: map[string][]byte{ 166 "name": []byte("ArgoCD"), 167 "url": []byte("git@github.com:argoproj/argo-cd.git"), 168 "username": []byte("someUsername"), 169 "password": []byte("somePassword"), 170 }, 171 }, 172 &corev1.Secret{ 173 ObjectMeta: metav1.ObjectMeta{ 174 Namespace: testNamespace, 175 Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", "testProject"), 176 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 177 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 178 }, 179 Data: map[string][]byte{ 180 "name": []byte("Scoped ArgoCD"), 181 "url": []byte("git@github.com:argoproj/argo-cd.git"), 182 "username": []byte("someScopedUsername"), 183 "password": []byte("someScopedPassword"), 184 "project": []byte("testProject"), 185 }, 186 }, 187 &corev1.Secret{ 188 ObjectMeta: metav1.ObjectMeta{ 189 Namespace: testNamespace, 190 Name: "user-managed", 191 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 192 }, 193 Data: map[string][]byte{ 194 "name": []byte("UserManagedRepo"), 195 "url": []byte("git@github.com:argoproj/argoproj.git"), 196 "username": []byte("someOtherUsername"), 197 "password": []byte("someOtherPassword"), 198 }, 199 }, 200 &corev1.Secret{ 201 ObjectMeta: metav1.ObjectMeta{ 202 Namespace: testNamespace, 203 Name: "other-user-managed", 204 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 205 }, 206 Data: map[string][]byte{ 207 "name": []byte("Scoped UserManagedRepo"), 208 "url": []byte("git@github.com:argoproj/argoproj.git"), 209 "username": []byte("someOtherUsername"), 210 "password": []byte("someOtherPassword"), 211 "project": []byte("testProject"), 212 }, 213 }, 214 } 215 216 clientset := getClientset(repoSecrets...) 217 testee := &secretsRepositoryBackend{db: &db{ 218 ns: testNamespace, 219 kubeclientset: clientset, 220 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 221 }} 222 223 repository, err := testee.GetRepository(t.Context(), "git@github.com:argoproj/argo-cd.git", "") 224 require.NoError(t, err) 225 assert.NotNil(t, repository) 226 assert.Equal(t, "ArgoCD", repository.Name) 227 assert.Equal(t, "git@github.com:argoproj/argo-cd.git", repository.Repo) 228 assert.Equal(t, "someUsername", repository.Username) 229 assert.Equal(t, "somePassword", repository.Password) 230 231 repository, err = testee.GetRepository(t.Context(), "git@github.com:argoproj/argoproj.git", "") 232 require.NoError(t, err) 233 assert.NotNil(t, repository) 234 assert.Equal(t, "UserManagedRepo", repository.Name) 235 assert.Equal(t, "git@github.com:argoproj/argoproj.git", repository.Repo) 236 assert.Equal(t, "someOtherUsername", repository.Username) 237 assert.Equal(t, "someOtherPassword", repository.Password) 238 239 repository, err = testee.GetRepository(t.Context(), "git@github.com:argoproj/argo-cd.git", "testProject") 240 require.NoError(t, err) 241 assert.NotNil(t, repository) 242 assert.Equal(t, "Scoped ArgoCD", repository.Name) 243 assert.Equal(t, "git@github.com:argoproj/argo-cd.git", repository.Repo) 244 assert.Equal(t, "someScopedUsername", repository.Username) 245 assert.Equal(t, "someScopedPassword", repository.Password) 246 assert.Equal(t, "testProject", repository.Project) 247 248 repository, err = testee.GetRepository(t.Context(), "git@github.com:argoproj/argoproj.git", "testProject") 249 require.NoError(t, err) 250 assert.NotNil(t, repository) 251 assert.Equal(t, "Scoped UserManagedRepo", repository.Name) 252 assert.Equal(t, "git@github.com:argoproj/argoproj.git", repository.Repo) 253 assert.Equal(t, "someOtherUsername", repository.Username) 254 assert.Equal(t, "someOtherPassword", repository.Password) 255 assert.Equal(t, "testProject", repository.Project) 256 } 257 258 func TestSecretsRepositoryBackend_ListRepositories(t *testing.T) { 259 repoSecrets := []runtime.Object{ 260 &corev1.Secret{ 261 ObjectMeta: metav1.ObjectMeta{ 262 Namespace: testNamespace, 263 Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""), 264 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 265 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 266 }, 267 Data: map[string][]byte{ 268 "name": []byte("ArgoCD"), 269 "url": []byte("git@github.com:argoproj/argo-cd.git"), 270 "username": []byte("someUsername"), 271 "password": []byte("somePassword"), 272 }, 273 }, 274 &corev1.Secret{ 275 ObjectMeta: metav1.ObjectMeta{ 276 Namespace: testNamespace, 277 Name: "user-managed", 278 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 279 }, 280 Data: map[string][]byte{ 281 "name": []byte("UserManagedRepo"), 282 "url": []byte("git@github.com:argoproj/argoproj.git"), 283 "username": []byte("someOtherUsername"), 284 "password": []byte("someOtherPassword"), 285 }, 286 }, 287 } 288 289 clientset := getClientset(repoSecrets...) 290 testee := &secretsRepositoryBackend{db: &db{ 291 ns: testNamespace, 292 kubeclientset: clientset, 293 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 294 }} 295 296 repositories, err := testee.ListRepositories(t.Context(), nil) 297 require.NoError(t, err) 298 assert.Len(t, repositories, 2) 299 300 for _, repository := range repositories { 301 switch repository.Name { 302 case "ArgoCD": 303 assert.Equal(t, "git@github.com:argoproj/argo-cd.git", repository.Repo) 304 assert.Equal(t, "someUsername", repository.Username) 305 assert.Equal(t, "somePassword", repository.Password) 306 case "UserManagedRepo": 307 assert.Equal(t, "git@github.com:argoproj/argoproj.git", repository.Repo) 308 assert.Equal(t, "someOtherUsername", repository.Username) 309 assert.Equal(t, "someOtherPassword", repository.Password) 310 default: 311 assert.Fail(t, "unexpected repository found in list") 312 } 313 } 314 } 315 316 func TestSecretsRepositoryBackend_UpdateRepository(t *testing.T) { 317 managedRepository := &appsv1.Repository{ 318 Name: "Managed", 319 Repo: "git@github.com:argoproj/argo-cd.git", 320 Username: "someUsername", 321 Password: "somePassword", 322 } 323 managedProjectRepository := &appsv1.Repository{ 324 Name: "Managed", 325 Repo: "git@github.com:argoproj/argo-cd.git", 326 Username: "someUsername", 327 Password: "somePassword", 328 Project: "someProject", 329 } 330 userProvidedRepository := &appsv1.Repository{ 331 Name: "User Provided", 332 Repo: "git@github.com:argoproj/argoproj.git", 333 Username: "someOtherUsername", 334 Password: "someOtherPassword", 335 } 336 userProvidedProjectRepository := &appsv1.Repository{ 337 Name: "User Provided", 338 Repo: "git@github.com:argoproj/argoproj.git", 339 Username: "someOtherUsername", 340 Password: "someOtherPassword", 341 Project: "someProject", 342 } 343 newRepository := &appsv1.Repository{ 344 Name: "New", 345 Repo: "git@github.com:argoproj/argo-events.git", 346 Username: "foo", 347 Password: "bar", 348 } 349 350 managedSecretName := RepoURLToSecretName(repoSecretPrefix, managedRepository.Repo, "") 351 managedProjectSecretName := RepoURLToSecretName(repoSecretPrefix, managedProjectRepository.Repo, "someProject") 352 newSecretName := RepoURLToSecretName(repoSecretPrefix, newRepository.Repo, "") 353 repoSecrets := []runtime.Object{ 354 &corev1.Secret{ 355 ObjectMeta: metav1.ObjectMeta{ 356 Namespace: testNamespace, 357 Name: managedSecretName, 358 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 359 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 360 }, 361 Data: map[string][]byte{ 362 "name": []byte(managedRepository.Name), 363 "url": []byte(managedRepository.Repo), 364 "username": []byte(managedRepository.Username), 365 "password": []byte(managedRepository.Password), 366 }, 367 }, 368 &corev1.Secret{ 369 ObjectMeta: metav1.ObjectMeta{ 370 Namespace: testNamespace, 371 Name: managedProjectSecretName, 372 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 373 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 374 }, 375 Data: map[string][]byte{ 376 "name": []byte(managedProjectRepository.Name), 377 "url": []byte(managedProjectRepository.Repo), 378 "username": []byte(managedProjectRepository.Username), 379 "password": []byte(managedProjectRepository.Password), 380 "project": []byte(managedProjectRepository.Project), 381 }, 382 }, 383 &corev1.Secret{ 384 ObjectMeta: metav1.ObjectMeta{ 385 Namespace: testNamespace, 386 Name: "user-managed", 387 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 388 }, 389 Data: map[string][]byte{ 390 "name": []byte(userProvidedRepository.Name), 391 "url": []byte(userProvidedRepository.Repo), 392 "username": []byte(userProvidedRepository.Username), 393 "password": []byte(userProvidedRepository.Password), 394 }, 395 }, 396 &corev1.Secret{ 397 ObjectMeta: metav1.ObjectMeta{ 398 Namespace: testNamespace, 399 Name: "user-managed-scoped", 400 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 401 }, 402 Data: map[string][]byte{ 403 "name": []byte(userProvidedProjectRepository.Name), 404 "url": []byte(userProvidedProjectRepository.Repo), 405 "username": []byte(userProvidedProjectRepository.Username), 406 "password": []byte(userProvidedProjectRepository.Password), 407 "project": []byte(userProvidedProjectRepository.Project), 408 }, 409 }, 410 } 411 412 clientset := getClientset(repoSecrets...) 413 testee := &secretsRepositoryBackend{db: &db{ 414 ns: testNamespace, 415 kubeclientset: clientset, 416 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 417 }} 418 419 managedRepository.Username = "newUsername" 420 updateRepository, err := testee.UpdateRepository(t.Context(), managedRepository) 421 require.NoError(t, err) 422 assert.Same(t, managedRepository, updateRepository) 423 assert.Equal(t, managedRepository.Username, updateRepository.Username) 424 425 secret, err := clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), managedSecretName, metav1.GetOptions{}) 426 require.NoError(t, err) 427 assert.NotNil(t, secret) 428 assert.Equal(t, "newUsername", string(secret.Data["username"])) 429 430 userProvidedRepository.Username = "newOtherUsername" 431 updateRepository, err = testee.UpdateRepository(t.Context(), userProvidedRepository) 432 require.NoError(t, err) 433 assert.Same(t, userProvidedRepository, updateRepository) 434 assert.Equal(t, userProvidedRepository.Username, updateRepository.Username) 435 436 secret, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), "user-managed", metav1.GetOptions{}) 437 require.NoError(t, err) 438 assert.NotNil(t, secret) 439 assert.Equal(t, "newOtherUsername", string(secret.Data["username"])) 440 441 updateRepository, err = testee.UpdateRepository(t.Context(), newRepository) 442 require.NoError(t, err) 443 assert.Same(t, newRepository, updateRepository) 444 445 secret, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), newSecretName, metav1.GetOptions{}) 446 require.NoError(t, err) 447 assert.NotNil(t, secret) 448 assert.Equal(t, "foo", string(secret.Data["username"])) 449 450 managedProjectRepository.Username = "newUsername" 451 updateRepository, err = testee.UpdateRepository(t.Context(), managedProjectRepository) 452 require.NoError(t, err) 453 assert.Same(t, managedProjectRepository, updateRepository) 454 assert.Equal(t, managedProjectRepository.Username, updateRepository.Username) 455 456 secret, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), managedProjectSecretName, metav1.GetOptions{}) 457 require.NoError(t, err) 458 assert.NotNil(t, secret) 459 assert.Equal(t, "newUsername", string(secret.Data["username"])) 460 461 userProvidedProjectRepository.Username = "newUsernameScoped" 462 updateRepository, err = testee.UpdateRepository(t.Context(), userProvidedProjectRepository) 463 require.NoError(t, err) 464 assert.Same(t, userProvidedProjectRepository, updateRepository) 465 assert.Equal(t, userProvidedProjectRepository.Username, updateRepository.Username) 466 467 secret, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), "user-managed-scoped", metav1.GetOptions{}) 468 require.NoError(t, err) 469 assert.NotNil(t, secret) 470 assert.Equal(t, "newUsernameScoped", string(secret.Data["username"])) 471 } 472 473 func TestSecretsRepositoryBackend_DeleteRepository(t *testing.T) { 474 managedSecretName := RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", "") 475 managedScopedSecretName := RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", "someProject") 476 repoSecrets := []runtime.Object{ 477 &corev1.Secret{ 478 ObjectMeta: metav1.ObjectMeta{ 479 Namespace: testNamespace, 480 Name: managedSecretName, 481 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 482 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 483 }, 484 Data: map[string][]byte{ 485 "name": []byte("ArgoCD"), 486 "url": []byte("git@github.com:argoproj/argo-cd.git"), 487 "username": []byte("someUsername"), 488 "password": []byte("somePassword"), 489 }, 490 }, 491 &corev1.Secret{ 492 ObjectMeta: metav1.ObjectMeta{ 493 Namespace: testNamespace, 494 Name: managedScopedSecretName, 495 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 496 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 497 }, 498 Data: map[string][]byte{ 499 "name": []byte("ArgoCD"), 500 "url": []byte("git@github.com:argoproj/argo-cd.git"), 501 "username": []byte("someUsername"), 502 "password": []byte("somePassword"), 503 "project": []byte("someProject"), 504 }, 505 }, 506 &corev1.Secret{ 507 ObjectMeta: metav1.ObjectMeta{ 508 Namespace: testNamespace, 509 Name: "user-managed", 510 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository}, 511 }, 512 Data: map[string][]byte{ 513 "name": []byte("UserManagedRepo"), 514 "url": []byte("git@github.com:argoproj/argoproj.git"), 515 "username": []byte("someOtherUsername"), 516 "password": []byte("someOtherPassword"), 517 }, 518 }, 519 } 520 521 clientset := getClientset(repoSecrets...) 522 testee := &secretsRepositoryBackend{db: &db{ 523 ns: testNamespace, 524 kubeclientset: clientset, 525 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 526 }} 527 528 err := testee.DeleteRepository(t.Context(), "git@github.com:argoproj/argo-cd.git", "") 529 require.NoError(t, err) 530 531 _, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), managedSecretName, metav1.GetOptions{}) 532 require.Error(t, err) 533 534 _, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), managedScopedSecretName, metav1.GetOptions{}) 535 require.NoError(t, err) 536 537 err = testee.DeleteRepository(t.Context(), "git@github.com:argoproj/argo-cd.git", "someProject") 538 require.NoError(t, err) 539 540 _, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), managedScopedSecretName, metav1.GetOptions{}) 541 require.Error(t, err) 542 543 err = testee.DeleteRepository(t.Context(), "git@github.com:argoproj/argoproj.git", "") 544 require.NoError(t, err) 545 546 secret, err := clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), "user-managed", metav1.GetOptions{}) 547 require.NoError(t, err) 548 assert.NotNil(t, secret) 549 assert.Empty(t, secret.Labels[common.LabelValueSecretTypeRepository]) 550 } 551 552 func TestSecretsRepositoryBackend_CreateRepoCreds(t *testing.T) { 553 clientset := getClientset() 554 testee := &secretsRepositoryBackend{db: &db{ 555 ns: testNamespace, 556 kubeclientset: clientset, 557 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 558 }} 559 560 testCases := []struct { 561 name string 562 repoCreds appsv1.RepoCreds 563 // Note: URL needs to be a different one for every testCase 564 // otherwise we would need to use the DeleteRepoCreds method to clean up the secret after each test 565 // which results in an unwanted dependency in a unit test 566 }{ 567 { 568 name: "minimal_https_fields", 569 repoCreds: appsv1.RepoCreds{ 570 URL: "git@github.com:argoproj", 571 Username: "someUsername", 572 Password: "somePassword", 573 EnableOCI: true, 574 }, 575 }, 576 { 577 name: "with_proxy", 578 repoCreds: appsv1.RepoCreds{ 579 URL: "git@github.com:kubernetes", 580 Username: "anotherUsername", 581 Password: "anotherPassword", 582 Proxy: "https://proxy.argoproj.io:3128", 583 }, 584 }, 585 { 586 name: "with_noProxy", 587 repoCreds: appsv1.RepoCreds{ 588 URL: "git@github.com:proxy", 589 Username: "anotherUsername", 590 Password: "anotherPassword", 591 NoProxy: ".example.com,127.0.0.1", 592 }, 593 }, 594 } 595 596 for _, testCase := range testCases { 597 t.Run(testCase.name, func(t *testing.T) { 598 output, err := testee.CreateRepoCreds(t.Context(), &testCase.repoCreds) 599 require.NoError(t, err) 600 assert.Same(t, &testCase.repoCreds, output) 601 602 secret, err := clientset.CoreV1().Secrets(testNamespace).Get( 603 t.Context(), 604 RepoURLToSecretName(credSecretPrefix, testCase.repoCreds.URL, ""), 605 metav1.GetOptions{}, 606 ) 607 assert.NotNil(t, secret) 608 require.NoError(t, err) 609 610 assert.Equal(t, common.AnnotationValueManagedByArgoCD, secret.Annotations[common.AnnotationKeyManagedBy]) 611 assert.Equal(t, common.LabelValueSecretTypeRepoCreds, secret.Labels[common.LabelKeySecretType]) 612 613 // check every possible field of the secret if it has the same (default) value as the repoCred struct 614 // non-string fields must be parsed so that their default value matches the one of the corresponding type 615 assert.Equal(t, testCase.repoCreds.URL, string(secret.Data["url"])) 616 assert.Equal(t, testCase.repoCreds.Username, string(secret.Data["username"])) 617 assert.Equal(t, testCase.repoCreds.Password, string(secret.Data["password"])) 618 if enableOCI, err := strconv.ParseBool(string(secret.Data["githubAppPrivateKey"])); err == nil { 619 assert.Equal(t, strconv.FormatBool(testCase.repoCreds.EnableOCI), enableOCI) 620 } 621 assert.Equal(t, testCase.repoCreds.SSHPrivateKey, string(secret.Data["sshPrivateKey"])) 622 assert.Equal(t, testCase.repoCreds.TLSClientCertData, string(secret.Data["tlsClientCertData"])) 623 assert.Equal(t, testCase.repoCreds.TLSClientCertKey, string(secret.Data["tlsClientCertKey"])) 624 assert.Equal(t, testCase.repoCreds.Type, string(secret.Data["type"])) 625 assert.Equal(t, testCase.repoCreds.GithubAppPrivateKey, string(secret.Data["githubAppPrivateKey"])) 626 if githubAppPrivateKey, err := strconv.ParseInt(string(secret.Data["githubAppPrivateKey"]), 10, 64); err == nil { 627 assert.Equal(t, testCase.repoCreds.GithubAppId, githubAppPrivateKey) 628 } 629 if githubAppID, err := strconv.ParseInt(string(secret.Data["githubAppId"]), 10, 64); err == nil { 630 assert.Equal(t, testCase.repoCreds.GithubAppInstallationId, githubAppID) 631 } 632 assert.Equal(t, testCase.repoCreds.GitHubAppEnterpriseBaseURL, string(secret.Data["githubAppEnterpriseUrl"])) 633 assert.Equal(t, testCase.repoCreds.Proxy, string(secret.Data["proxy"])) 634 assert.Equal(t, testCase.repoCreds.NoProxy, string(secret.Data["noProxy"])) 635 }) 636 } 637 } 638 639 func TestSecretsRepositoryBackend_GetRepoCreds(t *testing.T) { 640 repoCredSecrets := []runtime.Object{ 641 &corev1.Secret{ 642 ObjectMeta: metav1.ObjectMeta{ 643 Namespace: testNamespace, 644 Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj", ""), 645 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 646 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 647 }, 648 Data: map[string][]byte{ 649 "url": []byte("git@github.com:argoproj"), 650 "username": []byte("someUsername"), 651 "password": []byte("somePassword"), 652 }, 653 }, 654 &corev1.Secret{ 655 ObjectMeta: metav1.ObjectMeta{ 656 Namespace: testNamespace, 657 Name: "user-managed", 658 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 659 }, 660 Data: map[string][]byte{ 661 "url": []byte("git@gitlab.com"), 662 "username": []byte("someOtherUsername"), 663 "password": []byte("someOtherPassword"), 664 }, 665 }, 666 &corev1.Secret{ 667 ObjectMeta: metav1.ObjectMeta{ 668 Namespace: testNamespace, 669 Name: "proxy-repo", 670 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 671 }, 672 Data: map[string][]byte{ 673 "url": []byte("git@gitlab.com"), 674 "username": []byte("someOtherUsername"), 675 "password": []byte("someOtherPassword"), 676 "proxy": []byte("https://proxy.argoproj.io:3128"), 677 "noProxy": []byte(".example.com,127.0.0.1"), 678 }, 679 }, 680 } 681 682 clientset := getClientset(repoCredSecrets...) 683 testee := &secretsRepositoryBackend{db: &db{ 684 ns: testNamespace, 685 kubeclientset: clientset, 686 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 687 }} 688 689 repoCred, err := testee.GetRepoCreds(t.Context(), "git@github.com:argoproj") 690 require.NoError(t, err) 691 require.NotNil(t, repoCred) 692 assert.Equal(t, "git@github.com:argoproj", repoCred.URL) 693 assert.Equal(t, "someUsername", repoCred.Username) 694 assert.Equal(t, "somePassword", repoCred.Password) 695 696 repoCred, err = testee.GetRepoCreds(t.Context(), "git@gitlab.com") 697 require.NoError(t, err) 698 assert.NotNil(t, repoCred) 699 assert.Equal(t, "git@gitlab.com", repoCred.URL) 700 assert.Equal(t, "someOtherUsername", repoCred.Username) 701 assert.Equal(t, "someOtherPassword", repoCred.Password) 702 if repoCred.Proxy != "" { 703 assert.Equal(t, "https://proxy.argoproj.io:3128", repoCred.Proxy) 704 } 705 if repoCred.NoProxy != "" { 706 assert.Equal(t, ".example.com,127.0.0.1", repoCred.NoProxy) 707 } 708 } 709 710 func TestSecretsRepositoryBackend_ListRepoCreds(t *testing.T) { 711 repoCredSecrets := []runtime.Object{ 712 &corev1.Secret{ 713 ObjectMeta: metav1.ObjectMeta{ 714 Namespace: testNamespace, 715 Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj", ""), 716 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 717 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 718 }, 719 Data: map[string][]byte{ 720 "url": []byte("git@github.com:argoproj"), 721 "username": []byte("someUsername"), 722 "password": []byte("somePassword"), 723 }, 724 }, 725 &corev1.Secret{ 726 ObjectMeta: metav1.ObjectMeta{ 727 Namespace: testNamespace, 728 Name: "user-managed", 729 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 730 }, 731 Data: map[string][]byte{ 732 "url": []byte("git@gitlab.com"), 733 "username": []byte("someOtherUsername"), 734 "password": []byte("someOtherPassword"), 735 }, 736 }, 737 } 738 739 clientset := getClientset(repoCredSecrets...) 740 testee := &secretsRepositoryBackend{db: &db{ 741 ns: testNamespace, 742 kubeclientset: clientset, 743 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 744 }} 745 746 repoCreds, err := testee.ListRepoCreds(t.Context()) 747 require.NoError(t, err) 748 assert.Len(t, repoCreds, 2) 749 assert.Contains(t, repoCreds, "git@github.com:argoproj") 750 assert.Contains(t, repoCreds, "git@gitlab.com") 751 } 752 753 func TestSecretsRepositoryBackend_UpdateRepoCreds(t *testing.T) { 754 managedCreds := &appsv1.RepoCreds{ 755 URL: "git@github.com:argoproj", 756 Username: "someUsername", 757 Password: "somePassword", 758 } 759 userProvidedCreds := &appsv1.RepoCreds{ 760 URL: "git@gitlab.com", 761 Username: "someOtherUsername", 762 Password: "someOtherPassword", 763 } 764 newCreds := &appsv1.RepoCreds{ 765 URL: "git@github.com:foobar", 766 Username: "foo", 767 Password: "bar", 768 } 769 770 managedCredsName := RepoURLToSecretName(credSecretPrefix, managedCreds.URL, "") 771 newCredsName := RepoURLToSecretName(credSecretPrefix, newCreds.URL, "") 772 repoCredSecrets := []runtime.Object{ 773 &corev1.Secret{ 774 ObjectMeta: metav1.ObjectMeta{ 775 Namespace: testNamespace, 776 Name: managedCredsName, 777 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 778 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 779 }, 780 Data: map[string][]byte{ 781 "url": []byte(managedCreds.URL), 782 "username": []byte(managedCreds.Username), 783 "password": []byte(managedCreds.Password), 784 }, 785 }, 786 &corev1.Secret{ 787 ObjectMeta: metav1.ObjectMeta{ 788 Namespace: testNamespace, 789 Name: "user-managed", 790 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 791 }, 792 Data: map[string][]byte{ 793 "url": []byte(userProvidedCreds.URL), 794 "username": []byte(userProvidedCreds.Username), 795 "password": []byte(userProvidedCreds.Password), 796 }, 797 }, 798 } 799 800 clientset := getClientset(repoCredSecrets...) 801 testee := &secretsRepositoryBackend{db: &db{ 802 ns: testNamespace, 803 kubeclientset: clientset, 804 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 805 }} 806 807 managedCreds.Username = "newUsername" 808 updateRepoCreds, err := testee.UpdateRepoCreds(t.Context(), managedCreds) 809 require.NoError(t, err) 810 assert.NotSame(t, managedCreds, updateRepoCreds) 811 assert.Equal(t, managedCreds.Username, updateRepoCreds.Username) 812 813 secret, err := clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), managedCredsName, metav1.GetOptions{}) 814 require.NoError(t, err) 815 assert.NotNil(t, secret) 816 assert.Equal(t, "newUsername", string(secret.Data["username"])) 817 818 userProvidedCreds.Username = "newOtherUsername" 819 updateRepoCreds, err = testee.UpdateRepoCreds(t.Context(), userProvidedCreds) 820 require.NoError(t, err) 821 assert.NotSame(t, userProvidedCreds, updateRepoCreds) 822 assert.Equal(t, userProvidedCreds.Username, updateRepoCreds.Username) 823 824 secret, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), "user-managed", metav1.GetOptions{}) 825 require.NoError(t, err) 826 assert.NotNil(t, secret) 827 assert.Equal(t, "newOtherUsername", string(secret.Data["username"])) 828 829 updateRepoCreds, err = testee.UpdateRepoCreds(t.Context(), newCreds) 830 require.NoError(t, err) 831 assert.Same(t, newCreds, updateRepoCreds) 832 833 secret, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), newCredsName, metav1.GetOptions{}) 834 require.NoError(t, err) 835 assert.NotNil(t, secret) 836 assert.Equal(t, "foo", string(secret.Data["username"])) 837 } 838 839 func TestSecretsRepositoryBackend_DeleteRepoCreds(t *testing.T) { 840 managedSecretName := RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", "") 841 repoSecrets := []runtime.Object{ 842 &corev1.Secret{ 843 ObjectMeta: metav1.ObjectMeta{ 844 Namespace: testNamespace, 845 Name: managedSecretName, 846 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 847 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 848 }, 849 Data: map[string][]byte{ 850 "url": []byte("git@github.com:argoproj"), 851 "username": []byte("someUsername"), 852 "password": []byte("somePassword"), 853 }, 854 }, 855 &corev1.Secret{ 856 ObjectMeta: metav1.ObjectMeta{ 857 Namespace: testNamespace, 858 Name: "user-managed", 859 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 860 }, 861 Data: map[string][]byte{ 862 "url": []byte("git@gitlab.com"), 863 "username": []byte("someOtherUsername"), 864 "password": []byte("someOtherPassword"), 865 }, 866 }, 867 } 868 869 clientset := getClientset(repoSecrets...) 870 testee := &secretsRepositoryBackend{db: &db{ 871 ns: testNamespace, 872 kubeclientset: clientset, 873 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 874 }} 875 876 err := testee.DeleteRepoCreds(t.Context(), "git@github.com:argoproj") 877 require.NoError(t, err) 878 879 _, err = clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), managedSecretName, metav1.GetOptions{}) 880 require.Error(t, err) 881 882 err = testee.DeleteRepoCreds(t.Context(), "git@gitlab.com") 883 require.NoError(t, err) 884 885 secret, err := clientset.CoreV1().Secrets(testNamespace).Get(t.Context(), "user-managed", metav1.GetOptions{}) 886 require.NoError(t, err) 887 assert.NotNil(t, secret) 888 assert.Empty(t, secret.Labels[common.LabelValueSecretTypeRepoCreds]) 889 } 890 891 func TestSecretsRepositoryBackend_GetAllHelmRepoCreds(t *testing.T) { 892 repoCredSecrets := []runtime.Object{ 893 &corev1.Secret{ 894 ObjectMeta: metav1.ObjectMeta{ 895 Namespace: testNamespace, 896 Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj", ""), 897 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 898 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 899 }, 900 Data: map[string][]byte{ 901 "url": []byte("git@github.com:argoproj"), 902 "username": []byte("someUsername"), 903 "password": []byte("somePassword"), 904 "type": []byte("helm"), 905 }, 906 }, 907 &corev1.Secret{ 908 ObjectMeta: metav1.ObjectMeta{ 909 Namespace: testNamespace, 910 Name: RepoURLToSecretName(repoSecretPrefix, "git@gitlab.com", ""), 911 Annotations: map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, 912 Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, 913 }, 914 Data: map[string][]byte{ 915 "url": []byte("git@gitlab.com"), 916 "username": []byte("someOtherUsername"), 917 "password": []byte("someOtherPassword"), 918 "type": []byte("git"), 919 }, 920 }, 921 } 922 923 clientset := getClientset(repoCredSecrets...) 924 testee := &secretsRepositoryBackend{db: &db{ 925 ns: testNamespace, 926 kubeclientset: clientset, 927 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 928 }} 929 930 repoCreds, err := testee.GetAllHelmRepoCreds(t.Context()) 931 require.NoError(t, err) 932 assert.Len(t, repoCreds, 1) 933 } 934 935 func TestRepoCredsToSecret(t *testing.T) { 936 clientset := getClientset() 937 testee := &secretsRepositoryBackend{db: &db{ 938 ns: testNamespace, 939 kubeclientset: clientset, 940 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 941 }} 942 s := &corev1.Secret{} 943 creds := &appsv1.RepoCreds{ 944 URL: "URL", 945 Username: "Username", 946 Password: "Password", 947 SSHPrivateKey: "SSHPrivateKey", 948 EnableOCI: true, 949 TLSClientCertData: "TLSClientCertData", 950 TLSClientCertKey: "TLSClientCertKey", 951 Type: "Type", 952 GithubAppPrivateKey: "GithubAppPrivateKey", 953 GithubAppId: 123, 954 GithubAppInstallationId: 456, 955 GitHubAppEnterpriseBaseURL: "GitHubAppEnterpriseBaseURL", 956 } 957 958 s = testee.repoCredsToSecret(creds, s) 959 960 assert.Equal(t, []byte(creds.URL), s.Data["url"]) 961 assert.Equal(t, []byte(creds.Username), s.Data["username"]) 962 assert.Equal(t, []byte(creds.Password), s.Data["password"]) 963 assert.Equal(t, []byte(creds.SSHPrivateKey), s.Data["sshPrivateKey"]) 964 assert.Equal(t, []byte(strconv.FormatBool(creds.EnableOCI)), s.Data["enableOCI"]) 965 assert.Equal(t, []byte(creds.TLSClientCertData), s.Data["tlsClientCertData"]) 966 assert.Equal(t, []byte(creds.TLSClientCertKey), s.Data["tlsClientCertKey"]) 967 assert.Equal(t, []byte(creds.Type), s.Data["type"]) 968 assert.Equal(t, []byte(creds.GithubAppPrivateKey), s.Data["githubAppPrivateKey"]) 969 assert.Equal(t, []byte(strconv.FormatInt(creds.GithubAppId, 10)), s.Data["githubAppID"]) 970 assert.Equal(t, []byte(strconv.FormatInt(creds.GithubAppInstallationId, 10)), s.Data["githubAppInstallationID"]) 971 assert.Equal(t, []byte(creds.GitHubAppEnterpriseBaseURL), s.Data["githubAppEnterpriseBaseUrl"]) 972 assert.Equal(t, map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, s.Annotations) 973 assert.Equal(t, map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds}, s.Labels) 974 } 975 976 func TestRepoWriteCredsToSecret(t *testing.T) { 977 clientset := getClientset() 978 testee := &secretsRepositoryBackend{ 979 db: &db{ 980 ns: testNamespace, 981 kubeclientset: clientset, 982 settingsMgr: settings.NewSettingsManager(t.Context(), clientset, testNamespace), 983 }, 984 writeCreds: true, 985 } 986 s := &corev1.Secret{} 987 creds := &appsv1.RepoCreds{ 988 URL: "URL", 989 Username: "Username", 990 Password: "Password", 991 SSHPrivateKey: "SSHPrivateKey", 992 EnableOCI: true, 993 TLSClientCertData: "TLSClientCertData", 994 TLSClientCertKey: "TLSClientCertKey", 995 Type: "Type", 996 GithubAppPrivateKey: "GithubAppPrivateKey", 997 GithubAppId: 123, 998 GithubAppInstallationId: 456, 999 GitHubAppEnterpriseBaseURL: "GitHubAppEnterpriseBaseURL", 1000 } 1001 s = testee.repoCredsToSecret(creds, s) 1002 assert.Equal(t, []byte(creds.URL), s.Data["url"]) 1003 assert.Equal(t, []byte(creds.Username), s.Data["username"]) 1004 assert.Equal(t, []byte(creds.Password), s.Data["password"]) 1005 assert.Equal(t, []byte(creds.SSHPrivateKey), s.Data["sshPrivateKey"]) 1006 assert.Equal(t, []byte(strconv.FormatBool(creds.EnableOCI)), s.Data["enableOCI"]) 1007 assert.Equal(t, []byte(creds.TLSClientCertData), s.Data["tlsClientCertData"]) 1008 assert.Equal(t, []byte(creds.TLSClientCertKey), s.Data["tlsClientCertKey"]) 1009 assert.Equal(t, []byte(creds.Type), s.Data["type"]) 1010 assert.Equal(t, []byte(creds.GithubAppPrivateKey), s.Data["githubAppPrivateKey"]) 1011 assert.Equal(t, []byte(strconv.FormatInt(creds.GithubAppId, 10)), s.Data["githubAppID"]) 1012 assert.Equal(t, []byte(strconv.FormatInt(creds.GithubAppInstallationId, 10)), s.Data["githubAppInstallationID"]) 1013 assert.Equal(t, []byte(creds.GitHubAppEnterpriseBaseURL), s.Data["githubAppEnterpriseBaseUrl"]) 1014 assert.Equal(t, map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, s.Annotations) 1015 assert.Equal(t, map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCredsWrite}, s.Labels) 1016 } 1017 1018 func TestRaceConditionInRepoCredsOperations(t *testing.T) { 1019 // Create a single shared secret that will be accessed concurrently 1020 sharedSecret := &corev1.Secret{ 1021 ObjectMeta: metav1.ObjectMeta{ 1022 Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""), 1023 Namespace: testNamespace, 1024 Labels: map[string]string{ 1025 common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds, 1026 }, 1027 }, 1028 Data: map[string][]byte{ 1029 "url": []byte("git@github.com:argoproj/argo-cd.git"), 1030 "username": []byte("test-user"), 1031 "password": []byte("test-pass"), 1032 }, 1033 } 1034 1035 // Create test credentials that we'll use for conversion 1036 repoCreds := &appsv1.RepoCreds{ 1037 URL: "git@github.com:argoproj/argo-cd.git", 1038 Username: "test-user", 1039 Password: "test-pass", 1040 } 1041 1042 backend := &secretsRepositoryBackend{} 1043 1044 var wg sync.WaitGroup 1045 concurrentOps := 50 1046 errChan := make(chan error, concurrentOps*2) // Channel to collect errors 1047 1048 // Launch goroutines that perform concurrent operations 1049 for i := 0; i < concurrentOps; i++ { 1050 wg.Add(2) 1051 1052 // One goroutine converts from RepoCreds to Secret 1053 go func() { 1054 defer wg.Done() 1055 defer func() { 1056 if r := recover(); r != nil { 1057 errChan <- fmt.Errorf("panic in repoCredsToSecret: %v", r) 1058 } 1059 }() 1060 _ = backend.repoCredsToSecret(repoCreds, sharedSecret) 1061 }() 1062 1063 // Another goroutine converts from Secret to RepoCreds 1064 go func() { 1065 defer wg.Done() 1066 defer func() { 1067 if r := recover(); r != nil { 1068 errChan <- fmt.Errorf("panic in secretToRepoCred: %v", r) 1069 } 1070 }() 1071 creds, err := backend.secretToRepoCred(sharedSecret) 1072 if err != nil { 1073 errChan <- fmt.Errorf("error in secretToRepoCred: %w", err) 1074 return 1075 } 1076 // Verify data integrity 1077 if creds.URL != repoCreds.URL || creds.Username != repoCreds.Username || creds.Password != repoCreds.Password { 1078 errChan <- fmt.Errorf("data mismatch in conversion: expected %v, got %v", repoCreds, creds) 1079 } 1080 }() 1081 } 1082 1083 wg.Wait() 1084 close(errChan) 1085 1086 // Check for any errors that occurred during concurrent operations 1087 for err := range errChan { 1088 t.Errorf("concurrent operation error: %v", err) 1089 } 1090 1091 // Verify final state 1092 finalCreds, err := backend.secretToRepoCred(sharedSecret) 1093 require.NoError(t, err) 1094 assert.Equal(t, repoCreds.URL, finalCreds.URL) 1095 assert.Equal(t, repoCreds.Username, finalCreds.Username) 1096 assert.Equal(t, repoCreds.Password, finalCreds.Password) 1097 } 1098 1099 func TestRaceConditionInRepositoryOperations(t *testing.T) { 1100 // Create a single shared secret that will be accessed concurrently 1101 sharedSecret := &corev1.Secret{ 1102 ObjectMeta: metav1.ObjectMeta{ 1103 Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""), 1104 Namespace: testNamespace, 1105 Labels: map[string]string{ 1106 common.LabelKeySecretType: common.LabelValueSecretTypeRepository, 1107 }, 1108 }, 1109 Data: map[string][]byte{ 1110 "url": []byte("git@github.com:argoproj/argo-cd.git"), 1111 "name": []byte("test-repo"), 1112 "username": []byte("test-user"), 1113 "password": []byte("test-pass"), 1114 }, 1115 } 1116 1117 // Create test repository that we'll use for conversion 1118 repo := &appsv1.Repository{ 1119 Name: "test-repo", 1120 Repo: "git@github.com:argoproj/argo-cd.git", 1121 Username: "test-user", 1122 Password: "test-pass", 1123 } 1124 1125 backend := &secretsRepositoryBackend{} 1126 1127 var wg sync.WaitGroup 1128 concurrentOps := 50 1129 errChan := make(chan error, concurrentOps*2) // Channel to collect errors 1130 1131 // Launch goroutines that perform concurrent operations 1132 for i := 0; i < concurrentOps; i++ { 1133 wg.Add(2) 1134 1135 // One goroutine converts from Repository to Secret 1136 go func() { 1137 defer wg.Done() 1138 defer func() { 1139 if r := recover(); r != nil { 1140 errChan <- fmt.Errorf("panic in repositoryToSecret: %v", r) 1141 } 1142 }() 1143 _ = backend.repositoryToSecret(repo, sharedSecret) 1144 }() 1145 1146 // Another goroutine converts from Secret to Repository 1147 go func() { 1148 defer wg.Done() 1149 defer func() { 1150 if r := recover(); r != nil { 1151 errChan <- fmt.Errorf("panic in secretToRepository: %v", r) 1152 } 1153 }() 1154 repository, err := secretToRepository(sharedSecret) 1155 if err != nil { 1156 errChan <- fmt.Errorf("error in secretToRepository: %w", err) 1157 return 1158 } 1159 // Verify data integrity 1160 if repository.Name != repo.Name || repository.Repo != repo.Repo || 1161 repository.Username != repo.Username || repository.Password != repo.Password { 1162 errChan <- fmt.Errorf("data mismatch in conversion: expected %v, got %v", repo, repository) 1163 } 1164 }() 1165 } 1166 1167 wg.Wait() 1168 close(errChan) 1169 1170 // Check for any errors that occurred during concurrent operations 1171 for err := range errChan { 1172 t.Errorf("concurrent operation error: %v", err) 1173 } 1174 1175 // Verify final state 1176 finalRepo, err := secretToRepository(sharedSecret) 1177 require.NoError(t, err) 1178 assert.Equal(t, repo.Name, finalRepo.Name) 1179 assert.Equal(t, repo.Repo, finalRepo.Repo) 1180 assert.Equal(t, repo.Username, finalRepo.Username) 1181 assert.Equal(t, repo.Password, finalRepo.Password) 1182 }