github.com/argoproj/argo-cd/v2@v2.10.9/server/applicationset/applicationset_test.go (about) 1 package applicationset 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/argoproj/pkg/sync" 8 "github.com/stretchr/testify/assert" 9 v1 "k8s.io/api/core/v1" 10 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 "k8s.io/apimachinery/pkg/runtime" 12 "k8s.io/client-go/kubernetes/fake" 13 k8scache "k8s.io/client-go/tools/cache" 14 15 "github.com/argoproj/argo-cd/v2/common" 16 "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset" 17 appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 18 apps "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake" 19 appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions" 20 "github.com/argoproj/argo-cd/v2/server/rbacpolicy" 21 "github.com/argoproj/argo-cd/v2/util/assets" 22 "github.com/argoproj/argo-cd/v2/util/db" 23 "github.com/argoproj/argo-cd/v2/util/errors" 24 "github.com/argoproj/argo-cd/v2/util/rbac" 25 "github.com/argoproj/argo-cd/v2/util/settings" 26 ) 27 28 const ( 29 testNamespace = "default" 30 fakeRepoURL = "https://git.com/repo.git" 31 ) 32 33 func fakeRepo() *appsv1.Repository { 34 return &appsv1.Repository{ 35 Repo: fakeRepoURL, 36 } 37 } 38 39 func fakeCluster() *appsv1.Cluster { 40 return &appsv1.Cluster{ 41 Server: "https://cluster-api.example.com", 42 Name: "fake-cluster", 43 Config: appsv1.ClusterConfig{}, 44 } 45 } 46 47 // return an ApplicationServiceServer which returns fake data 48 func newTestAppSetServer(objects ...runtime.Object) *Server { 49 f := func(enf *rbac.Enforcer) { 50 _ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV) 51 enf.SetDefaultRole("role:admin") 52 } 53 scopedNamespaces := "" 54 return newTestAppSetServerWithEnforcerConfigure(f, scopedNamespaces, objects...) 55 } 56 57 // return an ApplicationServiceServer which returns fake data 58 func newTestNamespacedAppSetServer(objects ...runtime.Object) *Server { 59 f := func(enf *rbac.Enforcer) { 60 _ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV) 61 enf.SetDefaultRole("role:admin") 62 } 63 scopedNamespaces := "argocd" 64 return newTestAppSetServerWithEnforcerConfigure(f, scopedNamespaces, objects...) 65 } 66 67 func newTestAppSetServerWithEnforcerConfigure(f func(*rbac.Enforcer), namespace string, objects ...runtime.Object) *Server { 68 kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{ 69 ObjectMeta: metav1.ObjectMeta{ 70 Namespace: testNamespace, 71 Name: "argocd-cm", 72 Labels: map[string]string{ 73 "app.kubernetes.io/part-of": "argocd", 74 }, 75 }, 76 }, &v1.Secret{ 77 ObjectMeta: metav1.ObjectMeta{ 78 Name: "argocd-secret", 79 Namespace: testNamespace, 80 }, 81 Data: map[string][]byte{ 82 "admin.password": []byte("test"), 83 "server.secretkey": []byte("test"), 84 }, 85 }) 86 ctx := context.Background() 87 db := db.NewDB(testNamespace, settings.NewSettingsManager(ctx, kubeclientset, testNamespace), kubeclientset) 88 _, err := db.CreateRepository(ctx, fakeRepo()) 89 errors.CheckError(err) 90 _, err = db.CreateCluster(ctx, fakeCluster()) 91 errors.CheckError(err) 92 93 defaultProj := &appsv1.AppProject{ 94 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"}, 95 Spec: appsv1.AppProjectSpec{ 96 SourceRepos: []string{"*"}, 97 Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}}, 98 }, 99 } 100 myProj := &appsv1.AppProject{ 101 ObjectMeta: metav1.ObjectMeta{Name: "my-proj", Namespace: "default"}, 102 Spec: appsv1.AppProjectSpec{ 103 SourceRepos: []string{"*"}, 104 Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}}, 105 }, 106 } 107 108 objects = append(objects, defaultProj, myProj) 109 110 fakeAppsClientset := apps.NewSimpleClientset(objects...) 111 factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(namespace), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {})) 112 fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace) 113 114 enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil) 115 f(enforcer) 116 enforcer.SetClaimsEnforcerFunc(rbacpolicy.NewRBACPolicyEnforcer(enforcer, fakeProjLister).EnforceClaims) 117 118 settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace) 119 120 // populate the app informer with the fake objects 121 appInformer := factory.Argoproj().V1alpha1().Applications().Informer() 122 // TODO(jessesuen): probably should return cancel function so tests can stop background informer 123 //ctx, cancel := context.WithCancel(context.Background()) 124 go appInformer.Run(ctx.Done()) 125 if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) { 126 panic("Timed out waiting for caches to sync") 127 } 128 // populate the appset informer with the fake objects 129 appsetInformer := factory.Argoproj().V1alpha1().ApplicationSets().Informer() 130 go appsetInformer.Run(ctx.Done()) 131 if !k8scache.WaitForCacheSync(ctx.Done(), appsetInformer.HasSynced) { 132 panic("Timed out waiting for caches to sync") 133 } 134 135 projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer() 136 go projInformer.Run(ctx.Done()) 137 if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) { 138 panic("Timed out waiting for caches to sync") 139 } 140 141 server := NewServer( 142 db, 143 kubeclientset, 144 enforcer, 145 fakeAppsClientset, 146 appInformer, 147 factory.Argoproj().V1alpha1().ApplicationSets().Lister(), 148 fakeProjLister, 149 settingsMgr, 150 testNamespace, 151 sync.NewKeyLock(), 152 []string{testNamespace, "external-namespace"}, 153 ) 154 return server.(*Server) 155 } 156 157 func newTestAppSet(opts ...func(appset *appsv1.ApplicationSet)) *appsv1.ApplicationSet { 158 appset := appsv1.ApplicationSet{ 159 ObjectMeta: metav1.ObjectMeta{ 160 Namespace: testNamespace, 161 }, 162 Spec: appsv1.ApplicationSetSpec{ 163 Template: appsv1.ApplicationSetTemplate{ 164 Spec: appsv1.ApplicationSpec{ 165 Project: "default", 166 }, 167 }, 168 }, 169 } 170 for i := range opts { 171 opts[i](&appset) 172 } 173 return &appset 174 } 175 176 func testListAppsetsWithLabels(t *testing.T, appsetQuery applicationset.ApplicationSetListQuery, appServer *Server) { 177 validTests := []struct { 178 testName string 179 label string 180 expectedResult []string 181 }{ 182 {testName: "Equality based filtering using '=' operator", 183 label: "key1=value1", 184 expectedResult: []string{"AppSet1"}}, 185 {testName: "Equality based filtering using '==' operator", 186 label: "key1==value1", 187 expectedResult: []string{"AppSet1"}}, 188 {testName: "Equality based filtering using '!=' operator", 189 label: "key1!=value1", 190 expectedResult: []string{"AppSet2", "AppSet3"}}, 191 {testName: "Set based filtering using 'in' operator", 192 label: "key1 in (value1, value3)", 193 expectedResult: []string{"AppSet1", "AppSet3"}}, 194 {testName: "Set based filtering using 'notin' operator", 195 label: "key1 notin (value1, value3)", 196 expectedResult: []string{"AppSet2"}}, 197 {testName: "Set based filtering using 'exists' operator", 198 label: "key1", 199 expectedResult: []string{"AppSet1", "AppSet2", "AppSet3"}}, 200 {testName: "Set based filtering using 'not exists' operator", 201 label: "!key2", 202 expectedResult: []string{"AppSet2", "AppSet3"}}, 203 } 204 //test valid scenarios 205 for _, validTest := range validTests { 206 t.Run(validTest.testName, func(t *testing.T) { 207 appsetQuery.Selector = validTest.label 208 res, err := appServer.List(context.Background(), &appsetQuery) 209 assert.NoError(t, err) 210 apps := []string{} 211 for i := range res.Items { 212 apps = append(apps, res.Items[i].Name) 213 } 214 assert.Equal(t, validTest.expectedResult, apps) 215 }) 216 } 217 218 invalidTests := []struct { 219 testName string 220 label string 221 errorMesage string 222 }{ 223 {testName: "Set based filtering using '>' operator", 224 label: "key1>value1", 225 errorMesage: "error parsing the selector"}, 226 {testName: "Set based filtering using '<' operator", 227 label: "key1<value1", 228 errorMesage: "error parsing the selector"}, 229 } 230 //test invalid scenarios 231 for _, invalidTest := range invalidTests { 232 t.Run(invalidTest.testName, func(t *testing.T) { 233 appsetQuery.Selector = invalidTest.label 234 _, err := appServer.List(context.Background(), &appsetQuery) 235 assert.ErrorContains(t, err, invalidTest.errorMesage) 236 }) 237 } 238 } 239 240 func TestListAppSetsInNamespaceWithLabels(t *testing.T) { 241 testNamespace := "test-namespace" 242 appSetServer := newTestAppSetServer(newTestAppSet(func(appset *appsv1.ApplicationSet) { 243 appset.Name = "AppSet1" 244 appset.ObjectMeta.Namespace = testNamespace 245 appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"}) 246 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 247 appset.Name = "AppSet2" 248 appset.ObjectMeta.Namespace = testNamespace 249 appset.SetLabels(map[string]string{"key1": "value2"}) 250 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 251 appset.Name = "AppSet3" 252 appset.ObjectMeta.Namespace = testNamespace 253 appset.SetLabels(map[string]string{"key1": "value3"}) 254 })) 255 appSetServer.enabledNamespaces = []string{testNamespace} 256 appsetQuery := applicationset.ApplicationSetListQuery{AppsetNamespace: testNamespace} 257 258 testListAppsetsWithLabels(t, appsetQuery, appSetServer) 259 } 260 261 func TestListAppSetsInDefaultNSWithLabels(t *testing.T) { 262 appSetServer := newTestAppSetServer(newTestAppSet(func(appset *appsv1.ApplicationSet) { 263 appset.Name = "AppSet1" 264 appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"}) 265 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 266 appset.Name = "AppSet2" 267 appset.SetLabels(map[string]string{"key1": "value2"}) 268 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 269 appset.Name = "AppSet3" 270 appset.SetLabels(map[string]string{"key1": "value3"}) 271 })) 272 appsetQuery := applicationset.ApplicationSetListQuery{} 273 274 testListAppsetsWithLabels(t, appsetQuery, appSetServer) 275 } 276 277 // This test covers https://github.com/argoproj/argo-cd/issues/15429 278 // If the namespace isn't provided during listing action, argocd's 279 // default namespace must be used and not all the namespaces 280 func TestListAppSetsWithoutNamespace(t *testing.T) { 281 testNamespace := "test-namespace" 282 appSetServer := newTestNamespacedAppSetServer(newTestAppSet(func(appset *appsv1.ApplicationSet) { 283 appset.Name = "AppSet1" 284 appset.ObjectMeta.Namespace = testNamespace 285 appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"}) 286 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 287 appset.Name = "AppSet2" 288 appset.ObjectMeta.Namespace = testNamespace 289 appset.SetLabels(map[string]string{"key1": "value2"}) 290 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 291 appset.Name = "AppSet3" 292 appset.ObjectMeta.Namespace = testNamespace 293 appset.SetLabels(map[string]string{"key1": "value3"}) 294 })) 295 appSetServer.enabledNamespaces = []string{testNamespace} 296 appsetQuery := applicationset.ApplicationSetListQuery{} 297 298 res, err := appSetServer.List(context.Background(), &appsetQuery) 299 assert.NoError(t, err) 300 assert.Equal(t, 0, len(res.Items)) 301 } 302 303 func TestCreateAppSet(t *testing.T) { 304 testAppSet := newTestAppSet() 305 appServer := newTestAppSetServer() 306 testAppSet.Spec.Generators = []appsv1.ApplicationSetGenerator{ 307 { 308 List: &appsv1.ListGenerator{}, 309 }, 310 } 311 createReq := applicationset.ApplicationSetCreateRequest{ 312 Applicationset: testAppSet, 313 } 314 _, err := appServer.Create(context.Background(), &createReq) 315 assert.NoError(t, err) 316 } 317 318 func TestCreateAppSetTemplatedProject(t *testing.T) { 319 testAppSet := newTestAppSet() 320 appServer := newTestAppSetServer() 321 testAppSet.Spec.Template.Spec.Project = "{{ .project }}" 322 createReq := applicationset.ApplicationSetCreateRequest{ 323 Applicationset: testAppSet, 324 } 325 _, err := appServer.Create(context.Background(), &createReq) 326 assert.Equal(t, "error validating ApplicationSets: the Argo CD API does not currently support creating ApplicationSets with templated `project` fields", err.Error()) 327 } 328 329 func TestCreateAppSetWrongNamespace(t *testing.T) { 330 testAppSet := newTestAppSet() 331 appServer := newTestAppSetServer() 332 testAppSet.ObjectMeta.Namespace = "NOT-ALLOWED" 333 createReq := applicationset.ApplicationSetCreateRequest{ 334 Applicationset: testAppSet, 335 } 336 _, err := appServer.Create(context.Background(), &createReq) 337 338 assert.Equal(t, "namespace 'NOT-ALLOWED' is not permitted", err.Error()) 339 } 340 341 func TestGetAppSet(t *testing.T) { 342 appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 343 appset.Name = "AppSet1" 344 }) 345 346 appSet2 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 347 appset.Name = "AppSet2" 348 }) 349 350 appSet3 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 351 appset.Name = "AppSet3" 352 }) 353 354 t.Run("Get in default namespace", func(t *testing.T) { 355 356 appSetServer := newTestAppSetServer(appSet1, appSet2, appSet3) 357 358 appsetQuery := applicationset.ApplicationSetGetQuery{Name: "AppSet1"} 359 360 res, err := appSetServer.Get(context.Background(), &appsetQuery) 361 assert.NoError(t, err) 362 assert.Equal(t, "AppSet1", res.Name) 363 }) 364 365 t.Run("Get in named namespace", func(t *testing.T) { 366 367 appSetServer := newTestAppSetServer(appSet1, appSet2, appSet3) 368 369 appsetQuery := applicationset.ApplicationSetGetQuery{Name: "AppSet1", AppsetNamespace: testNamespace} 370 371 res, err := appSetServer.Get(context.Background(), &appsetQuery) 372 assert.NoError(t, err) 373 assert.Equal(t, "AppSet1", res.Name) 374 }) 375 376 t.Run("Get in not allowed namespace", func(t *testing.T) { 377 378 appSetServer := newTestAppSetServer(appSet1, appSet2, appSet3) 379 380 appsetQuery := applicationset.ApplicationSetGetQuery{Name: "AppSet1", AppsetNamespace: "NOT-ALLOWED"} 381 382 _, err := appSetServer.Get(context.Background(), &appsetQuery) 383 assert.Equal(t, "namespace 'NOT-ALLOWED' is not permitted", err.Error()) 384 }) 385 } 386 387 func TestDeleteAppSet(t *testing.T) { 388 appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 389 appset.Name = "AppSet1" 390 }) 391 392 appSet2 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 393 appset.Name = "AppSet2" 394 }) 395 396 appSet3 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 397 appset.Name = "AppSet3" 398 }) 399 400 t.Run("Delete in default namespace", func(t *testing.T) { 401 402 appSetServer := newTestAppSetServer(appSet1, appSet2, appSet3) 403 404 appsetQuery := applicationset.ApplicationSetDeleteRequest{Name: "AppSet1"} 405 406 res, err := appSetServer.Delete(context.Background(), &appsetQuery) 407 assert.NoError(t, err) 408 assert.Equal(t, &applicationset.ApplicationSetResponse{}, res) 409 }) 410 411 t.Run("Delete in named namespace", func(t *testing.T) { 412 413 appSetServer := newTestAppSetServer(appSet1, appSet2, appSet3) 414 415 appsetQuery := applicationset.ApplicationSetDeleteRequest{Name: "AppSet1", AppsetNamespace: testNamespace} 416 417 res, err := appSetServer.Delete(context.Background(), &appsetQuery) 418 assert.NoError(t, err) 419 assert.Equal(t, &applicationset.ApplicationSetResponse{}, res) 420 }) 421 } 422 423 func TestUpdateAppSet(t *testing.T) { 424 appSet := newTestAppSet(func(appset *appsv1.ApplicationSet) { 425 appset.ObjectMeta.Annotations = map[string]string{ 426 "annotation-key1": "annotation-value1", 427 "annotation-key2": "annotation-value2", 428 } 429 appset.ObjectMeta.Labels = map[string]string{ 430 "label-key1": "label-value1", 431 "label-key2": "label-value2", 432 } 433 }) 434 435 newAppSet := newTestAppSet(func(appset *appsv1.ApplicationSet) { 436 appset.ObjectMeta.Annotations = map[string]string{ 437 "annotation-key1": "annotation-value1-updated", 438 } 439 appset.ObjectMeta.Labels = map[string]string{ 440 "label-key1": "label-value1-updated", 441 } 442 }) 443 444 t.Run("Update merge", func(t *testing.T) { 445 446 appServer := newTestAppSetServer(appSet) 447 448 updated, err := appServer.updateAppSet(appSet, newAppSet, context.Background(), true) 449 450 assert.NoError(t, err) 451 assert.Equal(t, map[string]string{ 452 "annotation-key1": "annotation-value1-updated", 453 "annotation-key2": "annotation-value2", 454 }, updated.Annotations) 455 assert.Equal(t, map[string]string{ 456 "label-key1": "label-value1-updated", 457 "label-key2": "label-value2", 458 }, updated.Labels) 459 }) 460 461 t.Run("Update no merge", func(t *testing.T) { 462 463 appServer := newTestAppSetServer(appSet) 464 465 updated, err := appServer.updateAppSet(appSet, newAppSet, context.Background(), false) 466 467 assert.NoError(t, err) 468 assert.Equal(t, map[string]string{ 469 "annotation-key1": "annotation-value1-updated", 470 }, updated.Annotations) 471 assert.Equal(t, map[string]string{ 472 "label-key1": "label-value1-updated", 473 }, updated.Labels) 474 }) 475 476 }